首页 > 解决方案 > 通过 Symfony Forms & Constraints 进行复杂的域验证

问题描述

我有几个复杂的域对象。我使用 Symfony 表单来验证对它们的 REST 请求。假设,我有域Banner而且我有用于通过 ID 使用BannerFormType创建/更新具体横幅的端点。横幅有限制:每日和总计。业务规则是“两个限制都是必需的,但每天应该小于或等于总数”。所以我们有一种情况,我们应该使用自定义类约束来验证两个相互链接的字段。

所以,我的表格看起来像:

<?php

declare(strict_types=1);

namespace App;

class BannerFormType extends \Symfony\Component\Form\AbstractType
{
    public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('daily_limit', \Symfony\Component\Form\Extension\Core\Type\IntegerType::class, [
                'constraints' => [
                    new \Symfony\Component\Validator\Constraints\NotBlank(),
                ],
            ])
            ->add('total_limit', \Symfony\Component\Form\Extension\Core\Type\IntegerType::class, [
                'constraints' => [
                    new \Symfony\Component\Validator\Constraints\NotBlank(),
                ],
            ])
        ;
    }

    public function configureOptions(\Symfony\Component\OptionsResolver\OptionsResolver $resolver): void
    {
        parent::configureOptions($resolver);

        $resolver->setDefaults([
            'data_class' => Banner::class,
            'constraints' => [
                new BannerLimits(),
            ],
        ]);
    }
}

我们该约束的代码如下所示:

<?php

declare(strict_types=1);

namespace App;

class BannerLimitsValidator extends \Symfony\Component\Validator\ConstraintValidator
{
    public function validate($banner, \Symfony\Component\Validator\Constraint $constraint): void
    {
        if (!$banner instanceof Banner) {
            throw new \Symfony\Component\Validator\Exception\UnexpectedTypeException($banner, Banner::class);
        }

        if (!$constraint instanceof BannerLimits) {
            throw new \Symfony\Component\Validator\Exception\UnexpectedTypeException($constraint, BannerLimits::class);
        }

        /** @var \Symfony\Component\Form\FormInterface $form */
        $form = $this->context->getObject();

        $totalLimit = $form->get('total_limit')->getNormData();
        $dailyLimit = $form->get('daily_limit')->getNormData();

        if ($totalLimit < $dailyLimit) {
            $this->context
                ->buildViolation('Daily limit should be less or equal than total.')
                ->atPath('daily_limit')
                ->addViolation();
        }
    }
}

一切正常,除了我们有一个非常耦合的验证器。如果我们想让它重用于其他形式,就会出现问题。例如,我想添加批量端点来更新横幅,因此它将使用BannerListFormType

当然,我可以通过选项在约束中传递字段数据,但是对于嵌套表单来说很难做到。另外,我可以直接在表单事件中调用验证器,但它看起来很奇怪。

此外,使用我的方法,由于与表单结构的耦合约束,很难使用\Symfony\Component\Validator\Test\ConstraintValidatorTestCase对验证器进行单元测试。

你能给我什么建议?你们是怎么解决这样的问题的?

标签: phpsymfonyvalidationsymfony-formssymfony-validator

解决方案


推荐阅读