-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
25 changed files
with
648 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
<?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\Attribute; | ||
|
||
#[\Attribute(\Attribute::TARGET_CLASS)] | ||
final readonly class InheritanceMap implements MapperAttributeInterface | ||
{ | ||
/** | ||
* @param array<class-string,class-string> $map | ||
*/ | ||
public function __construct( | ||
private array $map = [] | ||
) { | ||
} | ||
|
||
/** | ||
* @return array<class-string,class-string> | ||
*/ | ||
public function getMap(): array | ||
{ | ||
return $this->map; | ||
} | ||
|
||
/** | ||
* @param class-string $sourceClass | ||
* @return class-string|null | ||
*/ | ||
public function getTargetClassFromSourceClass(string $sourceClass): ?string | ||
{ | ||
return $this->map[$sourceClass] ?? null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
<?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\Exception; | ||
|
||
use Rekalogika\Mapper\Attribute\InheritanceMap; | ||
|
||
class NotAClassException extends NotMappableValueException | ||
{ | ||
public function __construct(string $class) | ||
{ | ||
/** @var class-string $class */ | ||
|
||
try { | ||
$reflectionClass = new \ReflectionClass($class); | ||
|
||
if ($reflectionClass->isInterface()) { | ||
parent::__construct(sprintf( | ||
'Trying to map to "%s", but it is an interface, not a class. If you want to map to an interface, you need to add the attribute "%s" to the interface."', | ||
$class, | ||
InheritanceMap::class | ||
)); | ||
} else { | ||
parent::__construct(sprintf( | ||
'Trying to map to "%s", but it is not a class.', | ||
$class, | ||
)); | ||
} | ||
} catch (\ReflectionException) { | ||
parent::__construct(sprintf( | ||
'The name "%s" is not a valid class.', | ||
$class | ||
)); | ||
} | ||
} | ||
} |
33 changes: 33 additions & 0 deletions
33
src/Transformer/Exception/SourceClassNotInInheritanceMapException.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<?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\Exception; | ||
|
||
use Rekalogika\Mapper\Attribute\InheritanceMap; | ||
|
||
class SourceClassNotInInheritanceMapException extends NotMappableValueException | ||
{ | ||
/** | ||
* @param class-string $sourceClass | ||
* @param class-string $targetClass | ||
*/ | ||
public function __construct(string $sourceClass, string $targetClass) | ||
{ | ||
parent::__construct(sprintf( | ||
'Trying to map to a class with an inheritance map, but source class "%s" is not found in the "%s" attribute of the target class "%s"', | ||
$sourceClass, | ||
InheritanceMap::class, | ||
$targetClass | ||
)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
<?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; | ||
|
||
use Rekalogika\Mapper\Attribute\InheritanceMap; | ||
use Rekalogika\Mapper\Context\Context; | ||
use Rekalogika\Mapper\Exception\InvalidArgumentException; | ||
use Rekalogika\Mapper\Exception\UnexpectedValueException; | ||
use Rekalogika\Mapper\ObjectCache\ObjectCache; | ||
use Rekalogika\Mapper\Transformer\Contracts\MainTransformerAwareInterface; | ||
use Rekalogika\Mapper\Transformer\Contracts\MainTransformerAwareTrait; | ||
use Rekalogika\Mapper\Transformer\Contracts\TransformerInterface; | ||
use Rekalogika\Mapper\Transformer\Contracts\TypeMapping; | ||
use Rekalogika\Mapper\Transformer\Exception\SourceClassNotInInheritanceMapException; | ||
use Rekalogika\Mapper\Util\TypeFactory; | ||
use Symfony\Component\PropertyInfo\Type; | ||
|
||
final class InheritanceMapTransformer implements | ||
TransformerInterface, | ||
MainTransformerAwareInterface | ||
{ | ||
use MainTransformerAwareTrait; | ||
|
||
public function transform( | ||
mixed $source, | ||
mixed $target, | ||
?Type $sourceType, | ||
?Type $targetType, | ||
Context $context | ||
): mixed { | ||
|
||
# source must be an object | ||
|
||
if (!is_object($source)) { | ||
throw new InvalidArgumentException( | ||
\sprintf('Source must be an object, "%s" given.', \get_debug_type($source)) | ||
); | ||
} | ||
|
||
# target type must exist | ||
|
||
if ($targetType === null) { | ||
throw new InvalidArgumentException('Target type must be specified.'); | ||
} | ||
|
||
# target must be an interface or class | ||
|
||
$targetClass = $targetType->getClassName(); | ||
|
||
if ( | ||
$targetClass === null | ||
|| ( | ||
!\class_exists($targetClass) | ||
&& !\interface_exists($targetClass) | ||
) | ||
) { | ||
throw new InvalidArgumentException( | ||
\sprintf('Target class "%s" does not exist.', $targetClass ?? 'null') | ||
); | ||
} | ||
|
||
# gets the inheritance map | ||
|
||
$attributes = (new \ReflectionClass($targetClass)) | ||
->getAttributes(InheritanceMap::class); | ||
|
||
if (\count($attributes) === 0) { | ||
throw new InvalidArgumentException( | ||
\sprintf('Target class "%s" must have inheritance map.', $targetClass) | ||
); | ||
} | ||
|
||
$inheritanceMap = $attributes[0]->newInstance(); | ||
|
||
# gets the target class from the inheritance map | ||
|
||
$sourceClass = \get_class($source); | ||
$targetClassInMap = $inheritanceMap->getTargetClassFromSourceClass($sourceClass); | ||
|
||
if ($targetClassInMap === null) { | ||
throw new SourceClassNotInInheritanceMapException($sourceClass, $targetClass); | ||
} | ||
|
||
# pass the transformation back to the main transformer | ||
|
||
$concreteTargetType = TypeFactory::objectOfClass($targetClassInMap); | ||
|
||
$result = $this->getMainTransformer()->transform( | ||
source: $source, | ||
target: null, | ||
targetTypes: [$concreteTargetType], | ||
context: $context | ||
); | ||
|
||
# make sure $result is the correct type | ||
|
||
if (!is_object($result) || !is_a($result, $targetClassInMap)) { | ||
throw new UnexpectedValueException( | ||
\sprintf('Expecting an instance of "%s", "%s" given.', $targetClassInMap, \get_debug_type($result)) | ||
); | ||
} | ||
|
||
# cache the result. we cache the abstract class/interface as the key. | ||
# the concrete should be cached by whatever transformer that handles it. | ||
|
||
$context->get(ObjectCache::class) | ||
->saveTarget($source, $targetType, $result); | ||
|
||
return $result; | ||
} | ||
|
||
public function getSupportedTransformation(): iterable | ||
{ | ||
yield new TypeMapping( | ||
TypeFactory::object(), | ||
TypeFactory::objectOfClass(InheritanceMap::class) | ||
); | ||
} | ||
} |
Oops, something went wrong.