Passed
Pull Request — master (#93)
by Sergei
04:47 queued 02:36
created

DefinitionExtractor::fromCallable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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