5min.

AutoMapper 9 is out!

We are pleased to announce the release of AutoMapper 9.0 which brings a completely new experience creating mappers between objects 🎉

Let’s be honest, the first version of this library was merely a proof of concept. Despite being used in production on some of our projects, it was difficult to use, understand and configure for newcomers.

This new version is a rewriting of a lot of internals, and brings a lot of new features and improvements. We hope you will enjoy it as much as we do 😍

Section intitulée what-is-the-automapperWhat is the AutoMapper?

AutoMapper is a PHP object-object mapper. Object-object mapping works by transforming an input object (or array) of one type into an output object (or array) of a different type. What makes AutoMapper interesting is that it provides some conventions to take the dirty work out of figuring out how to map type A to type B. As long as type B follows AutoMapper’s established convention, almost zero configuration is needed to map two types.

When a transformation from A to B is done, AutoMapper will generate a Mapper class that will make the transformation. It looks a lot like Symfony’s normalizer and is optimized as much as possible.

Section intitulée what-s-newWhat’s new?

Section intitulée new-documentationNew Documentation

First of all we have rewritten the documentation, it is now more complete, and we hope, more understandable. It is still not perfect, specially on the examples, if you have some use cases you would like to see explained, please open an issue on the GitHub repository.

Section intitulée improved-usage-and-dxImproved Usage and DX

When you need to map an object to another, you can use the map() method of AutoMapper class:

$mapper = AutoMapper::create();
$result = $mapper->map($entity, EntityDTO::class);

Here, $result will be an EntityDTO instance. Moreover, thanks to built-in PHPDoc, static analyzer like PHPStan will interpret the value correctly.

Most of the time you won’t need more than this configuration. But in case you need it, you can now control how the mapping is done by using attributes. There are 4 attributes available:

  • MapTo and MapFrom to specify the source and target properties and how to map them;
  • Mapper to register a mapper and/or configure how the generation is done for it;
  • MapProvider to register a provider for a mapper : a different way to create the target object (i.e. fetch it from a database).

All those attributes have a lot of parameters to configure how the mapping is done, you can see the full documentation for more information.

Let’s see a complete example, where we want to do two things:

  1. Allow to map from an Entity class to an EntityDTO class;
  2. Allow to map from an Entity class to an array.

Here is our Entity class:

class Entity
{
	public ?string $title;
	public User $author;
}

Here is our EntityDTO class:

class EntityDTO
{
	public string $title;
	public string $authorFirstName;
	public string $authorLastName;

	public function __construct()
	{
    		$this->title = 'Default title';
	}
}

Section intitulée registering-a-mapperRegistering a Mapper

First we want to register mappers for both use cases, it is not mandatory but it will allow us to generate this mapper if we are using the Symfony Bundle with cache warmup, it avoids creating it on the fly which may not be possible on readonly filesystem.

use AutoMapper\Attribute as Mapper;

#[Mapper\Mapper(target: [EntityDto::class, 'array'])]
class Entity
{
	...

Maybe you don’t want to use the constructor of the target object when mapping to the EntityDTO class, you can configure that with the constructorStrategy parameter:

use AutoMapper\Attribute as Mapper;

#[Mapper\Mapper(target: [EntityDto::class, 'array'], constructorStrategy: ConstructorStrategy::NEVER)]
class Entity
{
	...

Section intitulée mapping-propertiesMapping Properties

Now we want to map the properties of the Entity class to the EntityDTO class, as you can see some of them don’t have the same name.

We want to flatten the author property of the Entity class to the authorFirstName and authorLastName properties of the EntityDTO class, this can be done by using the MapTo attribute:

use AutoMapper\Attribute as Mapper;

#[Mapper\Mapper(target: [EntityDto::class, 'array'], constructorStrategy: ConstructorStrategy::NEVER)]
class Entity
{
	...

	#[Mapper\MapTo(target: EntityDto::class, property: 'firstName', transformer: 'source.author.firstName')]
	#[Mapper\MapTo(target: EntityDto::class, property: 'lastName', transformer: 'source.author.lastName')]
	public User $author;

	...

It is also possible to do that in the EntityDTO class with the MapFrom attribute, which can be useful if you don’t have control of the source class (i.e. using a class from a third party library):

class EntityDTO
{
	...

	#[Mapper\MapFrom(source: Entity::class, transformer: 'source.author.firstName')]
	public string $authorFirstName;
	#[Mapper\MapFrom(source: Entity::class, transformer: 'source.author.lastName')]
	public string $authorLastName;

	...
}

We use the transformer parameter to specify how the mapping is done, in this example it leverages the expression language, with the variable source being an Entity instance in this case.

We may have used a callback or a custom transformer but in most cases the expression language can handle your logic.

Section intitulée virtual-propertiesVirtual Properties

Now we want to map this entity to an array and add an url property that is not present in the Entity class, we can do that with the MapTo attribute defined on this class:

use AutoMapper\Attribute as Mapper;

#[Mapper\Mapper(target: [EntityDto::class, 'array'], constructorStrategy: ConstructorStrategy::NEVER)]
#[Mapper\MapTo(target: 'array', property: 'url', transformer: UrlTransformer::class)]
class Entity
{
	...

We also need to create the UrlTransformer class:

class UrlTransformer implements PropertyTransformerInterface
{
	public function __construct(private UrlGenerator $urlGenerator)
	{
	}

	public function transform(mixed $value, object|array $source, array $context): mixed
	{
    	return $this->urlGenerator->generate('entity_show', ['title' => $source->title]);
	}
}

Please note that the UrlTransformer class is a service, AutoMapper will not create it for you, you need to register it either when creating the AutoMapper instance or by using the Symfony Bundle.

That’s it, you can now map an Entity object to an EntityDTO object or an array with your custom logic.

Again, this shouldn’t be something you need all the time and only in very specific cases. There is a lot more you can do with AutoMapper 9 to customize your logic, please check the documentation for more information.

Section intitulée new-built-in-symfony-bundleNew Built-in Symfony Bundle

The Symfony Bundle is now integrated in the main package, we believe this will simplify the installation and configuration and avoid version mismatch between the bundle and the main package.

AutoMapper profiler tab #1 AutoMapper profiler tab #2

Section intitulée new-debugging-and-profilingNew Debugging and Profiling

We have also added tools to help you debug and profile your mappers when using the Symfony Bundle, you can now debug the generated code with the debug:mapper command or with a new panel in the Symfony Profiler.

Symfony’s debug:mapper output

Section intitulée new-integration-with-symfony-s-serializer-and-api-platformNew Integration with Symfony’s Serializer and API Platform

From the start this library was also a way to improve performance of serialization in Symfony applications, and we have extended this by integrating it also with API Platform.

API Platform support is still in its early stage, but we believe it will be a great addition to the library.

However, you may not want to replace all your existing serializers at first, so we also make sure this version delivers a way to migrate progressively to AutoMapper without breaking your existing code. Look at our migration guide for more information on how to achieve this.

Section intitulée what-s-nextWhat’s next?

Further versions will focus on stability and improving third party integrations, we also want to provide more ways to customize the mappers.

If you have any idea or feature request, please open an issue or create a pull request on the GitHub repository

We hope you will enjoy this new version, and we are looking forward to seeing what you will build with it.

Happy mapping!

Commentaires et discussions

Ces clients ont profité de notre expertise