Installation:
composer require devouted/request-mapper
Ensure your project meets the requirements (PHP ≥ 8.2, Symfony 6.4+).
First Use Case: Create a DTO (Data Transfer Object) with annotated constructor parameters. Example:
use RequestMapper\Attribute\FromQuery;
class CreateUserRequest
{
public function __construct(
#[FromQuery]
public string $name,
#[FromQuery]
public string $email,
) {}
}
Integration:
Register the RequestMapper service in your Symfony kernel or DI container:
// config/services.yaml
services:
RequestMapper\Serializer\Denormalizer\RequestMapperDenormalizer:
tags: [serializer.normalizer]
Usage in Controller:
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Serializer\SerializerInterface;
public function createUser(Request $request, SerializerInterface $serializer)
{
$dto = $serializer->deserialize($request, CreateUserRequest::class, 'request_mapper');
// Use $dto->name, $dto->email...
}
Request Parameter Mapping:
Use attributes (FromQuery, FromHeader, FromPath, FromUploads) to map request data to DTO properties.
class UpdateProfileRequest
{
public function __construct(
#[FromQuery(name: 'page')]
public int $page = 1,
#[FromHeader(name: 'X-API-Key')]
public string $apiKey,
) {}
}
Nested Objects:
Combine with Symfony’s Serializer to handle nested DTOs.
class UserProfile
{
public function __construct(
#[FromQuery]
public string $bio,
#[FromQuery]
public Address $address,
) {}
}
class Address
{
public function __construct(
#[FromQuery]
public string $street,
) {}
}
File Uploads: Map uploaded files to properties.
class UploadMediaRequest
{
public function __construct(
#[FromUploads]
public array $images = [],
#[FromPath]
public int $albumId,
) {}
}
Validation Integration:
Pair with Symfony’s Validator for runtime validation.
use Symfony\Component\Validator\Constraints as Assert;
class LoginRequest
{
#[FromQuery]
#[Assert\NotBlank]
public string $username;
#[FromQuery]
#[Assert\NotBlank]
public string $password;
}
Custom Formatters: Extend the package by creating custom formatters for non-standard request sources.
use RequestMapper\Formatter\FormatterInterface;
class CustomHeaderFormatter implements FormatterInterface
{
public function format($value, string $name, array $context): mixed
{
return strtoupper($value);
}
}
Attribute Order Matters:
If multiple attributes target the same source (e.g., FromQuery and FromHeader for the same parameter), the last declared attribute in the constructor takes precedence.
Missing Parameters:
Unmapped parameters in the request (e.g., a required FromQuery field missing) will throw a DenormalizationException. Handle gracefully with default values or custom exception handling.
File Uploads:
FromUploads expects files to be in the files key of the request. For custom file keys, use a custom formatter:
#[FromUploads(key: 'custom_files')]
public array $files;
Symfony Serializer Conflict:
Ensure the RequestMapperDenormalizer is registered after the default Symfony denormalizers to avoid conflicts.
PHP 8.2+ Features: Leverage PHP 8.2+ features like read-only properties for immutable DTOs:
class ImmutableRequest
{
public function __construct(
#[FromQuery]
public readonly string $name,
) {}
}
config/packages/serializer.yaml to debug deserialization:
framework:
serializer:
debug: true
RequestMapperDenormalizer passes the request object as context. Access it via:
$request = $context['request'];
Reuse DTOs:
Create reusable DTOs for common request patterns (e.g., PaginationRequest, AuthRequest).
Type Safety:
Use PHP 8.1+ union types or null defaults for optional fields:
#[FromQuery]
public string|null $optionalField = null;
Testing:
Mock the Request object in tests:
$request = new Request([], [], ['HTTP_ACCEPT_LANGUAGE' => 'en']);
$dto = $serializer->deserialize($request, GetArticleQuery::class, 'request_mapper');
Performance: For high-traffic APIs, cache serialized DTOs if the request structure is static.
Extending Attributes:
Create custom attributes by extending RequestMapper\Attribute\AbstractAttribute:
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_PARAMETER)]
class FromCookie extends AbstractAttribute
{
public function getSource(): string
{
return 'cookie';
}
}
How can I help you explore Laravel packages today?