Skip to content

Commit c856a35

Browse files
committed
Extract PropertyResolver strategy
1 parent 42f08d7 commit c856a35

5 files changed

Lines changed: 189 additions & 45 deletions

File tree

src/Validators/Attributes.php

Lines changed: 16 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,17 @@
1515
use Attribute;
1616
use ReflectionAttribute;
1717
use ReflectionClass;
18-
use ReflectionIntersectionType;
19-
use ReflectionNamedType;
2018
use ReflectionObject;
2119
use ReflectionProperty;
22-
use ReflectionUnionType;
2320
use Respect\Fluent\Attributes\Composable;
2421
use Respect\Validation\Id;
2522
use Respect\Validation\Message\Template;
2623
use Respect\Validation\Result;
2724
use Respect\Validation\Validator;
25+
use Respect\Validation\Validators\Attributes\CompositePropertyResolver;
26+
use Respect\Validation\Validators\Attributes\DeclaredTypePropertyResolver;
27+
use Respect\Validation\Validators\Attributes\ExplicitAttributePropertyResolver;
28+
use Respect\Validation\Validators\Attributes\PropertyResolver;
2829
use Respect\Validation\Validators\Core\Reducer;
2930

3031
use function spl_object_id;
@@ -43,6 +44,17 @@ final class Attributes implements Validator
4344
/** @var array<int, true> */
4445
private array $visited = [];
4546

47+
private readonly PropertyResolver $propertyResolver;
48+
49+
public function __construct(
50+
PropertyResolver|null $propertyResolver = null,
51+
) {
52+
$this->propertyResolver = $propertyResolver ?? new CompositePropertyResolver(
53+
new DeclaredTypePropertyResolver(),
54+
new ExplicitAttributePropertyResolver(),
55+
);
56+
}
57+
4658
public function evaluate(mixed $input): Result
4759
{
4860
$id = new Id('attributes');
@@ -87,7 +99,7 @@ private function getPropertyValidators(ReflectionObject $reflection): array
8799
{
88100
$validators = [];
89101
foreach ($this->getProperties($reflection) as $propertyName => $property) {
90-
$propertyValidators = $this->getPropertyInnerValidators($property);
102+
$propertyValidators = $this->propertyResolver->resolve($property, $this);
91103
if ($propertyValidators === []) {
92104
continue;
93105
}
@@ -101,47 +113,6 @@ private function getPropertyValidators(ReflectionObject $reflection): array
101113
return $validators;
102114
}
103115

104-
/** @return array<Validator> */
105-
private function getPropertyInnerValidators(ReflectionProperty $property): array
106-
{
107-
$propertyValidators = [];
108-
$hasExplicitAttributes = false;
109-
foreach ($property->getAttributes(Validator::class, ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
110-
$propertyValidator = $attribute->getName() === self::class ? $this : $attribute->newInstance();
111-
$hasExplicitAttributes = $propertyValidator === $this;
112-
$propertyValidators[] = $propertyValidator;
113-
}
114-
115-
if ($hasExplicitAttributes) {
116-
return $propertyValidators;
117-
}
118-
119-
$type = $property->getType();
120-
if ($type instanceof ReflectionNamedType) {
121-
if (!$type->isBuiltin()) {
122-
$propertyValidators[] = $this;
123-
}
124-
}
125-
126-
if ($type instanceof ReflectionIntersectionType) {
127-
$propertyValidators[] = $this;
128-
}
129-
130-
if ($type instanceof ReflectionUnionType) {
131-
foreach ($type->getTypes() as $innerType) {
132-
if (!$innerType instanceof ReflectionNamedType || $innerType->isBuiltin()) {
133-
continue;
134-
}
135-
136-
/** @var class-string $class */
137-
$class = $innerType->getName();
138-
$propertyValidators[] = new Given(new Instance($class), $this);
139-
}
140-
}
141-
142-
return $propertyValidators;
143-
}
144-
145116
/** @return array<ReflectionProperty> */
146117
private function getProperties(ReflectionObject $reflection): array
147118
{
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
/*
4+
* SPDX-License-Identifier: MIT
5+
* SPDX-FileCopyrightText: (c) Respect Project Contributors
6+
* SPDX-FileContributor: Henrique Moody <henriquemoody@gmail.com>
7+
*/
8+
9+
declare(strict_types=1);
10+
11+
namespace Respect\Validation\Validators\Attributes;
12+
13+
use ReflectionProperty;
14+
use Respect\Validation\Validator;
15+
use Respect\Validation\Validators\Attributes;
16+
17+
use function array_values;
18+
use function in_array;
19+
20+
final class CompositePropertyResolver implements PropertyResolver
21+
{
22+
/** @var list<PropertyResolver> */
23+
private readonly array $resolvers;
24+
25+
public function __construct(PropertyResolver ...$resolvers)
26+
{
27+
$this->resolvers = array_values($resolvers);
28+
}
29+
30+
/** @return array<Validator> */
31+
public function resolve(ReflectionProperty $property, Attributes $attributes): array
32+
{
33+
$accumulated = [];
34+
35+
foreach ($this->resolvers as $resolver) {
36+
$validators = $resolver->resolve($property, $attributes);
37+
if ($validators === []) {
38+
continue;
39+
}
40+
41+
// When more than one resolver recognizes the same property (e.g. a
42+
// class-typed property annotated with #[Attributes], which both
43+
// DeclaredTypePropertyResolver and ExplicitAttributePropertyResolver
44+
// emit `$attributes` for), collapse duplicate `$attributes` entries
45+
// so the nested Attributes validator runs once instead of tripping
46+
// its circular-reference guard on the second pass.
47+
foreach ($validators as $validator) {
48+
if ($validator === $attributes && in_array($validator, $accumulated, true)) {
49+
continue;
50+
}
51+
52+
$accumulated[] = $validator;
53+
}
54+
}
55+
56+
return $accumulated;
57+
}
58+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
/*
4+
* SPDX-License-Identifier: MIT
5+
* SPDX-FileCopyrightText: (c) Respect Project Contributors
6+
* SPDX-FileContributor: Henrique Moody <henriquemoody@gmail.com>
7+
*/
8+
9+
declare(strict_types=1);
10+
11+
namespace Respect\Validation\Validators\Attributes;
12+
13+
use ReflectionIntersectionType;
14+
use ReflectionNamedType;
15+
use ReflectionProperty;
16+
use ReflectionUnionType;
17+
use Respect\Validation\Validator;
18+
use Respect\Validation\Validators\Attributes;
19+
use Respect\Validation\Validators\Given;
20+
use Respect\Validation\Validators\Instance;
21+
22+
final class DeclaredTypePropertyResolver implements PropertyResolver
23+
{
24+
/** @return array<Validator> */
25+
public function resolve(ReflectionProperty $property, Attributes $attributes): array
26+
{
27+
$type = $property->getType();
28+
29+
if ($type instanceof ReflectionNamedType) {
30+
if ($type->isBuiltin()) {
31+
return [];
32+
}
33+
34+
return [$attributes];
35+
}
36+
37+
if ($type instanceof ReflectionIntersectionType) {
38+
return [$attributes];
39+
}
40+
41+
if ($type instanceof ReflectionUnionType) {
42+
$validators = [];
43+
foreach ($type->getTypes() as $innerType) {
44+
if (!$innerType instanceof ReflectionNamedType || $innerType->isBuiltin()) {
45+
continue;
46+
}
47+
48+
/** @var class-string $class */
49+
$class = $innerType->getName();
50+
$validators[] = new Given(new Instance($class), $attributes);
51+
}
52+
53+
return $validators;
54+
}
55+
56+
return [];
57+
}
58+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
/*
4+
* SPDX-License-Identifier: MIT
5+
* SPDX-FileCopyrightText: (c) Respect Project Contributors
6+
* SPDX-FileContributor: Henrique Moody <henriquemoody@gmail.com>
7+
*/
8+
9+
declare(strict_types=1);
10+
11+
namespace Respect\Validation\Validators\Attributes;
12+
13+
use ReflectionAttribute;
14+
use ReflectionProperty;
15+
use Respect\Validation\Validator;
16+
use Respect\Validation\Validators\Attributes;
17+
18+
final class ExplicitAttributePropertyResolver implements PropertyResolver
19+
{
20+
/** @return array<Validator> */
21+
public function resolve(ReflectionProperty $property, Attributes $attributes): array
22+
{
23+
$validators = [];
24+
foreach ($property->getAttributes(Validator::class, ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
25+
$propertyValidator = $attribute->getName() === Attributes::class ? $attributes : $attribute->newInstance();
26+
$validators[] = $propertyValidator;
27+
}
28+
29+
return $validators;
30+
}
31+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
/*
4+
* SPDX-License-Identifier: MIT
5+
* SPDX-FileCopyrightText: (c) Respect Project Contributors
6+
* SPDX-FileContributor: Henrique Moody <henriquemoody@gmail.com>
7+
*/
8+
9+
declare(strict_types=1);
10+
11+
namespace Respect\Validation\Validators\Attributes;
12+
13+
use ReflectionProperty;
14+
use Respect\Validation\Validator;
15+
use Respect\Validation\Validators\Attributes;
16+
17+
/**
18+
* Resolves validators from properties.
19+
*
20+
* @internal
21+
*/
22+
interface PropertyResolver
23+
{
24+
/** @return array<Validator> */
25+
public function resolve(ReflectionProperty $property, Attributes $attributes): array;
26+
}

0 commit comments

Comments
 (0)