Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symfony form validation vs validator service

I'm having a strange issue and I think I'm missing something.

I'm using Symfony 2.7 and I'm working on a user password update function.

I have a user entity (Extends FosUserBundle user entity) with multiple properties. One of them is the plainPassword (ofcourse not persisted to DB).

User.php

...

/**
 * @ORM\Table(name="prefix_users")
 * @ORM\Entity(repositoryClass="UserRepository")
 *
 * @UniqueEntity(fields={"email"}, message="unique", groups={"registration"})
 * @UniqueEntity("username", message="unique", groups={"registration"})
 *
 * @ExclusionPolicy("all")
 */
class User extends BaseUser
{

...

    /* @var string
     *
     * @Assert\NotBlank(message="blank",groups={"accountUpdate", "passwordUpdate"})
     * @Assert\Length(min=8, minMessage="short", max=4096, maxMessage="long")
     */
    protected $plainPassword;

As you can see I'm using annotations for the validation of my properties. I'm also using validation groups to be sure only the correct properties will be validated.

I have created a form

UserUpdatePasswordType.php

class UserUpdatePasswordType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('plainPassword', 'repeated', array(
            'type' => 'password',
            'invalid_message' => 'password_mismatch',
        ));
    }

    /**
     * @param OptionsResolverInterface $resolver
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'My\Bundle\UserBundle\Entity\User',
            'csrf_protection' => false,
            'intention' => 'resetting',
            /**
             * Need to add method to prevent this form from bein marked as invalid.
             * @see http://sroze.io/2013/10/30/symfony2-form-patch-requests-and-handlerequest-form-never-valid/
             */
            'method' => 'PATCH'
        ));
    }

I'm building a restful API with FOSRestBundle and I'd like to provide a nice and clean output if form validation fails.

As far as I know I can validate a form in two ways:

  1. $formErrors = $this->get('validator')->validate($user, null, ['passwordUpdate']);
  2. $form->isValid() and to get errors $form->getErrors()

Now the strange part comes, the two methods above give different validation errors.

Request parameters

user_update_password[plainPassword][first]:sffsdfdsfds
user_update_password[plainPassword][second]:fdsfdsfsd

Response from $formErrors = $this->get('validator')->validate($user, null, ['passwordUpdate']); The response is not correct, because the plainPassword is not blank.

{
      "property_path": "plainPassword",
      "message": "blank"
}

Response form $form->isValid() which seems to look better

"form": {
      "children": {
        "plainPassword": {
          "children": {
            "first": {
              "errors": [
                "password_mismatch"
              ]
            },
            "second": {}
          }
        }
      }
    },
    "errors": []

The only difference I can see is the difference that I'm not providing a validation group name in the $form->isValid().

Why am I getting different results and what should I do to fix? Can I provide a validation group name to the $form->isValid() or should I fix a problem with the $validator->validate code?

I would love to see how this is managed in other Symfony2 based APIs...

like image 329
ivodvb Avatar asked Sep 23 '15 15:09

ivodvb


2 Answers

There're three reasons of difference.

  1. You probably don't set data from request to model when validating by service. Use $form->submit($request) as a safe proxy data setter
  2. You don't use validation groups in form (do it in setDefaults) BTW. Your @Assert\Length won't be used until you add groups to this annotation or add validation group Default to service/form validators array
  3. You used repeated type in form, which have additional Constraints in comparison to your model Constraints. Form validator checks for both model and form constraints belonging to passed validation groups (by default 'Default' group is used)
like image 102
Maciej Pyszyński Avatar answered Oct 27 '22 17:10

Maciej Pyszyński


Actually, there is another difference (besides using of validation group). When you are using Form (rather than just a Validator), there is a 'repeated' type involved.

Validation

One of the key features of the repeated field is internal validation (you don't need to do anything to set this up) that forces the two fields to have a matching value. If the two fields don't match, an error will be shown to the user.

So actually in one case you're using an additional constraint, and at another you are not. No surprise validations give different results.

So in your case I would probably make own implementation of "repeat" for a password in the User object and would use a validator service.

Another option that I see is to use a Form object to validate User, but it's not a clean and clear way to make API.

like image 41
Dmitry Malyshenko Avatar answered Oct 27 '22 17:10

Dmitry Malyshenko