Passed
Push — feat/refactor-dependency-resol... ( b9d356 )
by Jesús
04:17
created

DependencyResolver::resolveClass()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 10
c 1
b 0
f 0
nc 4
nop 1
dl 0
loc 19
ccs 11
cts 11
cp 1
crap 4
rs 9.9332
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Gacela\Framework\ClassResolver\DependencyResolver;
6
7
use ReflectionClass;
8
use ReflectionMethod;
9
use ReflectionNamedType;
10
use ReflectionParameter;
11
12
use function is_callable;
13
use function is_object;
14
15
final class DependencyResolver
16
{
17
    /** @var array<class-string,class-string|callable|object> */
18
    private array $mappingInterfaces;
19
20
    /**
21
     * @param array<class-string,class-string|callable|object> $mappingInterfaces
22
     */
23 21
    public function __construct(array $mappingInterfaces)
24
    {
25 21
        $this->mappingInterfaces = $mappingInterfaces;
26
    }
27
28
    /**
29
     * @param class-string $resolvedClassName
1 ignored issue
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
30
     *
31
     * @return list<mixed>
32
     */
33 21
    public function resolveDependencies(string $resolvedClassName): array
34
    {
35 21
        $reflection = new ReflectionClass($resolvedClassName);
36 21
        $constructor = $reflection->getConstructor();
37 21
        if (!$constructor) {
38 17
            return [];
1 ignored issue
show
Bug Best Practice introduced by
The expression return array() returns the type array which is incompatible with the documented return type Gacela\Framework\ClassRe...DependencyResolver\list.
Loading history...
39
        }
40
41
        /** @var list<mixed> $dependencies */
42 4
        $dependencies = [];
43 4
        foreach ($constructor->getParameters() as $parameter) {
44 4
            $paramType = $parameter->getType();
45 4
            if ($paramType) {
46
                /** @psalm-suppress MixedAssignment */
47 4
                $dependencies[] = $this->resolveDependenciesRecursively($parameter);
48
            }
49
        }
50
51 4
        return $dependencies;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $dependencies returns the type Gacela\Framework\ClassRe...DependencyResolver\list which is incompatible with the type-hinted return array.
Loading history...
52
    }
53
54
    /**
55
     * @return mixed
56
     */
57 4
    private function resolveDependenciesRecursively(ReflectionParameter $parameter)
58
    {
59 4
        $this->invalidArgumentParam($parameter);
60
61
        /** @var ReflectionNamedType $paramType */
62 4
        $paramType = $parameter->getType();
63
64
        /** @var class-string $paramTypeName */
65 4
        $paramTypeName = $paramType->getName();
66 4
        if ($this->isScalar($paramTypeName) && $parameter->isDefaultValueAvailable()) {
67
            return $parameter->getDefaultValue();
68
        }
69
70 4
        return $this->resolveClass($paramTypeName);
71
    }
72
73 4
    private function invalidArgumentParam(ReflectionParameter $parameter): void
74
    {
75
        /** @var ReflectionNamedType $paramType */
76 4
        $paramType = $parameter->getType();
77
78 4
        $paramTypeName = $paramType->getName();
79
80 4
        if (!$parameter->hasType()) {
81
            throw DependencyInvalidArgumentException::noParameterTypeFor($parameter->getName());
82
        }
83
84 4
        if ($this->isScalar($paramTypeName) && !$parameter->isDefaultValueAvailable()) {
85
            /** @var ReflectionClass $reflectionClass */
86
            $reflectionClass = $parameter->getDeclaringClass();
87
            throw DependencyInvalidArgumentException::unableToResolve($paramTypeName, $reflectionClass->getName());
88
        }
89
    }
90
91 4
    private function isScalar(string $paramTypeName): bool
92
    {
93 4
        return !class_exists($paramTypeName)
94 4
            && !interface_exists($paramTypeName);
95
    }
96
97
    /**
98
     * @param class-string $paramTypeName
1 ignored issue
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
99
     *
100
     * @return mixed
101
     */
102 4
    private function resolveClass(string $paramTypeName)
103
    {
104
        /** @var mixed $mappedClass */
105 4
        $mappedClass = $this->mappingInterfaces[$paramTypeName] ?? null;
106 4
        if (is_callable($mappedClass)) {
107 1
            return $mappedClass();
108
        }
109
110 4
        if (is_object($mappedClass)) {
111 3
            return $mappedClass;
112
        }
113
114 1
        $reflection = $this->resolveReflectionClass($paramTypeName);
115 1
        $constructor = $reflection->getConstructor();
116 1
        if (!$constructor) {
117 1
            return $reflection->newInstance();
118
        }
119
120 1
        return $this->resolveInnerDependencies($constructor, $reflection);
121
    }
122
123
    /**
124
     * @param class-string $paramTypeName
1 ignored issue
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
125
     */
126 1
    private function resolveReflectionClass(string $paramTypeName): ReflectionClass
127
    {
128 1
        $reflection = new ReflectionClass($paramTypeName);
129
130 1
        if ($reflection->isInstantiable()) {
131 1
            return $reflection;
132
        }
133
134
        /** @var mixed $concreteClass */
135 1
        $concreteClass = $this->mappingInterfaces[$reflection->getName()];
136
137 1
        if ($concreteClass !== null) {
138
            /** @var class-string $concreteClass */
139 1
            return new ReflectionClass($concreteClass);
140
        }
141
142
        throw DependencyNotFoundException::mapNotFoundForClassName($reflection->getName());
143
    }
144
145 1
    private function resolveInnerDependencies(ReflectionMethod $constructor, ReflectionClass $reflection): object
146
    {
147
        /** @var list<mixed> $innerDependencies */
148 1
        $innerDependencies = [];
149
150 1
        foreach ($constructor->getParameters() as $constructorParameter) {
151 1
            $paramType = $constructorParameter->getType();
152 1
            if ($paramType) {
153
                /** @psalm-suppress MixedAssignment */
154 1
                $innerDependencies[] = $this->resolveDependenciesRecursively($constructorParameter);
155
            }
156
        }
157
158 1
        return $reflection->newInstanceArgs($innerDependencies);
159
    }
160
}
161