Skip to content

Commit

Permalink
chore(doc): add doc about mapping
Browse files Browse the repository at this point in the history
  • Loading branch information
joelwurtz committed Mar 22, 2024
1 parent c2b1ca2 commit 2c1e5c8
Show file tree
Hide file tree
Showing 12 changed files with 514 additions and 7 deletions.
2 changes: 1 addition & 1 deletion docs/_nav.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
- [Cache](getting-started/loader.md)
- [Mapping](mapping/index.md)
- [MapTo and MapFrom attributes](mapping/attributes.md)
- [Symfony Serializer](mapping/serializer.md)
- [Ignoring properties](mapping/ignoring-properties.md)
- [Conditional mapping](mapping/conditional-mapping.md)
- [Groups](mapping/groups.md)
- [Transformer](mapping/transformer.md)
- [Symfony Serializer](mapping/serializer.md)
- [Mapping inheritance](mapping/inheritance.md)
- [Symfony Bundle](bundle/index.md)
- [Installation](bundle/installation.md)
Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
- [Understanding the `source` and `target`](source-and-target.md)
- [Using the context](context.md)
- [Configuration](configuration.md)
- [Cache and Loaders](loader.md)
- [Cache](loader.md)
73 changes: 73 additions & 0 deletions docs/mapping/attributes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# `#[MapTo]` and `#[MapFrom]` attributes

The `#[MapTo]` and `#[MapFrom]` attributes allow you to define the mapping between a property of the source and the target object.

Respectively, the `#[MapTo]` attribute is used on a property of the `source` object, and the `#[MapFrom]` attribute
is used on a property of the `target` object.

They both allow the same arguments, but since you can map to or from a generic data structure, they may be needed
depending on the context.

## Usage

They can be used on :

* a public or private property (also in promoted properties)

```php
class Entity
{
#[MapTo(name: 'name')]
public string $title;
}
```

* a public or private method

```
class EntityDto
{
private string $name;
#[MapFrom(name: 'title')]
public function setName($name): void
{
$this->name = $name;
}
}
```

* a class (to add virtual properties)

```
#[MapTo(name: 'virtualProperty')]
class Entity {}
```

## Specifying the target or source

The `#[MapTo]` and `#[MapFrom]` attributes allow you to specify on which target or source this attribute should be applied.
You can use this attribute multiple times on the same property to handle behavior for different targets or sources.

```php
class Entity
{
#[MapTo(target: EntityDto::class, name: 'name')]
#[MapTo(target: 'array', name: 'title')]
public string $title;
}
```



```php
class EntityDto
{
#[MapFrom(source: Entity::class, name: 'title')]
#[MapFrom(source: 'array', name: 'name')]
public string $name;
}
```

> [!WARNING]
> If multiple `#[MapTo]` and/or `#[MapFrom]` attributes target the same property an exception will be thrown.
85 changes: 85 additions & 0 deletions docs/mapping/conditional-mapping.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Conditional Mapping

Ignoring a property is a good way to exclude it from the mapping process, but sometimes you may want to map a property conditionally.
This can be done using the `#[MapTo]` or `#[MapFrom]` attributes with the `if` argument.

The argument may accept several types of values:

### Expression language

You can use the Symfony Expression Language to define the condition.
In this context the `source` object is available as `source` and the `context` array is available as `context`.

```php
class Source
{
public bool $propertyIsValid = true;

#[MapTo(if: 'source.propertyIsValid and (context["custom_key"] ?? false) == true')]
public $property;
}
```

If you use the Bundle version of the AutoMapper, there is also [additional functions available](../bundle/expression-language.md).

> [!NOTE]
> In standalone mode we do not provide any functions to the expression language.
> However we are interested in adding some functions to the expression language in the future. If you have some use
> cases that you would like to see covered, please open an issue on the GitHub repository.
### PHP function

You can use a php function to define the condition. This function must return a boolean value.

```php
class Source
{
#[MapTo(if: 'boolval')]
public string $property = '';
}
```

> [!WARNING]
> If the PHP function need more arguments than the `source` object and the `context` array, it will throw an exception.
### Static callback

You can use a static callback to define the condition.

```php
class Source
{
public bool $propertyIsValid = true;

#[MapTo(if: [self::class, 'isPropertyValid'])]
public $property;

public static function isPropertyValid(Source $source, array $context): bool
{
return $source->propertyIsValid && ($context['custom_key'] ?? false) === true;
}
}
```

The static callback can accept the `source` object and the `context` array as arguments.

### Dynamic callback

You can also reference a method of the object declaring the attribute to define the condition.

```php
class Source
{
public bool $propertyIsValid = true;

#[MapTo(if: 'isPropertyValid')]
public $property;

public function isPropertyValid(): bool
{
return $this->propertyIsValid;
}
}
```

The dynamic callback can accept the `source` object and the `context` array as arguments.
32 changes: 32 additions & 0 deletions docs/mapping/groups.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Groups

In addition to use the Symfony Serializer `#[Groups]` attribute, you can also use the `#[MapTo]` and `#[MapFrom]`
attributes to define groups of properties that should be mapped.

```php
class Source
{
#[MapTo(target: 'array', groups: ['group1', 'group2'])]
public $groupedProperty;
}
```

When doing so the property will be mapped only if the context contains at least one group defined in the `groups` argument.

### Cumulative groups

When using both groups from the Symfony Serializer `#[Groups]` attribute and the `groups` argument from the `#[MapTo]`
or `#[MapFrom]` attributes, the latter groups will override the former groups.

```php
use Symfony\Component\Serializer\Attribute\Groups;

class Source
{
#[Groups(['group1', 'group2'])]
#[MapTo(target: 'array', groups: ['group3'])]
public $groupedProperty;
}
```

In this case the property will be mapped only if the context contains the `group3` group.
28 changes: 28 additions & 0 deletions docs/mapping/ignoring-properties.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Ignoring properties

Sometimes you may want to ignore a property during the mapping process. This can be done using the `#[MapTo]` or `#[MapFrom]` attributes
with the `ignore` argument set to `true`.

```php
class Source
{
#[MapTo(target: SourceDTO::class, ignore: true)]
#[MapTo(target: 'array', ignore: false)]
public $ignoredProperty;
}
```

Setting `ignore` to `false` may be useful when used in conjunction with the `#[Ignore]` attribute from the Symfony Serializer.

```php
use Symfony\Component\Serializer\Attribute\Ignore;

class Source
{
#[Ignore]
#[MapTo(target: SourceDTO::class, ignore: false)]
public $ignoredProperty;
}
```

In this case the property will be mapped to the `SourceDTO` class, but will be ignored when using the Symfony Serializer.
3 changes: 3 additions & 0 deletions docs/mapping/index.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Mapping

Despite doing its best to map objects automatically, AutoMapper provides a lot of ways to customize the mapping between
a `source` and a `target`.

- [MapTo and MapFrom attributes](attributes.md)
- [Symfony Serializer attributes](serializer.md)
- [Ignoring properties](ignoring-properties.md)
Expand Down
32 changes: 32 additions & 0 deletions docs/mapping/inheritance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Inheritance Mapping

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.

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

/** @var string */
public $name;

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

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

[Learn more about the Symfony Serializer inheritance mapping](https://symfony.com/doc/current/components/serializer.html#serializing-interfaces-and-abstract-classes)

> [!NOTE]
> If you don't use the Symfony Serializer we do not provide, yet, any way to determine the correct class to instantiate.
90 changes: 86 additions & 4 deletions docs/mapping/serializer.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,87 @@
### Normalizer Bridge 🌁
# Symfony Serializer Attributes

A Normalizer Bridge is available, aiming to be 100% feature compatible with the ObjectNormalizer of the
``symfony/serializer`` component. The goal of this bridge **is not to replace the ObjectNormalizer** but rather
providing a very fast alternative.
Symfony Serializer is a powerful component that can serialize and deserialize objects to and from various formats.
It can use several attributes to customize the serialization process.

When this component is available, AutoMapper can use these attributes to customize the mapping process.

### `#[Groups]`

The Symfony Serializer `#[Groups]` attribute can be used to define groups of properties that should be mapped.

```php
use Symfony\Component\Serializer\Attribute\Groups;

class Source
{
#[Groups(['group1', 'group2'])]
public $groupedProperty;
}
```

>! [!WARNING]
> When both `target` and `source` objects have groups, the property will be mapped only if the context contains at least
> one group from the `target` object and one group from the `source` object.
[More information on the Groups attribute](https://symfony.com/doc/current/components/serializer.html#attributes-groups)

### `#[Ignore]`

The Symfony Serializer `#[Ignore]` attribute can be used to ignore a property during the mapping process.

```php
use Symfony\Component\Serializer\Attribute\Ignore;

class Source
{
#[Ignore]
public $ignoredProperty;
}
```

[More information on the Ignore attribute](https://symfony.com/doc/current/components/serializer.html#ignoring-attributes)

### `#[MaxDepth]`

The Symfony Serializer `#[MaxDepth]` attribute can be used to limit the depth of the serialization process.

```php
use Symfony\Component\Serializer\Attribute\MaxDepth;

class Source
{
#[MaxDepth(1)]
public $nestedProperty;
}
```

[More information on the MaxDepth attribute](https://symfony.com/doc/current/components/serializer.html#handling-serialization-depth)

### Name converters

AutoMapper can use the Symfony Serializer name converters to convert the property names, when mapping to
or from an array.

```php
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;

$autoMapper = AutoMapper::create(nameConverter: new CamelCaseToSnakeCaseNameConverter());
```

[More information on the Name converters](https://symfony.com/doc/current/components/serializer.html#converting-property-names-when-serializing-and-deserializing)

### Normalizer Bridge

Additionally, this library provide a normalizer which implements the `Symfony\Component\Serializer\Normalizer\NormalizerInterface`
interface.

It's goal is to be as close as possible to the `ObjectNormalizer` of the `symfony/serializer` component, but with a focus on
performance.

```php
use AutoMapper\Normalizer\AutoMapperNormalizer;
use Symfony\Component\Serializer\Serializer;

$autoMapper = AutoMapper::create();
$serializer = new Serializer([new AutoMapperNormalizer($autoMapper)]);
```
Loading

0 comments on commit 2c1e5c8

Please sign in to comment.