jolicode/automapper-bundle
Symfony bundle integrating JoliCode Automapper for fast, type-safe object-to-object mapping. Configure mappings, inject the mapper service, and convert entities, DTOs, and other objects with minimal boilerplate.
The bundle is a thin Symfony DI wrapper around jolicode/automapper; almost everything you'll actually use lives in the library, so budget most of your reading time there.
Minimal setup
composer require jolicode/automapper-bundle
As of 8.1.0 the bundle requires jolicode/automapper >= 8.2. That constraint is the whole content of the release (GH#12), so the first thing to check after installing is what the solver actually picked:
composer show jolicode/automapper jolicode/automapper-bundle
Register it manually — with effectively no adoption there is no Flex recipe, so expect to edit config/bundles.php yourself:
Jolicode\AutoMapperBundle\AutoMapperBundle::class => ['all' => true],
There is nothing you must configure; the bundle's job is to register AutoMapper\AutoMapperInterface (and the mapper registry/metadata services around it) as services and to autoconfigure your own MapperInterface implementations as mappers.
First use case: entity → DTO
This is the 80% case. Replace hand-written BookDto::fromEntity() factories with a single injected service:
use AutoMapper\AutoMapperInterface;
final class BookController
{
public function __construct(private AutoMapperInterface $autoMapper) {}
public function __invoke(Book $book): BookDto
{
return $this->autoMapper->map($book, BookDto::class);
}
}
Where to look first
bin/console debug:container AutoMapper — confirm the service id/alias the bundle registered (it varies between versions).vendor/jolicode/automapper-bundle/README.md — short; installation, the service id, and the supported AutoMapper range.jolicode/automapper README/source under vendor/jolicode/automapper — the attribute reference (#[MapFrom], #[MapTo], #[Mapper]), the AutoMapper::create() signature and the generated-mapper model. This is the real documentation, and it is where the behaviour actually changed between 8.1 and 8.2.Because the bundle is still a very thin, low-ceremony layer, keep the alternative in your back pocket from day one — you can replace it with a few lines of services.yaml if you ever need to drop it:
services:
AutoMapper\AutoMapperInterface:
factory: ['AutoMapper\AutoMapper', 'create']
arguments:
$cacheDirectory: '%kernel.cache_dir%/automapper'
DTO mapping at the edges of the application. Map Doctrine entities to API resources / view models in controllers, state providers, or dedicated *Assembler services. Keep mapping attributes on DTOs, not on entities, so your domain stays free of transport concerns.
Attribute-driven renames instead of hand-written hydrators. When names diverge, be explicit rather than relying on magic:
use AutoMapper\Attribute\MapFrom;
final class BookDto
{
#[MapFrom('title')]
public string $name;
#[MapFrom('author.name')]
public string $authorName;
}
Use #[MapFrom] on the target and #[MapTo] on the source depending on which side you consider authoritative; pick one direction per DTO pair and stay consistent.
Custom mappers for anything non-trivial. Computed fields, formatting, service calls (money conversion, URL generation) belong in a mapper class rather than a transformer chain:
use AutoMapper\Attribute\Mapper;
use AutoMapper\MapperInterface;
#[Mapper(source: Book::class, target: BookDto::class)]
final class BookMapper implements MapperInterface
{
public function __construct(private PriceFormatter $formatter) {}
public function map($source, array $context = []): BookDto
{
// explicit construction, full control
}
}
The bundle autoconfigures these (it registers MapperInterface for autoconfiguration), so no tags are needed — but verify with debug:container --tag if yours is ignored, and fall back to an explicit tag if the autoconfiguration doesn't fire.
Transformers and conditions for value-level tweaks. #[MapFrom(source: 'createdAt', transformer: ...)] covers the cases too small for a custom mapper. Keep transformers pure and single-purpose; once you start chaining them, promote to a mapper class. Note that the transformer/condition API surface is defined by automapper, not the bundle — after the 8.1.0 jump to >= 8.2 re-read that section of the library changelog before assuming your transformers behave identically.
Collections. Mapping recurses into arrays/iterables, but element types need to be discoverable (native type or docblock). For lists, either map each item explicitly in a loop or confirm the collection handling works for your version before depending on it.
Context for request-specific behavior. map($source, Target::class, ['groups' => [...]]) style context is passed through to custom mappers and transformers — useful for "admin view vs. public view" DTOs without duplicating mapper classes.
Upgrade as a paired change. Because the bundle version is essentially a proxy for the library's supported range, treat automapper + automapper-bundle as one unit: bump both in the same PR, re-run your mapping tests, and clear the mapper cache so the generated code is regenerated against the new library. Never upgrade the bundle alone and assume "it's just a DI wrapper, nothing changed".
Integration points that pay off: ApiPlatform state providers/processors, Messenger handlers converting messages to domain objects, and Symfony Form data mappers. All are just constructor injection of AutoMapperInterface.
composer.json.jolicode/automapper 8.0/8.1 (because a transitive dependency pins it, or you're on an older PHP), composer update will silently refuse to move you to bundle 8.1.0. Diagnose with composer why-not jolicode/automapper 8.2 rather than reading the solver output, and if you can't move the library, stay on the previous bundle tag deliberately and record why.bin/console cache:clear (or delete var/cache/<env>/automapper) whenever mapping behavior looks wrong for no reason. Do this before you open a bug report — half of them are cache.#[MapFrom]/#[MapTo] and write a mapping test per DTO pair. These tests are also your regression net for library bumps.create() — but prefer public promoted properties in DTOs and avoid the reflection cost.automapper >= 8.2, re-verify readonly promoted properties and union types round-trip before designing new DTOs around them.MapFrom/MapTo) → transformer → custom MapperInterface with #[Mapper] → MapperRegistry override. Only reach for the registry when you need to replace built-in behavior globally.How can I help you explore Laravel packages today?