在Symfony API中自动匹配property_path

时间:2019-11-28 13:45:47

标签: rest symfony formbuilder symfony-3.3

我正在用Symfony3构建一个REST-API。

作为示例,以下是使用FormBuilderInterface制作的表单中Price的API字段。下面的代码示例为ApiBundle/Form/PriceType.php

class PriceType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name', TextType::class, array(
                  'description' => 'Name',

              ))
             ->add('price_category', EntityPublicKeyTextType::class, array(
                'class' => 'MyCustomBundle:PriceCategory',
                'property_path' => 'priceCategory',
            ))

问题是关于具有以下内容的字段的良好响应消息:验证错误。对于默认的symfony类型(例如IntegerType,TextType),它可以自动找到property_path并向我发出有用的错误消息。这是API响应,但有两个错误:

  • name可以很好地解决(因为我看到了它涉及的领域,
    • 对于price_category,它无法解决(第二条消息)。
    {
      "name": [
        "This value is too long. It should have 50 characters or less."
      ],
      "0": "This value should not be null."
    }

解决问题。我为price_category字段添加了'property_path' => 'priceCategory'。 property_path的值与其中定义了变量protected $priceCategory;的BaseBundle / Entity / Price.php匹配。

添加property_path后,错误消息看起来还不错。

{
  "name": [
    "This value is too long. It should have 50 characters or less."
  ],
  "price_category": [
    "This value should not be null."
  ]
}

price_category的类是EntityPublicKeyTextType,它是从TextType中抽象出来的(它可以执行错误操作)。

因此,我有以下问题:为避免手动为所有字段添加property_path,我必须向继承的类EntityPublicKeyTextType中添加什么?

任何解决此问题的提示都非常欢迎

最佳内线

编辑:

EntityPublicKeyTextType:

class EntityPublicKeyTextType extends AbstractType
{
    /**
     * @var ObjectManager
     */
    private $om;

    /**
     * @param ObjectManager $om
     */
    public function __construct(ObjectManager $om)
    {
        $this->om = $om;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $transformer = new ObjectToPublicKeyTransformer(
            $this->om,
            $options['class'],
            $options['public_key'],
            $options['remove_whitespaces'],
            $options['multiple'],
            $options['string_separator'],
            $options['extra_find_by']
        );
        $builder->addModelTransformer($transformer);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver
            ->setRequired(array(
                'class',
                'public_key'
            ))
            ->setDefaults(array(
                'multiple' => false,
                'string_separator' => false,
                'extra_find_by' => array(),
                'remove_whitespaces' => true,
            ));
    }

    public function getParent()
    {
        return TextType::class;
    }

    public function getBlockPrefix()
    {
        return 'entity_public_key_text';
    }
}

ObjectToPublicKeyTransformer:

class ObjectToPublicKeyTransformer implements DataTransformerInterface
{
    /**
     * @var PropertyAccessorInterface
     */
    private $propertyAccessor;

    /**
     * @var ObjectManager
     */
    private $om;

    /**
     * @var string
     */
    private $class;

    /**
     * @var string|string[]
     */
    private $publicKey;

    /**
     * @var bool
     */
    private $removeWhitespaces;

    /**
     * @var boolean
     */
    private $multiple;

    /**
     * @var boolean|string
     */
    private $stringSeparator;

    /**
     * @var array
     */
    private $extraFindBy;

    public function __construct(
        ObjectManager $om,
        string $class,
        $publicKey,
        bool $removeWhitespaces,
        bool $multiple = false,
        $stringSeparator = false,
        array $extraFindBy = array(),
        PropertyAccessorInterface $propertyAccessor = null
    ) {
        $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
        $this->om = $om;
        $classMetadata = $om->getClassMetadata($class);
        $this->class = $classMetadata->getName();
        $this->publicKey = $publicKey;
        $this->stringSeparator = $stringSeparator;
        $this->multiple = $multiple;
        $this->extraFindBy = $extraFindBy;
        $this->removeWhitespaces = $removeWhitespaces;
    }

    /**
     * Transforms an object / Collection of objects to a publicKey string / array of publicKey strings.
     *
     * @param   object|Collection $object
     * @return  string|array
     */
    public function transform($object)
    {
        if (null == $object) {
            return null;
        }

        if (is_array($this->publicKey)) {
            $publicKey = $this->publicKey[0];
        } else {
            $publicKey = $this->publicKey;
        }

        if ($this->multiple) {
            if ($object instanceof Collection) {
                $values = array();

                foreach ($object as $objectItem) {
                    $values[] = (string)$this->propertyAccessor->getValue($objectItem, $publicKey);
                }

                if ($this->stringSeparator) {
                    return implode($this->stringSeparator, $values);
                }

                return $values;
            }
        } else {
            return (string)$this->propertyAccessor->getValue($object, $publicKey);
        }
    }

    /**
     * Transforms an publicKey string / array of public key strings to an object / Collection of objects.
     *
     * @param   string|array $value
     * @return  object|Collection
     *
     * @throws TransformationFailedException if object is not found.
     */
    public function reverseTransform($value)
    {
        if (null === $value) {
            return $this->multiple ? new ArrayCollection() : null;
        }

        if (is_array($this->publicKey)) {
            $publicKeys = $this->publicKey;
        } else {
            $publicKeys = array($this->publicKey);
        }

        if ($this->multiple) {
            if ($this->stringSeparator) {
                $value = explode($this->stringSeparator, $value);
            }

            if (is_array($value)) {
                $objects = new ArrayCollection();

                foreach ($value as $valueItem) {
                    foreach ($publicKeys as $publicKey) {
                        $object = $this->findObject($valueItem, $publicKey);

                        if ($object instanceof $this->class) {
                            $objects->add($object);
                            break;
                        }
                    }
                }

                return $objects;
            }
        }

        foreach ($publicKeys as $publicKey) {
            $object = $this->findObject($value, $publicKey);

            if ($object instanceof $this->class) {
                return $object;
            }
        }

        return $this->multiple ? new ArrayCollection() : null;
    }

    private function findObject($value, $publicKey)
    {
        if ($this->removeWhitespaces) {
            $value = str_replace(' ', '', $value);
        }
        $findBy = array_merge([$publicKey => $value], $this->extraFindBy);
        $object = $this->om->getRepository($this->class)->findOneBy($findBy);

        return $object;
    }
}

1 个答案:

答案 0 :(得分:0)

如果您还提供Price模型/实体类,这将很有用。似乎您在模型(priceCategory)中使用驼峰大小写作为属性名称,然后在表单(price_category)中使用蛇形大小写。

如果您对模型和表单使用相同的约定,则验证错误将自动映射到正确的属性。

解释是,Symfony的映射器仍然可以通过将蛇转换为驼峰式大小写,反之亦然来映射您的字段,这就是为什么即使不使用property_path选项,表单仍然可以工作并提交值的原因。但是问题是验证器不执行此映射,并且不能匹配正确的属性(price_category-> priceCategory)。