From 2a9c2a7118e0eed3a6c5312a3e5264eee36d0cdd Mon Sep 17 00:00:00 2001 From: Priyadi Iman Nurcahyo <1102197+priyadi@users.noreply.github.com> Date: Sun, 13 Oct 2024 00:32:19 +0700 Subject: [PATCH] feat: extra target values --- CHANGELOG.md | 1 + src/Context/ExtraTargetValues.php | 44 +++++++++++++++++++ .../ObjectToObjectTransformer.php | 12 +++++ src/Util/ClassUtil.php | 2 +- .../rekalogika-mapper/generated-mappings.php | 6 +++ .../Fixtures/ExtraTargetValues/SomeObject.php | 19 ++++++++ .../SomeObjectWithConstructorDto.php | 22 ++++++++++ .../IntegrationTest/ExtraTargetValuesTest.php | 42 ++++++++++++++++++ 8 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 src/Context/ExtraTargetValues.php create mode 100644 tests/src/Fixtures/ExtraTargetValues/SomeObject.php create mode 100644 tests/src/Fixtures/ExtraTargetValues/SomeObjectWithConstructorDto.php create mode 100644 tests/src/IntegrationTest/ExtraTargetValuesTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index b6a10931..019ad9c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * perf: `ObjectToObjectTransformer` optimization * perf: move `TypeResolver` outside of the hot path * perf(`ObjectCache`): use sentinel value instead of exception +* feat: extra target values ## 1.10.0 diff --git a/src/Context/ExtraTargetValues.php b/src/Context/ExtraTargetValues.php new file mode 100644 index 00000000..73fece01 --- /dev/null +++ b/src/Context/ExtraTargetValues.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +namespace Rekalogika\Mapper\Context; + +/** + * Adds additional values to the target object + */ +final readonly class ExtraTargetValues +{ + /** + * @param array> $arguments + */ + public function __construct( + private array $arguments = [], + ) {} + + /** + * @param list $classes The class and its parent classes and interfaces. + * @return array + */ + public function getArgumentsForClass(array $classes): array + { + $arguments = []; + + foreach ($classes as $class) { + if (isset($this->arguments[$class])) { + $arguments = array_merge($arguments, $this->arguments[$class]); + } + } + + return $arguments; + } +} diff --git a/src/Transformer/Implementation/ObjectToObjectTransformer.php b/src/Transformer/Implementation/ObjectToObjectTransformer.php index 5c1b6723..c48a5248 100644 --- a/src/Transformer/Implementation/ObjectToObjectTransformer.php +++ b/src/Transformer/Implementation/ObjectToObjectTransformer.php @@ -18,6 +18,7 @@ use Rekalogika\Mapper\CacheWarmer\WarmableObjectToObjectMetadataFactoryInterface; use Rekalogika\Mapper\CacheWarmer\WarmableTransformerInterface; use Rekalogika\Mapper\Context\Context; +use Rekalogika\Mapper\Context\ExtraTargetValues; use Rekalogika\Mapper\Context\MapperOptions; use Rekalogika\Mapper\Exception\InvalidArgumentException; use Rekalogika\Mapper\ObjectCache\ObjectCache; @@ -42,6 +43,7 @@ use Rekalogika\Mapper\Transformer\TransformerInterface; use Rekalogika\Mapper\Transformer\TypeMapping; use Rekalogika\Mapper\Transformer\Util\ReaderWriter; +use Rekalogika\Mapper\Util\ClassUtil; use Rekalogika\Mapper\Util\TypeFactory; use Rekalogika\Mapper\Util\TypeGuesser; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; @@ -379,6 +381,16 @@ private function generateConstructorArguments( } } + if (($extraTargetValues = $context(ExtraTargetValues::class)) !== null) { + $targetValues = $extraTargetValues + ->getArgumentsForClass(ClassUtil::getAllClassesFromObject($objectToObjectMetadata->getTargetClass())); + + /** @var mixed $value */ + foreach ($targetValues as $property => $value) { + $constructorArguments->addArgument($property, $value); + } + } + return $constructorArguments; } diff --git a/src/Util/ClassUtil.php b/src/Util/ClassUtil.php index 3df7380b..367a42cb 100644 --- a/src/Util/ClassUtil.php +++ b/src/Util/ClassUtil.php @@ -172,7 +172,7 @@ public static function getSkippedProperties( /** * @param object|class-string $objectOrClass - * @return array + * @return list */ public static function getAllClassesFromObject( object|string $objectOrClass, diff --git a/tests/config/rekalogika-mapper/generated-mappings.php b/tests/config/rekalogika-mapper/generated-mappings.php index b208898f..5c462da5 100644 --- a/tests/config/rekalogika-mapper/generated-mappings.php +++ b/tests/config/rekalogika-mapper/generated-mappings.php @@ -387,6 +387,12 @@ target: \Rekalogika\Mapper\Tests\Fixtures\EnumAndStringableDto\ObjectWithStringPropertyDto::class ); + $mappingCollection->addObjectMapping( + // tests/src/IntegrationTest/ExtraTargetValuesTest.php on line 26 + source: \Rekalogika\Mapper\Tests\Fixtures\ExtraTargetValues\SomeObject::class, + target: \Rekalogika\Mapper\Tests\Fixtures\ExtraTargetValues\SomeObjectWithConstructorDto::class + ); + $mappingCollection->addObjectMapping( // tests/src/IntegrationTest/InheritanceReversedTest.php on line 29 source: \Rekalogika\Mapper\Tests\Fixtures\InheritanceDto\ConcreteClassADto::class, diff --git a/tests/src/Fixtures/ExtraTargetValues/SomeObject.php b/tests/src/Fixtures/ExtraTargetValues/SomeObject.php new file mode 100644 index 00000000..dbe2eae7 --- /dev/null +++ b/tests/src/Fixtures/ExtraTargetValues/SomeObject.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +namespace Rekalogika\Mapper\Tests\Fixtures\ExtraTargetValues; + +class SomeObject +{ + public string $property = 'someProperty'; +} diff --git a/tests/src/Fixtures/ExtraTargetValues/SomeObjectWithConstructorDto.php b/tests/src/Fixtures/ExtraTargetValues/SomeObjectWithConstructorDto.php new file mode 100644 index 00000000..1a534440 --- /dev/null +++ b/tests/src/Fixtures/ExtraTargetValues/SomeObjectWithConstructorDto.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +namespace Rekalogika\Mapper\Tests\Fixtures\ExtraTargetValues; + +final readonly class SomeObjectWithConstructorDto +{ + public function __construct( + public string $property, + public ?\DateTimeInterface $date, + ) {} +} diff --git a/tests/src/IntegrationTest/ExtraTargetValuesTest.php b/tests/src/IntegrationTest/ExtraTargetValuesTest.php new file mode 100644 index 00000000..7d716c70 --- /dev/null +++ b/tests/src/IntegrationTest/ExtraTargetValuesTest.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +namespace Rekalogika\Mapper\Tests\IntegrationTest; + +use Rekalogika\Mapper\Context\Context; +use Rekalogika\Mapper\Context\ExtraTargetValues; +use Rekalogika\Mapper\Tests\Common\FrameworkTestCase; +use Rekalogika\Mapper\Tests\Fixtures\ExtraTargetValues\SomeObject; +use Rekalogika\Mapper\Tests\Fixtures\ExtraTargetValues\SomeObjectWithConstructorDto; + +class ExtraTargetValuesTest extends FrameworkTestCase +{ + public function testExtraArgumentsOnTargetConstructor(): void + { + $target = $this->mapper->map( + source: new SomeObject(), + target: SomeObjectWithConstructorDto::class, + context: Context::create( + new ExtraTargetValues([ + SomeObjectWithConstructorDto::class => [ + 'date' => new \DateTimeImmutable('2021-01-01'), + ], + ]), + ), + ); + + $this->assertSame('someProperty', $target->property); + $this->assertInstanceOf(\DateTimeImmutable::class, $target->date); + $this->assertSame('2021-01-01', $target->date->format('Y-m-d')); + } +}