Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 17 additions & 13 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]
### Added
- [GH#297](https://github.com/jolicode/automapper/pull/297) : Support PHP 8.5 and Symfony 8, this library now use the `TypeInfo` Component for types instead of PropertyInfo directly.
- Debug command now show the type of each property mapped, transformers will also display more information.
- Profiler now show the type of each property mapped, transformers will also display more information.
- Add a castor task to serve the symfony app in tests for debugging purpose.
- [GH#304](https://github.com/jolicode/automapper/pull/304) ; Allow to override source and/or target property type
- Add support for static callable in attribute transformer
- Initial support for nested properties
- Add support for object invokable transformer in attribute transformer
- Add a new interface `PropertyTransformerComputeInterface` to allow property transformers with supports, to compute a value that will be fixed during code generation.
- Support ObjectMapper attributes
- Add an implementation for Symfony `ObjectMapperInterface` using AutoMapper
- [GH#297](https://github.com/jolicode/automapper/pull/297) Support PHP 8.5 and Symfony 8, this library now use the `TypeInfo` Component for types instead of PropertyInfo directly.
- [GH#297](https://github.com/jolicode/automapper/pull/297) Debug command now show the type of each property mapped, transformers will also display more information.
- [GH#297](https://github.com/jolicode/automapper/pull/297) Profiler now show the type of each property mapped, transformers will also display more information.
- [GH#304](https://github.com/jolicode/automapper/pull/304) Allow to override source and/or target property type.
- [GH#314](https://github.com/jolicode/automapper/pull/314) Add support for static callable in attribute transformer.
- [GH#317](https://github.com/jolicode/automapper/pull/317) Initial support for nested properties.
- [GH#318](https://github.com/jolicode/automapper/pull/318) Add support for lazy mapping.
- [GH#316](https://github.com/jolicode/automapper/pull/316) Add support for object invokable transformer in attribute transformer.
- [GH#319](https://github.com/jolicode/automapper/pull/319) Add support for discriminator with `Mapper` attribute.
- [GH#320](https://github.com/jolicode/automapper/pull/320) Add a new interface `PropertyTransformerComputeInterface` to allow property transformers with supports, to compute a value that will be fixed during code generation.
- [GH#306](https://github.com/jolicode/automapper/pull/306) Support ObjectMapper attributes.
- [GH#306](https://github.com/jolicode/automapper/pull/306) Add an implementation for Symfony `ObjectMapperInterface` using AutoMapper.

### Changed
- [BC Break] `PropertyTransformerSupportInterface` does not use a `TypesMatching` anymore, you can get the type directly from `SourcePropertyMetadata` or `TargetPropertyMetadata`.
- [BC BREAK] `ProviderInterface::provide` method now receive also the identifiers of the object to provide.
- **[BC Break]** [GH#297](https://github.com/jolicode/automapper/pull/297) `PropertyTransformerSupportInterface` does not use a `TypesMatching` anymore, you can get the type directly from `SourcePropertyMetadata` or `TargetPropertyMetadata`.
- **[BC Break]** [GH#297](https://github.com/jolicode/automapper/pull/297) `ProviderInterface::provide` method now receive also the identifiers of the object to provide.

### Fixed
- [GH#303](https://github.com/jolicode/automapper/pull/302) Fix api platform not returning an iri when there is no property mapped.

### Miscellaneous
- [GH#297](https://github.com/jolicode/automapper/pull/297) Add a castor task to serve the symfony app in tests for debugging purpose.

## [9.5.0] - 2025-09-18
### Added
- [GH#260](https://github.com/jolicode/automapper/pull/260) Add support for identifiers detection and comparison of objects, this allow mappers to detect if objects are equals based on some properties, which allow better deep merge / update of collections.
Expand Down
5 changes: 4 additions & 1 deletion docs/_nav.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,25 @@
- [Mapping](mapping/index.md)
- [Mapper attribute](mapping/mapper-attribute.md)
- [MapTo and MapFrom attributes](mapping/attributes.md)
- [Symfony Serializer](mapping/serializer.md)
- [Nested properties](mapping/nested.md)
- [Mapping Collections](mapping/map-collection.md)
- [Ignoring properties](mapping/ignoring-properties.md)
- [Conditional mapping](mapping/conditional-mapping.md)
- [Groups](mapping/groups.md)
- [Transformer](mapping/transformer.md)
- [Provider](mapping/provider.md)
- [Type](mapping/type.md)
- [Mapping inheritance](mapping/inheritance.md)
- [Identifier: mapping existing objects](mapping/identifier.md)
- [DateTime format](mapping/date-time.md)
- [Symfony Serializer](mapping/serializer.md)
- [Symfony Bundle](bundle/index.md)
- [Installation](bundle/installation.md)
- [Configuration](bundle/configuration.md)
- [Cache Warmup](bundle/cache-warmup.md)
- [Expression Language](bundle/expression-language.md)
- [Api Platform](bundle/api-platform.md)
- [Object Mapper](bundle/object-mapper.md)
- [Migrate existing application](bundle/migrate.md)
- [Debugging](bundle/debugging.md)
- [Upgrading](upgrading/upgrading-10.0.md)
Expand Down
3 changes: 3 additions & 0 deletions docs/bundle/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ automapper:
reload_strategy: "always"
serializer_attributes: true
api_platform: false
object_mapper: false
name_converter: null
mapping:
paths:
Expand Down Expand Up @@ -79,6 +80,8 @@ indicate if we use the attribute of the symfony/serializer during the mapping, t
`#[MaxDepth]`, `#[Ignore]` and `#[DiscriminatorMap]` attributes;
* `api_platform` (default: `false`): A boolean which indicate if we use services from the api-platform/core package and
inject extra data (json ld) in the mappers when we map a Resource class to or from an array.
* `object_mapper` (default: `false`): A boolean which indicate if we use attributes from the symfony/object-mapper
component to configure the mapping, and use AutoMapper as an implementation for `ObjectMapperInterface`
* `doctrine` (default: `false`): A boolean which indicate if we want to use service from doctrine to extract more
information about the classes, and use doctrine to fetch existing objects. It will use by default the `doctrine.orm.entity_manager`
service if present, will then try to use the `doctrine_mongodb.odm.document_manager` service, or throw an exception if none of them
Expand Down
1 change: 1 addition & 0 deletions docs/bundle/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ features linked to Symfony way of doing things.
- [Cache Warmup](cache-warmup.md)
- [Expression Language](expression-language.md)
- [Api Platform](api-platform.md)
- [Object Mapper](object-mapper.md)
- [Migrate existing application](migrate.md)
- [Debugging](debugging.md)
14 changes: 14 additions & 0 deletions docs/bundle/object-mapper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Object Mapper integration

> [!WARNING]
> The object mapper integration is in a experimental state, and may change in the future.
> Some behavior may not be handled correctly, and some features may not be implemented.
>
> If you find a bug or missing feature, please report it on the [issue tracker](https://github.com/jolicode/automapper/issues).

This bundle provides a way to integrate with [Object Mapper Component of Symfony](https://symfony.com/doc/current/object_mapper.html)
by reading the mapping metadata from the Object Mapper `#[Map]` attributes. and also by providing an
implementation of the `Symfony\Component\ObjectMapper\ObjectMapperInterface` interface using the
AutoMapper library.

You have to enable the `object_mapper` option in the configuration to use this feature.
3 changes: 2 additions & 1 deletion docs/mapping/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ a `source` and a `target`.

- [Mapper attribute](mapper-attribute.md)
- [MapTo and MapFrom attributes](attributes.md)
- [Symfony Serializer attributes](serializer.md)
- [Nested properties](nested.md)
- [Mapping a collection](map-collection.md)
- [Ignoring properties](ignoring-properties.md)
- [Conditional mapping](conditional-mapping.md)
Expand All @@ -16,3 +16,4 @@ a `source` and a `target`.
- [Mapping inheritance](inheritance.md)
- [Identifier: mapping existing objects](identifier.md)
- [DateTime format](date-time.md)
- [Symfony Serializer attributes](serializer.md)
50 changes: 40 additions & 10 deletions docs/mapping/inheritance.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,47 @@
A `source` or `target` class may inherit from another class.

When creating the mapping, AutoMapper can determine the correct mapping by using the inheritance information from
the Symfony Serializer `#[DiscriminatorMap]` attribute.
the `#[Mapper]` attribute.

```php
#[DiscriminatorMap(typeProperty: 'type', mapping: [
'cat' => Cat::class,
'dog' => Dog::class,
'fish' => Fish::class,
])]
#[Mapper(discriminator: new Discriminator(
mapping: [
DogDto::class => Dog::class,
CatDto::class => Cat::class,
]
))]
abstract class Pet
{
/** @var string */
public $type;
public $name;

/** @var PetOwner */
public $owner;
}
```

When mapping a `Pet` object, AutoMapper will automatically determine the correct class to instantiate based on
the instance of the property.

If it's a `Dog` class it will map to a `DogDto` class, and if it's a `Cat` class it will map to a `CatDto` class.

Note that the key is the `target` class, and the value is the `source` class.

## Mapping to an array

When mapping to an array there is no class to determine the correct mapping. In this case, instead of using the instance
of the object, AutoMapper will use the value of a specific property to determine the correct mapping.

```php
#[Mapper(target: 'array', discriminator: new Discriminator(
property: 'type',
mapping: [
'dog' => Dog::class,
'cat' => Cat::class,
]
))]
abstract class Pet
{
/** @var string */
public $name;

Expand All @@ -24,9 +52,11 @@ abstract class Pet
}
```

When mapping a `Pet` object, AutoMapper will automatically determine the correct class to instantiate based on the `type` property.
In this example, when mapping to / from an array, AutoMapper will write / read the `type` property to determine
the correct mapping.

[Learn more about the Symfony Serializer inheritance mapping](https://symfony.com/doc/current/components/serializer.html#serializing-interfaces-and-abstract-classes)
If the `type` property is equal to `dog`, it will map to / from the `Dog` class, and if it's equal to `cat`,
it will map to / from the `Cat` class.

> [!NOTE]
> If you don't use the Symfony Serializer we do not provide, yet, any way to determine the correct class to instantiate.
> It also possible to use the same principle when mapping to a data structure that don't have inheritance.
52 changes: 52 additions & 0 deletions docs/mapping/nested.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Nested properties

The `#[MapTo]` and `#[MapFrom]` attributes support mapping to and from nested properties using dot notation in the `property` parameter
of the attributes.

```php
class UserDto
{
#[MapTo(property: 'address.street')]
public string $streetAddress;

#[MapTo(property: 'address.city')]
public string $cityAddress;

public string $name;
}

class User
{
public Address $address;
public string $name;

public function __construct()
{
$this->address = new Address();
}
}

class Address
{
public string $street;
public string $city;
}
```

When mapping from `UserDto` to `User`, the `streetAddress` and `cityAddress` properties will be mapped to the `street` and `city` properties of the nested `address` property in the `User` class.

```php
$mapper->map(new UserDto(
streetAddress: '123 Main St',
cityAddress: 'Springfield',
name: 'John Doe'
), User::class);
```

This will result in a `User` object with an `Address` object where `street` is '123 Main St' and `city` is 'Springfield', and `name` is 'John Doe'.

It can also works in the opposite direction when mapping from `User` to `UserDto` using the `#[MapFrom]` attribute.

> [!WARNING]
> When using nested properties, the intermediate objects (like `Address` in this case) need to be properly
> initialized before mapping to avoid null reference errors.
17 changes: 17 additions & 0 deletions docs/mapping/serializer.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,20 @@ use Symfony\Component\Serializer\Serializer;
$autoMapper = AutoMapper::create();
$serializer = new Serializer([new AutoMapperNormalizer($autoMapper)]);
```

### Discriminator Map

The Symfony Serializer `#[DiscriminatorMap]` attribute can be used to define a discriminator map for polymorphic mapping.

```php
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;

#[DiscriminatorMap(typeProperty: 'type', mapping: [
'dog' => Dog::class,
'cat' => Cat::class,
])]
abstract class Pet
{
public $name;
}
```
50 changes: 47 additions & 3 deletions docs/mapping/transformer.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,9 @@ namespace App\Transformer;
use AutoMapper\Metadata\MapperMetadata;
use AutoMapper\Metadata\SourcePropertyMetadata;
use AutoMapper\Metadata\TargetPropertyMetadata;
use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerInterface;
use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerSupportInterface;

class UrlTransformer implements PropertyTransformerInterface, PropertyTransformerSupportInterface
class UrlTransformer implements PropertyTransformerSupportInterface
{
public function __construct(private UrlGeneratorInterface $urlGenerator)
{
Expand Down Expand Up @@ -158,7 +157,7 @@ If you have multiple transformers that can be applied to the same transformation
```php
namespace App\Transformer;

class UrlTransformer implements PropertyTransformerInterface, PropertyTransformerSupportInterface, PrioritizedPropertyTransformerInterface
class UrlTransformer implements PropertyTransformerSupportInterface, PrioritizedPropertyTransformerInterface
{
// ...

Expand All @@ -170,3 +169,48 @@ class UrlTransformer implements PropertyTransformerInterface, PropertyTransforme
```

When multiple transformers can be applied, the one with the highest priority will be used.

### Computing extra data for the transformer

In some cases you may want to compute extra data that will be passed to the transformer. This is possible by
implementing the `PropertyTransformerDataProviderInterface` interface.

```php
namespace App\Transformer;

use AutoMapper\Metadata\MapperMetadata;
use AutoMapper\Metadata\SourcePropertyMetadata;
use AutoMapper\Metadata\TargetPropertyMetadata;
use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerSupportInterface;

class UrlTransformer implements PropertyTransformerComputeInterface
{
public function __construct()
{
}

public function supports(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): bool
{
return $source->type->isIdentifiedBy(TypeIdentifier::INT && $source->property === 'id' && $target->property === 'url';
}

public function compute(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): mixed
{
// Compute extra data here
return 'some computed data';
}

public function transform(mixed $value, object|array $source, array $context, mixed $computed = null): mixed
{
return $computed . $value;
}
}
```

The value returned by the `compute` method will be created when generating the mapper and passed as a static value to
the `transform` method each time it is needed.

> [!WARNING]
> This value is only computed when the transformer comes from guessed with the `supports` method. If you specify the transformer
> manually in the `#[MapTo]` or `#[MapFrom]` attribute, the `compute` method will not be called and there will be no value passed
> to the `transform` method.
Loading
Loading