Passed
Push — main ( 901a88...cf9313 )
by Chema
52s queued 13s
created

DependencyResolver   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 132
Duplicated Lines 0 %

Test Coverage

Coverage 98.18%

Importance

Changes 0
Metric Value
eloc 46
c 0
b 0
f 0
dl 0
loc 132
rs 10
ccs 54
cts 55
cp 0.9818
wmc 22

8 Methods

Rating   Name   Duplication   Size   Complexity  
A resolveReflectionClass() 0 17 3
A __construct() 0 3 1
A resolveDependencies() 0 16 3
A resolveInnerDependencies() 0 14 3
A isScalar() 0 4 2
A checkInvalidArgumentParam() 0 14 4
A resolveClass() 0 19 4
A resolveDependenciesRecursively() 0 14 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Gacela\Container;
6
7
use Gacela\Container\Exception\DependencyInvalidArgumentException;
8
use Gacela\Container\Exception\DependencyNotFoundException;
9
use ReflectionClass;
10
use ReflectionMethod;
11
use ReflectionNamedType;
12
use ReflectionParameter;
13
14
use function is_callable;
15
use function is_object;
16
17
final class DependencyResolver
18
{
19
    /**
20
     * @param array<class-string,class-string|callable|object> $bindings
21
     */
22 19
    public function __construct(
23
        private array $bindings = [],
24
    ) {
25 19
    }
26
27
    /**
28
     * @param class-string $resolvedClassName
29
     *
30
     * @return list<mixed>
31
     */
32 19
    public function resolveDependencies(string $resolvedClassName): array
33
    {
34 19
        $reflection = new ReflectionClass($resolvedClassName);
35 19
        $constructor = $reflection->getConstructor();
36 19
        if (!$constructor) {
37 3
            return [];
38
        }
39
40
        /** @var list<mixed> $dependencies */
41 16
        $dependencies = [];
42 16
        foreach ($constructor->getParameters() as $parameter) {
43
            /** @psalm-suppress MixedAssignment */
44 16
            $dependencies[] = $this->resolveDependenciesRecursively($parameter);
45
        }
46
47 13
        return $dependencies;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $dependencies returns the type Gacela\Container\list which is incompatible with the type-hinted return array.
Loading history...
48
    }
49
50 16
    private function resolveDependenciesRecursively(ReflectionParameter $parameter): mixed
51
    {
52 16
        $this->checkInvalidArgumentParam($parameter);
53
54
        /** @var ReflectionNamedType $paramType */
55 14
        $paramType = $parameter->getType();
56
57
        /** @var class-string $paramTypeName */
58 14
        $paramTypeName = $paramType->getName();
59 14
        if ($parameter->isDefaultValueAvailable()) {
60 10
            return $parameter->getDefaultValue();
61
        }
62
63 11
        return $this->resolveClass($paramTypeName);
64
    }
65
66 16
    private function checkInvalidArgumentParam(ReflectionParameter $parameter): void
67
    {
68 16
        if (!$parameter->hasType()) {
69 1
            throw DependencyInvalidArgumentException::noParameterTypeFor($parameter->getName());
70
        }
71
72
        /** @var ReflectionNamedType $paramType */
73 15
        $paramType = $parameter->getType();
74 15
        $paramTypeName = $paramType->getName();
75
76 15
        if ($this->isScalar($paramTypeName) && !$parameter->isDefaultValueAvailable()) {
77
            /** @var ReflectionClass $reflectionClass */
78 1
            $reflectionClass = $parameter->getDeclaringClass();
79 1
            throw DependencyInvalidArgumentException::unableToResolve($paramTypeName, $reflectionClass->getName());
80
        }
81
    }
82
83 15
    private function isScalar(string $paramTypeName): bool
84
    {
85 15
        return !class_exists($paramTypeName)
86 15
            && !interface_exists($paramTypeName);
87
    }
88
89
    /**
90
     * @param class-string $paramTypeName
91
     */
92 11
    private function resolveClass(string $paramTypeName): mixed
93
    {
94
        /** @var mixed $bindClass */
95 11
        $bindClass = $this->bindings[$paramTypeName] ?? null;
96 11
        if (is_callable($bindClass)) {
97
            return $bindClass();
98
        }
99
100 11
        if (is_object($bindClass)) {
101 2
            return $bindClass;
102
        }
103
104 9
        $reflection = $this->resolveReflectionClass($paramTypeName);
105 8
        $constructor = $reflection->getConstructor();
106 8
        if ($constructor === null) {
107 1
            return $reflection->newInstance();
108
        }
109
110 7
        return $this->resolveInnerDependencies($constructor, $reflection);
111
    }
112
113
    /**
114
     * @param class-string $paramTypeName
115
     */
116 9
    private function resolveReflectionClass(string $paramTypeName): ReflectionClass
117
    {
118 9
        $reflection = new ReflectionClass($paramTypeName);
119
120 9
        if ($reflection->isInstantiable()) {
121 7
            return $reflection;
122
        }
123
124
        /** @var mixed $concreteClass */
125 2
        $concreteClass = $this->bindings[$reflection->getName()] ?? null;
126
127 2
        if ($concreteClass !== null) {
128
            /** @var class-string $concreteClass */
129 1
            return new ReflectionClass($concreteClass);
130
        }
131
132 1
        throw DependencyNotFoundException::mapNotFoundForClassName($reflection->getName());
133
    }
134
135 7
    private function resolveInnerDependencies(ReflectionMethod $constructor, ReflectionClass $reflection): object
136
    {
137
        /** @var list<mixed> $innerDependencies */
138 7
        $innerDependencies = [];
139
140 7
        foreach ($constructor->getParameters() as $constructorParameter) {
141 7
            $paramType = $constructorParameter->getType();
142 7
            if ($paramType) {
143
                /** @psalm-suppress MixedAssignment */
144 7
                $innerDependencies[] = $this->resolveDependenciesRecursively($constructorParameter);
145
            }
146
        }
147
148 7
        return $reflection->newInstanceArgs($innerDependencies);
0 ignored issues
show
Bug introduced by
$innerDependencies of type Gacela\Container\list is incompatible with the type array expected by parameter $args of ReflectionClass::newInstanceArgs(). ( Ignorable by Annotation )

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

148
        return $reflection->newInstanceArgs(/** @scrutinizer ignore-type */ $innerDependencies);
Loading history...
149
    }
150
}
151