Passed
Push — main ( fedd50...6979ab )
by Chema
02:23
created

DependencyResolver   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 132
Duplicated Lines 0 %

Test Coverage

Coverage 96.36%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 46
c 1
b 0
f 0
dl 0
loc 132
ccs 53
cts 55
cp 0.9636
rs 10
wmc 23

8 Methods

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

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