Symfony 2 Transformer by Object Form Type

I am trying to create a new form type in Symfony 2. It is based on an entity type, it uses select2 in the interface and I need the user to be able to select an existing object or create a new one.

My idea was to send an entity identifier and allow it to be converted to the default entity type if the user selects an existing object or sends something like "_new: entered text" if the user enters a new value. Then this line should be converted to a new form object using my own model transformer, which should look something like this:

<?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',
            ))

...

+4
2

, , , . , 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

All Articles