Passed
Push — master ( f3c96f...ef615e )
by Sergei
03:08 queued 01:07
created

DefinitionExtractor   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 116
Duplicated Lines 0 %

Test Coverage

Coverage 82.22%

Importance

Changes 0
Metric Value
wmc 21
eloc 41
c 0
b 0
f 0
dl 0
loc 116
ccs 37
cts 45
cp 0.8222
rs 10

6 Methods

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