Passed
Push — feat/refactor-dependency-resol... ( b9d356...5e9164 )
by Chema
08:13
created

DependencyResolver::invalidArgumentParam()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 15
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 4.8437

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 7
c 1
b 0
f 0
nc 3
nop 1
dl 0
loc 15
ccs 5
cts 8
cp 0.625
crap 4.8437
rs 10
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->checkInvalidArgumentParam($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 checkInvalidArgumentParam(ReflectionParameter $parameter): void
74
    {
75 4
        if (!$parameter->hasType()) {
76
            throw DependencyInvalidArgumentException::noParameterTypeFor($parameter->getName());
77
        }
78
79
        /** @var ReflectionNamedType $paramType */
80 4
        $paramType = $parameter->getType();
81 4
        $paramTypeName = $paramType->getName();
82
83 4
        if ($this->isScalar($paramTypeName) && !$parameter->isDefaultValueAvailable()) {
84
            /** @var ReflectionClass $reflectionClass */
85
            $reflectionClass = $parameter->getDeclaringClass();
86
            throw DependencyInvalidArgumentException::unableToResolve($paramTypeName, $reflectionClass->getName());
87
        }
88
    }
89
90 4
    private function isScalar(string $paramTypeName): bool
91
    {
92 4
        return !class_exists($paramTypeName)
93 4
            && !interface_exists($paramTypeName);
94
    }
95
96
    /**
97
     * @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...
98
     *
99
     * @return mixed
100
     */
101 4
    private function resolveClass(string $paramTypeName)
102
    {
103
        /** @var mixed $mappedClass */
104 4
        $mappedClass = $this->mappingInterfaces[$paramTypeName] ?? null;
105 4
        if (is_callable($mappedClass)) {
106 1
            return $mappedClass();
107
        }
108
109 4
        if (is_object($mappedClass)) {
110 3
            return $mappedClass;
111
        }
112
113 1
        $reflection = $this->resolveReflectionClass($paramTypeName);
114 1
        $constructor = $reflection->getConstructor();
115 1
        if (!$constructor) {
116 1
            return $reflection->newInstance();
117
        }
118
119 1
        return $this->resolveInnerDependencies($constructor, $reflection);
120
    }
121
122
    /**
123
     * @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...
124
     */
125 1
    private function resolveReflectionClass(string $paramTypeName): ReflectionClass
126
    {
127 1
        $reflection = new ReflectionClass($paramTypeName);
128
129 1
        if ($reflection->isInstantiable()) {
130 1
            return $reflection;
131
        }
132
133
        /** @var mixed $concreteClass */
134 1
        $concreteClass = $this->mappingInterfaces[$reflection->getName()];
135
136 1
        if ($concreteClass !== null) {
137
            /** @var class-string $concreteClass */
138 1
            return new ReflectionClass($concreteClass);
139
        }
140
141
        throw DependencyNotFoundException::mapNotFoundForClassName($reflection->getName());
142
    }
143
144 1
    private function resolveInnerDependencies(ReflectionMethod $constructor, ReflectionClass $reflection): object
145
    {
146
        /** @var list<mixed> $innerDependencies */
147 1
        $innerDependencies = [];
148
149 1
        foreach ($constructor->getParameters() as $constructorParameter) {
150 1
            $paramType = $constructorParameter->getType();
151 1
            if ($paramType) {
152
                /** @psalm-suppress MixedAssignment */
153 1
                $innerDependencies[] = $this->resolveDependenciesRecursively($constructorParameter);
154
            }
155
        }
156
157 1
        return $reflection->newInstanceArgs($innerDependencies);
158
    }
159
}
160