dto-core is a lightweight, typed, and flexible base layer for working with Data Transfer Objects (DTOs) in PHP 8.1+.
It supports strict/lenient modes, nested DTOs, lazy evaluation via Closure
, and full debug output.
- Strict or lenient validation modes
- Full support for nested DTOs (recursive)
- Property type validation and class-level error tracking
make()
method (recommended) for flexible and different modes using, ornew DTOClass()
for strict usingtoArray()
method converts the current object instance into an array representationClosure
support for lazy-loaded propertiesdebug()
method with full structural output- Framework-agnostic β works with pure PHP classes
- Optimized for PHP 8.1+
- Compatible with Symfony 6.4, Laravel 10+, and modern typed PHP codebases.
- PHP 8.1 or higher
- symfony/var-dumper
Add to composer.json
"repositories": [
{
"type": "vcs",
"url": "https://github.com/technoquill/dto-core"
}
]
composer require technoquill/dto-core
dto-core
supports two primary approaches for defining DTO properties, depending on the desired level of strictness, static typing, and compatibility with constructor-based instantiation.
Properties are declared directly in the constructor using PHP 8.1+ promoted parameters.
final class UserDTO extends AbstractDTO
{
public function __construct(
public int $id,
public string $email,
public bool $blocked,
public string $created_at
) {}
}
- Ideal for strict, fully defined DTOs
- Perfect compatibility with
make()
and nativenew DTO(...)
- Properties are readonly by default and ideal for domain logic
Properties are declared as public class-level variables (optionally with defaults), typically without a constructor.
final class UserAddressDTO extends AbstractDTO
{
public string $street;
public string $city;
public string $postalCode;
public string $country;
public ?string $state = null;
public ?string $houseNumber = null;
public ?string $apartment = null;
}
- Useful for flexible, input-bound structures
- Well-suited for use with
make(array $data)
where construction is data-driven - Allows partial initialization (
strict: false
)
Mixing constructor-promoted properties and public properties in the same DTO is not allowed.
// β This is not valid:
final class InvalidDTO extends AbstractDTO
{
public string $name;
public function __construct(
public int $id
) {}
}
This ensures predictable property population, reliable type enforcement, and consistent validation.
- Use constructor DTOs for domain-level, immutable structures
- Use dynamic DTOs for input mapping, transformation, or API data
Both styles are fully supported by make()
, debug()
, toArray()
, and nested DTO handling.
final class UserDTO extends AbstractDTO
{
public function __construct(
public int $id,
public string $type,
public string $first_name,
public string $last_name,
public string $email,
public string $phone,
public UserAddressDTO|array $address = [], // for ex. relation
public string $annotation,
public bool $blocked,
public string $created_at
)
{
}
}
final class UserAddressDTO extends AbstractDTO
{
public function __construct(
public string $street,
public string $city,
public string $postalCode,
public string $country,
public ?string $state = null,
public ?string $houseNumber = null,
public ?string $apartment = null
)
{}
}
// Create via array
$user = UserDTO::make([
'id' => 435,
'type' => 'manager',
'first_name' => 'John',
'last_name' => 'Smith',
'email' => '[email protected]',
'phone' => '123456789',
'address' => UserAddressDTO::make([
'street' => '123 Main St',
'city' => 'New York',
'postalCode' => '10001',
'country' => 'USA',
//'state' => 'NY', // optional; dto makes it nullable, because the property may be NULL by default
'houseNumber' => '123',
'apartment' => '123A',
])->toArray(),
'annotation' => static fn() => strip_tags('<p>Some annotation</p>'),
'blocked' => false,
'created_at' => '2022-10-03 22:59:52'
])->toArray();
dd($user);
// or
$dtoData = PaymentDTO::make($data, false);
dd($dtoData->getErrors());
// Data output to an array is supported
$payment = PaymentDTO::make($data)->toArray();
final class OrderDTO extends AbstractDTO {
public function __construct(
public int $id,
public PaymentDTO $payment
) {}
}
final class PaymentDTO extends AbstractDTO {
public function __construct(
public int $id,
public float $amount
) {}
}
$order = OrderDTO::make([
'id' => 10,
'payment' => PaymentDTO::make([
'id' => 435,
'amount' => 765.35,
])
]);
DTOs can also be instantiated directly via the constructor (including nested DTOs), but all values must be fully typed and pre-resolved.
Note:
Always works in strict mode
$payment = (new PaymentDTO(435, 765.35))->toArray();
src/
βββ AbstractDTO.php
βββ Contracts/
β βββ DTOInterface.php
βββ Support/
β βββ LoggerContext.php
βββ Traits/
β βββ DebuggableTrait.php
β βββ DTOTrait.php
This package is released under the MIT license.
Β© technoquill