codememory/dto
Auto-hydrate PHP/Symfony DTOs from request/array data using rules and decorators. Supports name conversion (e.g., snake_case), enum casting via attributes, and event hooks during processing. Build a manager with caching and reflection for fast mapping.
Installation
composer require codememory/dto
Basic Manager Initialization
Create a service provider (e.g., DtoServiceProvider) to bootstrap the manager:
// app/Providers/DtoServiceProvider.php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Codememory\Dto\DataTransferObjectManager;
use Codememory\Dto\PropertyGrouper;
use Codememory\Dto\Factory\PropertyExecutionContextFactory;
use Codememory\Dto\NameConverter\SnakeCaseNameConverter;
use Codememory\Dto\Processors\ClassDecoratorProcessor;
use Codememory\Dto\Registrars\ClassDecoratorRegistrar;
use Codememory\Dto\Processors\PropertyDecoratorProcessor;
use Codememory\Dto\Registrars\DecoratorTypeRegistrar;
use Codememory\Dto\Registrars\PropertyDecoratorRegistrar;
use Codememory\Dto\Factory\ClassExecutionContextFactory;
use Codememory\Dto\Factory\PropertyWrapperFactory;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Codememory\Reflection\ReflectorManager;
class DtoServiceProvider extends ServiceProvider
{
public function register()
{
$cache = new FilesystemAdapter('codememory', [
'directory' => storage_path('framework/cache/codememory'),
]);
$reflectorManager = new ReflectorManager($cache);
$eventDispatcher = new EventDispatcher();
$this->app->singleton(DataTransferObjectManager::class, function () use ($reflectorManager, $eventDispatcher) {
return new DataTransferObjectManager(
$reflectorManager,
new PropertyGrouper(
new PropertyExecutionContextFactory(
new SnakeCaseNameConverter()
)
),
new ClassDecoratorProcessor(
new ClassDecoratorRegistrar(),
$eventDispatcher
),
new PropertyDecoratorProcessor(
new DecoratorTypeRegistrar(),
new PropertyDecoratorRegistrar(),
$eventDispatcher
),
new ClassExecutionContextFactory(
new PropertyWrapperFactory()
)
);
});
}
}
Register the provider in config/app.php under providers.
First Use Case: Hydrating a DTO Define a DTO class with decorators:
// app/Dtos/UserDto.php
namespace App\Dtos;
use Codememory\Dto\Decorators\Property;
class UserDto
{
public function __construct(
public string $name,
#[Property\ToEnum]
public UserRole $role,
#[Property\ToEnum(value: true)]
public UserStatus $status
) {}
}
enum UserRole { ADMIN, EDITOR, VIEWER }
enum UserStatus: string { ACTIVE = 'active', INACTIVE = 'inactive' }
Use the manager in a controller:
// app/Http/Controllers/UserController.php
use App\Dtos\UserDto;
use Codememory\Dto\DataTransferObjectManager;
class UserController extends Controller
{
public function store(Request $request, DataTransferObjectManager $manager)
{
$data = $request->validate([
'name' => 'required|string',
'role' => 'required|string',
'status' => 'required|string',
]);
$userDto = $manager->hydrate(UserDto::class, $data);
// Use $userDto...
}
}
SnakeCaseNameConverter automatically maps snake_case input to camelCase properties (e.g., foo_bar → $fooBar).class AddressDto {}
class UserDto {
public function __construct(public AddressDto $address) {}
}
$userDto = $manager->hydrate(UserDto::class, [
'address' => ['street' => '123 Main St']
]);
@Property\ToEnum, @Property\SymfonyValidation, etc., to transform or validate input.
#[Property\ToEnum]
public UserRole $role; // Converts 'ADMIN' string to UserRole::ADMIN
@Decorator\Class\* attributes.$validated = $request->validate([
'name' => 'required|string|max:255',
'role' => 'required|in:ADMIN,EDITOR,VIEWER',
]);
$dto = $manager->hydrate(UserDto::class, $validated);
FormRequest to hydrate DTOs directly:
use Codememory\Dto\DataTransferObjectManager;
class StoreUserRequest extends FormRequest
{
public function authorize(): bool { return true; }
public function rules(): array { return [...]; }
public function hydrate(UserDto $dto, DataTransferObjectManager $manager): UserDto
{
return $manager->hydrate(UserDto::class, $this->validated());
}
}
class UserService {
public function createUser(UserDto $dto) {
// Business logic using $dto
}
}
$eventDispatcher->addListener(
AfterProcessedTypeDecoratorsEvent::class,
fn($event) => $this->validateSymfonyConstraints($event)
);
@Property\Trim, @Property\DefaultValue).DataTransferObjectManager to test DTO hydration:
$manager = $this->createMock(DataTransferObjectManager::class);
$manager->method('hydrate')->willReturn(new UserDto(...));
$handler = new SymfonyValidationHandler();
$handler->process($decorator, $context); // Assert metadata
No Optional Parameters
@Property\DefaultValue decorators:
#[Property\DefaultValue('GUEST')]
public UserRole $role; // Defaults to UserRole::GUEST if missing
Case Sensitivity in Enums
@Property\ToEnum is case-sensitive for string-backed enums.value: true for string enums:
#[Property\ToEnum(value: true)]
public UserStatus $status; // Matches 'active'/'inactive' strings
Caching Overhead
ReflectorManager caches reflection data, which may cause stale metadata if classes change.$cache = new FilesystemAdapter('codememory', [
'directory' => storage_path('cache'),
'default_lifetime' => 3600, // 1 hour
]);
Event Dispatcher Order
boot() method to ensure order.Nested Object Hydration
Metadata Inspection
$metadata = $manager->getReflectorManager()
->getClassMetadata(UserDto::class);
dd($metadata);
**Event Debug
How can I help you explore Laravel packages today?