rjds / php-dto
A PHP library to map associative arrays to typed DTOs using attributes.
Requires
- php: ^8.1
Requires (Dev)
- infection/infection: ^0.27
- phpro/grumphp: ^1.13
- phpstan/phpstan: ^1.12 || ^2.0
- phpstan/phpstan-strict-rules: ^1.6 || ^2.0
- phpunit/phpunit: ^10.5
- squizlabs/php_codesniffer: ^3.10 || ^4.0
- dev-main
- v1.2.0
- v1.1.0
- v1.0.0
- dev-dependabot/composer/phpstan/phpstan-strict-rules-2.0.11
- dev-dependabot/composer/phpstan/phpstan-2.1.54
- dev-dependabot/github_actions/step-security/harden-runner-2.19.1
- dev-dependabot/composer/phpstan/phpstan-2.1.51
- dev-dependabot/github_actions/googleapis/release-please-action-5
- dev-dependabot/github_actions/step-security/harden-runner-2.19.0
- dev-release-please--branches--main
- dev-chore/release-2.0.0-notes
- dev-fix/bc-check-shallow-fetch
- dev-feat/mapping-exception-phpstan-dx
This package is auto-updated.
Last update: 2026-05-04 11:48:09 UTC
README
A PHP library to map associative arrays to typed DTOs using attributes.
Installation
Install from Packagist:
composer require rjds/php-dto
Requirements:
- PHP 8.1 or higher
Overview
DtoMapper converts associative arrays to typed constructor-based DTOs.
use Rjds\PhpDto\Attribute\ArrayOf; use Rjds\PhpDto\Attribute\CastTo; use Rjds\PhpDto\Attribute\MapFrom; use Rjds\PhpDto\DtoMapper; final class TagDto { public function __construct( public readonly string $name, public readonly string $url, ) { } } final class ArtistDto { /** @param list<TagDto> $tags */ public function __construct( public readonly string $name, #[MapFrom('stats.play_count')] #[CastTo('int')] public readonly int $playCount, #[ArrayOf(TagDto::class)] public readonly array $tags, ) { } } $mapper = new DtoMapper(); $artist = $mapper->map([ 'name' => 'Arctic Monkeys', 'stats' => ['play_count' => '150316'], 'tags' => [ ['name' => 'rock', 'url' => 'https://www.last.fm/tag/rock'], ['name' => 'indie', 'url' => 'https://www.last.fm/tag/indie'], ], ], ArtistDto::class); echo $artist->playCount; // 150316 (int) echo $artist->tags[0]->name; // rock
Features
- Zero-boilerplate name-based mapping for constructor arguments
#[MapFrom]support for renamed and nested keys using dot notation#[CastTo]support forint,float,string,bool, anddatetime#[ArrayOf]support for mapping nested DTO collections- Constructor default values respected when source keys are missing
MappingExceptionwith structured context (DTO class, parameter, map key, array index, nestedArrayOfparent) for easier debugging- Optional PHPStan extension to narrow
DtoMapper::map()return types
Errors
Mapping failures throw Rjds\PhpDto\Exception\MappingException (a subclass of InvalidArgumentException). Use getDtoClass(), getParameterName(), getMapKey(), getArrayIndex(), and getParentDtoClass() to locate problems in large payloads. Failures while mapping an #[ArrayOf] element chain the inner exception via getPrevious() / getPreviousMappingException().
Static analysis (PHPStan)
DtoMapper::map() is documented with @template and @param class-string<T> so PHPStan can infer the concrete DTO when you pass a class constant:
$artist = $mapper->map($data, ArtistDto::class); // $artist is inferred as ArtistDto when the second argument is a literal ::class
For clearer results in all setups, include the bundled extension from your phpstan.neon:
includes: - vendor/rjds/php-dto/phpstan-extension.neon
That extension refines the return type when the second argument is a constant string or a class-string<SpecificDto> type.
Documentation
Extended usage and attribute details are in the GitHub Wiki. The wiki is tracked as a Git submodule at wiki/. Clone the library with git clone --recurse-submodules, or run git submodule update --init after a plain clone. To publish wiki edits, commit inside wiki/ and run git push origin master from that directory (GitHub creates the wiki Git remote when you add the first wiki page in the repository Wiki tab, if it is not there yet).
Quick Reference
#[MapFrom]
Map a parameter from a different key, including nested paths:
#[MapFrom('profile.first_name')] public readonly string $firstName
#[CastTo]
Cast scalar string input into strongly typed DTO fields:
#[CastTo('datetime')] public readonly \DateTimeImmutable $registeredAt
#[ArrayOf]
Map list entries to nested DTO instances:
#[ArrayOf(TagDto::class)] public readonly array $tags
Development
composer install php vendor/bin/grumphp run
Run mutation testing:
php vendor/bin/infection --threads=4
Contributing
Contributions are welcome. See CONTRIBUTING.md for branch strategy, commit conventions, and PR workflow.
License
This project is released under the MIT License. See LICENSE for details and CHANGELOG.md for release history.