From e578163fe25eaff74025dff94ebd66e4437e2b79 Mon Sep 17 00:00:00 2001 From: Priyadi Iman Nurcahyo <1102197+priyadi@users.noreply.github.com> Date: Wed, 21 Feb 2024 01:44:05 +0700 Subject: [PATCH] feat: `stdClass` to `stdClass` mapping should work correctly. --- CHANGELOG.md | 1 + .../ObjectToObjectTransformer.php | 35 +++++++++++++++++++ .../ObjectToObjectMetadataFactory.php | 4 +++ .../ObjectToObjectMetadata.php | 16 +++++++-- tests/Common/FrameworkTestCase.php | 10 ++++++ .../AnotherObjectExtendingStdClass.php | 18 ++++++++++ tests/IntegrationTest/DynamicPropertyTest.php | 16 +++++++-- 7 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 tests/Fixtures/DynamicProperty/AnotherObjectExtendingStdClass.php diff --git a/CHANGELOG.md b/CHANGELOG.md index d1b1dae..5e7e5a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ * fix: Fix dynamic properties in Symfony profiler panel. * fix: Fix `PresetTransformer`. * fix: mapping to object extending `stdClass` to property with no setter. +* feat: `stdClass` to `stdClass` mapping should work correctly. ## 1.0.0 diff --git a/src/Transformer/Implementation/ObjectToObjectTransformer.php b/src/Transformer/Implementation/ObjectToObjectTransformer.php index 22f931f..3835a76 100644 --- a/src/Transformer/Implementation/ObjectToObjectTransformer.php +++ b/src/Transformer/Implementation/ObjectToObjectTransformer.php @@ -154,6 +154,21 @@ public function transform( // map properties if it is not a proxy if (!$canUseTargetProxy) { + // map dynamic properties if both are stdClass or allow dynamic + // properties + + if ( + $objectToObjectMetadata->sourceAllowsDynamicProperties() + && $objectToObjectMetadata->targetAllowsDynamicProperties() + ) { + $this->mapDynamicProperties( + source: $source, + target: $target, + objectToObjectMetadata: $objectToObjectMetadata, + context: $context + ); + } + $this->readSourceAndWriteTarget( source: $source, target: $target, @@ -552,6 +567,26 @@ private function transformValue( return $targetPropertyValue; } + private function mapDynamicProperties( + object $source, + object $target, + ObjectToObjectMetadata $objectToObjectMetadata, + Context $context + ): void { + $sourceProperties = $objectToObjectMetadata->getSourceProperties(); + + /** @var mixed $sourcePropertyValue */ + foreach (get_object_vars($source) as $sourceProperty => $sourcePropertyValue) { + if (!in_array($sourceProperty, $sourceProperties, true)) { + try { + $target->{$sourceProperty} = $sourcePropertyValue; + } catch (\Error) { + // ignore + } + } + } + } + public function getSupportedTransformation(): iterable { yield new TypeMapping(TypeFactory::object(), TypeFactory::object(), true); diff --git a/src/Transformer/ObjectToObjectMetadata/Implementation/ObjectToObjectMetadataFactory.php b/src/Transformer/ObjectToObjectMetadata/Implementation/ObjectToObjectMetadataFactory.php index 153fb47..5733f46 100644 --- a/src/Transformer/ObjectToObjectMetadata/Implementation/ObjectToObjectMetadataFactory.php +++ b/src/Transformer/ObjectToObjectMetadata/Implementation/ObjectToObjectMetadataFactory.php @@ -137,6 +137,8 @@ public function createObjectToObjectMetadata( // iterate over properties to map + $effectivePropertiesToMap = []; + foreach ($propertiesToMap as $targetProperty) { $sourceProperty = $targetProperty; @@ -349,6 +351,7 @@ public function createObjectToObjectMetadata( ); $propertyMappings[] = $propertyMapping; + $effectivePropertiesToMap[] = $targetProperty; } $objectToObjectMetadata = new ObjectToObjectMetadata( @@ -357,6 +360,7 @@ public function createObjectToObjectMetadata( providedTargetClass: $providedTargetClass, sourceAllowsDynamicProperties: $sourceAllowsDynamicProperties, targetAllowsDynamicProperties: $targetAllowsDynamicProperties, + sourceProperties: $effectivePropertiesToMap, allPropertyMappings: $propertyMappings, instantiable: $instantiable, cloneable: $cloneable, diff --git a/src/Transformer/ObjectToObjectMetadata/ObjectToObjectMetadata.php b/src/Transformer/ObjectToObjectMetadata/ObjectToObjectMetadata.php index 0e34887..abc7a1a 100644 --- a/src/Transformer/ObjectToObjectMetadata/ObjectToObjectMetadata.php +++ b/src/Transformer/ObjectToObjectMetadata/ObjectToObjectMetadata.php @@ -53,6 +53,7 @@ * @param array $allPropertyMappings * @param array $initializableTargetPropertiesNotInSource * @param array $targetProxySkippedProperties + * @param array $sourceProperties */ public function __construct( private string $sourceClass, @@ -60,6 +61,7 @@ public function __construct( private string $providedTargetClass, private bool $sourceAllowsDynamicProperties, private bool $targetAllowsDynamicProperties, + private array $sourceProperties, array $allPropertyMappings, private bool $instantiable, private bool $cloneable, @@ -111,6 +113,7 @@ public function withTargetProxy( providedTargetClass: $this->providedTargetClass, sourceAllowsDynamicProperties: $this->sourceAllowsDynamicProperties, targetAllowsDynamicProperties: $this->targetAllowsDynamicProperties, + sourceProperties: $this->sourceProperties, allPropertyMappings: $this->allPropertyMappings, instantiable: $this->instantiable, cloneable: $this->cloneable, @@ -133,6 +136,7 @@ public function withReasonCannotUseProxy( providedTargetClass: $this->providedTargetClass, sourceAllowsDynamicProperties: $this->sourceAllowsDynamicProperties, targetAllowsDynamicProperties: $this->targetAllowsDynamicProperties, + sourceProperties: $this->sourceProperties, allPropertyMappings: $this->allPropertyMappings, instantiable: $this->instantiable, cloneable: $this->cloneable, @@ -284,13 +288,21 @@ public function constructorIsEager(): bool return $this->constructorIsEager; } - public function getSourceAllowsDynamicProperties(): bool + public function sourceAllowsDynamicProperties(): bool { return $this->sourceAllowsDynamicProperties; } - public function getTargetAllowsDynamicProperties(): bool + public function targetAllowsDynamicProperties(): bool { return $this->targetAllowsDynamicProperties; } + + /** + * @return array + */ + public function getSourceProperties(): array + { + return $this->sourceProperties; + } } diff --git a/tests/Common/FrameworkTestCase.php b/tests/Common/FrameworkTestCase.php index dbde32d..0574e96 100644 --- a/tests/Common/FrameworkTestCase.php +++ b/tests/Common/FrameworkTestCase.php @@ -18,6 +18,7 @@ use Doctrine\Persistence\ManagerRegistry; use PHPUnit\Framework\TestCase; use Rekalogika\Mapper\Context\Context; +use Rekalogika\Mapper\Debug\MapperDataCollector; use Rekalogika\Mapper\Debug\TraceableTransformer; use Rekalogika\Mapper\MapperInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -115,4 +116,13 @@ public function getEntityManager(): EntityManagerInterface return $this->entityManager = $this->doctrineInit(); } + + public function getDataCollector(): MapperDataCollector + { + $result = $this->get('rekalogika.mapper.data_collector'); + + $this->assertInstanceOf(MapperDataCollector::class, $result); + + return $result; + } } diff --git a/tests/Fixtures/DynamicProperty/AnotherObjectExtendingStdClass.php b/tests/Fixtures/DynamicProperty/AnotherObjectExtendingStdClass.php new file mode 100644 index 0000000..86537d1 --- /dev/null +++ b/tests/Fixtures/DynamicProperty/AnotherObjectExtendingStdClass.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +namespace Rekalogika\Mapper\Tests\Fixtures\DynamicProperty; + +class AnotherObjectExtendingStdClass extends \stdClass +{ +} diff --git a/tests/IntegrationTest/DynamicPropertyTest.php b/tests/IntegrationTest/DynamicPropertyTest.php index e13500a..21a0562 100644 --- a/tests/IntegrationTest/DynamicPropertyTest.php +++ b/tests/IntegrationTest/DynamicPropertyTest.php @@ -14,6 +14,7 @@ namespace Rekalogika\Mapper\Tests\IntegrationTest; use Rekalogika\Mapper\Tests\Common\FrameworkTestCase; +use Rekalogika\Mapper\Tests\Fixtures\DynamicProperty\AnotherObjectExtendingStdClass; use Rekalogika\Mapper\Tests\Fixtures\DynamicProperty\ObjectExtendingStdClass; use Rekalogika\Mapper\Tests\Fixtures\DynamicProperty\ObjectExtendingStdClassWithProperties; use Rekalogika\Mapper\Tests\Fixtures\Scalar\ObjectWithScalarProperties; @@ -127,18 +128,26 @@ public function testObjectToObjectExtendingStdClass(): void public function testStdClassToStdClass(): void { - $source = new \stdClass(); + $source = new ObjectExtendingStdClass(); + /** @psalm-suppress UndefinedPropertyAssignment */ $source->a = 1; + /** @psalm-suppress UndefinedPropertyAssignment */ $source->b = 'string'; + /** @psalm-suppress UndefinedPropertyAssignment */ $source->c = true; + /** @psalm-suppress UndefinedPropertyAssignment */ $source->d = 1.1; - $target = $this->mapper->map($source, \stdClass::class); + $target = $this->mapper->map($source, AnotherObjectExtendingStdClass::class); $this->assertInstanceOf(\stdClass::class, $target); + /** @psalm-suppress UndefinedPropertyFetch */ $this->assertSame(1, $target->a); + /** @psalm-suppress UndefinedPropertyFetch */ $this->assertSame('string', $target->b); + /** @psalm-suppress UndefinedPropertyFetch */ $this->assertTrue($target->c); + /** @psalm-suppress UndefinedPropertyFetch */ $this->assertSame(1.1, $target->d); } @@ -148,6 +157,7 @@ public function testStdClassToStdClassWithExplicitProperties(): void $source->public = 'public'; $source->private = 'private'; $source->constructor = 'constructor'; + $source->dynamic = 'dynamic'; $target = $this->mapper->map($source, ObjectExtendingStdClassWithProperties::class); @@ -155,5 +165,7 @@ public function testStdClassToStdClassWithExplicitProperties(): void $this->assertSame('public', $target->public); $this->assertNull($target->getPrivate()); $this->assertEquals('constructor', $target->getConstructor()); + /** @psalm-suppress UndefinedPropertyFetch */ + $this->assertSame('dynamic', $target->dynamic); } }