我已经创建了一个自定义表单验证器,它似乎不能工作。它在表单提交期间被调用,并在验证失败时返回false。但是它似乎并没有告诉表单验证失败。
(代码框滚动)<?php
namespace Redacted'AppBundle'Validator'Constraints;
use Symfony'Component'HttpFoundation'RequestStack;
use Symfony'Component'Validator'Constraint;
use Symfony'Component'Validator'ConstraintValidator;
class RequiredIfPlainPasswordSetValidator extends ConstraintValidator
{
/**
* RequestStack instance.
*
* @var Symfony'Component'HttpFoundation'RequestStack
*/
protected $requestStack;
/**
* dependency injection.
*
* @param Symfony'Component'HttpFoundation'RequestStack $requestStack
*/
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
}
/**
* if the plain password has been set, the current password must not be
* empty.
*
* @param string $currentPassword
* @param Symfony'Component'Validator'Constraint $constraint
*
* @return bool
*/
public function validate($currentPassword, Constraint $constraint)
{
$plainPassword = $this->requestStack->getCurrentRequest()->request
->get('security_settings')['plainPassword']['first'];
if ($plainPassword && !$currentPassword) {
return false;
}
return true;
}
}
下面是约束条件:
<?php
namespace Redacted'AppBundle'Validator'Constraints;
use Symfony'Component'Validator'Constraint;
/**
* @Annotation
*/
class RequiredIfPlainPasswordSet extends Constraint
{
/**
* error message
*
* @var string
*/
protected $message = 'Please enter your current password.';
/**
* the alias for the related validator in services.yml
*
* @return string
*/
public function validatedBy()
{
return 'required_if_plain_password_set';
}
}
和app/config/services.yml
的相关部分:
services:
redacted.appbundle.required_if_plain_password_set:
class: Redacted'AppBundle'Validator'Constraints'RequiredIfPlainPasswordSetValidator
arguments: ["@request_stack"]
tags:
- { name: validator.constraint_validator, alias: required_if_plain_password_set }
我已经创建了一个自定义表单类型:(代码框滚动)
<?php
namespace Redacted'AppBundle'Form;
use Symfony'Component'OptionsResolver'OptionsResolver;
use Symfony'Component'Form'FormBuilderInterface;
use Symfony'Component'Form'AbstractType;
class SecuritySettingsFormType extends AbstractType
{
/**
* the name of the form type
*
* @return string
*/
public function getName()
{
return 'security_settings';
}
/**
* add fields to form
*
* @param Symfony'Component'Form'FormBuilderInterface $formBuilder
* @param array $options
*/
public function buildForm(FormBuilderInterface $formBuilder, array $options)
{
$formBuilder
->add('email')
->add('currentPassword', 'password')
->add('plainPassword', 'repeated', [
'type' => 'password',
'invalid_message' => 'Passwords must match.',
]);
}
/**
* configureOptions.
*
* @param Symfony'Component'OptionsResolver'OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$options = [
'data_class' => 'AppBundle'Entity'User',
'validation_groups' => ['security_settings'],
];
$resolver->setDefaults($options);
}
}
由于data_type
而对User
实体进行验证。以下是User
实体的相关部分:
<?php
namespace Redacted'AppBundle'Entity;
use Doctrine'ORM'Mapping as ORM;
use Symfony'Component'Security'Core'User'UserInterface;
use Symfony'Component'Validator'Constraints as Assert;
use Redacted'AppBundle'Validator'Constraints'RequiredIfPlainPasswordSet;
class User implements UserInterface, 'Serializable
{
// ...
/**
* @var string (default: null)
*
* @Assert'NotBlank(message="user.email_required", groups={"security_settings"})
* @Assert'Email(message="security_settings.valid_email" groups={"security_settings"})
* @ORM'Column(name="email", type="string", length=255, nullable=true, unique=true)
*/
private $email = null;
/**
* current password - used for validation only
*
* @RequiredIfPlainPasswordSet(
* message="security_settings.current_password_required",
* groups={"security_settings"}
* )
* @var string
*/
protected $currentPassword;
// ... setters and getters for above, etc.
}
最后,这是我用来检查的控制器方法。我将省略视图,因为它可能无关紧要。(我将控制器定义为服务,因此没有redirectToRoute()
等)
// in the controller class...
/**
* Security settings page - email, password, etc.
*
* @Route("/security-settings", name="security_settings")
* @Template()
*
* @param Symfony'Component'HttpFoundation'Request $request
*
* @return array|Symfony'Component'HttpFoundation'RedirectResponse
*/
public function securitySettingsAction(Request $request)
{
$loggedInUser = $this->tokenStorage->getToken()->getUser();
$form = $this->formFactory
->create(new SecuritySettingsFormType(), $loggedInUser);
$form->handleRequest($request);
if ($form->isValid()) {
$user = $form->getData();
// persist the user
$this->saveUserSettings($user);
// set a success message
$this->setSecuritySettingsFlashSuccess($request);
// redirect back
$route = $this->router->generate('security_settings');
return new RedirectResponse($route);
}
return ['form' => $form->createView()];
}
这个想法是,只有在输入新密码时才需要当前密码。虽然validate()
函数被调用并在应该的时候返回false,但表单的isValid()
返回true并且正在保存。如果我将@Assert'NotBlank(groups={"security_settings"})
断言添加到User::currentPassword
字段,则将触发并成功失败,因此将查找该字段上的验证注释。
我错过了什么?
问题是ConstraintValidator
的validate()
方法不应该只返回true或false,它应该建立一个像这样的冲突:
/**
* if the plain password has been set, the current password must not be
* empty.
*
* @param string $currentPassword
* @param Symfony'Component'Validator'Constraint $constraint
*/
public function validate($currentPassword, Constraint $constraint)
{
$plainPassword = $this->requestStack->getCurrentRequest()->request
->get('security_settings')['plainPassword']['first'];
if ($plainPassword && !$currentPassword) {
$this->context->buildViolation($constraint->message)
->atPath('currentPassword')->addViolation();
}
}
谢谢我的同事发现了它。现在一切正常