Symfony is a popular PHP framework that provides many features to simplify web development. One of these features is the ability to validate API requests using data transfer objects (DTOs) and annotations. In this article, we will see how to use this feature to create a simple API endpoint for creating a post with some related fields.
What is a DTO?
A DTO is a class that represents the data that is transferred between different layers of an application, such as the presentation layer and the business layer. A DTO can be used to map the values from the request, validate them, and pass them to the service layer. A DTO can also be used to return the data from the service layer to the presentation layer.
How to create a DTO in Symfony?
To create a DTO in Symfony, we just need to create a class with some public properties that will hold the values from the request. We can also use annotations to define the validation rules for each property, such as NotBlank
, Type
, Range
, etc. These annotations are provided by the Symfony Validator component, which is a powerful tool for validating data in Symfony.
Please ensure that all the required packages are installed before starting.install all necessary packages before starting:
composer require symfony/serializer
composer require symfony/validator
For example, let's create a DTO class for creating a post with some related fields, such as title, content, author, category, and tags. We can use the following code to create the class:
<?php
namespace App\Dto;
use Symfony\Component\Validator\Constraints as Assert;
class CreatePostDto
{
public function __construct(
#[Assert\NotBlank]
#[Assert\Type('string')]
public readonly string $title,
#[Assert\NotBlank]
#[Assert\Type('string')]
public readonly string $content,
#[Assert\NotBlank]
#[Assert\Type('string')]
public readonly string $author,
#[Assert\NotBlank]
#[Assert\Type('string')]
public readonly string $category,
#[Assert\Type('array')]
public ?array $tags = null,
) {
}
}
As you can see, we have defined the properties of the DTO class and used annotations to specify the validation rules for each property. We have also used the readonly
modifier to make the properties immutable, which means they cannot be changed after the object is created. This is a good practice to ensure the consistency and integrity of the data.
How to use a DTO in a controller?
To use a DTO in a controller, we just need to use the class as a dependency in the method of the controller with the annotation #[MapRequestPayload]
. This annotation tells Symfony to automatically map the values from the request into the object of the DTO class. We can also use the #[Route]
annotation to define the route for the controller method.
For example, let's create a controller method for creating a post using the DTO class we created before. We can use the following code to create the method:
<?php
namespace App\Controller;
use App\Dto\CreatePostDto;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
use Symfony\Component\Routing\Annotation\Route;
class PostController extends AbstractController
{
#[Route('/api/v1/posts', name: 'app_api_create_post_v1', methods: ['POST'])]
public function v1Create(#[MapRequestPayload] CreatePostDto $createPost): JsonResponse
{
// Here we can use the $createPost object to pass the data to the service layer
// For simplicity, we will just return the data as a JSON response
return $this->json([
'response' => 'ok',
'title' => $createPost->title,
'content' => $createPost->content,
'author' => $createPost->author,
'category' => $createPost->category,
'tags' => $createPost->tags,
]);
}
}
As you can see, we have used the #[MapRequestPayload]
annotation to inject the CreatePostDto
object into the controller method. We have also used the #[Route]
annotation to define the route for the method, which is /api/v1/posts
with the POST
method. Inside the method, we can use the $createPost
object to access the data from the request and pass it to the service layer. For simplicity, we will just return the data as a JSON response using the json
method of the AbstractController
class.
How to test the API endpoint?
To test the API endpoint, we can use a tool like curl
to send a request to the endpoint with the data we want to create a post. We need to make sure to add the Content-Type: application/json
header to the request, as we are sending the data in JSON format. We can also use the -v
option to see the verbose output of the request and the response.
For example, let's send a request to the endpoint with the following data:
{
"title": null,
"content": "In this article, we will see how to use DTOs and annotations to validate API request in Symfony",
"author": "John Doe",
"category": "PHP",
"tags": ["Symfony", "API", "Validation"]
}
We can use the following command to send the request:
curl -v --request POST \
--url http://127.0.0.1:8001/api/v1/posts \
--header 'Content-Type: application/json' \
--data '{
"title": null,
"content": "In this article, we will see how to use DTOs and annotations to validate API request in Symfony",
"author": "John Doe",
"category": "PHP",
"tags": ["Symfony", "API", "Validation"]
}'
And here is the result:
< HTTP/1.1 422
< Content-Type: application/json
{
"type": "https:\/\/symfony.com\/errors\/validation",
"title": "Validation Failed",
"status": 422,
"detail": "This value should be of type unknown.\ntitle: This value should not be blank.",
"violations": [
{
"propertyPath": "title",
"title": "This value should not be blank.",
"template": "This value should not be blank.",
"parameters": {
"{{ value }}": "null"
},
"type": "urn:uuid:c1051bb4-d103-4f74-8988-acbcafc7fdc3"
}
]
}
As you can see, we have successfully created a post using the API endpoint and received the data back as a JSON response.
Conclusion
In this article, we have seen how to use DTOs and annotations to validate API request in Symfony. This is a simple and elegant way to handle the data from the request, validate it, and pass it to the service layer. We have also seen how to use the #[MapRequestPayload]
annotation to automatically map the values from the request into the object of the DTO class. We have also seen how to test the API endpoint using curl.
We hope you have enjoyed this article and learned something new. If you have any questions or feedback, please feel free to leave a comment below. Thank you for reading!
Top comments (2)
Thanks, useful post.
In my opinion, it would be useful to expand the article with examples when the response is not json, but an html page. Where should a developer catch a validation exception and how to display a custom HTML error page?
Can we test the input DTO integrity with PHP Unit ?