【问题标题】:[Symfony][Form] Add validator/constraint to property only if it has changed[Symfony][Form] 仅在属性发生更改时才向属性添加验证器/约束
【发布时间】:2014-06-24 08:14:15
【问题描述】:

我有以下场景:我正在验证约会,并且有一个自定义验证器,它告诉用户他选择的日期是否有效。如果该日期已被另一个实体阻止,则该日期无效。这在添加新实体时完美无缺。

现在我想仅在日期本身已更改时才在编辑时触发日期验证。因此,仅更改约会的标题不应验证日期。

我的实体类:

use Doctrine\ORM\Mapping as ORM;
use Acme\Bundle\Validator\Constraints as AcmeAssert;

/**
 * Appointment
 *
 * @ORM\Entity
 * @AcmeAssert\DateIsValid
 */
class Appointment
{
  /**
   * @ORM\Column(name="title", type="string", length=255)
   *
   * @var string
   */
  protected $title;

  /**
   * @ORM\Column(name="date", type="date")
   *
   * @var \DateTime
   */
  protected $date;
}

验证器类(用作服务):

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
/**
 * Validates the date of an appointment.
 */
class DateIsValidValidator extends ConstraintValidator
{
    /**
     * {@inheritdoc}
     */
    public function validate($appointment, Constraint $constraint)
    {
        if (null === $date = $appointment->getDate()) {
            return;
        }

        /* Do some magic to validate date */
        if (!$valid) {
            $this->context->addViolationAt('date', $constraint->message);
        }
    }
}

对应的Constraint类被设置为以实体类为目标。

use Symfony\Component\Validator\Constraint;

/**
 * @Annotation
 */
class DateIsValid extends Constraint
{
    public $message = 'The date is not valid!';

    /**
     * {@inheritdoc}
     */
    public function getTargets()
    {
        return self::CLASS_CONSTRAINT;
    }

    /**
     * {@inheritdoc}
     */
    public function validatedBy()
    {
        return 'acme.validator.appointment.date';
    }
}

现在我找不到依赖日期更改的干净方法。我可以简单地跟踪我的实体中的旧日期,但如果我想实现更复杂的约束,那感觉不是一个合适的解决方案。 :[

干杯

【问题讨论】:

  • 要知道字段是否已更改,您应该使用 UnitOfWork。我不知道是否可以在验证上下文中使用它。
  • 有几个关于(错误)以这种方式使用 UOW 的主题,我倾向于不接触它。

标签: forms validation symfony constraints


【解决方案1】:

从 symfony 2.3 开始你可以使用Form Events 来解决这个问题。通过在表单创建时存储(和克隆)原始实体,我将更改检查代码添加到我的 FormType。

然后添加了一个 POST_SUBMIT 事件侦听器来检查字段是否已更改。侦听器可以将验证错误添加到您的字段。

use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormError;
use Acme\Bundle\Entity\Appointment;

class AppointmentType extends AbstractType
{
    private $originalAppointment;

    public function __construct(Appointment $original)
    {
        // save the original entity 
        $this->originalAppointment = clone $original;
    }

    // ...

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        // define your fields

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

    public function dateCheckListener(FormEvent $event)
    {
        $appointment = $event->getData();
        $form        = $event->getForm();

        // if no appointments exist, we can skip the check
        if (empty($appointment) || empty($this->originalAppointment)) {
            return;
        }

        if ($appointment->getDate() !== $this->originalAppointment->getDate()) {

            // the dates changed, you can call your validator here

            if ('dates are not valid') {
                $form->get('date')->addError(new FormError('We have a problem.'));
            }
        }
    }
}

在您的控制器中,您可以使用原始约会创建此 formType:

$appointment = $this->getYourAppointmentSomehow();

$form = $this->createForm(new AppointmentType($appointment), $appointment);

【讨论】:

    【解决方案2】:

    也许您想尝试使用 preUpdate-Listener 而不是自定义验证约束?

    教义文档中的Section 10.5.4 给出了验证侦听器“ValidCreditCardListener”的示例。

    我知道这不适用于自动表单验证,但我认为这是最快的方式。

    编辑: 另一种选择可能是对约会类的日期字段使用@UniqueEntiy 约束。这不会破坏表单验证,但会导致额外的数据库查询(据我所知)

    【讨论】:

    • preUpdate-Listener 在某些情况下可能很方便,但我关心的是“抛出异常以中断刷新事件”的部分。因此,这将被排除在常规验证过程之外,您需要格外小心。 UniqueEntity 不起作用,因为我从 ACME 代码中删除了一些比“每天只有一次约会”更复杂的逻辑。
    【解决方案3】:

    也许您会发现这篇文章很有用,可用于检查更改了哪些属性。在 symfony 中一切皆有可能。您最终可能会编写实体侦听器、侦听器解析器等。事情可以变得非常先进。

    http://docs.doctrine-project.org/en/latest/reference/change-tracking-policies.html

    注意setter方法:

    public function setData($data)
    {
        if ($data != $this->data) {
            $this->_onPropertyChanged('data', $this->data, $data);
            $this->data = $data;
        }
    }
    

    你看到诀窍了吗?:)

    我还会使用 !== 运算符来检查变量类型。

    您还可以简化事情。您不需要调用 _onPropertyChanged,而是调用该函数,该函数会将属性“dateChanged”设置为 true。然后使用方法:

    public function getGroupSequence()
    {
        if($this->dateChanged)
        {
            return ['date_check'];
        }
        else
        {
            return false;
        }
    }
    

    同时告诉你的类它实现了GroupSequenceProviderInterface

    然后,您可以在您的 validation.yml 中使用验证组。

    【讨论】:

    • 是的,我偶然发现了ChangeTrackingPolicy,但我仍然不相信在每个设置器中添加内部侦听器和破解跟踪是一种可取的方法。感谢GroupSequenceProviderInterface 的提醒。不知道这一点,这可能会变得非常方便。
    猜你喜欢
    • 2015-01-11
    • 1970-01-01
    • 2014-02-03
    • 2022-01-14
    • 2014-08-10
    • 2018-03-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多