在 Symfony API 中自动处理 property_path

问题描述 投票:0回答:1

我有一个在 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 的值与定义了 var
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 抽象出来的(它可以很好地处理错误)。

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

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

最佳内切

编辑:

EntityPublicKeyText类型:

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

对象到公共密钥转换器:

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;
    }
}
php rest symfony formbuilder symfony-3.3
1个回答
0
投票

如果您还提供

Price
模型/实体类,将会很有用。看来您在模型中使用驼峰式大小写作为属性名称 (
priceCategory
),然后在表单中使用蛇形大小写 (
price_category
)。

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

解释是,Symfony 的映射器仍然可以通过将蛇形大小写转换为驼峰大小写来映射您的字段,反之亦然,这就是为什么即使不使用

property_path
选项,您的表单仍然可以工作并提交值。但问题是验证器不执行此映射并且无法匹配正确的属性(price_category ->priceCategory)。

© www.soinside.com 2019 - 2024. All rights reserved.