Skip to content

Commit

Permalink
refactor(PropertyMapper): $sourceClass is redundant & removed.
Browse files Browse the repository at this point in the history
  • Loading branch information
priyadi committed Feb 3, 2024
1 parent 496aa0e commit 32f96d7
Show file tree
Hide file tree
Showing 11 changed files with 234 additions and 75 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

* style: Remove remnants.
* test: Add PHPStan unused public.
* refactor(`PropertyMapper`): `$sourceClass` is redundant & removed.

## 0.5.26

Expand Down
24 changes: 24 additions & 0 deletions config/tests.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
*/

use Rekalogika\Mapper\Tests\Common\TestKernel;
use Rekalogika\Mapper\Tests\Fixtures\PropertyMapper\PropertyMapperWithClassAttribute;
use Rekalogika\Mapper\Tests\Fixtures\PropertyMapper\PropertyMapperWithConstructorWithClassAttribute;
use Rekalogika\Mapper\Tests\Fixtures\PropertyMapper\PropertyMapperWithConstructorWithoutClassAttribute;
use Rekalogika\Mapper\Tests\Fixtures\PropertyMapper\PropertyMapperWithoutClassAttribute;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

return static function (ContainerConfigurator $containerConfigurator): void {
Expand All @@ -23,4 +27,24 @@
foreach ($serviceIds as $serviceId) {
$services->alias('test.' . $serviceId, $serviceId)->public();
};

$services->set(PropertyMapperWithoutClassAttribute::class)
->autowire()
->autoconfigure()
->public();

$services->set(PropertyMapperWithClassAttribute::class)
->autowire()
->autoconfigure()
->public();

$services->set(PropertyMapperWithConstructorWithoutClassAttribute::class)
->autowire()
->autoconfigure()
->public();

$services->set(PropertyMapperWithConstructorWithClassAttribute::class)
->autowire()
->autoconfigure()
->public();
};
168 changes: 111 additions & 57 deletions src/DependencyInjection/RekalogikaMapperExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

namespace Rekalogika\Mapper\DependencyInjection;

use Rekalogika\Mapper\Exception\LogicException;
use Rekalogika\Mapper\PropertyMapper\AsPropertyMapper;
use Rekalogika\Mapper\Tests\Common\TestKernel;
use Rekalogika\Mapper\Transformer\Contracts\TransformerInterface;
Expand Down Expand Up @@ -47,63 +48,116 @@ public function load(array $configs, ContainerBuilder $container)

$container->registerAttributeForAutoconfiguration(
AsPropertyMapper::class,
static function (
ChildDefinition $definition,
AsPropertyMapper $attribute,
\ReflectionMethod $reflector,
): void {
$tagAttributes = \get_object_vars($attribute);
$tagAttributes['method'] = $reflector->getName();

if (
!isset($tagAttributes['sourceClass'])
|| !isset($tagAttributes['targetClass'])
) {
$classReflection = $reflector->getDeclaringClass();
$classAttributeReflection = $classReflection
->getAttributes(AsPropertyMapper::class)[0] ?? null;

if ($classAttributeReflection === null) {
throw new \LogicException(
sprintf(
'Trying to lookup "sourceClass" or "targetClass" from "AsPropertyMapper" attribute attached to the class "%s" because one or more parameters is not defined in the attribute attached to the method "%s", however the attribute is not found.',
$definition->getClass() ?? $classReflection->getName(),
$reflector->getName()
)
);
}

$classAttribute = $classAttributeReflection->newInstance();
$tagAttributes['sourceClass'] ??= $classAttribute->sourceClass;
$tagAttributes['targetClass'] ??= $classAttribute->targetClass;
}

if (!isset($tagAttributes['property'])) {
$tagAttributes['property'] = $reflector->getName();
}

if (!isset($tagAttributes['sourceClass'])) {
throw new \LogicException(
sprintf(
'Missing source class attribute for property mapper service "%s", method "%s".',
$definition->getClass() ?? '?',
$reflector->getName()
)
);
}

if (!isset($tagAttributes['targetClass'])) {
throw new \LogicException(
sprintf(
'Missing target class attribute for property mapper service "%s", method "%s".',
$definition->getClass() ?? '?',
$reflector->getName()
)
);
}

$definition->addTag('rekalogika.mapper.property_mapper', $tagAttributes);
}
self::propertyMapperConfigurator(...)
);
}

private static function propertyMapperConfigurator(
ChildDefinition $definition,
AsPropertyMapper $attribute,
\ReflectionMethod $reflector,
): void {
/** @var array{sourceClass:class-string,targetClass:class-string,method:string} */
$tagAttributes = [];

// get the AsPropertyMapper attribute attached to the class

$classReflection = $reflector->getDeclaringClass();
$classAttributeReflection = $classReflection
->getAttributes(AsPropertyMapper::class)[0] ?? null;

// populate tag attributes from AsPropertyMapper attribute
// attached to the class

if ($classAttributeReflection !== null) {
$classAttribute = $classAttributeReflection->newInstance();
$tagAttributes['targetClass'] = $classAttribute->targetClass;

if ($classAttribute->property !== null) {
throw new LogicException(sprintf(
'"AsPropertyMapper" attribute attached to the class "%s" must not have "property" attribute.',
$classReflection->getName()
));
}
}

// populate tag attributes from AsPropertyMapper attribute

$tagAttributes['method'] = $reflector->getName();

if ($attribute->property !== null) {
$tagAttributes['property'] = $attribute->property;
}

if ($attribute->targetClass !== null) {
$tagAttributes['targetClass'] = $attribute->targetClass;
}

// Use the class of the first argument of the method as the source class

$parameters = $reflector->getParameters();
$firstParameter = $parameters[0] ?? null;
$type = $firstParameter?->getType();

if ($type === null || !$type instanceof \ReflectionNamedType) {
throw new LogicException(
sprintf(
'Unable to determine the source class for property mapper service "%s", method "%s".',
$definition->getClass() ?? '?',
$reflector->getName()
)
);
}

$tagAttributes['sourceClass'] = $type->getName();

// if the property is missing, assume it is the same as the
// method name

if (!isset($tagAttributes['property'])) {
$tagAttributes['property'] = $reflector->getName();
}

// if the sourceClass is not a class, throw an exception

if (!class_exists($tagAttributes['sourceClass'])) {
throw new \LogicException(
sprintf(
'Source class "%s" for property mapper service "%s", method "%s" does not exist.',
$tagAttributes['sourceClass'],
$definition->getClass() ?? '?',
$reflector->getName()
)
);
}

// if the targetClass is still missing, throw an exception

if (!isset($tagAttributes['targetClass'])) {
throw new \LogicException(
sprintf(
'Unable to determine the target class for property mapper service "%s", method "%s".',
$definition->getClass() ?? '?',
$reflector->getName()
)
);
}

// if the targetClass is not a class, throw an exception

if (!class_exists($tagAttributes['targetClass'])) {
throw new \LogicException(
sprintf(
'Target class "%s" for property mapper service "%s", method "%s" does not exist.',
$tagAttributes['targetClass'],
$definition->getClass() ?? '?',
$reflector->getName()
)
);
}

// finally

$definition->addTag('rekalogika.mapper.property_mapper', $tagAttributes);
}
}
2 changes: 0 additions & 2 deletions src/PropertyMapper/AsPropertyMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,10 @@
final readonly class AsPropertyMapper
{
/**
* @param class-string|null $sourceClass
* @param class-string|null $targetClass
*/
public function __construct(
public ?string $property = null,
public ?string $sourceClass = null,
public ?string $targetClass = null,
) {
}
Expand Down
1 change: 1 addition & 0 deletions tests/Common/TestKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,5 +99,6 @@ public static function getServiceIds(): iterable
yield 'rekalogika.mapper.command.mapping';
yield 'rekalogika.mapper.command.try';
yield 'rekalogika.mapper.command.try_property';
yield 'rekalogika.mapper.property_mapper.resolver';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@
use Rekalogika\Mapper\PropertyMapper\AsPropertyMapper;

#[AsPropertyMapper(
sourceClass: SomeObject::class,
targetClass: SomeObjectDto::class,
)]
class PropertyMapperB
class PropertyMapperWithClassAttribute
{
#[AsPropertyMapper('propertyB')]
public function mapPropertyB(SomeObject $object): string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@
use Rekalogika\Mapper\PropertyMapper\AsPropertyMapper;

#[AsPropertyMapper(
sourceClass: SomeObject::class,
targetClass: SomeObjectWithConstructorDto::class,
)]
class PropertyMapperWithConstructorB
class PropertyMapperWithConstructorWithClassAttribute
{
#[AsPropertyMapper('propertyB')]
public function mapPropertyB(SomeObject $object): string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,9 @@

use Rekalogika\Mapper\PropertyMapper\AsPropertyMapper;

class PropertyMapperWithConstructorA
class PropertyMapperWithConstructorWithoutClassAttribute
{
#[AsPropertyMapper(
sourceClass: SomeObject::class,
targetClass: SomeObjectWithConstructorDto::class,
property: 'propertyA',
)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,9 @@

use Rekalogika\Mapper\PropertyMapper\AsPropertyMapper;

class PropertyMapperA
class PropertyMapperWithoutClassAttribute
{
#[AsPropertyMapper(
sourceClass: SomeObject::class,
targetClass: SomeObjectDto::class,
property: 'propertyA',
)]
Expand Down
85 changes: 85 additions & 0 deletions tests/FrameworkTest/FrameworkTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,16 @@

use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;
use Rekalogika\Mapper\PropertyMapper\Contracts\PropertyMapperResolverInterface;
use Rekalogika\Mapper\PropertyMapper\Contracts\PropertyMapperServicePointer;
use Rekalogika\Mapper\Tests\Common\TestKernel;
use Rekalogika\Mapper\Tests\Fixtures\PropertyMapper\PropertyMapperWithClassAttribute;
use Rekalogika\Mapper\Tests\Fixtures\PropertyMapper\PropertyMapperWithConstructorWithClassAttribute;
use Rekalogika\Mapper\Tests\Fixtures\PropertyMapper\PropertyMapperWithConstructorWithoutClassAttribute;
use Rekalogika\Mapper\Tests\Fixtures\PropertyMapper\PropertyMapperWithoutClassAttribute;
use Rekalogika\Mapper\Tests\Fixtures\PropertyMapper\SomeObject;
use Rekalogika\Mapper\Tests\Fixtures\PropertyMapper\SomeObjectDto;
use Rekalogika\Mapper\Tests\Fixtures\PropertyMapper\SomeObjectWithConstructorDto;

class FrameworkTest extends TestCase
{
Expand All @@ -38,4 +47,80 @@ public function testWiring(): void
}
}

public function testPropertyMapperRegistration(): void
{
$propertyMapperA = $this->container?->get(PropertyMapperWithoutClassAttribute::class);
$propertyMapperB = $this->container?->get(PropertyMapperWithClassAttribute::class);

$this->assertInstanceOf(PropertyMapperWithoutClassAttribute::class, $propertyMapperA);
$this->assertInstanceOf(PropertyMapperWithClassAttribute::class, $propertyMapperB);

$propertyMapperResolver = $this->container?->get('test.rekalogika.mapper.property_mapper.resolver');

$this->assertInstanceOf(PropertyMapperResolverInterface::class, $propertyMapperResolver);

$result1 = $propertyMapperResolver->getPropertyMapper(
SomeObject::class,
SomeObjectDto::class,
'propertyA'
);

$this->assertEquals(
new PropertyMapperServicePointer(
PropertyMapperWithoutClassAttribute::class,
'mapPropertyA'
),
$result1,
);

$result2 = $propertyMapperResolver->getPropertyMapper(
SomeObject::class,
SomeObjectDto::class,
'propertyB'
);

$this->assertEquals(
new PropertyMapperServicePointer(
PropertyMapperWithClassAttribute::class,
'mapPropertyB'
),
$result2,
);

$result3 = $propertyMapperResolver->getPropertyMapper(
SomeObject::class,
SomeObjectDto::class,
'propertyC'
);

$this->assertNull($result3);

$result4 = $propertyMapperResolver->getPropertyMapper(
SomeObject::class,
SomeObjectWithConstructorDto::class,
'propertyA'
);

$this->assertEquals(
new PropertyMapperServicePointer(
PropertyMapperWithConstructorWithoutClassAttribute::class,
'mapPropertyA'
),
$result4,
);

$result5 = $propertyMapperResolver->getPropertyMapper(
SomeObject::class,
SomeObjectWithConstructorDto::class,
'propertyB'
);

$this->assertEquals(
new PropertyMapperServicePointer(
PropertyMapperWithConstructorWithClassAttribute::class,
'mapPropertyB'
),
$result5,
);
}
}
Loading

0 comments on commit 32f96d7

Please sign in to comment.