phpsymfonymodel-view-controllercode-structuresymfony-validator

How validate data from post in restful api


I need to validate some data before insert into the database, for that i create a little service that return invalid fields from an entity. It works ok when validating single entities.

class EntityValidator
{
    protected $validator;

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

    public function validate($entity)
    {
        $errors = $this->validator->validate($entity);
        $response = null;
        if ($errors->count()) {
            foreach ($errors as $error) {
                $response[$error->getPropertyPath()] = $error->getMessage();
            }
        }

        return $response;
    }
}

But I have been struggling to validate more complex problems for example: This is a restful api endpoint that receives a json with user_id and percentage in the body of the post, it will validate the entity to see if it's right as mapped with symfony validator constraints.

public function create(Request $request, EntityValidator $entityValidator)
{
    $data = json_decode($request->getContent(), true);
    $entityExample = new EntityExample();
    $entityExample
         ->setUserId($data['user_id'])
         ->setPercentage($data['percentage'])
    ;
    $errors = $entityValidator->validate($entityExample);
    // .. do other things ..
    return new JsonResponse($errors);    
}

But lets say that I receive an array of data and I going to insert many row at a time and there's a business logic that say "the sum of percentage of a user need to be 100"

public function create(Request $request, EntityValidator $entityValidator)
{
    $data = json_decode($request->getContent(), true);
    $totalPercentage = 0;
    foreach ($data as $element) {
         $entityExample = new EntityExample();
         $entityExample
             ->setUserId($element['user_id'])
             ->setPercentage($element['percentage'])
         ;
         $totalPercentage += $element['percentage'];
    }
    $errors = $entityValidator->validate($entityExample);
    if ($totalPecentage != 100) {
        $errors[] = 'Sum of percentage must be 100';
    }
    // .. do other things ..
    return new JsonResponse($errors);    
}

It seems wrong keep this kind of business logic inside controller but I don't know where put it, should I create a service just for that? then every endpoint that has more complex validation will be create a new service?


Solution

    1. Create a model representation of your JSON request payload. A model with public properties and nothing else. e.g. Let's say model is called Sale.
    2. Create a custom Validation Constraint which would be wired with Sale model. In this validation class you would iterate through Sale.percentage property and run your validation logic.
    3. In your controller, you call serializer component and validator component to validate the request.

    Full examples for each points above:

    1. Both links below have model examples but if you want more examples just do ctrl+f json in this page http://www.inanzzz.com/index.php/posts/symfony
    2. Class level custom assert validation constraint in symfony
    3. A simple way of handling request, response and exceptions in Symfony API. Copy and do not touch AbstractController. Do what UserController::create does for your own controller. He is using $this->data for demonstration purposes in same controller but you should pass it to a service and process it there.