Installation
Add the bundle to config/bundles.php:
DMP\RestBundle\RestBundle::class => ['all' => true],
Install via Composer:
composer require dmp/rest-bundle
First Controller
Create a DTO for request/response (e.g., src/DTO/TestDTO.php):
namespace App\DTO;
use Symfony\Component\Validator\Constraints as Assert;
class TestDTO {
#[Assert\NotBlank]
public string $name;
}
Route & Annotations
Define a controller (src/Controller/TestController.php):
use DMP\RestBundle\Annotation as Rest;
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
use Symfony\Component\Routing\Attribute\Route;
class TestController {
#[Route('/api/test', methods: ['POST'])]
#[Rest\Serializable(statusCode: 201)]
public function test(#[MapRequestPayload] TestDTO $request): TestDTO {
return $request; // Auto-serialized
}
}
Test It
Send a POST to /api/test with JSON body:
{"name": "Test"}
Expected response:
{"name": "Test"}
#[Route('/api/user', methods: ['POST'])]
#[Rest\Serializable]
public function createUser(#[MapRequestPayload] UserRequestDTO $request): UserResponseDTO {
$user = $this->userService->create($request);
return new UserResponseDTO($user);
}
#[MapRequestPayload] on parent objects.{
"errors": [
{"property": "name", "message": "This value should not be blank."}
]
}
#[Rest\Serializable] with a custom status code.#[MapQueryString] for GET requests:
#[Route('/api/search', methods: ['GET'])]
#[Rest\Serializable]
public function search(#[MapQueryString] SearchQueryDTO $query): SearchResultDTO {
return $this->searchService->execute($query);
}
Serializable to include metadata:
#[Rest\Serializable(statusCode: 200, metadata: ['pagination' => true])]
public function listUsers(): UserCollectionDTO { ... }
Response:
{
"data": [...],
"meta": {"pagination": {"total": 100}}
}
public function __construct(private UserService $userService) {}
Transactional trait for atomic operations.$dto = new TestDTO();
$dto->name = "Test";
$serializer = $this->container->get('serializer');
$json = $serializer->serialize($dto, 'json');
$this->assertEquals('{"name":"Test"}', $json);
client->request() with JSON payloads and assert responses.Hardcoded /api/ Prefix
/api/ or override the prefix in the bundle config (if exposed).DTO Serialization Issues
#[Groups({"api"})] with Symfony Serializer to avoid circular reference errors.#[SerializedName(ignore: true)] or use #[Groups({"api"})] selectively.Validation Overrides
ExceptionListener. Ensure your validator throws ValidationFailedException or extends ConstraintViolationListException.Exception Handling
RuntimeException) will return as:
{"errors": [{"message": "Exception message"}]}
To customize, create an event subscriber for kernel.exception.Query Parameter Conflicts
{id}), the query parameter will override the route parameter. Rename properties or use #[MapQueryString(alias: "custom_name")].Enable API Debugging
Add this to config/packages/dev/rest_bundle.yaml (if supported):
debug: true
This may expose detailed error traces in responses.
Log Validation Errors Configure Symfony’s validator to log errors:
# config/packages/validator.yaml
Symfony\Component\Validator\Validator\ValidatorInterface:
arguments:
$constraintValidatorFactory: '@validator.constraint_validator_factory'
$constraintValidatorFactories: []
$mapping: '@validator.mapping'
$listeners: ['@validator.listener.error_listener']
Check Event Dispatchers
The bundle may dispatch events (e.g., rest.exception). Subscribe to them for custom logic:
public static function getSubscribedEvents(): array {
return [
'rest.exception' => 'onRestException',
];
}
Custom Serialization Groups Extend the bundle’s serializer by adding custom groups to your DTOs:
#[Groups({"api", "admin"})]
public string $secretData;
Response Transformers Override the default response transformer by binding a custom service:
# config/services.yaml
services:
App\Rest\ResponseTransformer:
tags: ['rest.response_transformer']
Middleware Integration Add middleware to preprocess requests/responses:
public function onKernelRequest(RequestEvent $event) {
$request = $event->getRequest();
if ($request->isMethod('POST') && $request->getPathInfo() === '/api/test') {
$request->attributes->set('custom_flag', true);
}
}
Dynamic Route Prefixes
If the /api/ prefix is problematic, fork the bundle and modify DMP\RestBundle\Routing\Loader\RestRouteLoader to accept a configurable prefix.
DTO Caching
Cache DTO classes if they’re used frequently (e.g., with Symfony\Component\Cache\Adapter\AdapterInterface).
Batch Validation For bulk operations, validate DTOs in batches to reduce overhead:
$validator = $this->validator;
$errors = $validator->validate($dto1);
$errors = $validator->validate($dto2);
How can I help you explore Laravel packages today?