2

I want to use custom validators on various query parameters in my controller. The docs give this example:

 // validate a query parameter (a hash in this case)
    $incomingHashConstraint = new CustomAssert\IncomingHash();

    // use the validator to validate the value
    // If you're using the new 2.5 validation API (you probably are!)
    $errorList = $this->get('validator')->validate(
        $incomingHash,
        $incomingHashConstraint
    );

    if (0 === count($errorList)) {
        // ... this IS a valid hash
    } else {
        // this is *not* a valid hash
        $errorMessage = $errorList[0]->getMessage();

        // ... do something with the error
        throw $this->createNotFoundException('Not a valid hash ID ' . $incomingHash);
    }

It is pretty clunky to use this in a lot of controllers. Ideally I'd be able to use a custom validator as a requirement in the route, but that doesn't seem to be an option. Should these validators be a service? Ideally I'd want something like

if(!isValid($incomingHash, IncomingHashConstraint)) {
   throw \Exception(); }

Any suggestions on the best way to organize this? Thanks!

3
  • 1
    Well if you don't want to repeat all that logic in lots of controllers, then how about using a event listener? Like, before filter and instead of checking for multiple controller instances, you can add a simple interface to those controllers only and apply your logic. Commented Nov 28, 2015 at 11:39
  • 3
    instead of event listener you can define one controller with needed logic and then extends your controllers from it. Commented Nov 28, 2015 at 13:43
  • 1
    Have a look at the example below and especially the full example link in it which does what exactly @SergioIvanuzzo says above and I agree with him! Commented Nov 28, 2015 at 22:02

1 Answer 1

4

There is a very easy and clean way of doing it.

  1. The Request payload gets mapped to your custom model class and validated against you custom validation class.
  2. If there is an error then you get the list of errors.
  3. If there is no error then you get your model class back with all the payload data are mapped to the relevant properties in it then you use that model class for further logic(you should do extra logic in a service class not in controller).

I'll give a working example but if you want a full example then it is here. If you apply it, you'll have a very very thin controller. Literally no more than 15 lines. This way you'll have what exactly what @SergioIvanuzzo said above.

INSTALL jms/serializer-bundle Doc

composer require jms/serializer-bundle

// in AppKernel::registerBundles()
$bundles = array(
    // ...
    new JMS\SerializerBundle\JMSSerializerBundle(),
    // ...
);

CUSTOM MODEL CLASS

namespace Application\FrontendBundle\Model;

use Application\FrontendBundle\Validator\Constraint as PersonAssert;
use JMS\Serializer\Annotation as Serializer;

/**
 * @PersonAssert\Person
 */
class Person
{
    /**
     * @var int
     * @Serializer\Type("integer")
     */
    public $id;

    /**
     * @var string
     * @Serializer\Type("string")
     */
    public $name;

    /**
     * @var string
     * @Serializer\Type("string")
     */
    public $dob;

    /**
     * @var string
     * @Serializer\Type("string")
     */
    public $whatever;
}

YOUR CUSTOM VALIDATOR CLASSES

namespace Application\FrontendBundle\Validator\Constraint;

use Symfony\Component\Validator\Constraint;

/**
 * @Annotation
 */
class Person extends Constraint
{
    public function getTargets()
    {
        return self::CLASS_CONSTRAINT;
    }

    public function validatedBy()
    {
        return get_class($this).'Validator';
    }
}

namespace Application\FrontendBundle\Validator\Constraint;

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

class PersonValidator extends ConstraintValidator
{
    public function validate($person, Constraint $constraint)
    {
        if (!is_numeric($person->id)) {
            $this->context->buildViolation('Id must be a numeric value.')->addViolation();
        }

        if ($person->name == 'Acyra') {
            $this->context->buildViolation('You name is weird.')->addViolation();
        }

        if ($person->dob == '28/11/2014') {
            $this->context->buildViolation('You are too young.')->addViolation();
        }

        // I'm not interested in validating $whatever property of Person model!
    }
}

CONTROLLER

If you don't use your controller as a service then you can access validator and serializer services directly with $this->get('put_the_name_here') like you did above.

...
use JMS\Serializer\SerializerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
....

/**
 * @Route("person", service="application_frontend.controller.bank")
 */
class PersonController extends Controller
{
    private $validator;
    private $serializer;

    public function __construct(
        ValidatorInterface $validator,
        SerializerInterface $serializer
    ) {
        $this->validator = $validator;
        $this->serializer = $serializer;
    }

    /**
     * @param Request $request
     *
     * @Route("/person")
     * @Method({"POST"})
     *
     * @return Response
     */
    public function personAction(Request $request)
    {
        $person = $this->validatePayload(
            $request->getContent(),
            'Application\FrontendBundle\Model\Person'
        );
        if ($person instanceof Response) {
            return $person;
        }

        print_r($person);
        // Now you can carry on doing things in your service class
    }

    private function validatePayload($payload, $model, $format = 'json')
    {
        $payload = $this->serializer->deserialize($payload, $model, $format);

        $errors = $this->validator->validate($payload);
        if (count($errors)) {
            return new Response('Some errors', 400);
        }

        return $payload;
    }
}

EXAMPLES

Request 1

{
  "id": 66,
  "name": "Acyraaaaa",
  "dob": "11/11/1111",
  "whatever": "test"
}

Response 1

Application\FrontendBundle\Model\Person Object
(
    [id] => 66
    [name] => Acyraaaaa
    [dob] => 11/11/1111
    [whatever] => test
)

Request 2

{
  "id": "Hello",
  "name": "Acyra",
  "dob": "28/11/2014"
}

Response 2

400 Bad request
Some errors

If you go to link I gave you above and apply the rest then you would actually get proper error messages like:

{
    "errors": {
        "id": "Id must be a numeric value.",
        "name": "You name is weird.",
        "dob": "You are too young."
    }
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.