Ответ 1
Используйте Data Transformer для своего объекта. И в методе reverseTransform, если вы не найдете новую добавленную группу, просто создайте ее там вместо того, чтобы бросать TransformationFailedException.
В Symfony2 у меня есть BandType
, где я добавляю объект Tag
:
->add('tags', 'entity', [
'label' => 'Tags',
'class' => 'DbBundle:Tag',
'property' => 'title',
'multiple' => true,
])
Это создает несколько элементов select, где я могу выбрать существующие теги из базы данных (Doctrine). Но мне нужно добавить новые динамические теги, которые еще не существуют.
На стороне клиента я использую плагин jQuery Selectize.js, что позволяет мне добавлять новый тег в поле выбора. Но после отправки формы новые теги не сохраняются.
Итак, мой вопрос - , что является самым ясным способом сохранения новых элементов из окна выбора (тип поля сущности)?
Используйте Data Transformer для своего объекта. И в методе reverseTransform, если вы не найдете новую добавленную группу, просто создайте ее там вместо того, чтобы бросать TransformationFailedException.
Одним из возможных решений является использование FormEvents. Вот пример кода:
namespace AppBundle\Form;
use AppBundle\Entity\Tag;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\OptionsResolver\OptionsResolver;
class PostType extends AbstractType
{
/**
* @var ObjectManager
*/
private $manager;
/**
* Constructor
*
* @param ObjectManager $manager
*/
public function __construct(ObjectManager $manager)
{
$this->manager = $manager;
}
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title')
->add('content')
->add('tags')
;
$builder->get('tags')->addEventListener(
FormEvents::PRE_SUBMIT,
function (FormEvent $event) {
$choiceList = $event->getForm()->getConfig()->getAttribute('choice_list');
$array = is_null($event->getData()) ? [] : $event->getData();
$choices = $choiceList->getChoicesForValues($array);
if (count($choices) !== count($array)) {
$values = $choiceList->getValuesForChoices($choices);
$diff = array_merge(array_diff($values, $array), array_diff($array, $values));
foreach ($diff as $value) {
$new = new Tag($value);
$this->manager->persist($new);
$this->manager->flush();
$values[] = $new->getId();
}
$event->setData($values);
}
}
);
}
/**
* @param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Post'
));
}
}
Как описано в другом ответе, вы захотите использовать Data Transformer для своей сущности и вернуть новый объект, если вы 't найдите тот, который пользователь попросил.
Есть несколько способов сделать это. Это один из способов сделать это, упрощенное из приложения, которое просто использует selectize.js
, но эти концепции применимы к любому из них, которые могут быть у вас на вашем интерфейсе.
class SubjectTransformer implements DataTransformerInterface
{
protected $em;
public function __construct($em)
{
$this->em = $em;
}
//public function transform($val) { ... }
public function reverseTransform($str)
{
$repo = $this->em->getRepository('AppBundle:Subject');
$subject = $repo->findOneByName($str);
if($subject)
return $subject;
//Didn't find it, so it must be new
$subject = new Subject;
$subject->setName($str);
$this->em->persist($subject);
return $subject;
}
}
В частности, это DataTransformer
для entry_type
поля CollectionType
:
reverseTransform
, использует EM для извлечения значения из базы данныхДругие возможные варианты включают не вызов em->persist
; вызов em->flush
; или (возможно, идеально) передачу службы для управления поиском/созданием, а не напрямую с помощью диспетчера сущностей. (Такая служба может реализовать почти дублированное обнаружение, фильтрацию на плохом языке, только дать определенным пользователям возможность создавать новые теги и т.д.).