Test Failed
Push — main ( 39c21d...4c3e84 )
by Fractal
02:52
created

ConstraintExtractor::getClassMetadataFor()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 1
cts 1
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace FRZB\Component\RequestMapper\Extractor;
6
7
use Fp\Collections\ArrayList;
8
use FRZB\Component\DependencyInjection\Attribute\AsService;
9
use FRZB\Component\RequestMapper\Helper\ClassHelper;
10
use FRZB\Component\RequestMapper\Helper\ConstraintsHelper;
11
use FRZB\Component\RequestMapper\Helper\PropertyHelper;
12
use Symfony\Component\Validator\Constraints\All;
13
use Symfony\Component\Validator\Constraints\Collection;
14
use Symfony\Component\Validator\Exception\NoSuchMetadataException;
15
use Symfony\Component\Validator\Mapping\ClassMetadata as ClassMetadataImpl;
16
use Symfony\Component\Validator\Mapping\ClassMetadataInterface as ClassMetadata;
17
use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface as MetadataFactory;
18
use Symfony\Component\Validator\Mapping\PropertyMetadata as PropertyMetadataImpl;
19
use Symfony\Component\Validator\Mapping\PropertyMetadataInterface as PropertyMetadata;
20
use Symfony\Component\Validator\Validator\ValidatorInterface as Validator;
21
22
#[AsService(arguments: ['$metadataFactory' => Validator::class])]
23
class ConstraintExtractor
24
{
25 15
    public function __construct(
26
        private readonly MetadataFactory $metadataFactory,
27
    ) {
28
    }
29
30 15
    public function extract(string $class, array $parameters = []): ?Collection
31
    {
32
        try {
33 15
            return ConstraintsHelper::createCollection($this->extractConstraints($class, $parameters));
34 1
        } catch (NoSuchMetadataException|\ReflectionException) {
35 1
            return null;
36
        }
37
    }
38
39
    /** @throws NoSuchMetadataException|\ReflectionException */
40 15
    public function extractConstraints(string $class, array $parameters = []): array
41
    {
42 15
        $constraints = [];
43 15
        $classMetadata = $this->getClassMetadataFor($class);
44 15
        $reflectionClass = new \ReflectionClass($class);
45
46 15
        foreach ($reflectionClass->getProperties() as $property) {
47 15
            $propertyName = PropertyHelper::getName($property);
48 15
            $propertyValue = $parameters[$propertyName] ?? [];
49 15
            $propertyTypeName = PropertyHelper::getTypeName($property);
50 15
            $arrayTypeName = ConstraintsHelper::getArrayTypeAttribute($property)?->typeName;
51 15
            $propertyMetadata = $this->getPropertyMetadataFor($classMetadata, $propertyName);
52 15
            $propertyReflectionMember = $this->getPropertyReflectionMember($class, $propertyMetadata);
53 15
54
            $constraints[$propertyName] = match (true) {
55 15
                ConstraintsHelper::hasArrayTypeAttribute($propertyReflectionMember) => ArrayList::collect($propertyValue)->map(fn () => new All($this->extract($arrayTypeName, $propertyValue)))->toArray(),
0 ignored issues
show
Bug introduced by
It seems like $propertyReflectionMember can also be of type ReflectionMethod; however, parameter $rProperty of FRZB\Component\RequestMa...hasArrayTypeAttribute() does only seem to accept ReflectionProperty, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

55
                ConstraintsHelper::hasArrayTypeAttribute(/** @scrutinizer ignore-type */ $propertyReflectionMember) => ArrayList::collect($propertyValue)->map(fn () => new All($this->extract($arrayTypeName, $propertyValue)))->toArray(),
Loading history...
56 15
                ClassHelper::isNotBuiltinAndExists($propertyTypeName) => $this->extract($propertyTypeName),
57 15
                default => $propertyMetadata->getConstraints(),
58 15
            };
59
        }
60
61
        return $constraints;
62 15
    }
63
64
    /** @noinspection PhpIncompatibleReturnTypeInspection */
65
    private function getClassMetadataFor(string $class): ClassMetadata
66 15
    {
67
        return $this->metadataFactory->getMetadataFor($class) ?? new ClassMetadataImpl($class);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->metadataFa...g\ClassMetadata($class) could return the type Symfony\Component\Valida...pping\MetadataInterface which includes types incompatible with the type-hinted return Symfony\Component\Valida...\ClassMetadataInterface. Consider adding an additional type-check to rule them out.
Loading history...
68 15
    }
69
70
    private function getPropertyMetadataFor(ClassMetadata $classMetadata, string $propertyName): PropertyMetadata
71 15
    {
72
        return ArrayList::collect($classMetadata->getPropertyMetadata($propertyName))
73 15
            ->firstElement()
74 15
            ->getOrElse(new PropertyMetadataImpl($classMetadata->getClassName(), $propertyName))
75 15
        ;
76
    }
77
78
    private function getPropertyReflectionMember(string $class, PropertyMetadata $propertyMetadata): \ReflectionMethod|\ReflectionProperty
79 15
    {
80
        return $propertyMetadata->getReflectionMember($class);
81 15
    }
82
}
83