Home > Backend Development > PHP Tutorial > Creating focused domain applications. A Symfony approach (Managing validation errors)

Creating focused domain applications. A Symfony approach (Managing validation errors)

Susan Sarandon
Release: 2024-11-10 17:23:02
Original
469 people have browsed it

Creating focused domain applications. A Symfony approach (Managing validation errors)

Introduction

In the last post, we analyzed how the serializer and validator symfony components acted as infrastructure services providing us with tools that help us to perform common tasks in applications. We also learned why the UserInputDTO class was the element that belonged to our domain since it contained business rules and how to create an application layer service to perform the extracting and validating data flow.

In this second part, we are going to see how to manage validating errors and, as we did in the first part, we will identify which parts belong to the domain.

The Validation errors

The validation errors are returned by the Symfony validator component after validating the UserInputDTO following the rules established by using the validation constrains.

public function processData(string $content, string $dtoClass): object
{
     $requestData = json_decode($content, true);
     $userInputDTO = $serializer->denormalize($requestData, UserInputDTO::class);
     $errors = $validator->validate($userInputDTO);
     if(count($errors) > 0) {
         throw new ValidationFailedException($errors);
     }

     return $userInputDTO
}
Copy after login
Copy after login

As you can see in the code above, if the validation method finds errors, an exception of the type ValidationException is thrown. From here, we must decide how we want to show the errors to the user (domain / business rules) and what tools we will rely on so that the errors reach the user correctly (infrastructure and application).

Centralizing the catch of validation errors

The first thing we have to take into account is that we want to catch the validation errors whenever they occur. To achieve this, we will rely to the infrastructure layer.
The Symfony Kernel comes with a set of built-in kernel events to listen for special events. One of this event is the kernel exception event which is triggered when an exception is thrown. Let's use it for catching the ValidationException errors.

class KernelSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return [
            KernelEvents::EXCEPTION => 'onException'
        ];
    }

    public function onException(ExceptionEvent $event): void
    {
        $exception = $event->getThrowable();
        if($exception instanceof ValidationFailedException){
            // Business rules to build the errors
        }
    }
}
Copy after login
Copy after login

As we can see in the code above, the KernelSubscriber keeps listening to the KernelException event and it will only execute some logic when the catched exception is an instance of the ValidationFailedException class.
From here, we must define the logic that will be executed when the onException method detects that it is a validation error.

Creating a domain service to construct the errors array

As we are in charge of deciding how we are going to structure the errors (we define those business rules), the service which performs the logic will belong to our domain. Let's code it

class ValidationErrorsBuilder {

    public function buildErrors(ValidationFailedException $exception): array
    {
        $errors = [];
        foreach ($exception->getViolations() as $violation) {
            $errors[$violation->getPropertyPath()] = $violation->getMessage();
        }

        return $errors;
    }
}
Copy after login
Copy after login

The ValidationErrorsBuilder code is pretty simple: it loops the violation errors and creates an associative array where the keys are the properties which generated an error and the values are the error messages.

Using the ValidationErrorsBuilder

Now it's time for using our ValidationErrorsBuilder domain service. We are using it on the KernelSubscriber onException method.

public function processData(string $content, string $dtoClass): object
{
     $requestData = json_decode($content, true);
     $userInputDTO = $serializer->denormalize($requestData, UserInputDTO::class);
     $errors = $validator->validate($userInputDTO);
     if(count($errors) > 0) {
         throw new ValidationFailedException($errors);
     }

     return $userInputDTO
}
Copy after login
Copy after login

As you can see, after knowing that the exception is a ValidationFailedException, we use our domain service to get the validation errors array.
Now, let's see the following code:

class KernelSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return [
            KernelEvents::EXCEPTION => 'onException'
        ];
    }

    public function onException(ExceptionEvent $event): void
    {
        $exception = $event->getThrowable();
        if($exception instanceof ValidationFailedException){
            // Business rules to build the errors
        }
    }
}
Copy after login
Copy after login

We have added and new line where we set a Symfony JsonResponse holding the errors array as a new response and we specify that the returned HTTP code will be a 400 Bad Request.
We have been relied on the Symfony Response HTTP_BAD_REQUEST constant to specify the response HTTP code. As we are working in a Domain focused environment, we could have been created our custom domain class (for instance a php enum) but, as we only need to handle standard HTTP codes and there is no specific need for customization, we can use the Symfony HTTP codes although this makes us depend a little more on the framework.

And the application layer ?

We have not talked about the application layer so far. We have said at the beginning of the article that the Symfony framework comes with a useful built-in events such as the one we have used: The kernel exception event. Moreover, the symfony framework also offers us the EventSubscriberInterface by which we can create our custom event subscribers and listen to the events we need.
From this information, we can conclude that symfony offers us the kernel exception event and the EventSubscriberInterface but we have to use the interface to create the subscriber specifying which events we are going to listen to. Let's resume:

  • The event subscriber specifies that we listen to the Kernel Exception event.
  • The event subscriber checks whether the exception is an instance of the ValidationFailedException.
  • The event subscriber uses the domain service to build the errors array.
  • The event subscriber creates de JsonResponse holding the errors and sets it as a final response.

Does this sound familiar to you? Yes, the event subscriber is responsible for orchestration and coordination of managing the validation errors after the exception is thrown so we could say that the event subscriber would act as an application service.
If we want to go one step further, we could create an application layer service and use it in the subscriber.

class ValidationErrorsBuilder {

    public function buildErrors(ValidationFailedException $exception): array
    {
        $errors = [];
        foreach ($exception->getViolations() as $violation) {
            $errors[$violation->getPropertyPath()] = $violation->getMessage();
        }

        return $errors;
    }
}
Copy after login
Copy after login
public function onException(ExceptionEvent $event): void
{
    $exception = $event->getThrowable();
    if($exception instanceof ValidationFailedException){
       $errors = $this->validationErrorsBuilder->buildErrors($exception);
    }
}
Copy after login

Now, the ValidationErrorsProcessor would act as an application service coordinating the validation errors response management and using the ValidationErrorsBuilder domain service.

Conclusion

In this second article of this series, we have identified what components of the validation errors management process belong to the domain, which elements of the infrastructure we have used and how the Kernel subscriber can act as the application service.
In the next article, we will persist the entity in the database and we will analyze how to separate the logic of transforming the DTO into a persistible entity.

The above is the detailed content of Creating focused domain applications. A Symfony approach (Managing validation errors). For more information, please follow other related articles on the PHP Chinese website!

source:dev.to
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Latest Articles by Author
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template