Skip to content

Commit

Permalink
feat: Add attribute matching.
Browse files Browse the repository at this point in the history
  • Loading branch information
priyadi committed Jan 13, 2024
1 parent d87a028 commit 5e32e6a
Show file tree
Hide file tree
Showing 10 changed files with 163 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ 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, in addition to using
class names.
* Console commands for debugging.

## Future Features
Expand Down
22 changes: 22 additions & 0 deletions src/Attribute/MapperAttributeInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?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;

/**
* To allow matching using an attribute, your attribute class must implement
* this interface.
*/
interface MapperAttributeInterface
{
}
2 changes: 1 addition & 1 deletion src/Transformer/ObjectToObjectTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ private function resolveTargetPropertyValue(
}

/** @var mixed */
$targetPropertyValue = $this->mainTransformer?->transform(
$targetPropertyValue = $this->getMainTransformer()->transform(
source: $sourcePropertyValue,
target: $targetPropertyValue,
targetTypes: $targetPropertyTypes,
Expand Down
5 changes: 4 additions & 1 deletion src/TypeResolver/TypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
);
}
}
56 changes: 56 additions & 0 deletions src/Util/TypeUtil.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -353,4 +354,59 @@ private static function getAllClassesFromObject(

return $classes;
}

/**
* @param Type|MixedType $type
* @return array<int,Type>
*/
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<int,string>
*/
#[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;
}
}
7 changes: 5 additions & 2 deletions tests/Common/AbstractIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -31,6 +33,7 @@ public function setUp(): void
);
$this->mapper = $this->factory->getMapper();
$this->mainTransformer = $this->factory->getMainTransformer();
$this->typeResolver = $this->factory->getTypeResolver();
}

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

#[SomeAttribute]
class ObjectWithAttribute
{
}
21 changes: 21 additions & 0 deletions tests/Fixtures/Attribute/SomeAttribute.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?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\Attribute;

use Rekalogika\Mapper\Attribute\MapperAttributeInterface;

#[\Attribute(\Attribute::TARGET_CLASS)]
class SomeAttribute implements MapperAttributeInterface
{
}
32 changes: 32 additions & 0 deletions tests/IntegrationTest/AttributeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?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\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);
}
}

0 comments on commit 5e32e6a

Please sign in to comment.