luimedi / remap
A PHP object mapping library using attributes.
Requires
- php: >=8.3
Requires (Dev)
- brainmaestro/composer-git-hooks: ^3.0
- laravel/pint: ^1.27
- phpunit/phpunit: ^12
README
Remap is a lightweight PHP library for mapping arrays and objects into target objects using PHP attributes.
The mapping rules live in the target class, so the output model decides how data should be read and transformed.
Installation
composer require luimedi/remap
Quick Start
<?php use Luimedi\Remap\Mapper; use Luimedi\Remap\Attribute\ConstructorMapper; use Luimedi\Remap\Attribute\MapProperty; use Luimedi\Remap\Attribute\Cast\CastDateTime; class BookEntity { public function __construct( public string $title, public string $author, public DateTimeInterface $publishedAt, ) {} } #[ConstructorMapper] class BookResource { public function __construct( #[MapProperty(source: 'title')] public string $title, #[MapProperty(source: 'author')] public string $author, #[MapProperty(source: 'publishedAt')] #[CastDateTime] public string $publishedAt, ) {} } $mapper = new Mapper(); $mapper->bind(BookEntity::class, BookResource::class); $resource = $mapper->map(new BookEntity( title: 'El Hobbit', author: 'J.R.R. Tolkien', publishedAt: new DateTimeImmutable('1937-09-21'), ));
How It Works
- Define a target class.
- Add mapping attributes to its constructor parameters or public properties.
- Bind the source type to the target type.
- Call
map().
Mapping Styles
Constructor Mapping
Use #[ConstructorMapper] when your target is created through its constructor.
#[ConstructorMapper] class UserResource { public function __construct( #[MapProperty(source: 'username')] public string $username, #[MapProperty(source: 'registeredAt')] #[CastDateTime] public string $registeredAt, ) {} }
Property Mapping
Use #[PropertyMapper] when you prefer assigning public properties.
use Luimedi\Remap\Attribute\PropertyMapper; #[PropertyMapper] class ArticleResource { #[MapProperty(source: 'title')] public string $title; #[MapProperty(source: 'body')] public string $body; }
You can also combine both on the same class.
Mapping Attributes
MapProperty
Reads a value from an array or object property. It supports dot notation.
#[MapProperty(source: 'author.name')] public string $authorName;
MapGetter
Calls a method on the source object.
use Luimedi\Remap\Attribute\MapGetter; #[MapGetter(source: 'getType')] public string $type;
Built-in Casts
CastDateTime
Converts a DateTimeInterface or date string into an ISO-8601 string.
CastDefault
Provides a fallback value when the mapped value is missing or empty.
use Luimedi\Remap\Attribute\Cast\CastDefault; #[MapProperty(source: 'nickname')] #[CastDefault(default: 'anonymous')] public string $nickname;
CastTransformer
Maps nested arrays or objects into another target object.
use Luimedi\Remap\Attribute\Cast\CastTransformer; #[MapProperty(source: 'profile')] #[CastTransformer] public ?ProfileResource $profile = null;
CastIterable
Applies another caster to each item in an iterable.
use Luimedi\Remap\Attribute\Cast\CastIterable; use Luimedi\Remap\Attribute\Cast\CastTransformer; #[MapProperty(source: 'items')] #[CastIterable(class: CastTransformer::class)] public array $items = [];
Dynamic Binding
Instead of binding a source type to a fixed class, you can bind it to a resolver.
<?php use Luimedi\Remap\Mapper; $mapper = new Mapper(); $mapper->bind(UserInput::class, function ($from, $context) { return $from->isAdmin() ? AdminResource::class : UserResource::class; });
You can also pass extra data to a mapping call:
$result = $mapper->map($source, ['force_admin' => true]);
Error Handling
All library-specific exceptions extend Luimedi\Remap\Exception\RemapException.
use Luimedi\Remap\Exception\RemapException; try { $result = $mapper->map($source); } catch (RemapException $exception) { // Handle mapping errors here. }
Common exceptions:
BindingNotFoundExceptionBindingResolutionExceptionInvalidTargetTypeExceptionMapGetterResolutionExceptionMissingMappedValueException
Custom Casts
Create custom casts by implementing CastInterface.
<?php namespace App\Cast; use Attribute; use Luimedi\Remap\Contracts\CastInterface; use Luimedi\Remap\Contracts\ContextInterface; #[Attribute(Attribute::TARGET_PARAMETER | Attribute::TARGET_PROPERTY)] class AppendSuffix implements CastInterface { public function __construct( private string $suffix, ) {} public function cast(mixed $value, ContextInterface $context): mixed { if ($value === null) { return null; } return (string) $value . $this->suffix; } }
License
MIT. See LICENSE.