diff --git a/CHANGELOG.md b/CHANGELOG.md index e5cc192..7101209 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * refactor: Mover more logic to `TransformerRegistry`. * refactor: Move `MainTransformer` to its own namespace. * refactor: Refactor exception. +* feat: Add attribute matching. ## 0.5.4 diff --git a/README.md b/README.md index 080d691..ae1498d 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Full documentation is available at [rekalogika.dev/mapper](https://rekalogika.de full hydration of the source. * Manual mapping using a class method. * Easy to extend by creating new transformers, or decorating the existing ones. +* Match classes using attributes in your transformers. * Console commands for debugging. ## Future Features diff --git a/src/Attribute/MapperAttributeInterface.php b/src/Attribute/MapperAttributeInterface.php new file mode 100644 index 0000000..f2f19ba --- /dev/null +++ b/src/Attribute/MapperAttributeInterface.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +namespace Rekalogika\Mapper\Attribute; + +/** + * To allow matching using an attribute, your attribute class must implement + * this interface. + */ +interface MapperAttributeInterface +{ +} diff --git a/src/Transformer/ObjectToObjectTransformer.php b/src/Transformer/ObjectToObjectTransformer.php index a418a4f..36db757 100644 --- a/src/Transformer/ObjectToObjectTransformer.php +++ b/src/Transformer/ObjectToObjectTransformer.php @@ -166,7 +166,7 @@ private function resolveTargetPropertyValue( } /** @var mixed */ - $targetPropertyValue = $this->mainTransformer?->transform( + $targetPropertyValue = $this->getMainTransformer()->transform( source: $sourcePropertyValue, target: $targetPropertyValue, targetTypes: $targetPropertyTypes, diff --git a/src/TypeResolver/TypeResolver.php b/src/TypeResolver/TypeResolver.php index 61c856c..ee4187e 100644 --- a/src/TypeResolver/TypeResolver.php +++ b/src/TypeResolver/TypeResolver.php @@ -87,6 +87,9 @@ public function getApplicableTypeStrings(Type|MixedType $type): array return $type; } - return TypeUtil::getAllTypeStrings($type, true); + return array_merge( + TypeUtil::getAllTypeStrings($type, true), + TypeUtil::getAttributesTypeStrings($type) + ); } } diff --git a/src/Util/TypeUtil.php b/src/Util/TypeUtil.php index 493bf0f..d78b523 100644 --- a/src/Util/TypeUtil.php +++ b/src/Util/TypeUtil.php @@ -14,6 +14,7 @@ namespace Rekalogika\Mapper\Util; use DaveLiddament\PhpLanguageExtensions\Friend; +use Rekalogika\Mapper\Attribute\MapperAttributeInterface; use Rekalogika\Mapper\Exception\InvalidArgumentException; use Rekalogika\Mapper\MainTransformer\Exception\TransformerReturnsUnexpectedValueException; use Rekalogika\Mapper\Tests\UnitTest\Util\TypeUtil2Test; @@ -353,4 +354,59 @@ private static function getAllClassesFromObject( return $classes; } + + /** + * @param Type|MixedType $type + * @return array + */ + private static function getAttributesFromType( + Type|MixedType $type + ): array { + if ($type instanceof MixedType) { + return []; + } + + $class = $type->getClassName(); + + if ($class === null) { + return []; + } + + if (!class_exists($class) && !interface_exists($class) && !enum_exists($class)) { + return []; + } + + $attributes = (new \ReflectionClass($class)) + ->getAttributes( + MapperAttributeInterface::class, + \ReflectionAttribute::IS_INSTANCEOF + ); + + $attributeTypes = []; + + foreach ($attributes as $attribute) { + $attributeTypes[] = TypeFactory::objectOfClass($attribute->getName()); + } + + return $attributeTypes; + } + + /** + * @param Type $type + * @return array + */ + #[Friend(TypeResolver::class)] + public static function getAttributesTypeStrings( + Type|MixedType $type + ): array { + $attributes = self::getAttributesFromType($type); + + $attributeTypeStrings = []; + + foreach ($attributes as $attribute) { + $attributeTypeStrings[] = self::getTypeString($attribute); + } + + return $attributeTypeStrings; + } } diff --git a/tests/Common/AbstractIntegrationTest.php b/tests/Common/AbstractIntegrationTest.php index 8a8a9ba..7dd0868 100644 --- a/tests/Common/AbstractIntegrationTest.php +++ b/tests/Common/AbstractIntegrationTest.php @@ -14,15 +14,17 @@ namespace Rekalogika\Mapper\Tests\Common; use PHPUnit\Framework\TestCase; -use Rekalogika\Mapper\MainTransformer\MainTransformer; +use Rekalogika\Mapper\MainTransformer\MainTransformerInterface; use Rekalogika\Mapper\MapperInterface; use Rekalogika\Mapper\Transformer\Contracts\TransformerInterface; +use Rekalogika\Mapper\TypeResolver\TypeResolverInterface; abstract class AbstractIntegrationTest extends TestCase { protected MapperTestFactory $factory; protected MapperInterface $mapper; - protected MainTransformer $mainTransformer; + protected MainTransformerInterface $mainTransformer; + protected TypeResolverInterface $typeResolver; public function setUp(): void { @@ -31,6 +33,7 @@ public function setUp(): void ); $this->mapper = $this->factory->getMapper(); $this->mainTransformer = $this->factory->getMainTransformer(); + $this->typeResolver = $this->factory->getTypeResolver(); } /** diff --git a/tests/Fixtures/Attribute/ObjectWithAttribute.php b/tests/Fixtures/Attribute/ObjectWithAttribute.php new file mode 100644 index 0000000..a52493c --- /dev/null +++ b/tests/Fixtures/Attribute/ObjectWithAttribute.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +namespace Rekalogika\Mapper\Tests\Fixtures\Attribute; + +#[SomeAttribute] +class ObjectWithAttribute +{ +} diff --git a/tests/Fixtures/Attribute/SomeAttribute.php b/tests/Fixtures/Attribute/SomeAttribute.php new file mode 100644 index 0000000..272176a --- /dev/null +++ b/tests/Fixtures/Attribute/SomeAttribute.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +namespace Rekalogika\Mapper\Tests\Fixtures\Attribute; + +use Rekalogika\Mapper\Attribute\MapperAttributeInterface; + +#[\Attribute(\Attribute::TARGET_CLASS)] +class SomeAttribute implements MapperAttributeInterface +{ +} diff --git a/tests/IntegrationTest/AttributeTest.php b/tests/IntegrationTest/AttributeTest.php new file mode 100644 index 0000000..2dea835 --- /dev/null +++ b/tests/IntegrationTest/AttributeTest.php @@ -0,0 +1,32 @@ + + * + * 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\AbstractIntegrationTest; +use Rekalogika\Mapper\Tests\Fixtures\Attribute\ObjectWithAttribute; +use Rekalogika\Mapper\Tests\Fixtures\Attribute\SomeAttribute; +use Rekalogika\Mapper\Util\TypeFactory; + +class AttributeTest extends AbstractIntegrationTest +{ + public function testAttribute(): void + { + $class = ObjectWithAttribute::class; + $type = TypeFactory::objectOfClass($class); + + $typeStrings = $this->typeResolver->getApplicableTypeStrings($type); + + $this->assertContainsEquals(SomeAttribute::class, $typeStrings); + } +}