Passed
Push — master ( 0ab929...f14eca )
by Dmitriy
02:17
created

DefinitionExtractor   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 119
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 119
ccs 34
cts 44
cp 0.7727
rs 10
wmc 20

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 2 1
A getInstance() 0 7 2
A fromFunction() 0 7 2
B fromParameter() 0 52 10
A fromClassName() 0 21 5
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Definitions\Infrastructure;
6
7
use ReflectionClass;
8
use ReflectionException;
9
use ReflectionFunctionAbstract;
10
use ReflectionNamedType;
11
use ReflectionParameter;
12
use ReflectionUnionType;
13
use Yiisoft\Definitions\ClassDefinition;
14
use Yiisoft\Definitions\Contract\DefinitionInterface;
15
use Yiisoft\Definitions\Exception\NotFoundException;
16
use Yiisoft\Definitions\Exception\NotInstantiableClassException;
17
use Yiisoft\Definitions\Exception\NotInstantiableException;
18
use Yiisoft\Definitions\ParameterDefinition;
19
20
/**
21
 * This class resolves dependencies by using class type hints.
22
 * Note that service names need not match the parameter names, parameter names are ignored
23
 *
24
 * @internal
25
 */
26
final class DefinitionExtractor
27
{
28
    private static ?self $instance = null;
29
30
    /**
31
     * @psalm-var array<string, array<string, DefinitionInterface>>
32
     */
33
    private static array $dependencies = [];
34
35 1
    private function __construct()
36
    {
37 1
    }
38
39 33
    public static function getInstance(): self
40
    {
41 33
        if (self::$instance === null) {
42 1
            self::$instance = new self();
43
        }
44
45 33
        return self::$instance;
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::instance could return the type null which is incompatible with the type-hinted return Yiisoft\Definitions\Infr...ure\DefinitionExtractor. Consider adding an additional type-check to rule them out.
Loading history...
46
    }
47
48
    /**
49
     * @psalm-param class-string $class
50
     *
51
     * @throws NotFoundException
52
     * @throws NotInstantiableException
53
     *
54
     * @return DefinitionInterface[]
55
     * @psalm-return array<string, DefinitionInterface>
56
     */
57 29
    public function fromClassName(string $class): array
58
    {
59 29
        if (isset(self::$dependencies[$class])) {
60 17
            return self::$dependencies[$class];
61
        }
62
63
        try {
64 12
            $reflectionClass = new ReflectionClass($class);
65 1
        } catch (ReflectionException $e) {
66 1
            throw new NotFoundException($class);
67
        }
68
69 11
        if (!$reflectionClass->isInstantiable()) {
70 1
            throw new NotInstantiableClassException($class);
71
        }
72
73 10
        $constructor = $reflectionClass->getConstructor();
74 10
        $dependencies = $constructor === null ? [] : $this->fromFunction($constructor);
75 10
        self::$dependencies[$class] = $dependencies;
76
77 10
        return $dependencies;
78
    }
79
80
    /**
81
     * @return DefinitionInterface[]
82
     * @psalm-return array<string, DefinitionInterface>
83
     */
84 14
    public function fromFunction(ReflectionFunctionAbstract $reflectionFunction): array
85
    {
86 14
        $result = [];
87 14
        foreach ($reflectionFunction->getParameters() as $parameter) {
88 14
            $result[$parameter->getName()] = $this->fromParameter($parameter);
89
        }
90 14
        return $result;
91
    }
92
93 14
    private function fromParameter(ReflectionParameter $parameter): DefinitionInterface
94
    {
95 14
        $type = $parameter->getType();
96
97 14
        if ($type === null || $parameter->isVariadic()) {
98 2
            return new ParameterDefinition($parameter);
99
        }
100
101
        // PHP 8 union type is used as type hint
102
        /** @psalm-suppress UndefinedClass, TypeDoesNotContainType */
103 13
        if ($type instanceof ReflectionUnionType) {
104
            $types = [];
105
            /** @var ReflectionNamedType $unionType */
106
            foreach ($type->getTypes() as $unionType) {
107
                if (!$unionType->isBuiltin()) {
108
                    $typeName = $unionType->getName();
109
                    if ($typeName === 'self') {
110
                        // If type name is "self", it means that called class and
111
                        // $parameter->getDeclaringClass() returned instance of `ReflectionClass`.
112
                        /** @psalm-suppress PossiblyNullReference */
113
                        $typeName = $parameter->getDeclaringClass()->getName();
114
                    }
115
116
                    $types[] = $typeName;
117
                }
118
            }
119
120
            if ($types === []) {
121
                return new ParameterDefinition($parameter);
122
            }
123
124
            /** @psalm-suppress MixedArgument */
125
            return new ClassDefinition(implode('|', $types), $type->allowsNull());
126
        }
127
128
        /** @var ReflectionNamedType $type */
129
130
        // Our parameter has a class type hint
131 13
        if (!$type->isBuiltin()) {
132 10
            $typeName = $type->getName();
133 10
            if ($typeName === 'self') {
134
                // If type name is "self", it means that called class and
135
                // $parameter->getDeclaringClass() returned instance of `ReflectionClass`.
136
                /** @psalm-suppress PossiblyNullReference */
137 1
                $typeName = $parameter->getDeclaringClass()->getName();
138
            }
139
140 10
            return new ClassDefinition($typeName, $type->allowsNull());
141
        }
142
143
        // Our parameter does have a built-in type hint
144 4
        return new ParameterDefinition($parameter);
145
    }
146
}
147