Passed
Push — master ( 7a62dc...2dc019 )
by Alexander
02:08
created

DefinitionExtractor   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 107
Duplicated Lines 0 %

Test Coverage

Coverage 75%

Importance

Changes 0
Metric Value
wmc 19
eloc 37
dl 0
loc 107
c 0
b 0
f 0
ccs 30
cts 40
cp 0.75
rs 10

5 Methods

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