Symfony 2 Transformer на типе сущности

Я пытаюсь создать новый тип формы в Symfony 2. Он основан на типе сущности, он использует выбор2 на веб-интерфейсе, и мне нужно, чтобы пользователь мог выбрать существующую сущность или создать новую.

Моя идея состояла в том, чтобы отправить идентификатор сущности и позволить ему быть преобразованным в тип сущности по умолчанию, если пользователь выбрал существующую сущность, или отправить что-то вроде «_new: введенный текст», если пользователь вводит новое значение. Затем эта строка должна быть преобразована в новую сущность формы моим собственным преобразователем модели, который должен выглядеть примерно так:

<?php
namespace Acme\MainBundle\Form\DataTransformer;

use Symfony\Component\Form\DataTransformerInterface;

class EmptyEntityTransformer
implements DataTransformerInterface
{
private $entityName;
public function __construct($entityName)
{
$this->entityName = $entityName;
}
public function transform($val)
{
return $val;
}
public function reverseTransform($val)
{
$ret = $val;
if (substr($val, 0, 5) == '_new:') {
$param = substr($val, 5);
$ret = new $this->entityName($param);
}
return $ret;
}
}

К сожалению, трансформатор вызывается только тогда, когда выбран существующий объект. Когда я ввожу новое значение, строка отправляется в запросе, но метод reverseTransform преобразователя не вызывается вообще.

Я новичок в Symfony, поэтому даже не знаю, правильный ли этот подход. Есть ли у вас идеи, как это решить?

редактировать:
Мой код типа формы:

<?php

namespace Acme\MainBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Acme\MainBundle\Form\DataTransformer\EmptyEntityTransformer;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;

class Select2EntityType
extends AbstractType
{
protected $router;
public function __construct(Router $router)
{
$this->router = $router;
}
/**
* {@inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
parent::setDefaultOptions($resolver);
$resolver->setDefaults(array(
'placeholder' => null,
'path' => false,
'pathParams' => null,
'allowNew' => false,
'newClass' => false,
));
}

public function getParent()
{
return 'entity';
}

public function getName()
{
return 's2_entity';
}

public function buildForm(FormBuilderInterface $builder, array $options)
{
if ($options['newClass']) {
$transformer = new EmptyEntityTransformer($options['newClass']);
$builder->addModelTransformer($transformer);
}
}

public function buildView(FormView $view, FormInterface $form, array $options)
{
$field = $view->vars['name'];
$parentData = $form->getParent()->getData();
$opts = array();
if (null !== $parentData) {
$accessor = PropertyAccess::createPropertyAccessor();
$val = $accessor->getValue($parentData, $field);
if (is_object($val)) {
$getter = 'get' . ucfirst($options['property']);
$opts['selectedLabel'] = $val->$getter();
}
elseif ($choices = $options['choices']) {
if (is_array($choices) && array_key_exists($val, $choices)) {
$opts['selectedLabel'] = $choices[$val];
}
}
}

$jsOpts = array('placeholder');

foreach ($jsOpts as $jsOpt) {
if (!empty($options[$jsOpt])) {
$opts[$jsOpt] = $options[$jsOpt];
}
}
$view->vars['allowNew'] = !empty($options['allowNew']);
$opts['allowClear'] = !$options['required'];
if ($options['path']) {
$ajax = array();
if (!$options['path']) {
throw new \RuntimeException('You must define path option to use ajax');
}
$ajax['url'] = $this->router->generate($options['path'], array_merge($options['pathParams'], array(
'fieldName' => $options['property'],
)));
$ajax['quietMillis'] = 250;
$opts['ajax'] = $ajax;
}
$view->vars['options'] = $opts;
}
}

а затем я создаю этот тип формы:

class EditType
extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('masterProject', 's2_entity', array(
'label' => 'Label',
'class' => 'MyBundle:MyEntity',
'property' => 'name',
'path' => 'my_route',
'pathParams' => array('entityName' => 'name'),
'allowNew' => true,
'newClass' => '\\...\\MyEntity',
))

Спасибо за ваши предложения

5

Решение

Я думаю, что нашел ответ, однако я не совсем уверен, является ли это правильным решением. Когда я попытался понять, как работает EntityType, я заметил, что он использует EntityChoiceList для получения списка доступных параметров, и в этом классе есть метод getChoicesForValues, который вызывается при преобразовании идентификаторов в сущности. Поэтому я реализовал свой собственный ChoiceList, который добавляет мой собственный класс в конец возвращаемого массива:

<?php
namespace Acme\MainBundle\Form\ChoiceList;

use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;class EmptyEntityChoiceList
extends EntityChoiceList
{
private $newClassName = null;
public function __construct(ObjectManager $manager, $class, $labelPath = null, EntityLoaderInterface $entityLoader = null, $entities = null,  array $preferredEntities = array(), $groupPath = null, PropertyAccessorInterface $propertyAccessor = null, $newClassName = null)
{
parent::__construct($manager, $class, $labelPath, $entityLoader, $entities, $preferredEntities, $groupPath, $propertyAccessor);
$this->newClassName = $newClassName;
}
public function getChoicesForValues(array $values)
{
$ret = parent::getChoicesForValues($values);
foreach ($values as $value) {
if (is_string($value) && substr($value, 0, 5) == '_new:') {
$val = substr($value, 5);
if ($this->newClassName) {
$val = new $this->newClassName($val);
}
$ret[] = $val;
}
}
return $ret;
}
}

Регистрация этого ChoiceList в типе формы немного сложна, потому что имя класса исходного списка выбора жестко закодировано в DoctrineType, который расширяет EntityType, но нетрудно понять, как это сделать, если вы посмотрите на этот класс.

Причина, по которой DataTransformer не вызывается, вероятно, заключается в том, что EntityType способен возвращать массив результатов, и преобразование применяется к каждому элементу этой коллекции. Если результирующий массив пуст, очевидно, нет элемента для вызова преобразователя.

0

Другие решения

У меня был точно такой же вопрос, как и у вас, я решил использовать FormEvent с еще DataTransformer

Идея состоит в том, чтобы переключить тип поля (объект один) непосредственно перед отправкой.

public function preSubmit(FormEvent $event)
{
$data = $event->getData();
$form = $event->getForm();
if (substr($data['project'], 0, 5) == '_new:') {
$form->add('project', ProjectCreateByNameType::class, $options);
}
}

Это заменит project поле с новым пользовательским перед отправкой, если это необходимо.

ProjectCreateByNameType может продлить TextField и должен добавить DataTransformer,

0

По вопросам рекламы [email protected]