Skip to content

Commit 5f787bb

Browse files
authored
Merge pull request #33 from rekalogika:work/stdclass
feat: `stdClass` to `stdClass` mapping should work correctly.
2 parents d094695 + e578163 commit 5f787bb

File tree

7 files changed

+96
-4
lines changed

7 files changed

+96
-4
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
* fix: Fix dynamic properties in Symfony profiler panel.
1818
* fix: Fix `PresetTransformer`.
1919
* fix: mapping to object extending `stdClass` to property with no setter.
20+
* feat: `stdClass` to `stdClass` mapping should work correctly.
2021

2122
## 1.0.0
2223

src/Transformer/Implementation/ObjectToObjectTransformer.php

+35
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,21 @@ public function transform(
154154
// map properties if it is not a proxy
155155

156156
if (!$canUseTargetProxy) {
157+
// map dynamic properties if both are stdClass or allow dynamic
158+
// properties
159+
160+
if (
161+
$objectToObjectMetadata->sourceAllowsDynamicProperties()
162+
&& $objectToObjectMetadata->targetAllowsDynamicProperties()
163+
) {
164+
$this->mapDynamicProperties(
165+
source: $source,
166+
target: $target,
167+
objectToObjectMetadata: $objectToObjectMetadata,
168+
context: $context
169+
);
170+
}
171+
157172
$this->readSourceAndWriteTarget(
158173
source: $source,
159174
target: $target,
@@ -552,6 +567,26 @@ private function transformValue(
552567
return $targetPropertyValue;
553568
}
554569

570+
private function mapDynamicProperties(
571+
object $source,
572+
object $target,
573+
ObjectToObjectMetadata $objectToObjectMetadata,
574+
Context $context
575+
): void {
576+
$sourceProperties = $objectToObjectMetadata->getSourceProperties();
577+
578+
/** @var mixed $sourcePropertyValue */
579+
foreach (get_object_vars($source) as $sourceProperty => $sourcePropertyValue) {
580+
if (!in_array($sourceProperty, $sourceProperties, true)) {
581+
try {
582+
$target->{$sourceProperty} = $sourcePropertyValue;
583+
} catch (\Error) {
584+
// ignore
585+
}
586+
}
587+
}
588+
}
589+
555590
public function getSupportedTransformation(): iterable
556591
{
557592
yield new TypeMapping(TypeFactory::object(), TypeFactory::object(), true);

src/Transformer/ObjectToObjectMetadata/Implementation/ObjectToObjectMetadataFactory.php

+4
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ public function createObjectToObjectMetadata(
137137

138138
// iterate over properties to map
139139

140+
$effectivePropertiesToMap = [];
141+
140142
foreach ($propertiesToMap as $targetProperty) {
141143
$sourceProperty = $targetProperty;
142144

@@ -349,6 +351,7 @@ public function createObjectToObjectMetadata(
349351
);
350352

351353
$propertyMappings[] = $propertyMapping;
354+
$effectivePropertiesToMap[] = $targetProperty;
352355
}
353356

354357
$objectToObjectMetadata = new ObjectToObjectMetadata(
@@ -357,6 +360,7 @@ public function createObjectToObjectMetadata(
357360
providedTargetClass: $providedTargetClass,
358361
sourceAllowsDynamicProperties: $sourceAllowsDynamicProperties,
359362
targetAllowsDynamicProperties: $targetAllowsDynamicProperties,
363+
sourceProperties: $effectivePropertiesToMap,
360364
allPropertyMappings: $propertyMappings,
361365
instantiable: $instantiable,
362366
cloneable: $cloneable,

src/Transformer/ObjectToObjectMetadata/ObjectToObjectMetadata.php

+14-2
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,15 @@
5353
* @param array<int,PropertyMapping> $allPropertyMappings
5454
* @param array<int,string> $initializableTargetPropertiesNotInSource
5555
* @param array<string,true> $targetProxySkippedProperties
56+
* @param array<int,string> $sourceProperties
5657
*/
5758
public function __construct(
5859
private string $sourceClass,
5960
private string $targetClass,
6061
private string $providedTargetClass,
6162
private bool $sourceAllowsDynamicProperties,
6263
private bool $targetAllowsDynamicProperties,
64+
private array $sourceProperties,
6365
array $allPropertyMappings,
6466
private bool $instantiable,
6567
private bool $cloneable,
@@ -111,6 +113,7 @@ public function withTargetProxy(
111113
providedTargetClass: $this->providedTargetClass,
112114
sourceAllowsDynamicProperties: $this->sourceAllowsDynamicProperties,
113115
targetAllowsDynamicProperties: $this->targetAllowsDynamicProperties,
116+
sourceProperties: $this->sourceProperties,
114117
allPropertyMappings: $this->allPropertyMappings,
115118
instantiable: $this->instantiable,
116119
cloneable: $this->cloneable,
@@ -133,6 +136,7 @@ public function withReasonCannotUseProxy(
133136
providedTargetClass: $this->providedTargetClass,
134137
sourceAllowsDynamicProperties: $this->sourceAllowsDynamicProperties,
135138
targetAllowsDynamicProperties: $this->targetAllowsDynamicProperties,
139+
sourceProperties: $this->sourceProperties,
136140
allPropertyMappings: $this->allPropertyMappings,
137141
instantiable: $this->instantiable,
138142
cloneable: $this->cloneable,
@@ -284,13 +288,21 @@ public function constructorIsEager(): bool
284288
return $this->constructorIsEager;
285289
}
286290

287-
public function getSourceAllowsDynamicProperties(): bool
291+
public function sourceAllowsDynamicProperties(): bool
288292
{
289293
return $this->sourceAllowsDynamicProperties;
290294
}
291295

292-
public function getTargetAllowsDynamicProperties(): bool
296+
public function targetAllowsDynamicProperties(): bool
293297
{
294298
return $this->targetAllowsDynamicProperties;
295299
}
300+
301+
/**
302+
* @return array<int,string>
303+
*/
304+
public function getSourceProperties(): array
305+
{
306+
return $this->sourceProperties;
307+
}
296308
}

tests/Common/FrameworkTestCase.php

+10
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Doctrine\Persistence\ManagerRegistry;
1919
use PHPUnit\Framework\TestCase;
2020
use Rekalogika\Mapper\Context\Context;
21+
use Rekalogika\Mapper\Debug\MapperDataCollector;
2122
use Rekalogika\Mapper\Debug\TraceableTransformer;
2223
use Rekalogika\Mapper\MapperInterface;
2324
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -115,4 +116,13 @@ public function getEntityManager(): EntityManagerInterface
115116

116117
return $this->entityManager = $this->doctrineInit();
117118
}
119+
120+
public function getDataCollector(): MapperDataCollector
121+
{
122+
$result = $this->get('rekalogika.mapper.data_collector');
123+
124+
$this->assertInstanceOf(MapperDataCollector::class, $result);
125+
126+
return $result;
127+
}
118128
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of rekalogika/mapper package.
7+
*
8+
* (c) Priyadi Iman Nurcahyo <https://rekalogika.dev>
9+
*
10+
* For the full copyright and license information, please view the LICENSE file
11+
* that was distributed with this source code.
12+
*/
13+
14+
namespace Rekalogika\Mapper\Tests\Fixtures\DynamicProperty;
15+
16+
class AnotherObjectExtendingStdClass extends \stdClass
17+
{
18+
}

tests/IntegrationTest/DynamicPropertyTest.php

+14-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace Rekalogika\Mapper\Tests\IntegrationTest;
1515

1616
use Rekalogika\Mapper\Tests\Common\FrameworkTestCase;
17+
use Rekalogika\Mapper\Tests\Fixtures\DynamicProperty\AnotherObjectExtendingStdClass;
1718
use Rekalogika\Mapper\Tests\Fixtures\DynamicProperty\ObjectExtendingStdClass;
1819
use Rekalogika\Mapper\Tests\Fixtures\DynamicProperty\ObjectExtendingStdClassWithProperties;
1920
use Rekalogika\Mapper\Tests\Fixtures\Scalar\ObjectWithScalarProperties;
@@ -127,18 +128,26 @@ public function testObjectToObjectExtendingStdClass(): void
127128

128129
public function testStdClassToStdClass(): void
129130
{
130-
$source = new \stdClass();
131+
$source = new ObjectExtendingStdClass();
132+
/** @psalm-suppress UndefinedPropertyAssignment */
131133
$source->a = 1;
134+
/** @psalm-suppress UndefinedPropertyAssignment */
132135
$source->b = 'string';
136+
/** @psalm-suppress UndefinedPropertyAssignment */
133137
$source->c = true;
138+
/** @psalm-suppress UndefinedPropertyAssignment */
134139
$source->d = 1.1;
135140

136-
$target = $this->mapper->map($source, \stdClass::class);
141+
$target = $this->mapper->map($source, AnotherObjectExtendingStdClass::class);
137142

138143
$this->assertInstanceOf(\stdClass::class, $target);
144+
/** @psalm-suppress UndefinedPropertyFetch */
139145
$this->assertSame(1, $target->a);
146+
/** @psalm-suppress UndefinedPropertyFetch */
140147
$this->assertSame('string', $target->b);
148+
/** @psalm-suppress UndefinedPropertyFetch */
141149
$this->assertTrue($target->c);
150+
/** @psalm-suppress UndefinedPropertyFetch */
142151
$this->assertSame(1.1, $target->d);
143152
}
144153

@@ -148,12 +157,15 @@ public function testStdClassToStdClassWithExplicitProperties(): void
148157
$source->public = 'public';
149158
$source->private = 'private';
150159
$source->constructor = 'constructor';
160+
$source->dynamic = 'dynamic';
151161

152162
$target = $this->mapper->map($source, ObjectExtendingStdClassWithProperties::class);
153163

154164
$this->assertInstanceOf(\stdClass::class, $target);
155165
$this->assertSame('public', $target->public);
156166
$this->assertNull($target->getPrivate());
157167
$this->assertEquals('constructor', $target->getConstructor());
168+
/** @psalm-suppress UndefinedPropertyFetch */
169+
$this->assertSame('dynamic', $target->dynamic);
158170
}
159171
}

0 commit comments

Comments
 (0)