Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: separate dynamic properties determination to dedicated class #178

Merged
merged 5 commits into from
Sep 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
* test: test different setter return types
* feat: support for immutable adder and remover
* feat: optional second argument for getting the existing target value
* refactor: separate dynamic properties determination to dedicated class

## 1.8.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
use Rekalogika\Mapper\Transformer\Exception\SourceClassNotInInheritanceMapException;
use Rekalogika\Mapper\Transformer\ObjectToObjectMetadata\Implementation\Util\ClassMetadataFactory;
use Rekalogika\Mapper\Transformer\ObjectToObjectMetadata\Implementation\Util\ClassMetadataFactoryInterface;
use Rekalogika\Mapper\Transformer\ObjectToObjectMetadata\Implementation\Util\DynamicPropertiesDeterminer;
use Rekalogika\Mapper\Transformer\ObjectToObjectMetadata\Implementation\Util\PropertyMappingResolver;
use Rekalogika\Mapper\Transformer\ObjectToObjectMetadata\Implementation\Util\PropertyMetadataFactory;
use Rekalogika\Mapper\Transformer\ObjectToObjectMetadata\Implementation\Util\PropertyMetadataFactoryInterface;
Expand Down Expand Up @@ -62,17 +63,21 @@ public function __construct(
private ProxyFactoryInterface $proxyFactory,
TypeResolverInterface $typeResolver,
) {
$dynamicPropertiesDeterminer = new DynamicPropertiesDeterminer();

$this->propertyMetadataFactory = new PropertyMetadataFactory(
propertyReadInfoExtractor: $propertyReadInfoExtractor,
propertyWriteInfoExtractor: $propertyWriteInfoExtractor,
propertyTypeExtractor: $propertyTypeExtractor,
typeResolver: $typeResolver,
dynamicPropertiesDeterminer: $dynamicPropertiesDeterminer,
);

$this->classMetadataFactory = new ClassMetadataFactory(
eagerPropertiesResolver: $eagerPropertiesResolver,
propertyListExtractor: $propertyListExtractor,
propertyWriteInfoExtractor: $propertyWriteInfoExtractor,
dynamicPropertiesDeterminer: $dynamicPropertiesDeterminer,
);

$this->propertyMappingResolver = new PropertyMappingResolver(
Expand Down Expand Up @@ -190,14 +195,12 @@ public function createObjectToObjectMetadata(
->createPropertyMetadata(
class: $sourceClass,
property: $sourceProperty,
allowsDynamicProperties: $sourceClassMetadata->hasReadableDynamicProperties(),
);

$targetPropertyMetadata = $this->propertyMetadataFactory
->createPropertyMetadata(
class: $targetClass,
property: $targetProperty,
allowsDynamicProperties: $targetClassMetadata->hasWritableDynamicProperties(),
);

// determine if source property is lazy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public function __construct(
private EagerPropertiesResolverInterface $eagerPropertiesResolver,
private PropertyListExtractorInterface $propertyListExtractor,
private PropertyWriteInfoExtractorInterface $propertyWriteInfoExtractor,
private DynamicPropertiesDeterminer $dynamicPropertiesDeterminer,
) {}

/**
Expand All @@ -41,11 +42,11 @@ public function createClassMetadata(string $class): ClassMetadata
$reflection = new \ReflectionClass($class);

$hasReadableDynamicProperties =
$this->allowsDynamicProperties($reflection)
$this->dynamicPropertiesDeterminer->allowsDynamicProperties($class)
|| method_exists($class, '__get');

$hasWritableDynamicProperties =
$this->allowsDynamicProperties($reflection)
$this->dynamicPropertiesDeterminer->allowsDynamicProperties($class)
|| method_exists($class, '__set');

$internal = $reflection->isInternal();
Expand Down Expand Up @@ -81,20 +82,6 @@ class: $class,
);
}

/**
* @param \ReflectionClass<object> $class
*/
private function allowsDynamicProperties(\ReflectionClass $class): bool
{
do {
if ($class->getAttributes(\AllowDynamicProperties::class) !== []) {
return true;
}
} while ($class = $class->getParentClass());

return false;
}

/**
* @param class-string $class
* @param list<object> $attributes
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?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\ObjectToObjectMetadata\Implementation\Util;

use Rekalogika\Mapper\Util\ClassUtil;

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

/**
* @param class-string $class
*/
public function allowsDynamicProperties(string $class): bool
{
if (isset($this->cache[$class])) {
return $this->cache[$class];
}

return $this->cache[$class] = ClassUtil::allowsDynamicProperties($class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public function __construct(
private PropertyWriteInfoExtractorInterface $propertyWriteInfoExtractor,
private PropertyTypeExtractorInterface $propertyTypeExtractor,
private TypeResolverInterface $typeResolver,
private DynamicPropertiesDeterminer $dynamicPropertiesDeterminer,
) {
$this->propertyPathMetadataFactory = new PropertyPathMetadataFactory(
propertyTypeExtractor: $propertyTypeExtractor,
Expand All @@ -55,15 +56,14 @@ public function __construct(
public function createPropertyMetadata(
string $class,
string $property,
bool $allowsDynamicProperties,
): PropertyMetadata {

// property path

if ($this->isPropertyPath($property)) {
return $this->propertyPathMetadataFactory->createPropertyMetadata(
class: $class,
property: $property,
allowsDynamicProperties: $allowsDynamicProperties,
);
}

Expand All @@ -79,9 +79,9 @@ class: $class,
->getConstructorPropertyWriteInfo($class, $property);

[$readMode, $readName, $readVisibility] = $this->processPropertyReadInfo(
readInfo: $readInfo,
class: $class,
property: $property,
allowsDynamicProperties: $allowsDynamicProperties,
readInfo: $readInfo,
);

[$constructorWriteMode, $constructorWriteName] =
Expand Down Expand Up @@ -110,10 +110,10 @@ class: $class,
$replaceable,
]
= $this->processPropertyWriteInfo(
class: $class,
property: $property,
readInfo: $readInfo,
writeInfo: $writeInfo,
property: $property,
allowsDynamicProperties: $allowsDynamicProperties,
);

[$types, $scalarType, $nullable] =
Expand Down Expand Up @@ -158,13 +158,17 @@ class: $class,
}

/**
* @param class-string $class
* @return array{ReadMode,?string,Visibility}
*/
private function processPropertyReadInfo(
?PropertyReadInfo $readInfo,
string $class,
string $property,
bool $allowsDynamicProperties,
?PropertyReadInfo $readInfo,
): array {
$allowsDynamicProperties = $this->dynamicPropertiesDeterminer
->allowsDynamicProperties($class);

if ($readInfo === null) {
// if source allows dynamic properties, including stdClass
if ($allowsDynamicProperties) {
Expand Down Expand Up @@ -217,14 +221,18 @@ private function processConstructorWriteInfo(
}

/**
* @param class-string $class
* @return array{WriteMode,?string,Visibility,?string,Visibility,bool}
*/
private function processPropertyWriteInfo(
string $class,
string $property,
?PropertyReadInfo $readInfo,
?PropertyWriteInfo $writeInfo,
string $property,
bool $allowsDynamicProperties,
): array {
$allowsDynamicProperties = $this->dynamicPropertiesDeterminer
->allowsDynamicProperties($class);

$removerWriteName = null;
$removerWriteVisibility = Visibility::None;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,5 @@ interface PropertyMetadataFactoryInterface
public function createPropertyMetadata(
string $class,
string $property,
bool $allowsDynamicProperties,
): PropertyMetadata;
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ public function __construct(
public function createPropertyMetadata(
string $class,
string $property,
bool $allowsDynamicProperties,
): PropertyMetadata {
$propertyPathObject = new PropertyPath($property);

Expand Down
20 changes: 20 additions & 0 deletions src/Util/ClassUtil.php
Original file line number Diff line number Diff line change
Expand Up @@ -354,4 +354,24 @@ private static function getAttributesFromMethod(

return $attributes;
}

/**
* @param class-string $class
*/
public static function allowsDynamicProperties(string $class): bool
{
if (is_a($class, \stdClass::class, true)) {
return true;
}

$class = new \ReflectionClass($class);

do {
if ($class->getAttributes(\AllowDynamicProperties::class) !== []) {
return true;
}
} while ($class = $class->getParentClass());

return false;
}
}
61 changes: 61 additions & 0 deletions tests/src/IntegrationTest/DynamicPropertyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,70 @@
use Rekalogika\Mapper\Tests\Fixtures\DynamicProperty\ObjectWithNonNullPropertyThatCannotBeCastFromNull;
use Rekalogika\Mapper\Tests\Fixtures\Scalar\ObjectWithScalarProperties;
use Rekalogika\Mapper\Tests\Fixtures\ScalarDto\ObjectWithScalarPropertiesDto;
use Rekalogika\Mapper\Transformer\ObjectToObjectMetadata\Implementation\Util\DynamicPropertiesDeterminer;

class DynamicPropertyTest extends FrameworkTestCase
{
/**
* @param class-string $class
* @dataProvider provideDynamicPropertiesDetermination
*/
public function testDynamicPropertiesDetermination(
string $class,
bool $expected,
): void {
$determiner = new DynamicPropertiesDeterminer();
$actual = $determiner->allowsDynamicProperties($class);

$this->assertEquals($expected, $actual);
}

/**
* @return iterable<array-key,array{class-string,bool}>
*/
public static function provideDynamicPropertiesDetermination(): iterable
{
yield 'stdClass' => [
\stdClass::class,
true,
];

yield 'ObjectExtendingStdClass' => [
ObjectExtendingStdClass::class,
true,
];

yield 'ObjectWithScalarProperties' => [
ObjectWithScalarProperties::class,
false,
];

yield 'ObjectWithScalarPropertiesDto' => [
ObjectWithScalarPropertiesDto::class,
false,
];

yield 'AnotherObjectExtendingStdClass' => [
AnotherObjectExtendingStdClass::class,
true,
];

yield 'ObjectExtendingStdClassWithExplicitScalarProperties' => [
ObjectExtendingStdClassWithExplicitScalarProperties::class,
true,
];

yield 'ObjectExtendingStdClassWithProperties' => [
ObjectExtendingStdClassWithProperties::class,
true,
];

yield 'ObjectWithNonNullPropertyThatCannotBeCastFromNull' => [
ObjectWithNonNullPropertyThatCannotBeCastFromNull::class,
false,
];
}

// from stdclass to object

public function testStdClassToObject(): void
Expand Down
2 changes: 1 addition & 1 deletion tests/src/IntegrationTest/MapPropertyPathTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public function testPropertyPathMetadataFactory(
$library->addShelf($shelf);

$metadata = $propertyPathAwarePropertyTypeExtractor
->createPropertyMetadata($class, $path, false);
->createPropertyMetadata($class, $path);

$types = $metadata->getTypes();
$attributes = $metadata->getAttributes();
Expand Down
2 changes: 2 additions & 0 deletions tests/src/IntegrationTest/ValueObjectTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use Rekalogika\Mapper\Tests\Fixtures\WitherMethod\ObjectWithImmutableSetter;
use Rekalogika\Mapper\Transformer\EagerPropertiesResolver\EagerPropertiesResolverInterface;
use Rekalogika\Mapper\Transformer\ObjectToObjectMetadata\Implementation\Util\ClassMetadataFactory;
use Rekalogika\Mapper\Transformer\ObjectToObjectMetadata\Implementation\Util\DynamicPropertiesDeterminer;
use Symfony\Component\Clock\DatePoint;
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface;
Expand Down Expand Up @@ -77,6 +78,7 @@ public function testValueObject(string $class, bool $isValueObject): void
eagerPropertiesResolver: $eagerPropertiesResolver,
propertyWriteInfoExtractor: $propertyWriteInfoExtractor,
propertyListExtractor: $propertyListExtractor,
dynamicPropertiesDeterminer: new DynamicPropertiesDeterminer(),
);

$classMetadata = $classMetadataFactory->createClassMetadata($class);
Expand Down