Passed
Push — feature/improve-mapping-interf... ( 71e998...24bbf4 )
by Jesús
03:38
created

DependencyResolver::resolveInnerDependencies()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 6
c 1
b 0
f 0
nc 3
nop 2
dl 0
loc 14
ccs 7
cts 7
cp 1
crap 3
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
        if (!$parameter->hasType()) {
60
            throw DependencyInvalidArgumentException::noParameterTypeFor($parameter->getName());
61
        }
62
63
        /** @var ReflectionNamedType $paramType */
64 4
        $paramType = $parameter->getType();
65
66
        /** @var class-string $paramTypeName */
67 4
        $paramTypeName = $paramType->getName();
68 4
        if ($this->isScalar($paramTypeName)) {
69
            if ($parameter->isDefaultValueAvailable()) {
70
                return $parameter->getDefaultValue();
71
            }
72
73
            /** @var ReflectionClass $reflectionClass */
74
            $reflectionClass = $parameter->getDeclaringClass();
75
            throw DependencyInvalidArgumentException::unableToResolve($paramTypeName, $reflectionClass->getName());
76
        }
77
78
        /** @var mixed $mappedClass */
79 4
        $mappedClass = $this->mappingInterfaces[$paramTypeName] ?? null;
80 4
        if (is_callable($mappedClass)) {
81 1
            return $mappedClass();
82
        }
83
84 4
        if (is_object($mappedClass)) {
85 3
            return $mappedClass;
86
        }
87
88 1
        $reflection = $this->resolveReflectionClass($paramTypeName);
89 1
        $constructor = $reflection->getConstructor();
90 1
        if (!$constructor) {
91 1
            return $reflection->newInstance();
92
        }
93
94 1
        return $this->resolveInnerDependencies($constructor, $reflection);
95
    }
96
97 4
    private function isScalar(string $paramTypeName): bool
98
    {
99 4
        return !class_exists($paramTypeName)
100 4
            && !interface_exists($paramTypeName);
101
    }
102
103
    /**
104
     * @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...
105
     */
106 1
    private function resolveReflectionClass(string $paramTypeName): ReflectionClass
107
    {
108 1
        $reflection = new ReflectionClass($paramTypeName);
109
110 1
        if ($reflection->isInstantiable()) {
111 1
            return $reflection;
112
        }
113
114
        /** @var mixed $concreteClass */
115 1
        $concreteClass = $this->mappingInterfaces[$reflection->getName()];
116
117 1
        if ($concreteClass !== null) {
118
            /** @var class-string $concreteClass */
119 1
            return new ReflectionClass($concreteClass);
120
        }
121
122
        throw DependencyNotFoundException::mapNotFoundForClassName($reflection->getName());
123
    }
124
125 1
    private function resolveInnerDependencies(ReflectionMethod $constructor, ReflectionClass $reflection): object
126
    {
127
        /** @var list<mixed> $innerDependencies */
128 1
        $innerDependencies = [];
129
130 1
        foreach ($constructor->getParameters() as $constructorParameter) {
131 1
            $paramType = $constructorParameter->getType();
132 1
            if ($paramType) {
133
                /** @psalm-suppress MixedAssignment */
134 1
                $innerDependencies[] = $this->resolveDependenciesRecursively($constructorParameter);
135
            }
136
        }
137
138 1
        return $reflection->newInstanceArgs($innerDependencies);
139
    }
140
}
141