动态表格上的Symfony数据变换器

时间:2015-09-08 16:14:53

标签: php symfony

我正在使用FOSRestBundle构建一个API,用于将产品添加到购物篮中。为了保持这个例子的简单,我们有一系列不同尺寸的产品。

我希望能够在JSON请求中指定大小代码。例如:

{
    "product": 3,
    "size": "S"
}

(我也想使用产品代码而不是数据库ID,但那是另一天!)

项目的其他部分我使用数据转换器完成了类似的任务,但这些是更简单的形式,其中值不会根据其他字段选择值而改变。

所以我目前的篮子形式......

class BasketAddType extends AbstractType
{
    protected $em;

    public function __construct(EntityManager $em)
    {
        $this->em = $em;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('product', 'entity', [
                'class' => 'CatalogueBundle:Product',
            ]);

        $builder->get('product')->addEventListener(FormEvents::POST_SUBMIT, [$this, 'onPostSubmit']);
    }

    public function onPostSubmit(FormEvent $event)
    {
        // this will be Product entity
        $form = $event->getForm();

        $this->addElements($form->getParent(), $form->getData());
    }

    protected function addElements(FormInterface $form, Product $product = null)
    {
        if (is_null($product)) {
            $sizes = [];
        } else {
            $sizes = $product->getSizes();
        }

        $form
            ->add('size', 'size', [
                'choices' => $sizes
            ]);
    }

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

我上面使用的自定义大小表单类型是我可以添加模型转换器。如答案https://stackoverflow.com/a/19590707/3861815

中所示
class SizeType extends AbstractType
{
    protected $em;

    public function __construct(EntityManager $em)
    {
        $this->em = $em;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->addModelTransformer(new SizeTransformer($this->em));
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults([
            'class' => 'CatalogueBundle\Entity\Size'
        ]);
    }

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

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

最后是变压器。

class SizeTransformer implements DataTransformerInterface
{
    protected $em;

    public function __construct(EntityManager $em)
    {
        $this->em = $em;
    }

    public function transform($size)
    {
        if (null === $size) {
            return '';
        }

        return $size->getCode();
    }

    public function reverseTransform($code)
    {
        // WE NEVER GET HERE?
        $size = $this->em->getRepository('CatalogueBundle:Size')
        ->findOneByCode($code);

        if (null === $size) {
            throw new TransformationFailedException('No such size exists');
        }

        return $size;
    }
}

所以我在exit;中快速reverseTransform并且它从未被触发,所以我总是会在size元素上收到有关它无效的错误。

将数据转换器放到大小字段上的最佳方法是什么?

2 个答案:

答案 0 :(得分:0)

这是一个依赖字段
我有一个与类别实体有关的产品实体,与实体有关 这是我的添加产品表格的代码

class ProductsType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $propertyPathToCategory = 'category';
        $builder
                ->add('title')
                ->add('description','textarea')
                ->add('collection','entity', array(
                        'class' => 'FMFmBundle:Collections',
                        'empty_value'   => 'Collection',
                        'choice_label' => 'title'
                        ))
                ->addEventSubscriber(new AddCategoryFieldSubscriber($propertyPathToCategory));
    }
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'FM\FmBundle\Entity\Products'
        ));
    }
    public function getName()
    {
        return 'fm_fmbundle_products';
    }
}

AddCategoryFieldSubscriber

//Form/EventListener
class AddCategoryFieldSubscriber implements EventSubscriberInterface
{
    private $propertyPathToCategory;

    public function __construct($propertyPathToCategory)
    {
        $this->propertyPathToCategory = $propertyPathToCategory;
    }

    public static function getSubscribedEvents()
    {
        return array(
                FormEvents::PRE_SET_DATA  => 'preSetData',
                FormEvents::PRE_SUBMIT    => 'preSubmit'
        );
    }

    private function addCategoryForm($form, $collection_id)
    {
        $formOptions = array(
                'class'         => 'FMFmBundle:Categories',
                'empty_value'   => 'Category',
                'label'         => 'Category',
                'attr'          => array(
                'class' => 'Category_selector',
                ),
                'query_builder' => function (EntityRepository $repository) use ($collection_id) {
                $qb = $repository->createQueryBuilder('category')
                ->innerJoin('category.collection', 'collection')
                ->where('collection.id = :collection')
                ->setParameter('collection', $collection_id)
                ;

                return $qb;
                }
                );

        $form->add($this->propertyPathToCategory, 'entity', $formOptions);
    }

    public function preSetData(FormEvent $event)
    {
        $data = $event->getData();
        $form = $event->getForm();

        if (null === $data) {
            return;
        }

        $accessor    = PropertyAccess::createPropertyAccessor();

        $category        = $accessor->getValue($data, $this->propertyPathToCategory);
        $collection_id = ($category) ? $category->getCollection()->getId() : null;

        $this->addCategoryForm($form, $collection_id);
    }

    public function preSubmit(FormEvent $event)
    {
        $data = $event->getData();
        $form = $event->getForm();

        $collection_id = array_key_exists('collection', $data) ? $data['collection'] : null;

        $this->addCategoryForm($form, $collection_id);
    }
}

在控制器中添加新动作

public function SelectCategoryAction(Request $request)
    {
        $collection_id = $request->get('collecton_id');
        $em = $this->getDoctrine()->getManager();
        $categories = $em->getRepository('FMFmBundle:Categories')->findByCollection($collection_id);
        $Jcategories=array();
        foreach($categories as $category){
            $Jcategories[]=array(
                    'id' => $category->getId(),
                    'title' => $category->getTitle()
                    );
        }
        return new JsonResponse($Jcategories);
    }

为行动添加新路线

select_category:
    path:     /selectcategory
    defaults: { _controller: FMFmBundle:Product:SelectCategory }

和一些ajax

$("#collectionSelect").change(function(){
        var data = {
            collecton_id: $(this).val()
        };
        $.ajax({
            type: 'post',
            url: 'selectcategory',
            data: data,
            success: function(data) {
                var $category_selector = $('#categorySelect');
                $category_selector.empty();

                for (var i=0, total = data.length; i < total; i++)
                    $category_selector.append('<option value="' + data[i].id + '">' + data[i].title + '</option>');

            }
        });
    });

参考文献:

Dependent Forms

答案 1 :(得分:0)

所以问题是我在使用模型数据转换器时使用的是实体类型而不是文本类型。

这是我的工作代码,虽然可能并不完美,但主要形式

class BasketAddType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('product', 'entity', [
                'class' => 'CatalogueBundle:Product'
            ]);

        $builder->get('product')->addEventListener(FormEvents::POST_SUBMIT, [$this, 'onPostSubmit']);
    }

    public function onPostSubmit(FormEvent $event)
    {
        $form = $event->getForm()->getParent();
        $product = $event->getForm()->getData();

        $form
            ->add('size', 'size', [
                'sizes' => $product->getSizes()->toArray() // getSizes() is an ArrayCollection
            );
    }

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

我的自定义表单大小类型,它使用提供的大小选项应用模型转换器。

class SizeType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->addModelTransformer(new SizeTransformer($options['sizes']));
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setRequired([
            'sizes'
        ]);
    }

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

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

最后是尺寸变换器。

class SizeTransformer implements DataTransformerInterface
{
    protected $sizes;

    public function __construct(array $sizes)
    {
        $this->sizes = $sizes;
    }

    public function transform($size)
    {
        if (null === $size) {
            return '';
        }

        return $size->getCode();
    }

    public function reverseTransform($code)
    {
        foreach ($this->sizes as $size) {
            if ($size->getCode() == $code) {
                return $size;
            }
        }

        throw new TransformationFailedException('No such size exists');
    }
}

如果每种产品都有大量的尺寸,这种解决方案就不会很好用。猜测是否是这种情况我需要将EntityManager和产品都传递到变换器中并相应地查询DB。