diff --git a/CHANGELOG.md b/CHANGELOG.md index b41098ff..e41a88bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/docs/bundle/configuration.md b/docs/bundle/configuration.md index 8e128f7b..12bbfad7 100644 --- a/docs/bundle/configuration.md +++ b/docs/bundle/configuration.md @@ -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 @@ -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; @@ -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. diff --git a/docs/getting-started/configuration.md b/docs/getting-started/configuration.md index aac37cfc..ac8a7385 100644 --- a/docs/getting-started/configuration.md +++ b/docs/getting-started/configuration.md @@ -13,6 +13,7 @@ $configuration = new Configuration( constructorStrategy: 'auto', dateTimeFormat: \DateTimeInterface::RFC3339, attributesChecking: true, + groupsChecking: true, autoRegister: true, mapPrivateProperties: true, allowReadonlyTargetToPopulate: false, @@ -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 diff --git a/docs/mapping/groups.md b/docs/mapping/groups.md index cb625f66..30cd8088 100644 --- a/docs/mapping/groups.md +++ b/docs/mapping/groups.md @@ -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. diff --git a/src/Attribute/Mapper.php b/src/Attribute/Mapper.php index 80c62fa8..ec1024a7 100644 --- a/src/Attribute/Mapper.php +++ b/src/Attribute/Mapper.php @@ -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, diff --git a/src/Configuration.php b/src/Configuration.php index 352ae325..d0d16700 100644 --- a/src/Configuration.php +++ b/src/Configuration.php @@ -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. diff --git a/src/Event/GenerateMapperEvent.php b/src/Event/GenerateMapperEvent.php index 43fd99d2..c4a58ff1 100644 --- a/src/Event/GenerateMapperEvent.php +++ b/src/Event/GenerateMapperEvent.php @@ -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, diff --git a/src/EventListener/MapperListener.php b/src/EventListener/MapperListener.php index 6bc29071..366dd129 100644 --- a/src/EventListener/MapperListener.php +++ b/src/EventListener/MapperListener.php @@ -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; diff --git a/src/Metadata/MetadataFactory.php b/src/Metadata/MetadataFactory.php index 0e313d93..0ecb68cc 100644 --- a/src/Metadata/MetadataFactory.php +++ b/src/Metadata/MetadataFactory.php @@ -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 @@ -322,7 +323,7 @@ private function createGeneratorMetadata(MapperMetadata $mapperMetadata): Genera $propertyMappedEvent->maxDepth, $propertyMappedEvent->if, $propertyMappedEvent->groups, - $propertyMappedEvent->disableGroupsCheck, + $propertyMappedEvent->disableGroupsCheck ?? !$mapperCheckGroups, $propertyMappedEvent->identifier ?? false, ); } diff --git a/src/Symfony/Bundle/DependencyInjection/AutoMapperExtension.php b/src/Symfony/Bundle/DependencyInjection/AutoMapperExtension.php index 7dec8a0c..e4cd6ff9 100644 --- a/src/Symfony/Bundle/DependencyInjection/AutoMapperExtension.php +++ b/src/Symfony/Bundle/DependencyInjection/AutoMapperExtension.php @@ -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, @@ -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']) diff --git a/src/Symfony/Bundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/DependencyInjection/Configuration.php index 601d0af1..4ac7cd17 100644 --- a/src/Symfony/Bundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/DependencyInjection/Configuration.php @@ -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() diff --git a/tests/AutoMapperTest/IdentifierHash/map.php b/tests/AutoMapperTest/IdentifierHash/map.php index 4a59b224..c88c099a 100644 --- a/tests/AutoMapperTest/IdentifierHash/map.php +++ b/tests/AutoMapperTest/IdentifierHash/map.php @@ -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)] diff --git a/tests/AutoMapperTest/IgnoreGroup/expected.data b/tests/AutoMapperTest/IgnoreGroup/expected.data new file mode 100644 index 00000000..7892733c --- /dev/null +++ b/tests/AutoMapperTest/IgnoreGroup/expected.data @@ -0,0 +1,4 @@ +[ + "id" => "id" + "name" => "name" +] diff --git a/tests/AutoMapperTest/IgnoreGroup/map.php b/tests/AutoMapperTest/IgnoreGroup/map.php new file mode 100644 index 00000000..43d391b3 --- /dev/null +++ b/tests/AutoMapperTest/IgnoreGroup/map.php @@ -0,0 +1,25 @@ +map($group, 'array', ['groups' => ['group2']]); diff --git a/tests/AutoMapperTest/NestedPropertyAccessors/map.php b/tests/AutoMapperTest/NestedPropertyAccessors/map.php index 1550dc65..355c436f 100644 --- a/tests/AutoMapperTest/NestedPropertyAccessors/map.php +++ b/tests/AutoMapperTest/NestedPropertyAccessors/map.php @@ -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 diff --git a/tests/AutoMapperTest/NullableSourceNonNullableTarget/map.php b/tests/AutoMapperTest/NullableSourceNonNullableTarget/map.php index 5faba568..bc27e03d 100644 --- a/tests/AutoMapperTest/NullableSourceNonNullableTarget/map.php +++ b/tests/AutoMapperTest/NullableSourceNonNullableTarget/map.php @@ -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 diff --git a/tests/AutoMapperTest/UnionSourceProperty/map.php b/tests/AutoMapperTest/UnionSourceProperty/map.php index 40400df5..62144e97 100644 --- a/tests/AutoMapperTest/UnionSourceProperty/map.php +++ b/tests/AutoMapperTest/UnionSourceProperty/map.php @@ -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) diff --git a/tests/AutoMapperTest/VariadicConstructor/map.php b/tests/AutoMapperTest/VariadicConstructor/map.php index 723c738f..1ec29408 100644 --- a/tests/AutoMapperTest/VariadicConstructor/map.php +++ b/tests/AutoMapperTest/VariadicConstructor/map.php @@ -32,7 +32,7 @@ public function __construct( } } -return (function () { +return (static function () { $autoMapper = AutoMapperBuilder::buildAutoMapper(); $source = new Source(); diff --git a/tests/Bundle/Resources/App/Entity/Pet.php b/tests/Bundle/Resources/App/Entity/Pet.php index e9c245db..b4ad6df7 100644 --- a/tests/Bundle/Resources/App/Entity/Pet.php +++ b/tests/Bundle/Resources/App/Entity/Pet.php @@ -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 */