Passed
Pull Request — master (#85)
by Sergei
02:04
created

DefinitionExtractor::fromClassName()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3.0416

Importance

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