Php 基于动态表单的Symfony数据转换器

Php 基于动态表单的Symfony数据转换器,php,symfony,Php,Symfony,我正在使用FOSRestBundle构建一个API,用于将产品添加到篮子中。为了简化这个例子,我们有一系列不同尺寸的产品 我希望能够在JSON请求中指定大小代码。例如: { "product": 3, "size": "S" } (我也希望使用产品代码而不是数据库ID,但这是另一天!) 在项目的其他部分,我使用数据转换器完成了类似的任务,但这些是更简单的形式,其中的值不会根据其他字段选择的值进行更改 所以我现在的篮子形式 class BasketAddType extends

我正在使用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 '';
    }
}
我在上面使用的自定义大小表单类型是,这样我可以添加模型转换器。正如在这个答案中所发现的那样

最后是变压器

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;
    }
}
所以我快速退出了
reverseTransform
中,它从未被激发,因此我总是会在size元素上得到一个关于它无效的错误


在这里,将数据转换器放入大小字段的最佳方法是什么?

这是一个相关字段
我有一个与分类实体相关的产品实体,该分类实体与集合实体相关
这是我添加产品表单的代码

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>');

            }
        });
    });
$(“#collectionSelect”).change(函数(){
风险值数据={
collection_id:$(this.val()
};
$.ajax({
键入:“post”,
url:“selectcategory”,
数据:数据,
成功:功能(数据){
变量$category_selector=$('categorySelect');
$category_selector.empty();
对于(var i=0,total=data.length;i
参考资料:


因此,问题是在使用模型数据转换器时,我使用的是实体类型而不是文本类型

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');
    }
}
这是我的工作代码,虽然可能不是完美的,主要形式

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和产品都传递到transformer中,并相应地查询DB。

这与我目前所做的非常相似,但您仍然依赖于通过表单提交的实体主键。如果我移除数据转换器并提交带有大小主键的请求,例如。。。{“product”:3,“size”:21},它可以正常工作。