Search code examples
symfony4api-platform.com

Symfony validation doesnt' work with Api Platform when using custom action class


I'm using symfony 4 and Api Platform 2.5 . I have created a custom operation to change user password.

The problem is that when I send an empty oldPassword or any other required attributes,the validation doesn't work. As you can see, I used a validation group named "change_password".

This is a part of Entity User :

/**
 * @ApiResource(
 *     itemOperations={
 *         "user_change_password"={
 *             "method"="PUT",
 *             "path"="/users/change-password/{id}",
 *             "controller"=UserChangePassword::class,
 *             "formats"={"json"},
 *             "denormalization_context"={"groups"={"user:change-password"}},
 *             "normalization_context"={"groups"={"user:read"}},
 *             "validation_groups"={"change_password"},
 *             "access_control"="is_granted('EDIT', object)",
 *             "access_control_message"="access denied"
 *         },
 *     },
 * )
 *
 */
class User implements UserInterface, \Serializable, EquatableInterface
{
    /**
     * @Assert\NotBlank(
     *     message=Tthis value should not be blank",
     *     groups={"change_password"}
     * )
     * @SecurityAssert\UserPassword(
     *     message = "Wrong value for your current password",
     *     groups={"change_password"}
     * )
     * @Groups({"user:change-password"})
     */
    private $oldPassword;

    /**
     * @Assert\NotBlank(
     *     message="This value should not be blank",
     *     groups={"registration", "change_password"}
     * )
     * @Assert\Length(
     *     min="6",
     *     max="32",
     *     minMessage="Password must be at least ({{ limit }}) characters.",
     *     maxMessage="Password must have a maximum of ({{ limit }}) characters.",
     *     groups={"registration", "change_password"}
     * )
     * @Groups({"user:write", "user:change-password"})
     */
    private $plainPassword;

    /**
     * @Assert\NotBlank(
     *     message="This value should not be blank",
     *     groups={"registration", "change_password"}
     * )
     * @Assert\Expression(
     *     "this.getPlainPassword() == this.getRetypedPlainPassword()",
     *     groups={"registration", "change_password"}
     * )
     * @Groups({"user:write", "user:change-password"})
     */
    private $retypedPlainPassword;

}

This is the custom operation class :

class UserChangePassword
{

    public function __invoke(User $data)
    {
        $errors = $this->validator->validate($data);
        if (count($errors) > 0) {
            throw new ValidationException($errors);
        }

        dd($errors) ;

        $oldPassword = $data->getOldPassword();
        if ($this->passwordEncoder->isPasswordValid($data, $oldPassword)) {
            // do changes
        }

        return $data;

    }
}

The output of dd($erros) when the oldPassword is empty :

enter image description here

And without dd($erros) I get this error :

enter image description here


Solution

  • First

    For v2.5+ replace access_control to security. And replace access_control_message to security_message.

    Second

    Try add Default group in you'r validation_groups if you don't use custom controller:

    "validation_groups"={"Default", "change_password"},
    

    Or add Default group in constraint:

    //src/Entity/User.php
    
    @Assert\NotBlank(groups={"Default", "change_password"})
    

    and call validate:

    <?php
    // src/Controller/Api/CustomClass.php
    
    declare(strict_types=1);
    
    namespace App\Controller\Api;
    
    use ApiPlatform\Core\Validator\ValidatorInterface;
    use App\Entity\User;
    
    class CustomClass extends AbstractController
    {
        /**
         * @var ValidatorInterface
         */
        private $validator;
    
        public function __construct(ValidatorInterface $validator) {
            $this->validator = $validator;
        }
    
        public function __invoke(User $data): User
        {
            $this->validator->validate($data);
    
            //other code ...
        }
    }
    
    

    Also you can check this Url

    https://symfony.com/doc/current/validation/groups.html

    So the solution can be like this without adding "Default" :

        $errors = $this->validator->validate($data, null, ['change_password']);
        if (count($errors) > 0) {
            throw new ValidationException($errors);
        }