Installation:
composer require sajadsdi/dto-tool
Ensure your project uses PHP 8.1+ and sajadsdi/php-reflection (≥1.0).
First DTO Class:
Create a class with DTOTrait and define private properties:
use Sajadsdi\DtoTool\Concerns\DTOTrait;
class UserDTO
{
use DTOTrait;
private string $name;
private int $age;
}
First Use Case: Initialize and populate a DTO:
$user = new UserDTO();
$user->init(['name' => 'John', 'age' => 30]); // Mass assignment
$array = $user->toArray(); // Convert to array
DTOTrait: Auto-generates getters/setters for private properties.init(): Hydrate DTO from an array.toArray(): Convert DTO to an array.Use DTOs to decouple API/input layers from business logic:
// API Request → DTO → Service
$requestData = $request->all();
$userDTO = new UserDTO();
$userDTO->init($requestData);
$userService->create($userDTO);
Pair with Laravel’s Validator for input sanitization:
use Illuminate\Support\Facades\Validator;
$validator = Validator::make($request->all(), [
'name' => 'required|string|max:255',
'age' => 'integer|min:18',
]);
if ($validator->fails()) {
return response()->json(['errors' => $validator->errors()], 422);
}
$userDTO->init($request->all());
Leverage auto-generated methods for flexibility:
// Add a new property dynamically (if needed)
$userDTO->setEmail('john@example.com'); // Works even without explicit property
Useful for API responses or batch processing:
$users = collect([$userDTO1, $userDTO2])
->map(fn ($dto) => $dto->toArray());
Customize behavior for specific properties:
public function getName(): string
{
return ucfirst($this->name); // Auto-capitalize names
}
public function setAge(int $age): void
{
$this->age = max(18, $age); // Enforce minimum age
}
Create reusable factory methods:
class UserDTOFactory
{
public static function fromRequest(array $data): UserDTO
{
$dto = new UserDTO();
$dto->init($data);
return $dto;
}
}
Compose DTOs for complex structures:
class AddressDTO
{
use DTOTrait;
private string $street;
private string $city;
}
class UserDTO
{
use DTOTrait;
private string $name;
private AddressDTO $address; // Nested DTO
}
Property Naming Conflicts:
Avoid property names starting with get/set (e.g., getName as a property). The trait may misinterpret them.
Fix: Rename or use explicit getters/setters.
Visibility Changes:
The trait auto-generates methods for private/protected properties. Changing visibility (e.g., to public) breaks auto-magic.
Fix: Use explicit getters/setters or adjust visibility consistently.
Circular References in toArray():
Nested DTOs with circular references (e.g., UserDTO → OrderDTO → UserDTO) cause infinite loops.
Fix: Implement __toString() or use json_encode() with JSON_THROW_ON_ERROR.
Type Safety:
The trait does not enforce type hints in generated setters. Invalid types may slip through.
Fix: Override setters or use PHP 8.2’s #[SensitiveParameter] for critical fields.
Mass Assignment Risks:
init() blindly assigns all array keys. Untrusted data (e.g., user input) can overwrite properties.
Fix: Whitelist allowed fields or validate before init().
Check Generated Methods:
Use get_class_methods(UserDTO::class) to verify auto-generated methods.
Reflection Inspection: Debug property access issues with:
$reflection = new ReflectionClass(UserDTO::class);
var_dump($reflection->getProperties());
Override for Debugging:
Temporarily override toArray() to log data:
public function toArray(): array
{
$data = parent::toArray();
\Log::debug('DTO Data:', $data);
return $data;
}
Custom Initialization:
Extend init() to support nested DTOs or default values:
public function init(array $data): void
{
parent::init($data);
$this->address ??= new AddressDTO(); // Default nested DTO
}
Serialization Hooks:
Add JsonSerializable for custom JSON output:
implements JsonSerializable {
public function jsonSerialize(): array
{
return [
'full_name' => $this->getName(),
'age' => $this->getAge(),
];
}
}
DTO Events:
Use events (e.g., Laravel’s Observers) for pre/post-set hooks:
// In a service or observer
$dto->setName('John');
event(new DTOUpdated($dto));
Performance Optimization:
Cache toArray() results if DTOs are immutable:
private ?array $cachedArray = null;
public function toArray(): array
{
return $this->cachedArray ??= parent::toArray();
}
Trait Loading Order:
Ensure DTOTrait is the last trait in your class to avoid method conflicts.
PHP Reflection Dependencies:
The package relies on sajadsdi/php-reflection. Ensure it’s updated if you encounter reflection errors.
IDE Support:
Some IDEs (e.g., PHPStorm) may not auto-detect generated methods. Use @method PHPDoc annotations:
/**
* @method string getName()
* @method void setName(string $name)
*/
class UserDTO { ... }
How can I help you explore Laravel packages today?