thoemo/dto (2.1.1)

Published 2026-10-25 16:12:39 +00:00 by elnebuloso

Installation

{
	"repositories": [{
			"type": "composer",
			"url": ""
		}
	]
}
composer config repositories.thoemo composer 
composer require thoemo/dto:2.1.1

About this package

Handling Data Transfer Objects

thoemo/dto

Building blocks for data transfer objects that keep their state in a single array and read and write it by dot path ("address.city"), using jasny/dotkey.

Requirements

  • PHP >= 8.2
  • jasny/dotkey ^2.2.0 (installed with the package)

Installation

The package is served by the Forgejo Composer registry (no credentials needed). Add the repository to your composer.json:

"repositories": {
    "packages.link.thoemo.net/thoemo": {
        "type": "composer",
        "url": "https://packages.link.thoemo.net/api/packages/thoemo/composer"
    }
}

Then:

composer require thoemo/dto

The namespace is Thoemo\DTO. Before 2.1 it was elnebuloso\DTO; update your use statements when upgrading.

Overview

Type Purpose
AbstractReadDTO (class) Read-only DTO. Constructor takes the data array and merges it over getDefaults(). Implements ToArray and JsonSerializable.
DTOReadTrait (trait) Adds protected array $data and protected fetchValue(string $path, mixed $default = null).
DTOWriteTrait (trait) Includes DTOReadTrait and adds protected methods to change $data (below).
ToArray (interface) toArray(): array.

fetchValue() returns the value at the dot path, or $default if the path does not exist.

DTOWriteTrait methods, all protected and returning void:

Method Effect
storeValue(string $path, mixed $value) Sets the value at $path, creating intermediate arrays.
storeValues(string $path, array $values) Replaces the list at $path with $values.
pushValue(string $path, mixed $value) Appends to the list at $path and re-indexes it.
pushValues(string $path, array $values) Appends each value.
incrementValue(string $path, int $number = 1) Adds $number (a missing value counts as 0).
decrementValue(string $path, int $number = 1) Subtracts $number (a missing value counts as 0).
removeValue(string $path) Removes the key at $path.
copyValue(string $from, string $to, bool $allowEmpty = false) Copies the value. Does nothing if the source is empty(), unless $allowEmpty.
moveValue(string $from, string $to, bool $moveEmpty = false) Copies, then removes the source. Does nothing if the source is empty(), unless $moveEmpty.

Usage

Read DTO with defaults

use Thoemo\DTO\AbstractReadDTO;

final class UserDTO extends AbstractReadDTO
{
    public function getId(): string
    {
        return $this->fetchValue("id");
    }

    public function getRole(): string
    {
        return $this->fetchValue("role");
    }

    public function getCity(): ?string
    {
        return $this->fetchValue("address.city");
    }

    protected function getDefaults(): array
    {
        return ["role" => "user"];
    }
}

$user = new UserDTO(["id" => "42", "address" => ["city" => "Bonn"]]);

$user->getRole();   // "user"
$user->getCity();   // "Bonn"
$user->toArray();   // ["role" => "user", "id" => "42", "address" => ["city" => "Bonn"]]
json_encode($user); // same data as JSON

Defaults and data are combined with array_replace_recursive(): nested arrays are merged key by key, and that includes numeric keys. A default list ["a", "b"] given the data ["c"] becomes ["c", "b"], not ["c"].

Writable entity

There is no abstract writable class; use DTOWriteTrait in your own class and fill $data yourself:

use Thoemo\DTO\DTOWriteTrait;
use Thoemo\DTO\ToArray;

final class Article implements ToArray, JsonSerializable
{
    use DTOWriteTrait;

    public function __construct(array $data = [])
    {
        $this->data = $data;
    }

    public function rename(string $title): void
    {
        $this->storeValue("title", $title);
    }

    public function addTag(string $tag): void
    {
        $this->pushValue("tags", $tag);
    }

    public function registerView(): void
    {
        $this->incrementValue("stats.views");
    }

    public function toArray(): array
    {
        return $this->data;
    }

    public function jsonSerialize(): array
    {
        return $this->toArray();
    }
}

Upgrading from 1.x

Version 1.x was published as elnebuloso/dto and contained AbstractDTO and AbstractDTOBuilder. Both are gone in 2.0.

AbstractDTO → AbstractReadDTO:

  • The public getValue() is now the protected fetchValue(). Expose values through your own getters.
  • The protected array $defaults property is now the protected function getDefaults(): array method.
  • Defaults are merged with array_replace_recursive() instead of array_merge(), so nested arrays are merged instead of replaced.
  • getValue() fell back to the value in $defaults at the same path; fetchValue() only looks at $data (defaults reach it through the constructor).
  • New toArray(); jsonSerialize() returns the same array.

AbstractDTOBuilder → DTOWriteTrait in your own class (see above). The methods are now protected, return void instead of $this, and some were renamed:

1.x AbstractDTOBuilder 2.x DTOWriteTrait
getValue() fetchValue()
setValue() storeValue()
setValues() storeValues()
addValue() pushValue()
addValues() pushValues()
delValue() removeValue()
copyValue() copyValue()
moveValue() moveValue()

The builder took a JsonSerializable in its constructor; with the trait you set $this->data yourself.

Coming from thoemo/traits

thoemo/traits up to 1.4.x had its own DTOReadTrait and DTOWriteTrait (namespace elnebuloso\Traits). The versions here differ:

  • Namespace elnebuloso\Traits → Thoemo\DTO.
  • DTOReadTrait no longer provides jsonSerialize(). Extend AbstractReadDTO or implement it yourself.
  • incrementValue() and decrementValue() take an optional $number (default 1).
  • copyValue() and moveValue() are new.

Development

Tests and tools run in Docker through redo; build.yml holds the project's redo properties.

redo composer:install phpunit                     # tests on PHP 8.2 (php_version in build.yml)
redo composer:install phpunit -Dphp_version=8.4   # tests on another PHP version
redo commit                                       # clean:dist, prettier:fix, phplint, phpunit, composer:validate:require

Releases

Every push to main runs the GitLab pipeline: phplint, composer:validate:require and phpunit on PHP 8.2 to 8.5. It then raises the patch version in VERSION and uploads that version to the Forgejo registry. Only README.md, composer.json and src/ are part of the released archive.

Dependencies

Dependencies

ID Version
jasny/dotkey ^2.2.0
php >=8.2
Details
Composer
2026-10-25 16:12:39 +00:00
4
2026-10-25 16:24:47 +00:00
proprietary
5 KiB
Assets (1)
Versions (5) View all
2.2.2 2026-10-25
2.2.1 2026-10-25
2.1.1 2026-10-25
2.0.3 2026-10-25
2.0.2 2026-10-25