Ответ 1
Вы не решаете проблему под прямым углом. Если должен быть основной тег, это свойство не должно добавляться в тег, а в сущности, которая его содержит!
Я говорю об объекте data_class, связанном с формой, имеющей атрибут теги. Это объект, который должен иметь свойство mainTag.
Если он определен правильно, этот новый атрибут mainTag не будет логическим, поскольку он будет содержать экземпляр Тег и, следовательно, не будет связан с записью флажка.
Итак, как я его вижу, у вас должно быть свойство mainTag, содержащее ваш экземпляр и свойство теги, которое связывает все другие теги.
Проблема заключается в том, что поле вашей коллекции больше не будет содержать основной тег. Таким образом, вы также должны создать специальный getter getAllTags, который объединит ваш основной тег со всеми остальными и изменит определение вашей коллекции на:
$builder->add('allTags', 'collection', array(
'type' => new TagType(),
'label' => false,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false
));
Теперь, как мы можем добавить радио-боксы, вы можете спросить? Для этого вам нужно создать новое поле:
$builder->add('mainTag', 'radio', array(
'type' => 'choice',
'multiple' => false,
'expanded' => true,
'property_path' => 'mainTag.id', // Necessary, for 'choice' does not support data_classes
));
Это основы, однако, он только становится более сложным отсюда. Реальная проблема здесь в том, как отображается ваша форма. В том же поле вы смешиваете обычное отображение коллекции и отображение поля выбора родительской формы этой коллекции. Это заставит вас использовать форму.
Чтобы позволить некоторую возможность повторного использования, вам нужно создать настраиваемое поле. Связанный data_class:
class TagSelection
{
private mainTag;
private $tags;
public function getAllTags()
{
return array_merge(array($this->getMainTag()), $this->getTags());
}
public function setAllTags($tags)
{
// If the main tag is not null, search and remove it before calling setTags($tags)
}
// Getters, setters
}
Тип формы:
class TagSelectionType extends AbstractType
{
protected buildForm( ... )
{
$builder->add('allTags', 'collection', array(
'type' => new TagType(),
'label' => false,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false
));
// Since we cannot know which tags are available before binding or setting data, a listener must be used
$formFactory = $builder->getFormFactory();
$listener = function(FormEvent $event) use ($formFactory) {
$data = $event->getForm()->getData();
// Get all tags id currently in the data
$choices = ...;
// Careful, in PRE_BIND this is an array of scalars while in PRE_SET_DATA it is an array of Tag instances
$field = $this->factory->createNamed('mainTag', 'radio', null, array(
'type' => 'choice',
'multiple' => false,
'expanded' => true,
'choices' => $choices,
'property_path' => 'mainTag.id',
));
$event->getForm()->add($field);
}
$builder->addEventListener(FormEvent::PRE_SET_DATA, $listener);
$builder->addEventListener(FormEvent::PRE_BIND, $listener);
}
public function getName()
{
return 'tag_selection';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'TagSelection', // Adapt depending on class name
// 'prototype' => true,
));
}
}
Наконец, в шаблоне темы формы:
{% block tag_selection_widget %}
{% spaceless %}
{# {% set attr = attr|default({})|merge({'data-prototype': form_widget(prototype)}) %} #}
<ul {{ block('widget_attributes') }}>
{% for child in form.allTags %}
<li>{{ form_widget(form.mainTag[child.name]) }} {{ form_widget(child) }}</li>
{% endfor %}
</ul>
{% endspaceless %}
{% endblock tag_selection_widget %}
Наконец, мы должны включить, что в родительском объекте, который первоначально содержал теги:
class entity
{
// Doctrine definition and whatnot
private $tags;
// Doctrine definition and whatnot
private $mainTag;
...
public setAllTags($tagSelection)
{
$this->setMainTag($tagSelection->getMainTag());
$this->setTags($tagSelection->getTags());
}
public getAllTags()
{
$ret = new TagSelection();
$ret->setMainTag($this->getMainTag());
$ret->setTags($this->getTags());
return $ret;
}
...
}
И в оригинальной форме:
$builder->add('allTags', new TagSelection(), array(
'label' => false,
));
Я понимаю, что предлагаемое мной решение является подробным, однако, как мне кажется, оно является наиболее эффективным. То, что вы пытаетесь сделать, не может быть легко сделано в Symfony.
Вы также можете заметить, что в комментарии есть нечетная опция "prototype". Я просто хотел подчеркнуть очень полезное свойство "коллекции" в вашем случае: опция прототипа содержит пустой элемент вашей коллекции с заменяющими заполнителями. Это позволяет быстро добавлять новые элементы в поле коллекции с помощью javascript, больше информации здесь.