alvarez / concrete-dto
Installs: 1
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/alvarez/concrete-dto
Requires
- php: ^8.1
Requires (Dev)
- pestphp/pest: ^4.3
This package is not auto-updated.
Last update: 2026-01-14 06:36:13 UTC
README
ConcreteDTO
A small, explicit PHP Data Transfer Object library that keeps data predictable and readable.
Learn it in minutes, keep it in your toolbox for years.
Documentation • Installation • Quick Start • API Reference
Why ConcreteDTO?
- Tiny API focused on constructor-first DTOs
- Clear import/export helpers for arrays, JSON, and custom converters
- Immutability helpers to prevent sneaky mutations
- Framework-agnostic and dependency-light
Installation
composer require alvarez/concrete-dto
ConcreteDTO requires PHP 8.1+ and works with any framework or plain PHP codebase.
Quick Start
Define a DTO
<?php use Alvarez\ConcreteDto\AbstractDTO; final class UserDTO extends AbstractDTO { public function __construct( public readonly string $name, public readonly string $email, ) {} }
Import data
// From array $user = UserDTO::fromArray([ 'name' => 'Daniel Alvarez', 'email' => 'alvarez@alvarez.com', ]); // From JSON $user = UserDTO::fromJSON('{"name":"Daniel Alvarez","email":"alvarez@alvarez.com"}'); // From custom object use Alvarez\ConcreteDto\Contracts\DTOFrom; use Alvarez\ConcreteDto\Contracts\IsDTO; final class UserRequestToDTO implements DTOFrom { public static function handle(mixed $dataToConvert): IsDTO { return new UserDTO( name: $dataToConvert->fullName, email: $dataToConvert->contact, ); } } $request = new UserRequest('Daniel Alvarez', 'alvarez@alvarez.com'); $user = UserDTO::from(UserRequestToDTO::class, $request);
Export data
// To array $data = $user->toArray(); // ['name' => 'Daniel Alvarez', 'email' => 'alvarez@alvarez.com'] // To JSON $json = $user->toJson(); // {"name":"Daniel Alvarez","email":"alvarez@alvarez.com"} // To custom object use Alvarez\ConcreteDto\Contracts\DTOTo; final class UserDTOToModel implements DTOTo { public static function handle(IsDTO $dto): mixed { return new UserModel(name: $dto->name, email: $dto->email); } } $model = $user->to(UserDTOToModel::class); // Filter fields $publicData = $user->except(['email']); // ['name' => 'Daniel Alvarez']
Immutability
// Clone with updates $updated = $user->cloneWith(['email' => 'new@example.com']); // Original $user remains unchanged
Validation
Override the static validate() method to add custom validation logic. Validation runs automatically when using fromArray(), fromJSON(), or cloneWith().
For validation on direct instantiation with new, call self::validate($this->toArray()) in your constructor.
final class UserDTO extends AbstractDTO { public function __construct( public readonly string $name, public readonly string $email, ) { self::validate($this->toArray()); // Enable validation on direct instantiation } public static function validate(array $data): void { if (!filter_var($data['email'] ?? '', FILTER_VALIDATE_EMAIL)) { throw new InvalidArgumentException('Invalid email format'); } if (strlen($data['name'] ?? '') < 2) { throw new InvalidArgumentException('Name too short'); } } } // Validation runs automatically $user = UserDTO::fromArray(['name' => 'Daniel', 'email' => 'invalid']); // Throws InvalidArgumentException $user = new UserDTO('A', 'invalid@email'); // Also throws InvalidArgumentException because validate is called in constructor
API Reference
Importing Methods
| Method | Purpose |
|---|---|
fromArray(array $data): static |
Create DTO from associative array |
fromJSON(string $json): static |
Create DTO from JSON string |
from(DTOFrom|string $conversor, mixed $data): static |
Create DTO using custom converter |
Exporting Methods
| Method | Purpose |
|---|---|
toArray(): array |
Export DTO as associative array |
toJson(): string |
Export DTO as JSON string |
to(DTOTo|string $conversor): mixed |
Export DTO using custom converter |
Immutability Methods
| Method | Purpose |
|---|---|
cloneWith(array $fields): static |
Clone with field overrides |
except(array $keys): array |
Get array excluding specified fields |
Validation
| Method | Purpose |
|--------|---------|---|
| validate(array $data): void | Override to add custom validation logic before DTO creation |
Real-World Examples
HTTP API Response
Route::get('/user/{id}', function (Request $request) { $user = User::find($request->id); return response()->json($user->toArray()); });
Queue Job
dispatch(new SendEmailJob($user->toJson())); class SendEmailJob { public function handle() { $user = UserDTO::fromJSON($this->userData); Mail::to($user->email)->send(new WelcomeEmail($user)); } }
Domain Model Conversion
// Map DTO to Eloquent model for persistence $userModel = $userDTO->to(UserDTOToModel::class); $userModel->save();
Filtering Sensitive Data
// Remove email before caching user data $cacheData = $user->except(['email']); Cache::put("user.{$user->name}", $cacheData);
Documentation
For in-depth guides, visit the full documentation.
Topics covered:
- Getting Started - Installation and first DTO
- Importing Data - Arrays, JSON, and custom converters
- Exporting Data - Output formats and domain mapping
- Immutability - Safe updates and field filtering
Testing
composer test
Runs tests with Pest PHP.
License
This library is open-sourced software licensed under the MIT license.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.