Skip to content

Commit

Permalink
feat: utilize InheritanceMap on the source side to determine the ta…
Browse files Browse the repository at this point in the history
…rget class
  • Loading branch information
priyadi committed May 23, 2024
1 parent 40c76e6 commit bdd4630
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 13 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# CHANGELOG

## 1.5.0

* feat: utilize `InheritanceMap` on the source side to determine the target
class

## 1.4.0

* feat: `ramsey/uuid` support
Expand Down
9 changes: 9 additions & 0 deletions src/Attribute/InheritanceMap.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,13 @@ public function getTargetClassFromSourceClass(string $sourceClass): ?string
{
return $this->map[$sourceClass] ?? null;
}

/**
* @param class-string $targetClass
* @return class-string|null
*/
public function getSourceClassFromTargetClass(string $targetClass): ?string
{
return array_search($targetClass, $this->map, true) ?: null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,31 +57,87 @@ public function __construct(
) {
}

public function createObjectToObjectMetadata(
/**
* @param class-string $sourceClass
* @param class-string $targetClass
* @return class-string
*/
private function resolveTargetClass(
string $sourceClass,
string $targetClass,
): ObjectToObjectMetadata {
$providedTargetClass = $targetClass;

string $targetClass
): string {
$sourceReflection = new \ReflectionClass($sourceClass);
$providedTargetReflection = new \ReflectionClass($providedTargetClass);
$targetReflection = new \ReflectionClass($targetClass);

$targetAttributes = $targetReflection->getAttributes(InheritanceMap::class);

if (count($targetAttributes) > 0) {
// if the target has an InheritanceMap, we try to resolve the target
// class using the InheritanceMap

$inheritanceMap = $targetAttributes[0]->newInstance();

$resolvedTargetClass = $inheritanceMap->getTargetClassFromSourceClass($sourceClass);

if ($resolvedTargetClass === null) {
throw new SourceClassNotInInheritanceMapException($sourceClass, $targetClass);
}

return $resolvedTargetClass;
} elseif ($targetReflection->isAbstract() || $targetReflection->isInterface()) {
// if target doesn't have an inheritance map, but is also abstract
// or an interface, we try to find the InheritanceMap from the
// source

$parents = class_parents($sourceClass, true);
if ($parents === false) {
$parents = [];
}

$interfaces = class_implements($sourceClass, true);
if ($interfaces === false) {
$interfaces = [];
}

$sourceClasses = [
$sourceClass,
...$parents,
...$interfaces,
];

// check inheritance map
foreach ($sourceClasses as $currentSourceClass) {
$sourceReflection = new \ReflectionClass($currentSourceClass);
$sourceAttributes = $sourceReflection->getAttributes(InheritanceMap::class);

$attributes = $providedTargetReflection->getAttributes(InheritanceMap::class);
if (count($sourceAttributes) > 0) {
$inheritanceMap = $sourceAttributes[0]->newInstance();

if (count($attributes) > 0) {
$inheritanceMap = $attributes[0]->newInstance();
$resolvedTargetClass = $inheritanceMap->getSourceClassFromTargetClass($sourceClass);

$targetClass = $inheritanceMap->getTargetClassFromSourceClass($sourceClass);
if ($resolvedTargetClass === null) {
throw new SourceClassNotInInheritanceMapException($currentSourceClass, $targetClass);
}

if ($targetClass === null) {
throw new SourceClassNotInInheritanceMapException($sourceClass, $providedTargetClass);
return $resolvedTargetClass;
}
}
}

return $targetClass;
}

public function createObjectToObjectMetadata(
string $sourceClass,
string $targetClass,
): ObjectToObjectMetadata {
$providedTargetClass = $targetClass;
$sourceReflection = new \ReflectionClass($sourceClass);

$targetClass = $this->resolveTargetClass($sourceClass, $providedTargetClass);
$targetReflection = new \ReflectionClass($targetClass);

// dynamic properties

$sourceAllowsDynamicProperties = $this->allowsDynamicProperties($sourceReflection);
$targetAllowsDynamicProperties = $this->allowsDynamicProperties($targetReflection);

Expand Down
37 changes: 37 additions & 0 deletions tests/IntegrationTest/InheritanceReversedTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?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\IntegrationTest;

use Rekalogika\Mapper\Tests\Common\FrameworkTestCase;
use Rekalogika\Mapper\Tests\Fixtures\Inheritance\AbstractClass;
use Rekalogika\Mapper\Tests\Fixtures\Inheritance\ConcreteClassA;
use Rekalogika\Mapper\Tests\Fixtures\InheritanceDto\ConcreteClassADto;

class InheritanceReversedTest extends FrameworkTestCase
{
public function testMapDtoToAbstractClass(): void
{
$concreteClassADto = new ConcreteClassADto();
$concreteClassADto->propertyInA = 'xxpropertyInA';
$concreteClassADto->propertyInParent = 'xxpropertyInParent';

$result = $this->mapper->map($concreteClassADto, AbstractClass::class);

/** @var ConcreteClassA $result */

$this->assertInstanceOf(ConcreteClassA::class, $result);
$this->assertSame('xxpropertyInA', $result->propertyInA);
$this->assertSame('xxpropertyInParent', $result->propertyInParent);
}
}

0 comments on commit bdd4630

Please sign in to comment.