ivodvb
ivodvb

Reputation: 1164

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...

Upvotes: 12

Views: 1920

Answers (3)

Maciej Pyszyński
Maciej Pyszyński

Reputation: 9374

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)

Upvotes: 7

Adam Englander
Adam Englander

Reputation: 59

The big difference is that one validator is validating the model and the other is validating the form data. The model requires the field to not be empty. The FOSUser bundle ChangePasswordFormType adds the UserPassword constraint to the form for validation. The UserPassword validator checks that the form values were properly submitted.

If you are using www-form-urlencoded data in your API, use the form validator. It will give more precise errors as they will be related to the state of the data submitted not the state of the data after it was transformed into the model.

Upvotes: -2

Dmitry Malyshenko
Dmitry Malyshenko

Reputation: 3051

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.

Upvotes: 2

Related Questions