我在Symfony的表单验证处理中遇到了一些问题。我想根据其数据验证绑定到实体的表单。有相当多的信息如何动态修改表单字段使用FormEvents。关于这个主题,我缺少的是如何控制/修改验证。
我的简化用例是:
- 用户可以添加一个事件到日历。
- 验证检查是否已经存在事件。
- 如果发生碰撞,验证将抛出错误。
- 用户现在应该可以忽略这个错误/警告。
验证是作为Validator
实现的,Constraint::CLASS_CONSTRAINT
是目标(因为它考虑了更多的东西)。
I tried to:
- 绕过验证组,但无法找到访问实体范围验证器的权限。
- Hack周围的
FormEvents
和添加一个额外的字段,如"忽略日期警告"。绕过提交按钮,将其更改为"强制提交"之类的内容。
…但从来没有找到一个有效的解决方案。即使是使用基于单个属性的验证器的更简单的方法也行不通。(
是否有一个Symfony的方式来动态控制验证?
编辑:我的代码看起来像这样:use DoctrineORMMapping as ORM;
use AcmeBundleValidatorConstraints as AcmeAssert;
/**
* Appointment
*
* @ORMEntity
* @AcmeAssertDateIsValid
*/
class Appointment
{
/**
* @ORMColumn(name="title", type="string", length=255)
*
* @var string
*/
protected $title;
/**
* @ORMColumn(name="date", type="date")
*
* @var DateTime
*/
protected $date;
}
作为服务使用的验证器:
use SymfonyComponentValidatorConstraint;
use SymfonyComponentValidatorConstraintValidator;
/**
* 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 SymfonyComponentValidatorConstraint;
/**
* @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';
}
}
编辑2:尝试与FormEvents
…我还尝试了所有不同的项目。
$form = $formFactory->createBuilder()
->add('title', 'text')
->add('date', 'date')
->addEventListener(FormEvents::WHICHONE?, function(FormEvent $event) {
$form = $event->getForm();
// WHAT TO DO HERE?
$form->getErrors(); // Is always empty as all events run before validation?
// I need something like
if (!$dateIsValid) {
$form->setValidationGroup('ignoreWarning');
}
});
编辑3:约束声明正确。这不是问题所在:
services:
validator.acme.date:
class: AcmeBundleValidatorConstraintsDateValidator
arguments: ["@acme.other_service"]
tags:
- { name: validator.constraint_validator, alias: acme.validator.appointment.date }
验证是在实体上完成的,所有表单所做的就是执行对象的验证。您可以根据提交的数据选择组
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'validation_groups' => function(FormInterface $form) {
$data = $form->getData();
if (EntityClient::TYPE_PERSON == $data->getType()) {
return array('person');
} else {
return array('company');
}
},
));
}
我在嵌入式表单上使用这种方法时遇到了问题&&cascade-validation
编辑:使用flash来确定是否必须进行验证
// service definition
<service id="app.form.type.callendar" class="%app.form.type.callendar.class%">
<argument type="service" id="session" />
<tag name="form.type" alias="my_callendar" />
</service>
// some controller
public function somAvtion()
{
$form = $this->get('app.form.type.callendar');
...
}
// In the form
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'validation_groups' => function(FormInterface $form) {
$session = $form->getSession();
if ($session->getFlashBag()->get('callendar_warning', false)) {
return array(false);
} else {
return array('Validate_callendar');
}
},
));
}
用户如何与应用程序交互以告诉它忽略警告?有什么额外的按钮吗?在这种情况下,您可以简单地检查用于提交表单的按钮或添加某种隐藏字段(ignore_validation
)等。无论你最终从哪里获得用户输入(flash和依赖注入,基于提交的数据等),我都会使用验证组和闭包来确定要验证的内容(就像juanmf在他的回答中解释的那样)。
对于第二种方法(表单事件),你可以给事件监听器添加一个优先级:正如你在Symfony的表单验证事件监听器中看到的,它们使用FormEvents::POST_SUBMIT
来启动验证过程。如果你只是添加一个事件监听器,它会在验证监听器之前被调用,所以验证还没有发生。如果您为侦听器添加负优先级,您应该还能够访问表单验证错误:
$builder->addEventListener(FormEvents::POST_SUBMIT, function(){...}, -900);
老问题但是…
我首先会在表单中添加一个字段(acceptCollision),正如你和上面的其他答案所建议的。
那么验证器就可以这样做:
public function validate($appointment, Constraint $constraint)
{
if (null === $date = $appointment->getDate()) {
return;
}
if ($appointment->getAcceptCollision()) {
$valid = true;
} elseif (
// Check Unicity of the date (no collision)
) {
$valid = true;
} else {
$valid = false;
}
if (!$valid) {
$this->context->addViolationAt('date', $constraint->message);
}
}
我认为你遇到了问题,因为你使用了错误的概念。应该运行哪个验证的决定属于控制器,而不是验证器。
所以我只需在控制器中检查提交按钮被按下(或者是否有一个复选框被选中)并切换验证组。然而,表单在视觉上应该是不同的,所以我可能会为这两种状态创建两个表单(都扩展一个基本表单或一个使用选项的表单类型)。