This Symfony Bundle provides request objects support for Symfony controller actions.
Require the bundle with composer:
composer require nelexa/request-dto-bundle
To specify an object as an argument of a controller action, an object must implement one of 4 interfaces:
\Nelexa\RequestDtoBundle\Dto\QueryObjectInterface
query parameters for GET or HEAD request methods.\Nelexa\RequestDtoBundle\Dto\RequestObjectInterface
request parameters for POST, PUT or DELETE request methods (ex. Content-Type: application/x-www-form-urlencoded) or query parameters for GET and HEAD request methods.\Nelexa\RequestDtoBundle\Dto\RequestBodyObjectInterface
for POST, PUT, DELETE request body contents (ex. Content-Type: application/json).\Nelexa\RequestDtoBundle\Dto\ConstructRequestObjectInterface
for mapping a request for a data transfer object in the class constructor.
Create request DTO:
use Nelexa\RequestDtoBundle\Dto\RequestObjectInterface;
use Symfony\Component\Validator\Constraints as Assert;
class UserRegistrationRequest implements RequestObjectInterface
{
/** @Assert\NotBlank() */
public ?string $login = null;
/**
* @Assert\NotBlank()
* @Assert\Length(min="6")
*/
public ?string $password = null;
/**
* @Assert\NotBlank()
* @Assert\Email()
*/
public ?string $email = null;
}
Use in the controller:
<?php
declare(strict_types=1);
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Validator\ConstraintViolationListInterface;
class AppController extends AbstractController
{
/**
* @Route("/sign-up", methods={"POST"})
*/
public function registration(
UserRegistrationRequest $userRegistrationRequest,
ConstraintViolationListInterface $errors
): Response {
$data = ['success' => $errors->count() === 0];
if ($errors->count() > 0){
$data['errors'] = $errors;
}
else{
$data['data'] = $userRegistrationRequest;
}
return $this->json($data);
}
}
If you declare an argument with type \Symfony\Component\Validator\ConstraintViolationListInterface
as nullable, then if there are no errors, it will be null
.
...
/**
* @Route("/sign-up", methods={"POST"})
*/
public function registration(
UserRegistrationRequest $userRegistrationRequest,
?ConstraintViolationListInterface $errors
): Response {
return $this->json(
[
'success' => $errors === null,
'errors' => $errors,
]
);
}
...
If the argument \Symfony\Component\Validator\ConstraintViolationListInterface
is not declare, then the exception \Nelexa\RequestDtoBundle\Exception\RequestDtoValidationException
will be thrown, which will be converted to the json
or xml
format.
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class AppController extends AbstractController{
/**
* @Route("/sign-up", methods={"POST"})
*/
public function registration(UserRegistrationRequest $userRegistrationRequest): Response {
return $this->json(['success' => true]);
}
}
Send POST request:
curl 'https://127.0.0.1/registration' -H 'Accept: application/json' -H 'Content-Type: application/x-www-form-urlencoded' --data-raw 'login=johndoe'
Response:
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json
Content response:
{
"type": "https://tools.ietf.org/html/rfc7807",
"title": "Validation Failed",
"detail": "password: This value should not be blank.\nemail: This value should not be blank.",
"violations": [
{
"propertyPath": "password",
"title": "This value should not be blank.",
"parameters": {
"{{ value }}": "null"
},
"type": "urn:uuid:c1051bb4-d103-4f74-8988-acbcafc7fdc3"
},
{
"propertyPath": "email",
"title": "This value should not be blank.",
"parameters": {
"{{ value }}": "null"
},
"type": "urn:uuid:c1051bb4-d103-4f74-8988-acbcafc7fdc3"
}
]
}
use Nelexa\RequestDtoBundle\Dto\ConstructRequestObjectInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\ConstraintViolationListInterface;
class ExampleDTO implements ConstructRequestObjectInterface
{
/** @Assert\Range(min=1) */
private int $page;
/**
* @Assert\NotBlank
* @Assert\Regex("~^\d{10,13}$~", message="Invalid phone number")
*/
private string $phone;
public function __construct(Request $request)
{
$this->page = $request->request->getInt('p', 1);
// sanitize phone number
$phone = (string) $request->request->get('phone');
$phone = preg_replace('~\D~', '', $phone);
$this->phone = (string) $phone;
}
public function getPage(): int
{
return $this->page;
}
public function getPhone(): string
{
return $this->phone;
}
}
class AppController extends AbstractController
{
public function exampleAction(
ExampleDTO $dto,
ConstraintViolationListInterface $errors
): Response {
$data = [
'page' => $dto->getPage(),
'phone' => $dto->getPhone(),
'errors' => $errors,
];
return $this->json($data, $errors->count() === 0 ? 200 : 400);
}
}
Changes are documented in the releases page.
The MIT License (MIT). Please see LICENSE for more information.