Data validation is one of the most important parts of all Software Applications. If you are using PHP Symfony Framework, you have available a very powerful yet easy to use validator* component that validates objects against constraints. There is a large set of Constraints ready to use and even you can define your own custom constraints when using this component.

If you are implementing an Application as a Rest Api I recommend you first  take a look at Using DTOs in PHP Symfony

Using DTOs and the Param Converter in Symfony allows you to do Data validation in a very easy way, and very fast to implement, even if you have a large set of Api Endpoints consuming many different payloads, because you are validating all payloads in the same function.

Make a DTO to represent the structure of the payload. This is the first thing to validate, that the payload in the Api Request, most likely a json string, has exactly the same structure as defined in the DTO. I do this using JMS Serializer

...
        $json = $request->getContent();
        $class = $configuration->getClass();

        //Try to deserialize the request dto
        try {
            $dto = $this->serializer->deserialize($json, $class, AppConstants::API_SERIALIZE_FORMAT);
        } catch (\Throwable $e) {
            $jsonMessage = $this->customValidationServiceProvider->getJsonErrorMessage(json_last_error());
            throw new JsonException($e->getMessage() . ' more info:' . $jsonMessage);
        }
...

In here the JMS Serializer will try to map the json string in the body of the request (payload) into its corresponding DTO class.  In this case

AppConstants::API_SERIALIZE_FORMAT

 is 'json' format.

If the de-serialization is successful, then we make the validator component to validate the DTO:

...
        //If there are validation errors, throw validation exception
        $errors = $this->validator->validate($dto);
        if (!empty($errors)) {
            foreach ($errors as $error) {
                $this->validationErrors[] = array('field' => $error->getPropertyPath(), ' message' => $error->getMessage());
            }
            if (!empty($this->validationErrors)) {
                throw new PreconditionFailedException($this->serializer->serialize($this->validationErrors, AppConstants::API_SERIALIZE_FORMAT));
            }
        }
...

 

 The message set for each error thrown is the one you declared in each constraint in the DTO:

...
    /**
     * @var string valid Email
     * @Assert\NotNull
     * @Assert\NotBlank(message="Email Address required")
     * @Assert\Email(message="Invalid Email Address")
     * @JMS\Type("string")
     * @JMS\SerializedName("email")
     */
    public $email;
...

 Most types of validation constraints* are already available including one that allows you to define a regular expression with your own rules:

...
    /**
     * @var string Role Name
     * @Assert\NotNull
     * @Assert\NotBlank(message="Role name cannot be blank")
     * @Assert\Regex(pattern="/^[\w]+$/", message="Role names can only contain letters, digits and underscore")
     * @JMS\Type("string")
     * @JMS\SerializedName("name")
     */
    public $name;
...

 

No matter if the DTO has a complex structure and several nested DTOs... This validation works exactly the same way as shown above saving you tons of hours on implement data validation.

 More articles on this topic:

  1. How to serialize nested Dtos in PHP Symfony 6+
  2. How to deserialize and validate nested Dtos in Symfony 6+

 

* Read more on the validator component

 

Related Videos