Passed
Push — master ( 2b04c5...3174fa )
by Jesús
01:25 queued 12s
created

DependencyResolver   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 107
Duplicated Lines 0 %

Test Coverage

Coverage 77.27%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 42
c 2
b 0
f 0
dl 0
loc 107
ccs 34
cts 44
cp 0.7727
rs 10
wmc 16

4 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A findConcreteClassThatImplements() 0 16 2
B resolveDependenciesRecursively() 0 48 9
A resolveDependencies() 0 19 4
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Gacela\Framework\ClassResolver\DependencyResolver;
6
7
use Gacela\Framework\Config\GacelaFileConfig\GacelaConfigFileInterface;
8
use ReflectionClass;
9
use ReflectionNamedType;
10
use ReflectionParameter;
11
use RuntimeException;
12
use function is_callable;
13
14
final class DependencyResolver
15
{
16
    private GacelaConfigFileInterface $gacelaConfigFile;
17
18 14
    public function __construct(GacelaConfigFileInterface $gacelaConfigFile)
19
    {
20 14
        $this->gacelaConfigFile = $gacelaConfigFile;
21 14
    }
22
23
    /**
24
     * @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...
25
     *
26
     * @return list<mixed>
27
     */
28 14
    public function resolveDependencies(string $resolvedClassName): array
29
    {
30 14
        $reflection = new ReflectionClass($resolvedClassName);
31 14
        $constructor = $reflection->getConstructor();
32 14
        if (!$constructor) {
33 13
            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...
34
        }
35
36
        /** @var list<mixed> $dependencies */
37 1
        $dependencies = [];
38 1
        foreach ($constructor->getParameters() as $parameter) {
39 1
            $paramType = $parameter->getType();
40 1
            if ($paramType) {
41
                /** @psalm-suppress MixedAssignment */
42 1
                $dependencies[] = $this->resolveDependenciesRecursively($parameter);
43
            }
44
        }
45
46 1
        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...
47
    }
48
49
    /**
50
     * @return mixed
51
     */
52 1
    private function resolveDependenciesRecursively(ReflectionParameter $parameter)
53
    {
54
        /** @var ReflectionNamedType $paramType */
55 1
        $paramType = $parameter->getType();
56 1
        $type = $paramType->getName();
57
58 1
        if (!class_exists($type) && !interface_exists($type)) {
59
            return $parameter->getDefaultValue();
60
        }
61
62 1
        $reflection = new ReflectionClass($type);
63
64
        // If it's an interface we need to figure out which concrete class do we want to use
65 1
        if ($reflection->isInterface()) {
66 1
            $gacelaFileDependencies = $this->gacelaConfigFile->dependencies();
67 1
            $concreteClass = $gacelaFileDependencies[$reflection->getName()] ?? '';
68
            // a callable will be a way to bypass the instantiation and instead
69
            // use the result from the callable that was defined in the gacela config file.
70 1
            if (is_callable($concreteClass)) {
71
                return $concreteClass();
72
            }
73
            // if at this point there is no concrete class found for the interface we can
74
            // try one more thing looking for 1 concrete class that implements this interface
75 1
            if (empty($concreteClass)) {
76
                $concreteClass = $this->findConcreteClassThatImplements($reflection);
77
            }
78
79
            /** @var class-string $concreteClass */
80 1
            $reflection = new ReflectionClass($concreteClass);
0 ignored issues
show
Bug introduced by
It seems like $concreteClass can also be of type callable; however, parameter $objectOrClass of ReflectionClass::__construct() does only seem to accept object|string, maybe add an additional type check? ( Ignorable by Annotation )

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

80
            $reflection = new ReflectionClass(/** @scrutinizer ignore-type */ $concreteClass);
Loading history...
81
        }
82
83 1
        $constructor = $reflection->getConstructor();
84 1
        if (!$constructor) {
85 1
            return $reflection->newInstance();
86
        }
87
88
        /** @var list<mixed> $innerDependencies */
89 1
        $innerDependencies = [];
90
91 1
        foreach ($constructor->getParameters() as $constructorParameter) {
92 1
            $paramType = $constructorParameter->getType();
93 1
            if ($paramType) {
94
                /** @psalm-suppress MixedAssignment */
95 1
                $innerDependencies[] = $this->resolveDependenciesRecursively($constructorParameter);
96
            }
97
        }
98
99 1
        return $reflection->newInstanceArgs($innerDependencies);
1 ignored issue
show
Bug introduced by
$innerDependencies of type Gacela\Framework\ClassRe...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

99
        return $reflection->newInstanceArgs(/** @scrutinizer ignore-type */ $innerDependencies);
Loading history...
100
    }
101
102
    /**
103
     * @return class-string|string
1 ignored issue
show
Documentation Bug introduced by
The doc comment class-string|string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string|string.
Loading history...
104
     */
105
    private function findConcreteClassThatImplements(ReflectionClass $interface): string
106
    {
107
        // TODO: Not implemented yet
108
109
        // Dummy solution: if the concrete class lives next to its interface, and with the same name.
110
        $concreteClass = str_replace('Interface', '', $interface->getName());
111
        if (!class_exists($concreteClass)) {
112
            $error = <<<TXT
113
No concrete class was found that implements:
114
{$interface->getName()}
115
Did you forget to map this interface to a concrete class in gacela.json using the 'dependencies' key?
116
TXT;
117
            throw new RuntimeException($error);
118
        }
119
120
        return $concreteClass;
121
    }
122
}
123