Skip to content

Commit

Permalink
Merge pull request #34 from rekalogika:feat/mapping-stdclass-to-exist…
Browse files Browse the repository at this point in the history
…ing-property

feat: Mapping to existing values in a dynamic property.
  • Loading branch information
priyadi committed Feb 21, 2024
2 parents 5f787bb + 69684c2 commit 9d8e927
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 25 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* fix: Fix `PresetTransformer`.
* fix: mapping to object extending `stdClass` to property with no setter.
* feat: `stdClass` to `stdClass` mapping should work correctly.
* feat: Mapping to existing values in a dynamic property.

## 1.0.0

Expand Down
31 changes: 27 additions & 4 deletions src/Transformer/Implementation/ObjectToObjectTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public function transform(

// if sourceType and targetType are the same, just return the source

if (null === $target && TypeCheck::isSomewhatIdentical($sourceType, $targetType)) {
if (null === $target && TypeCheck::isSomewhatIdentical($sourceType, $targetType) && !$source instanceof \stdClass) {
return $source;
}

Expand Down Expand Up @@ -579,10 +579,33 @@ private function mapDynamicProperties(
foreach (get_object_vars($source) as $sourceProperty => $sourcePropertyValue) {
if (!in_array($sourceProperty, $sourceProperties, true)) {
try {
$target->{$sourceProperty} = $sourcePropertyValue;
} catch (\Error) {
// ignore
if (isset($target->{$sourceProperty})) {
/** @psalm-suppress MixedAssignment */
$currentTargetPropertyValue = $target->{$sourceProperty};
} else {
$currentTargetPropertyValue = null;
}


if ($currentTargetPropertyValue === null) {
/** @psalm-suppress MixedAssignment */
$targetPropertyValue = $sourcePropertyValue;
} else {
/** @var mixed */
$targetPropertyValue = $this->getMainTransformer()->transform(
source: $sourcePropertyValue,
target: $currentTargetPropertyValue,
sourceType: null,
targetTypes: [],
context: $context,
path: $sourceProperty,
);
}
} catch (\Throwable $e) {
$targetPropertyValue = null;
}

$target->{$sourceProperty} = $targetPropertyValue;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,8 @@ public function createObjectToObjectMetadata(
$targetWriteName = $targetProperty;
$targetWriteVisibility = Visibility::Public;
} else {
$effectivePropertiesToMap[] = $targetProperty;

continue;
}
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
* @param array<int,PropertyMapping> $allPropertyMappings
* @param array<int,string> $initializableTargetPropertiesNotInSource
* @param array<string,true> $targetProxySkippedProperties
* @param array<int,string> $sourceProperties
* @param array<int,string> $sourceProperties List of the source properties. Used by `ObjectToObjectTransformer` to determine if a property is a dynamic property. A property not listed here is considered dynamic.
*/
public function __construct(
private string $sourceClass,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

declare(strict_types=1);

/*
* This file is part of rekalogika/mapper package.
*
* (c) Priyadi Iman Nurcahyo <https://rekalogika.dev>
*
* 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 ObjectExtendingStdClassWithExplicitScalarProperties extends \stdClass
{
private int $a = 1;
private string $b = 'string';
private bool $c = true;
private float $d = 1.1;

public function getA(): int
{
return $this->a;
}

public function setA(int $a): self
{
$this->a = $a;

return $this;
}

public function getB(): string
{
return $this->b;
}

public function setB(string $b): self
{
$this->b = $b;

return $this;
}

public function isC(): bool
{
return $this->c;
}

public function setC(bool $c): self
{
$this->c = $c;

return $this;
}

public function getD(): float
{
return $this->d;
}

public function setD(float $d): self
{
$this->d = $d;

return $this;
}
}
104 changes: 84 additions & 20 deletions tests/IntegrationTest/DynamicPropertyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
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\ObjectExtendingStdClassWithExplicitScalarProperties;
use Rekalogika\Mapper\Tests\Fixtures\DynamicProperty\ObjectExtendingStdClassWithProperties;
use Rekalogika\Mapper\Tests\Fixtures\Scalar\ObjectWithScalarProperties;
use Rekalogika\Mapper\Tests\Fixtures\ScalarDto\ObjectWithScalarPropertiesDto;
Expand All @@ -35,10 +36,10 @@ public function testStdClassToObject(): void
$target = $this->mapper->map($source, ObjectWithScalarPropertiesDto::class);

$this->assertInstanceOf(ObjectWithScalarPropertiesDto::class, $target);
$this->assertSame(1, $target->a);
$this->assertSame('string', $target->b);
$this->assertEquals(1, $target->a);
$this->assertEquals('string', $target->b);
$this->assertTrue($target->c);
$this->assertSame(1.1, $target->d);
$this->assertEquals(1.1, $target->d);
}

public function testObjectExtendingStdClassToObject(): void
Expand All @@ -56,10 +57,10 @@ public function testObjectExtendingStdClassToObject(): void
$target = $this->mapper->map($source, ObjectWithScalarPropertiesDto::class);

$this->assertInstanceOf(ObjectWithScalarPropertiesDto::class, $target);
$this->assertSame(1, $target->a);
$this->assertSame('string', $target->b);
$this->assertEquals(1, $target->a);
$this->assertEquals('string', $target->b);
$this->assertTrue($target->c);
$this->assertSame(1.1, $target->d);
$this->assertEquals(1.1, $target->d);
}

public function testArrayCastToObjectToObject(): void
Expand All @@ -74,10 +75,10 @@ public function testArrayCastToObjectToObject(): void
$target = $this->mapper->map((object) $source, ObjectWithScalarPropertiesDto::class);

$this->assertInstanceOf(ObjectWithScalarPropertiesDto::class, $target);
$this->assertSame(1, $target->a);
$this->assertSame('string', $target->b);
$this->assertEquals(1, $target->a);
$this->assertEquals('string', $target->b);
$this->assertTrue($target->c);
$this->assertSame(1.1, $target->d);
$this->assertEquals(1.1, $target->d);
}

public function testStdClassWithoutPropertiesToObject(): void
Expand All @@ -92,6 +93,38 @@ public function testStdClassWithoutPropertiesToObject(): void
$this->assertNull($target->d);
}

public function testStdClassWithExtraPropertyToObject(): void
{
$source = new \stdClass();
$source->a = 1;
$source->b = 'string';
$source->c = true;
$source->d = 1.1;
$source->e = 'extra';

$target = $this->mapper->map($source, ObjectWithScalarPropertiesDto::class);

$this->assertInstanceOf(ObjectWithScalarPropertiesDto::class, $target);
$this->assertEquals(1, $target->a);
$this->assertEquals('string', $target->b);
$this->assertTrue($target->c);
$this->assertEquals(1.1, $target->d);
}

public function testObjectExtendingStdClassWithExplicitScalarPropertiesToObject(): void
{
$source = new ObjectExtendingStdClassWithExplicitScalarProperties();
/** @psalm-suppress UndefinedPropertyAssignment */
$source->e = 'extra';
$target = $this->mapper->map($source, ObjectWithScalarPropertiesDto::class);

$this->assertInstanceOf(ObjectWithScalarPropertiesDto::class, $target);
$this->assertEquals(1, $target->a);
$this->assertEquals('string', $target->b);
$this->assertTrue($target->c);
$this->assertEquals(1.1, $target->d);
}

// to stdClass

public function testObjectToStdClass(): void
Expand All @@ -101,10 +134,10 @@ public function testObjectToStdClass(): void

$this->assertInstanceOf(\stdClass::class, $target);

$this->assertSame(1, $target->a);
$this->assertSame('string', $target->b);
$this->assertEquals(1, $target->a);
$this->assertEquals('string', $target->b);
$this->assertTrue($target->c);
$this->assertSame(1.1, $target->d);
$this->assertEquals(1.1, $target->d);
}

public function testObjectToObjectExtendingStdClass(): void
Expand All @@ -115,13 +148,13 @@ public function testObjectToObjectExtendingStdClass(): void
$this->assertInstanceOf(ObjectExtendingStdClass::class, $target);

/** @psalm-suppress UndefinedPropertyFetch */
$this->assertSame(1, $target->a);
$this->assertEquals(1, $target->a);
/** @psalm-suppress UndefinedPropertyFetch */
$this->assertSame('string', $target->b);
$this->assertEquals('string', $target->b);
/** @psalm-suppress UndefinedPropertyFetch */
$this->assertTrue($target->c);
/** @psalm-suppress UndefinedPropertyFetch */
$this->assertSame(1.1, $target->d);
$this->assertEquals(1.1, $target->d);
}

// stdclass to stdclass
Expand All @@ -142,13 +175,13 @@ public function testStdClassToStdClass(): void

$this->assertInstanceOf(\stdClass::class, $target);
/** @psalm-suppress UndefinedPropertyFetch */
$this->assertSame(1, $target->a);
$this->assertEquals(1, $target->a);
/** @psalm-suppress UndefinedPropertyFetch */
$this->assertSame('string', $target->b);
$this->assertEquals('string', $target->b);
/** @psalm-suppress UndefinedPropertyFetch */
$this->assertTrue($target->c);
/** @psalm-suppress UndefinedPropertyFetch */
$this->assertSame(1.1, $target->d);
$this->assertEquals(1.1, $target->d);
}

public function testStdClassToStdClassWithExplicitProperties(): void
Expand All @@ -162,10 +195,41 @@ public function testStdClassToStdClassWithExplicitProperties(): void
$target = $this->mapper->map($source, ObjectExtendingStdClassWithProperties::class);

$this->assertInstanceOf(\stdClass::class, $target);
$this->assertSame('public', $target->public);
$this->assertEquals('public', $target->public);
$this->assertNull($target->getPrivate());
$this->assertEquals('constructor', $target->getConstructor());
/** @psalm-suppress UndefinedPropertyFetch */
$this->assertSame('dynamic', $target->dynamic);
$this->assertEquals('dynamic', $target->dynamic);
}

public function testStdClassToStdClassWithExistingValue(): void
{
$source = new \stdClass();
$source->property = new ObjectWithScalarProperties();

$target = new \stdClass();
$targetProperty = new ObjectWithScalarPropertiesDto();
$target->property = $targetProperty;

$this->mapper->map($source, $target);

$this->assertSame($targetProperty, $target->property);
}

public function testStdClassToStdClassWithExistingNullValue(): void
{
$source = new \stdClass();
$source->property = new ObjectWithScalarProperties();

$target = new \stdClass();
$target->property = null;

$this->mapper->map($source, $target);

/**
* @psalm-suppress TypeDoesNotContainType
* @phpstan-ignore-next-line
*/
$this->assertInstanceOf(ObjectWithScalarProperties::class, $target->property);
}
}

0 comments on commit 9d8e927

Please sign in to comment.