В моем проекте, который я решил реализовать на symfony, я столкнулся с проблемой динамического добавления связанных форм. В русскоязычных интернетах информации толком не нашел, а в заморском сегменте информация очень скупая и не совсем понятная. В итоге проблему решил и надеюсь, что мой опыт пригодится еще кому-нибудь.
Думаю, эта тема будет интересна начинающим изучать Symfony.
Имеем таблицу с анкетами, каждая из которых может иметь некоторое количество прикрепленных изображений. А может и не иметь.
Хотелось бы, чтобы добавление изображений происходило в той же форме, в которой заполняется информация о пользователе.
В классе формы для изображения настраиваем валидатор и виджет для поля «file»
В класс формы пользователей надо добавить следующий код:
Далее обновим шаблон формы модели «people», добавив в него отображение связанной формы:
Добавлять новые поля выбора файла будем javascript`ом. В источниках вдохновения добавление новой связанной формой реализовывалось через ajax. Отправлялся запрос на сервер, там создавалась новая форма, дальше она возвращалась в виде ответа и встраивалась в контейнер. В общем, было что-то вроде «кручу-верчу, запутать хочу».
Я решил не нагружать лишний раз сервер, а просто скопировать уже существующее поле, изменив у него имя. В итоге это работает гораздо быстрее и не нагружает сервер лишний раз.
Я использую в своей работе jQuery.
Теперь возвращаемся к нашему файлу PeopleForm.class.php. Надо добавить в него несколько функций.
Думаю, пустые поля можно было отсеять еще в функции bind, но меня поджимали сроки и катастрофически не хватало опыта с symfony. Поэтому реализовал эту схему таким образом.
После всех махинаций в анкете появляется поле для выбора файла. После того как выбирается какой-нибудь файл, появляется следующее. И так до бесконечности.
Правда у нашей новой формы есть одна особенность. Если вдруг форма не пройдет валидацию, то во время её повторного отображения с сообщениями об ошибках отрисуется столько полей для выбора файлов, со сколькими мы форму и отправили.
Одинаково работает как при редактировании анкеты, так и при добавлении новой.
Думаю, эта тема будет интересна начинающим изучать Symfony.
Имеем таблицу с анкетами, каждая из которых может иметь некоторое количество прикрепленных изображений. А может и не иметь.
Хотелось бы, чтобы добавление изображений происходило в той же форме, в которой заполняется информация о пользователе.
// config/doctrine/schema.yml
---
detect_relations: true
options:
charset: utf8
type: InnoDB
People:
columns:
name:
type: string(250)
default: null
Images:
columns:
id_person:
type: integer(8)
notnull: true
file:
type: string(250)
notnull: true
relations:
people:
class: People
local: id_person
foreign: id
foreignAlias: images
foreignType: many
owningSide: true
indexes:
fk_person:
fields: [id_person]
В классе формы для изображения настраиваем валидатор и виджет для поля «file»
// lib/form/doctrine/ImageForm.class.php
class ImageForm extends BaseImageForm
{
public function configure()
{
$this->useFields(array('file'));
$this->widgetSchema['file'] = new sfWidgetFormInputFile(array(
'label' => 'Файл',
));
$this->validatorSchema['file'] = new sfValidatorFile(array(
'required' => false,
'path' => sfConfig::get('sf_upload_dir'),
'mime_types' => 'web_images'
));
}
}
В класс формы пользователей надо добавить следующий код:
// lib/form/doctrine/PeopleForm.class.php
class PeopleForm extends BasePeopleForm
{
public function configure()
{
// ...
// Создаем новое изображение и указываем человека
$picture = new Image();
$picture->people = $this->getObject();
$pic_form = new ImageForm($picture);
$pictures_forms = new SfForm();
$pictures_forms->embedForm('picture_0', $pic_form);
// Подцепляем форму с изображениями к основной форме
$this->embedForm('pictures', $pictures_forms);
// В форме это будет отображаться инпутом с именем people[pictures][picture_0][file]
}
}
Далее обновим шаблон формы модели «people», добавив в него отображение связанной формы:
// apps/frontend/modules/people/templates/_form.php
<ul id="images">
<li><h3>Копии документов</h3></li>
<?php foreach($form['pictures'] AS $eForm) : ?>
<li><?php echo $eForm['file']->render() ?></li>
<?php endforeach ?>
</ul>
Добавлять новые поля выбора файла будем javascript`ом. В источниках вдохновения добавление новой связанной формой реализовывалось через ajax. Отправлялся запрос на сервер, там создавалась новая форма, дальше она возвращалась в виде ответа и встраивалась в контейнер. В общем, было что-то вроде «кручу-верчу, запутать хочу».
Я решил не нагружать лишний раз сервер, а просто скопировать уже существующее поле, изменив у него имя. В итоге это работает гораздо быстрее и не нагружает сервер лишний раз.
Я использую в своей работе jQuery.
function appendNewInputFile()
{
$('#images li:last input').one('change', function()
{
var input = $(this)
var reg = /\d+/
var num = reg.exec(input.attr('name'))
var new_name = input.attr('name').replace(reg, parseInt(num[0])+1)
$('#images').append('<li><input type="file"></li>')
$('#images li:last input').attr('name', new_name)
appendNewInputFile()
return false
})
}
Теперь возвращаемся к нашему файлу PeopleForm.class.php. Надо добавить в него несколько функций.
// lib/form/doctrine/PeopleForm.class.php
// ...
protected function doBind(array $values)
{
foreach($values['pictures'] AS $key => $picture)
{
// Удаляем из формы пустые поля для прикрепления изображений
if(empty($picture['file']['name']))
unset($values['pictures'][$key],
$this->embeddedForms['pictures'][$key],
$this->taintedValues['pictures'][$key],
$this->validatorSchema['pictures'][$key]
);
}
parent::doBind($values);
}
public function bind(array $taintedValues = null, array $taintedFiles = null)
{
// Добавляем связанные формы, которые мы создали с помощью JS, к основной
// Без этого валидацию мы не пройдем.
foreach($taintedFiles['pictures'] AS $key => $newPicture)
if(!isset($this['pictures'][$key]))
$this->addNewField($key);
parent::bind($taintedValues, $taintedFiles);
}
public function addNewField($name)
{
$picture = new Image();
$picture->people = $this->getObject();
$this->embeddedForms['pictures']->embedForm($name, new ImageForm($picture));
$this->embedForm('pictures', $this->embeddedForms['pictures']);
}
Думаю, пустые поля можно было отсеять еще в функции bind, но меня поджимали сроки и катастрофически не хватало опыта с symfony. Поэтому реализовал эту схему таким образом.
После всех махинаций в анкете появляется поле для выбора файла. После того как выбирается какой-нибудь файл, появляется следующее. И так до бесконечности.
Правда у нашей новой формы есть одна особенность. Если вдруг форма не пройдет валидацию, то во время её повторного отображения с сообщениями об ошибках отрисуется столько полей для выбора файлов, со сколькими мы форму и отправили.
Одинаково работает как при редактировании анкеты, так и при добавлении новой.