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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased

### Feature
- Allow to disable group checking globally or per mapper, instead of only per property

### Fixed
- Use the `MapFrom` attribute reference instead of `MapTo` when resolving transformers in `MapFromListener`
- Do not run a transformation on a null source value when the target is not nullable, a `TypeError` is thrown for typed properties instead of the transformation crashing on the null value
Expand Down
5 changes: 4 additions & 1 deletion docs/bundle/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ automapper:
constructor_strategy: 'auto'
date_time_format: !php/const DateTimeInterface::RFC3339
check_attributes: true
check_groups: true
auto_register: true
map_private_properties: true
allow_readonly_target_to_populate: false
Expand Down Expand Up @@ -52,6 +53,8 @@ automapper:
for more details about it;
* `check_attributes` (default: `true`): Check if the field should be mapped at runtime, this allow you to have dynamic
partial mapping, if you don't use this feature set it to false as it will improve the performance;
* `check_groups` (default: `true`): Does the generator should add code to check groups of the map or other attributes, if false
no group checking will be done at runtime, saving performance but may change behaviors also, use it if you don't use groups;
* `auto_register` (default: `true`): If the bundle should auto register the mappers in the container when it does not
exist, when set to `false` you have to register the mappers manually using the `mapping` option, this option is useful
when you cannot write to the disk, and you want to use the cache warmup;
Expand Down Expand Up @@ -93,4 +96,4 @@ component to configure the mapping, and use AutoMapper as an implementation for
* `paths`: A list of paths where to look for mappers to register; This will automatically register all classes
with the `#[Mapper]` attribute in the given paths.
* `mappers`: A list of mapping to register, each mapping should have a `source` and a `target` key, and can have
a `reverse` key to also register the reverse mapping.
a `reverse` key to also register the reverse mapping.
7 changes: 7 additions & 0 deletions docs/getting-started/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ $configuration = new Configuration(
constructorStrategy: 'auto',
dateTimeFormat: \DateTimeInterface::RFC3339,
attributesChecking: true,
groupsChecking: true,
autoRegister: true,
mapPrivateProperties: true,
allowReadonlyTargetToPopulate: false,
Expand Down Expand Up @@ -50,6 +51,12 @@ Setting this to false will not generate the code to check for `allowed_attribute
Some applications may not need this feature and disabling it will improve the performance as it avoid a check for each
property at runtime.

* `groupsChecking` (default: `true`)

Setting this to false will not generate the code to check for property `groups` at runtime.
Some applications may not need this feature and disabling it will improve the performance as it avoid a check for each
property at runtime.

* `autoRegister` (default: `true`)

AutoMapper generate the mappers on the fly when they are needed, also it store them in a cache directory. On the next
Expand Down
18 changes: 18 additions & 0 deletions docs/mapping/groups.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,21 @@ class Source
```

In this case the property will be mapped only if the context contains the `group3` group.

### Disabling groups

Even if you don't put groups in your property some checks are done at runtime to be sure that there is no groups in the context when mapping this property.

For a large entity set those checks are useless and degrade performance for no reason, you can disable a check for a specific property by using the "disableGroupsCheck" field of thre attribute :


```php

class Source
{
#[MapTo(target: 'array', disableGroupsCheck: true)]
public $property;
}
```

Those checks can also be disabled globally or per mapper by using the configuration.
1 change: 1 addition & 0 deletions src/Attribute/Mapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public function __construct(
public string|array|null $source = null,
public string|array|null $target = null,
public ?bool $checkAttributes = null,
public ?bool $checkGroups = null,
public ?ConstructorStrategy $constructorStrategy = null,
public ?bool $allowReadOnlyTargetToPopulate = null,
public ?bool $strictTypes = null,
Expand Down
4 changes: 4 additions & 0 deletions src/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ public function __construct(
* If the attributes should be checked to map the properties.
*/
public bool $attributeChecking = true,
/**
* Make mapper group aware, configure this to false when there is no groups behavior in your mapping, saving a lot of times.
*/
public bool $groupChecking = true,
/**
* If the mappers should be automatically generated if it does not exist
* Otherwise the mapper will throw a MapperNotFoundException.
Expand Down
1 change: 1 addition & 0 deletions src/Event/GenerateMapperEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public function __construct(
public array $properties = [],
public ?Provider $provider = null,
public ?bool $checkAttributes = null,
public ?bool $checkGroups = null,
public ?ConstructorStrategy $constructorStrategy = null,
public ?bool $allowReadOnlyTargetToPopulate = null,
public ?bool $strictTypes = null,
Expand Down
1 change: 1 addition & 0 deletions src/EventListener/MapperListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public function __invoke(GenerateMapperEvent $event): void

if ($directMapperAttribute) {
$event->checkAttributes ??= $directMapperAttribute->checkAttributes;
$event->checkGroups ??= $directMapperAttribute->checkGroups;
$event->constructorStrategy ??= $directMapperAttribute->constructorStrategy;
$event->allowReadOnlyTargetToPopulate ??= $directMapperAttribute->allowReadOnlyTargetToPopulate;
$event->strictTypes ??= $directMapperAttribute->strictTypes;
Expand Down
3 changes: 2 additions & 1 deletion src/Metadata/MetadataFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ private function createGeneratorMetadata(MapperMetadata $mapperMetadata): Genera
ksort($propertyEvents, SORT_NATURAL);

$propertiesMapping = [];
$mapperCheckGroups = $mapperEvent->checkGroups ?? $this->configuration->groupChecking;

foreach ($propertyEvents as $propertyMappedEvent) {
// Create the source property metadata
Expand Down Expand Up @@ -322,7 +323,7 @@ private function createGeneratorMetadata(MapperMetadata $mapperMetadata): Genera
$propertyMappedEvent->maxDepth,
$propertyMappedEvent->if,
$propertyMappedEvent->groups,
$propertyMappedEvent->disableGroupsCheck,
$propertyMappedEvent->disableGroupsCheck ?? !$mapperCheckGroups,
$propertyMappedEvent->identifier ?? false,
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
* constructor_strategy: string,
* date_time_format: string,
* check_attributes: bool,
* check_groups: bool,
* auto_register: bool,
* map_private_properties: bool,
* allow_readonly_target_to_populate: bool,
Expand Down Expand Up @@ -86,6 +87,7 @@ public function load(array $configs, ContainerBuilder $container): void
->setArgument('$constructorStrategy', ConstructorStrategy::tryFrom($config['constructor_strategy']) ?? ConstructorStrategy::AUTO)
->setArgument('$dateTimeFormat', $config['date_time_format'])
->setArgument('$attributeChecking', $config['check_attributes'])
->setArgument('$groupChecking', $config['check_groups'])
->setArgument('$autoRegister', $config['auto_register'])
->setArgument('$mapPrivateProperties', $config['map_private_properties'])
->setArgument('$allowReadOnlyTargetToPopulate', $config['allow_readonly_target_to_populate'])
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Bundle/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public function getConfigTreeBuilder(): TreeBuilder
->end()
->scalarNode('date_time_format')->defaultValue(\DateTimeInterface::RFC3339)->end()
->booleanNode('check_attributes')->defaultTrue()->end()
->booleanNode('check_groups')->defaultTrue()->end()
->booleanNode('auto_register')->defaultTrue()->end()
->booleanNode('map_private_properties')->defaultFalse()->end()
->booleanNode('allow_readonly_target_to_populate')->defaultFalse()->end()
Expand Down
2 changes: 1 addition & 1 deletion tests/AutoMapperTest/IdentifierHash/map.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

namespace AutoMapper\Tests\AutoMapperTest\IdentifierHash;

use AutoMapper\Attribute\Mapper;
use AutoMapper\Attribute\MapFrom;
use AutoMapper\Attribute\Mapper;
use AutoMapper\Tests\AutoMapperBuilder;

#[Mapper(source: 'array', strictTypes: true)]
Expand Down
4 changes: 4 additions & 0 deletions tests/AutoMapperTest/IgnoreGroup/expected.data
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[
"id" => "id"
"name" => "name"
]
25 changes: 25 additions & 0 deletions tests/AutoMapperTest/IgnoreGroup/map.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace AutoMapper\Tests\AutoMapperTest\IgnoreGroup;

use AutoMapper\Attribute\Mapper;
use AutoMapper\Attribute\MapTo;
use AutoMapper\Tests\AutoMapperBuilder;

#[Mapper(checkGroups: false)]
class GroupIgnore
{
#[MapTo(groups: ['group2'])]
public string $id = 'id';

#[MapTo(ignore: false)]
public string $name = 'name';
}

$autoMapper = AutoMapperBuilder::buildAutoMapper();

$group = new GroupIgnore();

return $autoMapper->map($group, 'array', ['groups' => ['group2']]);
2 changes: 1 addition & 1 deletion tests/AutoMapperTest/NestedPropertyAccessors/map.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class ZipSource
public string $zipcode = '75000';
}

return (function () {
return (static function () {
$autoMapper = AutoMapperBuilder::buildAutoMapper();

// uninitialized typed parent property: must be skipped, not crash
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class UntypedTarget
public $name = 'default name';
}

return (function () {
return (static function () {
$autoMapper = AutoMapperBuilder::buildAutoMapper();

// null source values on a non-nullable typed target: the transformation must not run on the null
Expand Down
2 changes: 1 addition & 1 deletion tests/AutoMapperTest/UnionSourceProperty/map.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class Target
public string $status;
}

return (function () {
return (static function () {
$autoMapper = AutoMapperBuilder::buildAutoMapper();

// union branches without a native type check (string -> \DateTimeImmutable, enum -> string)
Expand Down
2 changes: 1 addition & 1 deletion tests/AutoMapperTest/VariadicConstructor/map.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public function __construct(
}
}

return (function () {
return (static function () {
$autoMapper = AutoMapperBuilder::buildAutoMapper();

$source = new Source();
Expand Down
6 changes: 3 additions & 3 deletions tests/Bundle/Resources/App/Entity/Pet.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
use Symfony\Component\Serializer\Attribute\DiscriminatorMap;

#[DiscriminatorMap(typeProperty: 'type', mapping: [
'cat' => Cat::class,
'dog' => Dog::class,
])]
'cat' => Cat::class,
'dog' => Dog::class,
])]
class Pet
{
/** @var string */
Expand Down
Loading