Ergonomic PHP library for optimizing data transfer objects (DTOs).
Data transfer objects (DTOs) in PHP are very minimal classes that has the only objective of representing a piece of data for easy data transfer.
In modern PHP, they can be classes with only readonly properties.
DTOs are great, but when readonly DTOs need to be (re)created many times, problems may arise. Consider:
// create instance for usage...
$dto = new ReadOnlyDto(1);
// ...
// somewhere else unrelated
$anotherDto = new ReadOnlyDto(1);
// ...Here, $dto and $anotherDto are two different object instances; $dto == $anotherDto but $dto !== $anotherDto. This means:
- Unnecessarily high overall memory usage for such
readonlyDTOs, esp. when needing to duplicate them many times- An example could be the result dataset of a database JOIN query
- Impossible to use with e.g.
WeakMap, which relies on the specific object instances
With this ergonomic library, same readonly DTO instances can be conveniently deduplicated, so that e.g. memory usage may be minimized.
This is an example of the multiton pattern where multiple DTO instances are allowed to exist only if their identities are distinct.
Note that this library is flexible: it will only activate when explicitly requested by the user. In case the DTOs will never duplicate (e.g. RESTful API returning a single instance to the caller), simply don't invoke this library and this library will get out of the way.
via Composer:
composer require vectorial1024/multiton-dtoFirst, make your DTO use the special trait from this library:
use Vectorial1024\MultitonDto\MultitonDto;
public class ReadOnlyDto
{
// sample DTO class
use MultitonDtoTrait;
public function __construct(
public readonly int $theValue
) {
}
protected function provideDtoID(): string
{
// abstract function from trait; let this library know how to identify your DTO instances
return (string) $this->theValue;
}
}Then, you have the convenient option to convert any created instance to the shared multiton DTO instance:
// convert to shared DTO...
$sharedInstance = (new ReadOnlyDto(1))->toMultiton();
// ...or not at all; it's up to you. just double check your use case.
$unsharedInstance = new ReadOnlyDto(3);
// just that, when using this library, a useful behavior arises:
$secondInstance = (new ReadOnlyDto(2))->toMultiton();
assert($secondInstance === $sharedInstance);
// passes
assert($secondInstance !== $unsharedInstance);
// passesThese shared instances are remembered via WeakReference variables, which unfortunately will still occupy a very small amount of memory even when everything is gone.
For performance reasons, leftover DTO references are not automatically cleaned up. However, you may do this at an appropriate time during your program flow:
// tell this library to remove only the leftover expired DTO records...
ReadOnlyDto::cleanMultitons();
// ...or go nuclear and unlink everything...
ReadOnlyDto::resetMultitons();
// ...or perhaps this cleanup is not needed; it's up to you.Due to PHP technical limitations, if the parent class has chosen to use MultitonDtoTrait, then:
- child classes cannot opt out of the trait; and
- child classes must share their DTO instances with the parent class, and vice versa
As such, when reading DTO instances inside child classes, sometimes explicit casts are needed.
PHPUnit via Composer:
composer run-script test