Skip to content

Commit df40b7a

Browse files
committed
.
1 parent 4c61c29 commit df40b7a

17 files changed

+208
-157
lines changed

src/Blueprint/Application/Model/Assets/AttributeBlueprint.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public static function create(\ReflectionAttribute $attribute, ClassBlueprint|Pr
3939
return $instance;
4040
}
4141

42-
public static function createCollection(ClassBlueprint|PropertyBlueprint $parent): AssetsAggregate
42+
public static function createCollection(ClassBlueprint|ParameterBlueprint|PropertyBlueprint $parent): AssetsAggregate
4343
{
4444
$ref = $parent->getReflection();
4545

src/Blueprint/Application/Model/Assets/ClassBlueprint.php

+3
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@
88
use PBaszak\UltraMapper\Blueprint\Application\Exception\BlueprintException;
99
use PBaszak\UltraMapper\Blueprint\Application\Exception\ClassNotFoundException;
1010
use PBaszak\UltraMapper\Blueprint\Application\Model\Blueprint;
11+
use PBaszak\UltraMapper\Blueprint\Application\Trait\GetAttributes;
1112
use PBaszak\UltraMapper\Shared\Infrastructure\Normalization\Normalizable;
1213

1314
class ClassBlueprint implements Normalizable
1415
{
16+
use GetAttributes;
17+
1518
/** @var array<string, mixed> */
1619
public array $options = [];
1720

src/Blueprint/Application/Model/Assets/ParameterBlueprint.php

+4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ class ParameterBlueprint implements Normalizable
1818
public bool $hasDefaultValue;
1919
public mixed $defaultValue;
2020

21+
public AssetsAggregate $attributes;
22+
2123
public static function create(\ReflectionParameter $parameter, MethodBlueprint $parent): self
2224
{
2325
$instance = new self();
@@ -30,6 +32,8 @@ public static function create(\ReflectionParameter $parameter, MethodBlueprint $
3032
$instance->defaultValue = null;
3133
}
3234

35+
$instance->attributes = AttributeBlueprint::createCollection($instance);
36+
3337
return $instance;
3438
}
3539

src/Blueprint/Application/Model/Assets/PropertyBlueprint.php

+3-15
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@
66

77
use PBaszak\UltraMapper\Blueprint\Application\Enum\Visibility;
88
use PBaszak\UltraMapper\Blueprint\Application\Model\Type;
9-
use PBaszak\UltraMapper\Mapper\Application\Attribute\Groups;
9+
use PBaszak\UltraMapper\Blueprint\Application\Trait\GetAttributes;
1010
use PBaszak\UltraMapper\Shared\Infrastructure\Normalization\Normalizable;
1111

1212
class PropertyBlueprint implements Normalizable
1313
{
14+
use GetAttributes;
15+
1416
public const OPTIONS_NAME = 'name';
1517
public const OPTIONS_PATH = 'path';
1618

@@ -97,20 +99,6 @@ public function getReflection(): \ReflectionProperty
9799
return new \ReflectionProperty($this->class->name, $this->originName);
98100
}
99101

100-
/**
101-
* @return object[]
102-
*/
103-
public function getAttributes(string $class): array
104-
{
105-
/** @var array<AttributeBlueprint> $attrs */
106-
$attrs = $this->attributes[$class];
107-
108-
/** @var object[] $attrs */
109-
array_walk($attrs, fn (AttributeBlueprint $attr): object => $attr->newInstance());
110-
111-
return $attrs;
112-
}
113-
114102
public function normalize(): array
115103
{
116104
return [
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PBaszak\UltraMapper\Blueprint\Application\Trait;
6+
7+
use PBaszak\UltraMapper\Blueprint\Application\Model\Assets\AttributeBlueprint;
8+
use PBaszak\UltraMapper\Mapper\Application\Contract\AttributeInterface;
9+
10+
trait GetAttributes
11+
{
12+
/**
13+
* @return object[] array of attribute instances
14+
*/
15+
public function getAttributes(string $class, ?string $process = null): array
16+
{
17+
/** @var array<AttributeBlueprint> $attrs */
18+
$attrs = $this->attributes[$class] ?? [];
19+
20+
/** @var object[] $attrs */
21+
$attrs = array_map(fn (AttributeBlueprint $attr): object => $attr->newInstance(), $attrs);
22+
23+
if (!$process) {
24+
return $attrs;
25+
}
26+
27+
foreach ($attrs as $index => $attr) {
28+
if ($attr instanceof AttributeInterface) {
29+
$binaryProcessType = $attr::PROCESS_TYPE_MAP[$process];
30+
if (($attr->getProcessType() & $binaryProcessType) !== $binaryProcessType) {
31+
unset($attrs[$index], $attr);
32+
}
33+
}
34+
}
35+
36+
return array_values($attrs);
37+
}
38+
}

src/Mapper/Application/Attribute/Groups.php

+6-3
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,25 @@ class Groups implements AttributeInterface
1212
{
1313
use ThrowAttributeValidationExceptionTrait;
1414

15+
public array $groups;
16+
1517
/**
1618
* @param string|array $groups the groups that the property should be included in
1719
* @param array<string, mixed> $options Options are for modificators of the mapping process. If You need them, You can use them.
1820
*/
1921
public function __construct(
20-
public readonly string|array $groups,
22+
string|array $groups,
2123
public int $processType = self::DENORMALIZATION | self::NORMALIZATION | self::TRANSFORMATION | self::MAPPING,
22-
public readonly array $options = []
24+
public array $options = []
2325
) {
26+
$this->groups = is_array($groups) ? $groups : [$groups];
2427
}
2528

2629
public function validate(\ReflectionProperty|\ReflectionParameter|\ReflectionClass $reflection): void
2730
{
2831
// todo implement
2932
}
30-
33+
3134
public function getProcessType(): int
3235
{
3336
return $this->processType;

src/Mapper/Application/Attribute/MaxDepth.php

+5
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,9 @@ public function validate(\ReflectionProperty|\ReflectionParameter|\ReflectionCla
4242
{
4343
// todo implement
4444
}
45+
46+
public function getProcessType(): int
47+
{
48+
return $this->processType;
49+
}
4550
}

src/Mapper/Application/Model/Context.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ public function __construct(
1818

1919
/**
2020
* @param string[] $groups
21-
*
22-
* @return bool Success if the group is matching.
21+
*
22+
* @return bool success if the group is matching
2323
*/
2424
public function isGroupMatching(array $groups): bool
2525
{

src/Mapper/Domain/Matcher/Checker/RecursiveLoopChecker.php

+45-4
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@
55
namespace PBaszak\UltraMapper\Mapper\Domain\Matcher\Checker;
66

77
use PBaszak\UltraMapper\Blueprint\Application\Model\Assets\ClassBlueprint;
8+
use PBaszak\UltraMapper\Blueprint\Application\Model\Assets\PropertyBlueprint;
89
use PBaszak\UltraMapper\Blueprint\Application\Model\Blueprint;
10+
use PBaszak\UltraMapper\Mapper\Application\Attribute\Groups;
11+
use PBaszak\UltraMapper\Mapper\Application\Attribute\Ignore;
12+
use PBaszak\UltraMapper\Mapper\Application\Attribute\MaxDepth;
913
use PBaszak\UltraMapper\Mapper\Application\Model\Context;
1014
use PBaszak\UltraMapper\Mapper\Domain\Matcher\Contract\BlueprintCheckerInterface;
15+
use PBaszak\UltraMapper\Mapper\Domain\Matcher\Exception\BlueprintCheckerException;
1116
use PBaszak\UltraMapper\Mapper\Domain\Model\Process;
1217

1318
class RecursiveLoopChecker implements BlueprintCheckerInterface
@@ -17,14 +22,50 @@ class RecursiveLoopChecker implements BlueprintCheckerInterface
1722
public function check(Blueprint $blueprint, Process $process, Context $context): void
1823
{
1924
$this->blueprint = $blueprint;
20-
25+
foreach ($blueprint->blueprints as $index => $classBlueprint) {
26+
foreach ($process->processes as $processType) {
27+
$this->checkClassBlueprint($classBlueprint, $classBlueprint->name, $processType, $context);
28+
}
29+
}
2130
}
2231

23-
private function checkClassBlueprint(ClassBlueprint $blueprint, Process $process, Context $context): void
32+
private function checkClassBlueprint(ClassBlueprint $blueprint, string $actualCheckedClass, string $process, Context $context): void
2433
{
2534
/** @var PropertyBlueprint $property */
2635
foreach ($blueprint->properties as $property) {
27-
36+
$types = $property->type->getAllClassTypes();
37+
if (empty($types)) {
38+
continue;
39+
}
40+
41+
foreach ($types as $type) {
42+
if ($actualCheckedClass == $type) {
43+
// so, type is repeated in one of the children properties
44+
if (
45+
// if there is no Ignore attribute
46+
empty($property->getAttributes(Ignore::class, $process))
47+
// and no MaxDepth attribute
48+
&& empty($property->getAttributes(MaxDepth::class, $process))
49+
// and no Groups attribute
50+
&& (
51+
empty($groups = $property->getAttributes(Groups::class, $process))
52+
// or there is a Groups attribute and it's matching with the current context
53+
|| [false] !== array_values(array_unique(
54+
array_map(fn (Groups $groupsAttr): bool => $context->isGroupMatching($groupsAttr->groups), $groups)
55+
))
56+
)
57+
) {
58+
// then we are forced to throw exception about recursive loop
59+
throw new BlueprintCheckerException(sprintf('The %s::%s property is the recursive loop source.', $property->class->name, $property->originName), sprintf('There is a few ideas You can did right now. Use one of the following attributes for this property: Ignore, Groups with groups out of the current context (%s), MaxDepth.', implode(', ', $context->groups)));
60+
}
61+
}
62+
63+
foreach ($this->blueprint->blueprints as $classBlueprint) {
64+
if ($type == $classBlueprint->name && $actualCheckedClass != $classBlueprint->name) {
65+
$this->checkClassBlueprint($classBlueprint, $actualCheckedClass, $process, $context);
66+
}
67+
}
68+
}
2869
}
2970
}
30-
}
71+
}

src/Mapper/Domain/Matcher/Contract/ClassMatchingStrategy.php

+2-4
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ interface ClassMatchingStrategy
1212
{
1313
/**
1414
* Method checks if the strategy conditions are met.
15-
*
16-
* @return bool Success if the conditions are met.
15+
*
16+
* @return bool success if the conditions are met
1717
*/
1818
public function isStrategyConditionsMet(
1919
Context $context,
@@ -25,8 +25,6 @@ public function isStrategyConditionsMet(
2525

2626
/**
2727
* Method matches classes in the way defined by the strategy.
28-
*
29-
* @return void
3028
*/
3129
public function matchClasses(
3230
Context $context,

src/Mapper/Domain/Matcher/Contract/PropertyMatchingStrategy.php

+2-4
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ interface PropertyMatchingStrategy
1212
{
1313
/**
1414
* Method checks if the strategy conditions are met.
15-
*
16-
* @return bool Success if the conditions are met.
15+
*
16+
* @return bool success if the conditions are met
1717
*/
1818
public function isStrategyConditionsMet(
1919
Context $context,
@@ -25,8 +25,6 @@ public function isStrategyConditionsMet(
2525

2626
/**
2727
* Method matches properties in the way defined by the strategy.
28-
*
29-
* @return void
3028
*/
3129
public function matchProperties(
3230
Context $context,

src/Mapper/Domain/Matcher/Extender/DiscriminatorExtender.php

-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ private function handleClassBlueprint(ClassBlueprint $blueprint, Process $proces
2929
{
3030
/** @var PropertyBlueprint $property */
3131
foreach ($blueprint->properties as $property) {
32-
3332
}
3433
}
3534
}

src/Mapper/Domain/Matcher/Matcher.php

+11-11
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@
2020

2121
/**
2222
* Class Matcher contains methods required by interface for external access,
23-
* but also contains methods useful for matching strategies.
23+
* but also contains methods useful for matching strategies.
2424
*/
2525
class Matcher implements MatcherInterface
2626
{
2727
/**
2828
* @param PropertyMatchingStrategy[]|class-string<PropertyMatchingStrategy>[] $propertyMatchingStrategies
29-
* @param ClassMatchingStrategy[]|class-string<ClassMatchingStrategy>[] $classMatchingStrategies
29+
* @param ClassMatchingStrategy[]|class-string<ClassMatchingStrategy>[] $classMatchingStrategies
3030
*/
3131
public function __construct(
3232
/** @var PropertyMatchingStrategy[] */
@@ -76,10 +76,10 @@ public function matchProperties(
7676
foreach ($this->propertyMatchingStrategies as $strategy) {
7777
if ($strategy->isStrategyConditionsMet($context, $process, $origin, $sourceProperty, $targetProperty)) {
7878
$strategy->matchProperties($context, $process, $origin, $sourceProperty, $targetProperty);
79-
$requiredMatches--;
79+
--$requiredMatches;
8080
}
8181

82-
if ($requiredMatches === 0) {
82+
if (0 === $requiredMatches) {
8383
return true;
8484
}
8585
}
@@ -100,13 +100,13 @@ public function matchClassBlueprintsBasedOnProperty(
100100
PropertyBlueprint $target
101101
): void {
102102
$originClasses = $origin->type->getAllClassTypes();
103-
/** @var ClassBlueprint[] $originClasses */
103+
/* @var ClassBlueprint[] $originClasses */
104104
array_walk($originClasses, fn (string $originClass): ClassBlueprint => Blueprint::getBlueprint($origin)->blueprints[$originClass]);
105105
$sourceClasses = $source->type->getAllClassTypes();
106-
/** @var ClassBlueprint[] $sourceClasses */
106+
/* @var ClassBlueprint[] $sourceClasses */
107107
array_walk($sourceClasses, fn (string $sourceClass): ClassBlueprint => Blueprint::getBlueprint($origin)->blueprints[$sourceClass]);
108108
$targetClasses = $target->type->getAllClassTypes();
109-
/** @var ClassBlueprint[] $targetClasses */
109+
/* @var ClassBlueprint[] $targetClasses */
110110
array_walk($targetClasses, fn (string $targetClass): ClassBlueprint => Blueprint::getBlueprint($origin)->blueprints[$targetClass]);
111111

112112
foreach ($originClasses as $originClass) {
@@ -136,13 +136,13 @@ public function matchClassBlueprints(
136136
$matchedProperties = 0;
137137
foreach ($origin->properties as $index => $property) {
138138
if ($this->isPropertyIgnored($context, $process, $property)) {
139-
$totalProperties--;
139+
--$totalProperties;
140140
unset($origin->properties[$index]);
141141
continue;
142142
}
143143

144144
if ($this->matchProperties($context, $process, $property, $source, $target)) {
145-
$matchedProperties++;
145+
++$matchedProperties;
146146
}
147147
}
148148

@@ -183,8 +183,8 @@ function (object $object) use ($origin, $source, $target) {
183183
/**
184184
* Method checks if the property should be ignored.
185185
* Only origin property should be checked.
186-
*
187-
* @return bool Success if the property should be ignored.
186+
*
187+
* @return bool success if the property should be ignored
188188
*/
189189
protected function isPropertyIgnored(Context $context, Process $process, PropertyBlueprint $origin): bool
190190
{

0 commit comments

Comments
 (0)