PHPackages                             cerbero/dto - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. cerbero/dto

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

cerbero/dto
===========

Data Transfer Object (DTO)

2.2.0(5y ago)17119.4k↓47.1%5[1 PRs](https://github.com/cerbero90/dto/pulls)1MITPHPPHP ^7.1||^8.0

Since Mar 19Pushed 4y ago1 watchersCompare

[ Source](https://github.com/cerbero90/dto)[ Packagist](https://packagist.org/packages/cerbero/dto)[ Docs](https://github.com/cerbero/dto)[ Fund](https://paypal.me/AndreaMarcoSartori)[ RSS](/packages/cerbero-dto/feed)WikiDiscussions develop Synced 1mo ago

READMEChangelogDependencies (2)Versions (12)Used By (1)

DTO
===

[](#dto)

[![Author](https://camo.githubusercontent.com/fffbc89ca2742dccf8be167716e04b7125a913abae0da6a488131999dab8ca25/68747470733a2f2f696d672e736869656c64732e696f2f7374617469632f76313f6c6162656c3d617574686f72266d6573736167653d6365726265726f393026636f6c6f723d353041424631266c6f676f3d74776974746572267374796c653d666c61742d737175617265)](https://twitter.com/cerbero90)[![PHP Version](https://camo.githubusercontent.com/edc5b70b5e8a8a0f3a1ba11abc92263088a7983f85d8f5871ce811a2260ba7e0/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f6365726265726f2f64746f3f636f6c6f723d253233344635423933266c6f676f3d706870267374796c653d666c61742d737175617265)](https://www.php.net)[![Build Status](https://camo.githubusercontent.com/76aafa19d3390256aa00034a85ac80252ec5e3d83752effa5e078aff5a40532c/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f776f726b666c6f772f7374617475732f6365726265726f39302f64746f2f6275696c643f7374796c653d666c61742d737175617265266c6f676f3d676974687562)](https://github.com/cerbero90/dto/actions?query=workflow%3Abuild)[![Coverage Status](https://camo.githubusercontent.com/7b87420479ee211f5c28d7f9b14ef3db47a72e6e0c6c6e891c293ce7ddfd7602/68747470733a2f2f696d672e736869656c64732e696f2f7363727574696e697a65722f636f7665726167652f672f6365726265726f39302f64746f2e7376673f7374796c653d666c61742d737175617265266c6f676f3d7363727574696e697a6572)](https://scrutinizer-ci.com/g/cerbero90/dto/code-structure)[![Quality Score](https://camo.githubusercontent.com/098520b586c4a726bd85397c091b61d656f86837e83547e07c2ba656a299049d/68747470733a2f2f696d672e736869656c64732e696f2f7363727574696e697a65722f672f6365726265726f39302f64746f2e7376673f7374796c653d666c61742d737175617265266c6f676f3d7363727574696e697a6572)](https://scrutinizer-ci.com/g/cerbero90/dto)[![Latest Version](https://camo.githubusercontent.com/dcd35365a81c6738c77cf56884448c66860a662b95e1dfc28717eb08316fa4ac/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6365726265726f2f64746f2e7376673f6c6162656c3d76657273696f6e267374796c653d666c61742d737175617265)](https://packagist.org/packages/cerbero/dto)[![Software License](https://camo.githubusercontent.com/55c0218c8f8009f06ad4ddae837ddd05301481fcf0dff8e0ed9dadda8780713e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d627269676874677265656e2e7376673f7374796c653d666c61742d737175617265)](LICENSE.md)[![PSR-12](https://camo.githubusercontent.com/9ba5754b3504e97a10e2b2f84770a218e69e544e9de8943eceb0b67599c83c6c/68747470733a2f2f696d672e736869656c64732e696f2f7374617469632f76313f6c6162656c3d636f6d706c69616e6365266d6573736167653d5053522d313226636f6c6f723d626c7565267374796c653d666c61742d737175617265)](https://www.php-fig.org/psr/psr-12/)[![Total Downloads](https://camo.githubusercontent.com/79108053bcb7dfd743bacca5438ea066f1cc08aaac95a58512a49ddcfeabb977/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6365726265726f2f64746f2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/cerbero/dto)

This package was inspired by [Lachlan Krautz](https://github.com/lachlankrautz)' excellent [data-transfer-object](https://github.com/rexlabsio/data-transfer-object).

A data transfer object (DTO) is an object that carries data between processes. DTO does not have any behaviour except for storage, retrieval, serialization and deserialization of its own data. DTOs are simple objects that should not contain any business logic but rather be used for transferring data.

Install
-------

[](#install)

Via Composer

```
composer require cerbero/dto
```

Usage
-----

[](#usage)

- [Instantiate a DTO](#instantiate-a-dto)
- [Declare properties](#declare-properties)
- [Default values](#default-values)
- [Interact with values](#interact-with-values)
- [Available flags](#available-flags)
    - [NONE](#none)
    - [IGNORE\_UNKNOWN\_PROPERTIES](#ignore_unknown_properties)
    - [MUTABLE](#mutable)
    - [PARTIAL](#partial)
    - [CAST\_PRIMITIVES](#cast_primitives)
    - [CAMEL\_CASE\_ARRAY](#camel_case_array)
- [Default flags](#default-flags)
- [Interact with flags](#interact-with-flags)
- [Manipulate properties](#manipulate-properties)
- [Interact with properties](#interact-with-properties)
- [Convert into array](#convert-into-array)
- [Listen to events](#listen-to-events)
- [Convert into string](#convert-into-string)

### Instantiate a DTO

[](#instantiate-a-dto)

DTOs can be instantiated like normal classes or via the factory method `make()`. The parameters are optional and include the data to carry and the flags that dictate how the DTO should behave:

```
use const Cerbero\Dto\PARTIAL;

$data = [
    'name' => 'John',
    'address' => [
        'street' => 'King Street',
    ],
];

$dto = new SampleDto($data, PARTIAL);

$dto = SampleDto::make($data, PARTIAL);
```

In the example above, `$data` is an array containing the properties declared in the DTO and `PARTIAL` is a flag that let the DTO be instantiated even though it doesn't have all its properties set (we will see flags in more detail later).

Keys in the array `$data` can be either snake case or camel case, the proper case is automatically detected to match DTO properties.

### Declare properties

[](#declare-properties)

Properties can be declared in a DTO by using doc comment tags:

```
use Cerbero\Dto\Dto;
use Sample\Dtos\AddressDto;

/**
 * A sample user DTO.
 *
 * @property string $name
 * @property bool $isAdmin
 * @property mixed $something
 * @property \DateTime|null $birthday
 * @property UserDto[] $friends
 * @property AddressDto $address
 */
class UserDto extends Dto
{
    //
}
```

Either `@property` or `@property-read` can be used, followed by the expected data type and the desired property name. When expecting more than one type, we can separate them with a pipe `|` character, e.g. `\DateTime|null`.

A collection of types can be declared by adding the suffix `[]` to the data type, e.g. `UserDto[]`. It's important to declare the fully qualified name of classes, either in the doc comment or as a `use` statement.

Primitive types can be specified too, e.g. `string`, `bool`, `int`, `array`, etc. The pseudo-type `mixed` allow any type.

### Default values

[](#default-values)

While values can be set when instatiating a DTO, default values can also be defined in the DTO class:

```
use Cerbero\Dto\Dto;

/**
 * A sample user DTO.
 *
 * @property string $name
 */
class UserDto extends Dto
{
    protected static $defaultValues = [
        'name' => 'John',
    ];
}

// $user1->name will return: John
$user1 = new UserDto();

// $user2->name will return: Jack
$user2 = new UserDto(['name' => 'Jack']);
```

Please note that in the above example default values are overridden by the values passed during the DTO creation.

### Interact with values

[](#interact-with-values)

DTO property values can be accessed in several ways, but a `Cerbero\Dto\Exceptions\UnknownDtoPropertyException` is thrown if a requested property is not set:

```
// as an object
$user->address->street;

// as an array
$user['address']['street'];

// via dot notation
$user->get('address.street');

// via nested DTO
$user->address->get('street');
```

To check whether properties have a value, the following methods can be called:

```
// as an object
isset($user->address->street);

// as an array
isset($user['address']['street']);

// via dot notation
$user->has('address.street');

// via nested DTO
$user->address->has('street');
```

Please note that the above methods will return FALSE also if the property value is set to NULL (just like the default PHP behaviour). To check whether a property has actually been set, we can call `$user->hasProperty('address.street')` (we will see properties in more details later).

The outcome of setting a value depends on the flags set in a DTO. DTOs are immutable by default, so a new instance gets created when setting a value. Values can be changed in the same DTO instance only if a `MUTABLE` flag is set:

```
// throw Cerbero\Dto\Exceptions\ImmutableDtoException if immutable
$user->address->street = 'King Street';

// throw Cerbero\Dto\Exceptions\ImmutableDtoException if immutable
$user['address']['street'] = 'King Street';

// set the new value in the same instance if mutable or in a new instance if immutable
$user->set('address.street', 'King Street');

// set the new value in the same instance if mutable or in a new instance if immutable
$user->address->set('street', 'King Street');
```

Same applies when unsetting a value but only `PARTIAL` DTOs can have values unset, otherwise a `Cerbero\Dto\Exceptions\UnsetDtoPropertyException` is thrown:

```
// throw Cerbero\Dto\Exceptions\ImmutableDtoException if immutable
unset($user->address->street);

// throw Cerbero\Dto\Exceptions\ImmutableDtoException if immutable
unset($user['address']['street']);

// unset the new value in the same instance if mutable or in a new instance if immutable
$user->unset('address.street');

// unset the new value in the same instance if mutable or in a new instance if immutable
$user->address->unset('street');
```

### Available flags

[](#available-flags)

Flags determine how a DTO behaves and can be set when instantiating a new DTO. They support bitwise operations, so we can combine multiple behaviours via `PARTIAL | MUTABLE`.

#### NONE

[](#none)

The flag `Cerbero\Dto\NONE` is simply a placeholder and doesn't alter the behaviour of a DTO in any way.

#### IGNORE\_UNKNOWN\_PROPERTIES

[](#ignore_unknown_properties)

The flag `Cerbero\Dto\IGNORE_UNKNOWN_PROPERTIES` lets a DTO ignore extra data that is not part of its properties. If this flag is not provided, a `Cerbero\Dto\Exceptions\UnknownDtoPropertyException` is thrown when trying to set a property that is not declared.

#### MUTABLE

[](#mutable)

The flag `Cerbero\Dto\MUTABLE` lets a DTO override its property values without creating a new DTO instance, as DTOs are immutable by default. If not provided, a `Cerbero\Dto\Exceptions\ImmutableDtoException` is thrown when trying to alter a property without calling `set()` or `unset()`, e.g. `$dto->property = 'foo'` or `unset($dto['property'])`.

#### PARTIAL

[](#partial)

The flag `Cerbero\Dto\PARTIAL` lets a DTO be instantiated without some properties. If not provided, a `Cerbero\Dto\Exceptions\MissingValueException` is thrown when properties are missing or when unsetting a property.

#### CAST\_PRIMITIVES

[](#cast_primitives)

The flag `Cerbero\Dto\CAST_PRIMITIVES` lets a DTO cast property values if they don't match the expected primitive type. If not provided, a `Cerbero\Dto\Exceptions\UnexpectedValueException` is thrown when trying to set a value with a wrong primitive type.

#### CAMEL\_CASE\_ARRAY

[](#camel_case_array)

The flag `Cerbero\Dto\CAMEL_CASE_ARRAY` lets all DTO properties preserve their camel case names when a DTO is converted into an array.

### Default flags

[](#default-flags)

While flags can be set when instatiating a DTO, default flags can also be defined in the DTO class:

```
use Cerbero\Dto\Dto;

use const Cerbero\Dto\PARTIAL;
use const Cerbero\Dto\IGNORE_UNKNOWN_PROPERTIES;
use const Cerbero\Dto\MUTABLE;

/**
 * A sample user DTO.
 *
 * @property string $name
 */
class UserDto extends Dto
{
    protected static $defaultFlags = PARTIAL | IGNORE_UNKNOWN_PROPERTIES;
}

// $user->getFlags() will return: PARTIAL | IGNORE_UNKNOWN_PROPERTIES | MUTABLE
$user = UserDto::make($data, MUTABLE);
```

Default flags are combined with the flags passed during the DTO creation, which means that in the code above `$user` has the following flags set: `PARTIAL`, `IGNORE_UNKNOWN_PROPERTIES` and `MUTABLE`.

### Interact with flags

[](#interact-with-flags)

Default flags in a DTO can be retrieved by calling the static method `getDefaultFlags()`, whilst flags belonging to a DTO instance can be read via `getFlags()`:

```
// PARTIAL | IGNORE_UNKNOWN_PROPERTIES
UserDto::getDefaultFlags();

// PARTIAL | IGNORE_UNKNOWN_PROPERTIES | MUTABLE
$user->getFlags();
```

To determine whether a DTO has one or more flag set, we can call `hasFlags()`:

```
$user->hasFlags(PARTIAL); // true
$user->hasFlags(PARTIAL | MUTABLE); // true
$user->hasFlags(PARTIAL | NULLABLE); // false
```

DTO flags can be set again by calling the method `setFlags()`. If the DTO is mutable the flags are set against the current instance, otherwise a new instance of the DTO is created with the given flags:

```
$user = $user->setFlags(PARTIAL | NULLABLE);
```

In case we want to add one or more flags to the already set ones, we can call `addFlags()`. If the DTO is mutable the flags are added to the current instance, otherwise they are added to a new instance:

```
$user = $user->addFlags(CAMEL_CASE_ARRAY | CAST_PRIMITIVES);
```

Finally to remove flags, we can call `removeFlags()`. If the DTO is mutable the flags are removed from the current instance, otherwise they are removed from a new instance:

```
$user = $user->removeFlags(IGNORE_UNKNOWN_PROPERTIES | MUTABLE);
```

Please note that when flags are added, removed or set and affect DTO values, properties are re-mapped to apply the effects of the new flags.

### Manipulate properties

[](#manipulate-properties)

Along with `set()` there are other methods that can be called to manipulate a DTO properties. The method `merge()` joins the properties of a DTO with another DTO or anything iterable, e.g. an array:

```
$user1 = UserDto::make([
    'name' => 'John',
    'address' => [
        'street' => 'King Street',
    ],
], PARTIAL | IGNORE_UNKNOWN_PROPERTIES);

$user2 = UserDto::make([
    'name' => 'Anna',
    'address' => [
        'unit' => 10,
    ],
], PARTIAL | CAMEL_CASE_ARRAY);

// [
//     'name' => 'Anna',
//     'address' => [
//         'street' => 'King Street',
//         'unit' => 10,
//     ],
// ]
$mergedDto = $user1->merge($user2);

// PARTIAL | IGNORE_UNKNOWN_PROPERTIES | CAMEL_CASE_ARRAY
$mergedDto->getFlags();
```

In the example above, the two DTOs are immutable, so another DTO will be created after they merge. If `$user1` was mutable, its own properties would have changed without creating a new DTO instance. Please also note that even DTO flags are merged.

In order to let a DTO carry only some specific properties, we can call the `only()` method and pass a list of properties to keep:

```
$result = $user->only(['name', 'address'], CAST_PRIMITIVES);
```

Any optional flag passed as second parameter will be merged with the existing flags of the DTO. The changes will be applied to a new instance if the DTO is immutable or to the same instance if it is mutable.

The `only()` method has also an opposite method called `except` that keeps all the DTO properties except for the ones excluded:

```
$result = $user->except(['name', 'address'], CAST_PRIMITIVES);
```

Sometimes we may need to quickly alter the data of an immutable DTO. In order to do that while preserving the immutability of the DTO after the altering process, we can call the `mutate()` method:

```
$user->mutate(function (UserData $user) {
    $user->name = 'Jack';
});
```

### Interact with properties

[](#interact-with-properties)

During the creation of a DTO, properties are internally mapped from the data provided. The properties map is an associative array containing the property names as keys and instances of `Cerbero\Dto\DtoProperty` as values. To retrieve such map (maybe for inspection), we can call the `getPropertiesMap()` method:

```
// ['name' => Cerbero\Dto\DtoProperty, ...]
$map = $user->getPropertiesMap();
```

There are also methods to retrieve property names, all the `DtoProperty` instances, a singular `DtoProperty` instance and finally a method to determine if a property is set at all (useful for example to avoid false negatives when a property value is NULL):

```
// ['name', 'isAdmin', ...]
$names = $user->getPropertyNames();

// [Cerbero\Dto\DtoProperty, Cerbero\Dto\DtoProperty, ...]
$properties = $user->getProperties();

// Cerbero\Dto\DtoProperty instance for the property "name"
$nameProperty = $user->getProperty('name');

// TRUE as long as the property "name" is set (even if its value is NULL)
$hasName = $user->hasProperty('name');
```

### Convert into array

[](#convert-into-array)

As shown above, DTOs can behave like arrays, their values can be set and retrieved in an array fashion. DTO itself is iterable, hence can be used in a loop:

```
foreach($dto as $propertyName => $propertyValue) {
    // ...
}
```

We can call the method `toArray()` to get an array representation of a DTO and its nested DTOs. The resulting array will have keys in snake case by default, unless the DTO has the `CAMEL_CASE_ARRAY` flag:

```
// [
//     'name' => 'Anna',
//     'is_admin' => true,
//     'address' => [
//         'street' => 'King Street',
//         'unit' => 10,
//     ],
// ]
$user->toArray();
```

Sometimes we may want a value to be converted when a DTO turns into an array. To do so we can register value converters in the `ArrayConverter`:

```
use Cerbero\Dto\Manipulators\ArrayConverter;
use Cerbero\Dto\Manipulators\ValueConverter;

class DateTimeConverter implements ValueConverter
{
    public function fromDto($value)
    {
        return $value->format('Y-m-d');
    }

    public function toDto($value)
    {
        return new DateTime($value);
    }
}

ArrayConverter::instance()->setConversions([
    DateTime::class => DateTimeConverter::class,
]);

$user = UserDto::make(['birthday' => '01/01/2000']);
$user->birthday; // instance of DateTime
$user->toArray(); // ['birthday' => '01/01/2000']
```

Please note that conversions registered in `ArrayConverter` will apply to all DTOs, whenever they are turned into arrays. In order to transform values only for a specific DTO, read below about the `Listener` class.

Singular conversions can also be added or removed with the methods `addConversion()` and `removeConversion()`:

```
ArrayConverter::instance()->addConversion(DateTime::class, DateTimeConverter::class);

ArrayConverter::instance()->removeConversion(DateTime::class);
```

### Listen to events

[](#listen-to-events)

Whenever a DTO sets or gets one of its property values, a listener may intercept the event and alter the outcome. Every DTO can have one listener associated that can be registered via the `Listener` class:

```
use Cerbero\Dto\Manipulators\Listener;

class UserDtoListener
{
    public function setName($value)
    {
        return ucwords($value);
    }

    public function getSomething($value)
    {
        return $value === null ? rand() : $value;
    }
}

Listener::instance()->listen([
    UserDto::class => UserDtoListener::class,
]);

$user = UserDto::make(['name' => 'john doe', 'something' => null]);
$user->name; // John Doe
$user->something; // random integer
```

In the example above, `UserDtoListener` listens every time a `UserDto` property is set or accessed and calls the related method if existing. The convention behind listeners method names is concatenating the event (`set` or `get`) to the listened property name in camel case, e.g. `setName` or `getIsAdmin`.

Values returned by listener methods override the actual property values. Listeners are not only meant to alter values but also to run arbitrary logic when a DTO property is read or set.

Singular listeners can also be added or removed with the methods `addListener()` and `removeListener()`:

```
Listener::instance()->addListener(UserDto::class, UserDtoListener::class);

Listener::instance()->removeListener(UserDto::class);
```

### Convert into string

[](#convert-into-string)

Finally DTOs can be casted into strings. When that happens, their JSON representation is returned:

```
// {"name":"John Doe"}
(string) $user;
```

A more explicit way to turn a DTO into a JSON is calling the method `toJson()`, which has the same effect of encoding a DTO via `json_encode()`:

```
$user->toJson();

json_encode($user);
```

If some DTO values need a special transformation when encoded into JSON, such transformation can be defined in `ArrayConverter` (see the section [Convert into array](#convert-into-array) for more details).

Change log
----------

[](#change-log)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Testing
-------

[](#testing)

```
$ composer test
```

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING](CONTRIBUTING.md) and [CODE\_OF\_CONDUCT](CODE_OF_CONDUCT.md) for details.

Security
--------

[](#security)

If you discover any security related issues, please email  instead of using the issue tracker.

Credits
-------

[](#credits)

- [Lachlan Krautz](https://github.com/lachlankrautz)
- [Andrea Marco Sartori](https://twitter.com/cerbero90)
- [All Contributors](../../contributors)

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

40

—

FairBetter than 88% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity40

Moderate usage in the ecosystem

Community14

Small or concentrated contributor base

Maturity68

Established project with proven stability

 Bus Factor1

Top contributor holds 99% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~47 days

Recently: every ~100 days

Total

10

Last Release

1827d ago

Major Versions

1.4.1 → 2.0.02020-11-07

PHP version history (2 changes)1.0.0PHP ^7.1

2.1.0PHP ^7.1||^8.0

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/596523?v=4)[Matteo Picciolini](/maintainers/cerbero)[@cerbero](https://github.com/cerbero)

---

Top Contributors

[![cerbero90](https://avatars.githubusercontent.com/u/5838106?v=4)](https://github.com/cerbero90 "cerbero90 (103 commits)")[![DieZeeL](https://avatars.githubusercontent.com/u/49496565?v=4)](https://github.com/DieZeeL "DieZeeL (1 commits)")

---

Tags

data-transfer-objectdtodata-transfer-objectdto

###  Code Quality

TestsPHPUnit

Code StylePHP\_CodeSniffer

### Embed Badge

![Health badge](/badges/cerbero-dto/health.svg)

```
[![Health](https://phpackages.com/badges/cerbero-dto/health.svg)](https://phpackages.com/packages/cerbero-dto)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
