Passed
Pull Request — master (#8)
by Sergei
02:40
created

DefinitionExtractor::fromClassName()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 8
c 1
b 0
f 0
nc 4
nop 1
dl 0
loc 14
ccs 8
cts 8
cp 1
crap 4
rs 10
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 1
    private function __construct()
31
    {
32 1
    }
33
34 16
    public static function getInstance(): self
35
    {
36 16
        if (self::$instance === null) {
37 1
            self::$instance = new self();
38
        }
39
40 16
        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...
41
    }
42
43
    /**
44
     * @psalm-param class-string $class
45
     *
46
     * @throws NotFoundException
47
     * @throws NotInstantiableException
48
     *
49
     * @return DefinitionInterface[]
50
     * @psalm-return array<string, DefinitionInterface>
51
     */
52 12
    public function fromClassName(string $class): array
53
    {
54
        try {
55 12
            $reflectionClass = new ReflectionClass($class);
56 1
        } catch (ReflectionException $e) {
57 1
            throw new NotFoundException($class);
58
        }
59
60 11
        if (!$reflectionClass->isInstantiable()) {
61 1
            throw new NotInstantiableClassException($class);
62
        }
63
64 10
        $constructor = $reflectionClass->getConstructor();
65 10
        return $constructor === null ? [] : $this->fromFunction($constructor);
66
    }
67
68
    /**
69
     * @return DefinitionInterface[]
70
     * @psalm-return array<string, DefinitionInterface>
71
     */
72 14
    public function fromFunction(ReflectionFunctionAbstract $reflectionFunction): array
73
    {
74 14
        $result = [];
75 14
        foreach ($reflectionFunction->getParameters() as $parameter) {
76 14
            $result[$parameter->getName()] = $this->fromParameter($parameter);
77
        }
78 14
        return $result;
79
    }
80
81 14
    private function fromParameter(ReflectionParameter $parameter): DefinitionInterface
82
    {
83 14
        $type = $parameter->getType();
84
85 14
        if ($type === null || $parameter->isVariadic()) {
86 2
            return new ParameterDefinition($parameter);
87
        }
88
89
        // PHP 8 union type is used as type hint
90
        /** @psalm-suppress UndefinedClass, TypeDoesNotContainType */
91 13
        if ($type instanceof ReflectionUnionType) {
92
            $types = [];
93
            /** @var ReflectionNamedType $unionType */
94
            foreach ($type->getTypes() as $unionType) {
95
                if (!$unionType->isBuiltin()) {
96
                    $typeName = $unionType->getName();
97
                    if ($typeName === 'self') {
98
                        // If type name is "self", it means that called class and
99
                        // $parameter->getDeclaringClass() returned instance of `ReflectionClass`.
100
                        /** @psalm-suppress PossiblyNullReference */
101
                        $typeName = $parameter->getDeclaringClass()->getName();
102
                    }
103
104
                    $types[] = $typeName;
105
                }
106
            }
107
108
            if ($types === []) {
109
                return new ParameterDefinition($parameter);
110
            }
111
112
            /** @psalm-suppress MixedArgument */
113
            return new ClassDefinition(implode('|', $types), $type->allowsNull());
114
        }
115
116
        /** @var ReflectionNamedType $type */
117
118
        // Our parameter has a class type hint
119 13
        if (!$type->isBuiltin()) {
120 10
            $typeName = $type->getName();
121 10
            if ($typeName === 'self') {
122
                // If type name is "self", it means that called class and
123
                // $parameter->getDeclaringClass() returned instance of `ReflectionClass`.
124
                /** @psalm-suppress PossiblyNullReference */
125 1
                $typeName = $parameter->getDeclaringClass()->getName();
126
            }
127
128 10
            return new ClassDefinition($typeName, $type->allowsNull());
129
        }
130
131
        // Our parameter does have a built-in type hint
132 4
        return new ParameterDefinition($parameter);
133
    }
134
}
135