Passed
Push — main ( a5f538...a74a35 )
by Fractal
03:00
created

ParametersExtractor::mapPropertiesForArray()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 5
c 0
b 0
f 0
nc 1
nop 2
dl 0
loc 7
ccs 0
cts 6
cp 0
crap 2
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 Fp\Collections\Entry;
9
use Fp\Collections\HashMap;
10
use FRZB\Component\DependencyInjection\Attribute\AsService;
11
use FRZB\Component\PhpDocReader\Exception\ReaderException;
12
use FRZB\Component\PhpDocReader\Reader\ReaderInterface as PhpDocReader;
13
use FRZB\Component\RequestMapper\Helper\ClassHelper;
14
use FRZB\Component\RequestMapper\Helper\ConstraintsHelper;
15
use FRZB\Component\RequestMapper\Helper\SerializerHelper;
16
17
#[AsService]
18
class ParametersExtractor
19
{
20 19
    public function __construct(
21
        private readonly PhpDocReader $reader,
22
    ) {
23
    }
24
25 19
    public function extract(string $class, array $parameters): array
26
    {
27 19
        return [...$parameters, ...$this->mapProperties($this->getPropertyMapping($class, $parameters), $parameters)];
28
    }
29
30 19
    private function mapProperties(array $properties, array $parameters): array
31
    {
32 19
        $params = HashMap::collect($parameters);
33 19
        $props = HashMap::collect($properties);
34
35 19
        $complexTypes = $props
36 19
            ->filter(static fn (Entry $propEntry) => \is_array($propEntry->value))
37 19
            ->map(fn (Entry $propEntry) => $this->mapPropertiesForArray($propEntry, $params))
38 19
            ->toAssocArray()
39 19
            ->getOrElse([])
40
        ;
41
42 19
        $classTypes = $props
43 19
            ->filter(static fn (Entry $propEntry) => !\is_array($propEntry->value) && ClassHelper::isNotBuiltinAndExists($propEntry->value))
44 19
            ->map(fn (Entry $propEntry) => $this->extract($propEntry->value, $params->get($propEntry->key)->getOrElse([])))
45 19
            ->toAssocArray()
46 19
            ->getOrElse([])
47
        ;
48
49 19
        $enumTypes = $props
50 19
            ->filter(static fn (Entry $propEntry) => !\is_array($propEntry->value) && ClassHelper::isEnum($propEntry->value))
51 19
            ->map(fn (Entry $propEntry) => $this->mapEnum($propEntry->value, $params->get($propEntry->key)->getOrElse(null)))
52 19
            ->toAssocArray()
53 19
            ->getOrElse([])
54
        ;
55
56 19
        $simpleTypes = $props
57 19
            ->filter(static fn (Entry $propEntry) => !\is_array($propEntry->value) && !ClassHelper::isNotBuiltinAndExists($propEntry->value))
58 19
            ->map(static fn (Entry $propEntry) => $params->get($propEntry->key)->getOrElse(null))
59 19
            ->toAssocArray()
60 19
            ->getOrElse([])
61
        ;
62
63 19
        return [...$complexTypes, ...$classTypes, ...$enumTypes, ...$simpleTypes];
64
    }
65
66 3
    private function mapEnum(string $enumClassName, mixed $value = null): ?\BackedEnum
0 ignored issues
show
Bug introduced by
The type BackedEnum was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
67
    {
68
        return match (true) {
69 3
            is_subclass_of($enumClassName, \IntBackedEnum::class) && \is_int($value) => $enumClassName::tryFrom($value),
0 ignored issues
show
Bug introduced by
The type IntBackedEnum was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
70 3
            is_subclass_of($enumClassName, \StringBackedEnum::class) && \is_string($value) => $enumClassName::tryFrom($value),
0 ignored issues
show
Bug introduced by
The type StringBackedEnum was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
71 3
            default => null,
72
        };
73
    }
74
75
    private function mapPropertiesForArray(Entry $propEntry, HashMap $parameters): array
76
    {
77
        return HashMap::collect($parameters->get($propEntry->key)->get())
78
            ->map(static fn (Entry $paramEntry) => $propEntry->value[$paramEntry->key])
79
            ->map(fn (Entry $paramEntry) => $this->extract($paramEntry->value, $parameters->get($propEntry->key)->get()[$paramEntry->key] ?? []))
80
            ->toAssocArray()
81
            ->get()
82
        ;
83
    }
84
85 19
    private function getPropertyMapping(string $className, mixed $value): array
86
    {
87
        try {
88 19
            $properties = ArrayList::collect((new \ReflectionClass($className))->getProperties());
89
        } catch (\ReflectionException) {
90
            $properties = ArrayList::empty();
91
        }
92
93 19
        $complexTypes = $properties
94 19
            ->map(fn (\ReflectionProperty $p) => match (true) {
95 19
                ConstraintsHelper::hasArrayTypeAttribute($p) => [SerializerHelper::getSerializedNameAttribute($p)->getSerializedName() => ArrayList::range(0, \count($value))->map(fn () => ConstraintsHelper::getArrayTypeAttribute($p)->typeName)->toArray()],
96 19
                ConstraintsHelper::hasArrayDocBlock($p, $this->reader) => [SerializerHelper::getSerializedNameAttribute($p)->getSerializedName() => ArrayList::range(0, \count($value))->map(fn () => $this->getPropertyTypeFromDocBlock($p))->toArray()],
97 19
                default => [SerializerHelper::getSerializedNameAttribute($p)->getSerializedName() => []],
98
            })
99 19
            ->reduce(static fn (array $prev, array $next) => [...$prev, ...$next])
100 19
            ->getOrElse([])
101
        ;
102
103 19
        $simpleTypes = $properties
104 19
            ->filter(static fn (\ReflectionProperty $p) => !ConstraintsHelper::hasArrayTypeAttribute($p))
105 19
            ->map(static fn (\ReflectionProperty $p) => [SerializerHelper::getSerializedNameAttribute($p)->getSerializedName() => $p->getType()?->/** @scrutinizer ignore-call */ getName()])
106 19
            ->reduce(static fn (array $prev, array $next) => [...$prev, ...$next])
107 19
            ->getOrElse([])
108
        ;
109
110 19
        return [...$complexTypes, ...$simpleTypes];
111
    }
112
113
    private function getPropertyTypeFromDocBlock(\ReflectionProperty $property): ?string
114
    {
115
        try {
116
            return $this->reader->getPropertyClass($property);
117
        } catch (ReaderException) {
118
            return null;
119
        }
120
    }
121
}
122