Skip to content

Commit

Permalink
refactor: spin off resolveTargetClass() to separate class (#220)
Browse files Browse the repository at this point in the history
  • Loading branch information
priyadi authored Oct 11, 2024
1 parent 7798c35 commit a4cfdc5
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 58 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# CHANGELOG

## 1.10.1

* refactor: spin off `resolveTargetClass()` to separate class

## 1.10.0

* perf: warmable cache
Expand Down
12 changes: 12 additions & 0 deletions src/Transformer/MetadataUtil/MetadataUtilLocator.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
use Rekalogika\Mapper\Transformer\MetadataUtil\PropertyAccessInfoExtractor\PropertyAccessInfoExtractor;
use Rekalogika\Mapper\Transformer\MetadataUtil\PropertyMappingResolver\PropertyMappingResolver;
use Rekalogika\Mapper\Transformer\MetadataUtil\PropertyMetadataFactory\PropertyMetadataFactory;
use Rekalogika\Mapper\Transformer\MetadataUtil\TargetClassResolver\CachingTargetClassResolver;
use Rekalogika\Mapper\Transformer\MetadataUtil\TargetClassResolver\TargetClassResolver;
use Rekalogika\Mapper\Transformer\MetadataUtil\UnalterableDeterminer\UnalterableDeterminer;
use Rekalogika\Mapper\Transformer\ObjectToObjectMetadata\Implementation\ObjectToObjectMetadataFactory;
use Rekalogika\Mapper\Transformer\ObjectToObjectMetadata\ObjectToObjectMetadataFactoryInterface;
Expand Down Expand Up @@ -55,6 +57,8 @@ final class MetadataUtilLocator

private ?ObjectToObjectMetadataFactoryInterface $objectToObjectMetadataFactory = null;

private ?TargetClassResolverInterface $targetClassResolver = null;

public function __construct(
private readonly PropertyListExtractorInterface $propertyListExtractor,
private readonly PropertyTypeExtractorInterface $propertyTypeExtractor,
Expand Down Expand Up @@ -133,6 +137,13 @@ private function getPropertyMappingResolver(): PropertyMappingResolverInterface
);
}

private function getTargetClassResolver(): TargetClassResolverInterface
{
return $this->targetClassResolver ??= new CachingTargetClassResolver(
new TargetClassResolver(),
);
}

public function getObjectToObjectMetadataFactory(): ObjectToObjectMetadataFactoryInterface
{
return $this->objectToObjectMetadataFactory
Expand All @@ -142,6 +153,7 @@ public function getObjectToObjectMetadataFactory(): ObjectToObjectMetadataFactor
propertyMetadataFactory: $this->getPropertyMetadataFactory(),
classMetadataFactory: $this->getClassMetadataFactory(),
propertyMappingResolver: $this->getPropertyMappingResolver(),
targetClassResolver: $this->getTargetClassResolver(),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?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\Transformer\MetadataUtil\TargetClassResolver;

use Rekalogika\Mapper\Transformer\MetadataUtil\TargetClassResolverInterface;

/**
* @internal
*/
final class CachingTargetClassResolver implements TargetClassResolverInterface
{
/**
* @var array<class-string,array<class-string,class-string>>
*/
private array $cache = [];

public function __construct(
private readonly TargetClassResolverInterface $decorated,
) {}

#[\Override]
public function resolveTargetClass(string $sourceClass, string $targetClass): string
{
return $this->cache[$sourceClass][$targetClass]
??= $this->decorated->resolveTargetClass($sourceClass, $targetClass);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?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\Transformer\MetadataUtil\TargetClassResolver;

use Rekalogika\Mapper\Attribute\InheritanceMap;
use Rekalogika\Mapper\Transformer\Exception\SourceClassNotInInheritanceMapException;
use Rekalogika\Mapper\Transformer\MetadataUtil\TargetClassResolverInterface;
use Rekalogika\Mapper\Util\ClassUtil;

/**
* @internal
*/
final readonly class TargetClassResolver implements TargetClassResolverInterface
{
public function resolveTargetClass(
string $sourceClass,
string $targetClass,
): string {
$sourceReflection = new \ReflectionClass($sourceClass);
$targetReflection = new \ReflectionClass($targetClass);

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

if ($targetAttributes !== []) {
// 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

$sourceClasses = ClassUtil::getAllClassesFromObject($sourceClass);

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

if ($sourceAttributes !== []) {
$inheritanceMap = $sourceAttributes[0]->newInstance();

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

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

return $resolvedTargetClass;
}
}
}

return $targetClass;
}
}
36 changes: 36 additions & 0 deletions src/Transformer/MetadataUtil/TargetClassResolverInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?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\Transformer\MetadataUtil;

/**
* Resolves the target type hint to the actual class name. Especially useful
* for resolving abstract classes or interfaces to concrete classes.
*
* @internal
*/
interface TargetClassResolverInterface
{
/**
* Resolves the target type hint to the actual class name. Especially useful
* for resolving abstract classes or interfaces to concrete classes.
*
* @param class-string $sourceClass
* @param class-string $targetClass
* @return class-string
*/
public function resolveTargetClass(
string $sourceClass,
string $targetClass,
): string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
namespace Rekalogika\Mapper\Transformer\ObjectToObjectMetadata\Implementation;

use Rekalogika\Mapper\Attribute\Eager;
use Rekalogika\Mapper\Attribute\InheritanceMap;
use Rekalogika\Mapper\CustomMapper\PropertyMapperResolverInterface;
use Rekalogika\Mapper\Proxy\Exception\ProxyNotSupportedException;
use Rekalogika\Mapper\Proxy\ProxyFactoryInterface;
Expand All @@ -23,10 +22,10 @@
use Rekalogika\Mapper\Transformer\Context\TargetClassAttributes;
use Rekalogika\Mapper\Transformer\Context\TargetPropertyAttributes;
use Rekalogika\Mapper\Transformer\Exception\InternalClassUnsupportedException;
use Rekalogika\Mapper\Transformer\Exception\SourceClassNotInInheritanceMapException;
use Rekalogika\Mapper\Transformer\MetadataUtil\ClassMetadataFactoryInterface;
use Rekalogika\Mapper\Transformer\MetadataUtil\PropertyMappingResolverInterface;
use Rekalogika\Mapper\Transformer\MetadataUtil\PropertyMetadataFactoryInterface;
use Rekalogika\Mapper\Transformer\MetadataUtil\TargetClassResolverInterface;
use Rekalogika\Mapper\Transformer\ObjectToObjectMetadata\ObjectToObjectMetadata;
use Rekalogika\Mapper\Transformer\ObjectToObjectMetadata\ObjectToObjectMetadataFactoryInterface;
use Rekalogika\Mapper\Transformer\ObjectToObjectMetadata\PropertyMapping;
Expand All @@ -44,70 +43,18 @@ public function __construct(
private PropertyMetadataFactoryInterface $propertyMetadataFactory,
private ClassMetadataFactoryInterface $classMetadataFactory,
private PropertyMappingResolverInterface $propertyMappingResolver,
private TargetClassResolverInterface $targetClassResolver,
) {}

/**
* @param class-string $sourceClass
* @param class-string $targetClass
* @return class-string
*/
private function resolveTargetClass(
string $sourceClass,
string $targetClass,
): string {
$sourceReflection = new \ReflectionClass($sourceClass);
$targetReflection = new \ReflectionClass($targetClass);

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

if ($targetAttributes !== []) {
// 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

$sourceClasses = ClassUtil::getAllClassesFromObject($sourceClass);

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

if ($sourceAttributes !== []) {
$inheritanceMap = $sourceAttributes[0]->newInstance();

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

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

return $resolvedTargetClass;
}
}
}

return $targetClass;
}

#[\Override]
public function createObjectToObjectMetadata(
string $sourceClass,
string $targetClass,
): ObjectToObjectMetadata {
$providedTargetClass = $targetClass;
$targetClass = $this->resolveTargetClass($sourceClass, $providedTargetClass);

$targetClass = $this->targetClassResolver
->resolveTargetClass($sourceClass, $providedTargetClass);

$sourceClassMetadata = $this->classMetadataFactory->createClassMetadata($sourceClass);
$targetClassMetadata = $this->classMetadataFactory->createClassMetadata($targetClass);
Expand Down

0 comments on commit a4cfdc5

Please sign in to comment.