jolicode/automapper
A fast, flexible object-to-object mapper for PHP inspired by .NET's AutoMapper. Map between objects, arrays, and scalars using attributes, with automatic type conversion, nested mappings, custom transformers, and zero-config defaults. Framework-agnostic, works great with Symfony.
Install with Composer and confirm the PHP version target (the package leans on modern PHP features and AST codegen):
composer require jolicode/automapper
Build a mapper once and reuse it (it is a service, not a per-call helper):
use AutoMapper\AutoMapper;
$mapper = AutoMapper::create();
$dto = $mapper->map($entity, UserDto::class); // object -> object (populates class)
$array = $mapper->map($entity, 'array'); // object -> array
$entity = $mapper->map($payload, UserEntity::class); // array -> object
Where to look first:
AutoMapper::create() / AutoMapperBuilder — the supported entry points for wiring configuration, cache, and constructor behavior.AutoMapper\Attribute\Mapper, MapFrom, MapTo — the attribute surface you will use daily for name/shape mismatches.AutoMapper\Transformer\* — when built-in coercions aren't enough (dates, enums, value objects, collections).jolicode/automapper-bundle) if you're in a Laravel/Symfony-style DI app — it registers AutoMapperInterface and a debug command.First useful case: mapping an Eloquent-ish entity or domain model into an API DTO, and the reverse for write payloads. Start with one DTO, one attribute override, and grow from there.
DTO / response shaping. Map entities to typed DTOs at the controller or resource boundary so serialization and validation both operate on a stable class. Keep DTOs plain (public promoted properties or getters/setters) — the mapper infers types via property-info.
Reverse mapping for writes. map($request->validated(), CreateUser::class) gives you a command/entity object without hand-written hydration. Use MapFrom/MapTo when input keys differ from property names.
Attribute-driven config is the default dialect. Put behavior next to the class rather than in a central bootstrap:
use AutoMapper\Attribute\MapFrom;
class UserDto
{
public string $id;
#[MapFrom(source: User::class, property: 'display_name')]
public string $displayName;
#[MapFrom(source: User::class, property: 'created_at')]
public \DateTimeImmutable $createdAt;
}
Register pair-level config with #[Mapper(source: X::class, target: Y::class)] on the target when you need to override the whole mapping.
Custom transformers for value objects (Money, Email, UUID). Implement the transformer interface, declare supports() narrowly (source/target property types), and register it on the builder or via the bundle's service tag. Keep transformers pure and side-effect free — they run inside generated code and may run many times.
Collections and nesting. Register a per-item transformer (or a MapTo/MapFrom) for array<Foo> / Collection<Bar>; the mapper will not guess the element type from an untyped array. Nested object properties are mapped recursively, so deep graphs work — but see circular references below.
Context passing. Use the mapper context argument for per-call switches (e.g., "with relations", output format) and read it in transformers, rather than creating separate mapper instances per variant.
Framework integration. In a DI app, bind a single AutoMapperInterface and inject it. Prefer the bundle/extension so cache and transformer registration are centralized instead of calling create() in a helper.
map() for a pair compiles a PHP class; subsequent calls are fast. Ensure your cache backend (PSR-16) is persistent in production and warm it during deploy — don't let the first user request pay for it.AutoMapperBuilder options to control constructor use and private-property reading; don't toggle them ad hoc per call.DateTimeImmutable, int → backed enum, string → value object all need transformers or a matching source type. Silent nulls on required typed properties produce TypeErrors at map time — good for finding holes, bad for debugging.array $tags maps as array, but array<Comment> needs explicit configuration. Same for Laravel Collection vs. plain array.automapper:debug-style command) — you're reading exactly what executes. This is faster than reasoning about attributes.MapFrom/MapTo for naming, Mapper attribute for pair-level rules, context for runtime variation. Avoid subclassing the AutoMapper class itself; you'll lose compatibility with builder/bundle wiring.AutoMapper::create() calls (a common Laravel service-provider mistake) rebuild and recompile. Bind it as a singleton.How can I help you explore Laravel packages today?