Passed
Push — master ( db6fe4...d95098 )
by Mihail
09:49
created

DIReflector   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 122
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 9
Bugs 1 Features 1
Metric Value
wmc 24
eloc 58
c 9
b 1
f 1
dl 0
loc 122
ccs 51
cts 51
cp 1
rs 10

5 Methods

Rating   Name   Duplication   Size   Complexity  
A newInstance() 0 22 5
A newMethodFromCallable() 0 12 4
B getFromParameter() 0 38 9
A processMethodArguments() 0 10 2
A getFromParameterType() 0 17 4
1
<?php declare(strict_types=1);
2
3
/*
4
 * This file is part of the Koded package.
5
 *
6
 * (c) Mihail Binev <[email protected]>
7
 *
8
 * Please view the LICENSE distributed with this source code
9
 * for the full copyright and license information.
10
 *
11
 */
12
13
namespace Koded;
14
15
use Closure;
16
use ReflectionClass;
17
use ReflectionException;
18
use ReflectionFunction;
19
use ReflectionFunctionAbstract;
20
use ReflectionMethod;
21
use ReflectionParameter;
22
23
class DIReflector
24
{
25
    public function newInstance(
26 28
        DIContainerInterface $container,
27
        string $class,
28 28
        array $arguments
29 28
    ): object {
30
        try {
31 28
            $dependency = new ReflectionClass($class);
32 24
        } catch (ReflectionException $e) {
33 21
            throw DIException::forReflectionError($e);
34 22
        }
35
        $constructor = $dependency->getConstructor();
36
        if ($dependency->isInstantiable()) {
37 5
            return $constructor
38 1
                ? new $class(...$this->processMethodArguments($container, $constructor, $arguments))
39
                : new $class;
40
        }
41 4
        if (false === $constructor?->isPublic()) {
42 4
            throw DIException::forNonPublicMethod(
43
                $constructor->getDeclaringClass()->name,
44
                $constructor->name);
45
        }
46
        throw DIException::cannotInstantiate($dependency);
47
    }
48
49
    /**
50
     * @param DIContainerInterface       $container
51
     * @param ReflectionFunctionAbstract $method
52
     * @param array                      $arguments
53 23
     * @return array
54
     */
55
    public function processMethodArguments(
56
        DIContainerInterface $container,
57
        ReflectionFunctionAbstract $method,
58
        array $arguments
59 23
    ): array {
60 1
        $args = $method->getParameters();
61 1
        foreach ($args as $i => $param) {
62
            $args[$i] = $this->getFromParameterType($container, $param, $arguments[$i] ?? null);
63
        }
64 23
        return $args;
65
    }
66
67
    /**
68 23
     * @param callable $callable
69 1
     *
70
     * @return ReflectionFunctionAbstract
71
     * @throws ReflectionException
72 23
     */
73 18
    public function newMethodFromCallable(callable $callable): ReflectionFunctionAbstract
74 7
    {
75
        switch (\gettype($callable)) {
76 16
            case 'array':
77
                return new ReflectionMethod(...$callable);
0 ignored issues
show
Bug introduced by
$callable is expanded, but the parameter $objectOrMethod of ReflectionMethod::__construct() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

77
                return new ReflectionMethod(/** @scrutinizer ignore-type */ ...$callable);
Loading history...
78
            case 'object':
79 20
                if ($callable instanceof Closure) {
80
                    return new ReflectionFunction($callable);
81
                }
82
                return (new ReflectionClass($callable))->getMethod('__invoke');
83
            default:
84
                return new ReflectionFunction($callable);
85
        }
86
    }
87
88 6
    protected function getFromParameterType(
89
        DIContainerInterface $container,
90 6
        ReflectionParameter $parameter,
91 6
        mixed $value
92 6
    ): mixed {
93
        if (!$class = $parameter->getType()) {
94 1
            return $arguments[$parameter->getPosition()]
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $arguments seems to never exist and therefore isset should always be false.
Loading history...
95 1
                ?? $this->getFromParameter($container, $parameter, $value);
96 1
        }
97
        // Global parameter overriding / singleton instance?
98 1
        if (null !== $param = $this->getFromParameter($container, $parameter, $value)) {
99
            return $param;
100
        }
101 1
        if ($parameter->isDefaultValueAvailable()) {
102
            return $parameter->getDefaultValue();
103
        }
104
        return $container->new($class->getName());
0 ignored issues
show
Bug introduced by
The method getName() does not exist on ReflectionType. It seems like you code against a sub-type of ReflectionType such as ReflectionNamedType. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

104
        return $container->new($class->/** @scrutinizer ignore-call */ getName());
Loading history...
105 16
    }
106
107
    protected function getFromParameter(
108
        DIContainerInterface $container,
109
        ReflectionParameter $parameter,
110 16
        mixed $value
111 4
    ): mixed {
112 4
        $storage = $container->getStorage();
113
        try {
114
            $type = ($parameter->getType() ?? $parameter)->getName();
115
        } catch (\Error) {
116 15
            // i.e. for ReflectionUnionType, continue with processing
117 6
            return $value;
118
        }
119
120 12
        if (isset($storage[DIContainer::BINDINGS][$type])) {
121 1
            $type = $storage[DIContainer::BINDINGS][$type];
122
        }
123
        if (isset($storage[DIContainer::EXCLUDE][$type])) {
124 11
            if (\array_intersect(
125
                $storage[DIContainer::EXCLUDE][$type],
126
                \array_keys($storage[DIContainer::SINGLETONS])
127 16
            )) {
128
                return (clone $container)->new($type);
129 16
            }
130 16
        }
131
        if (isset($storage[DIContainer::SINGLETONS][$type])) {
132 16
            return $storage[DIContainer::SINGLETONS][$type];
133 10
        }
134
        if (isset($storage[DIContainer::NAMED]['$' . $parameter->name])) {
135
            return $storage[DIContainer::NAMED]['$' . $parameter->name];
136 16
        }
137 1
        try {
138 1
            return $value ?? $parameter?->getDefaultValue();
139
        } catch (\ReflectionException $e) {
140
            if ($parameter->getType()?->isBuiltin()) {
141
                throw DIException::forMissingArgument($type, $parameter, $e);
142 16
            }
143 2
        }
144
        return null;
145
    }
146
}
147