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 Error; |
||
17 | use ReflectionClass; |
||
18 | use ReflectionException; |
||
19 | use ReflectionFunction; |
||
20 | use ReflectionFunctionAbstract; |
||
21 | use ReflectionMethod; |
||
22 | use ReflectionParameter; |
||
23 | use function array_intersect; |
||
24 | use function array_keys; |
||
25 | use function gettype; |
||
26 | |||
27 | class DIReflector |
||
28 | { |
||
29 | 33 | public function newInstance( |
|
30 | IContainer $container, |
||
31 | string $class, |
||
32 | array $arguments): object |
||
33 | { |
||
34 | try { |
||
35 | 33 | $dependency = new ReflectionClass($class); |
|
36 | 1 | } catch (ReflectionException $e) { |
|
37 | 1 | throw DIException::forReflectionError($e); |
|
38 | } |
||
39 | 32 | $constructor = $dependency->getConstructor(); |
|
40 | 32 | if ($dependency->isInstantiable()) { |
|
41 | 27 | return $constructor |
|
42 | 24 | ? new $class(...$this->processMethodArguments($container, $constructor, $arguments)) |
|
43 | 25 | : new $class; |
|
44 | } |
||
45 | 6 | if (false === $constructor?->isPublic()) { |
|
46 | 1 | throw DIException::forNonPublicMethod( |
|
47 | 1 | $constructor->getDeclaringClass()->name, |
|
48 | 1 | $constructor->name); |
|
49 | } |
||
50 | 5 | throw DIException::cannotInstantiate($dependency); |
|
51 | } |
||
52 | |||
53 | /** |
||
54 | * @param IContainer $container |
||
55 | * @param ReflectionFunctionAbstract $method |
||
56 | * @param array $arguments |
||
57 | * @return array |
||
58 | */ |
||
59 | 26 | public function processMethodArguments( |
|
60 | IContainer $container, |
||
61 | ReflectionFunctionAbstract $method, |
||
62 | array $arguments): array |
||
63 | { |
||
64 | 26 | $args = $method->getParameters(); |
|
65 | 26 | foreach ($args as $i => $param) { |
|
66 | 21 | $args[$i] = $this->getFromParameterType($container, $param, $arguments[$i] ?? null); |
|
67 | } |
||
68 | 23 | return $args; |
|
69 | } |
||
70 | |||
71 | /** |
||
72 | * @param callable $callable |
||
73 | * @return ReflectionFunctionAbstract |
||
74 | * @throws ReflectionException |
||
75 | */ |
||
76 | 7 | public function newMethodFromCallable(callable $callable): ReflectionFunctionAbstract |
|
77 | { |
||
78 | 7 | return match (gettype($callable)) { |
|
79 | 6 | 'array' => new ReflectionMethod(...$callable), |
|
80 | 2 | 'object' => $callable instanceof Closure |
|
81 | 2 | ? new ReflectionFunction($callable) |
|
82 | 2 | : (new ReflectionClass($callable))->getMethod('__invoke'), |
|
83 | 7 | default => new ReflectionFunction($callable) |
|
84 | }; |
||
85 | } |
||
86 | |||
87 | 21 | protected function getFromParameterType( |
|
88 | IContainer $container, |
||
89 | ReflectionParameter $parameter, |
||
90 | mixed $value): mixed |
||
91 | { |
||
92 | 21 | if (!$class = $parameter->getType()) { |
|
93 | 2 | return $arguments[$parameter->getPosition()] |
|
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
![]() |
|||
94 | 2 | ?? $this->getFromParameter($container, $parameter, $value); |
|
95 | } |
||
96 | // Global parameter overriding / singleton instance? |
||
97 | 21 | if (null !== $param = $this->getFromParameter($container, $parameter, $value)) { |
|
98 | 11 | return $param; |
|
99 | } |
||
100 | 15 | if ($parameter->isDefaultValueAvailable()) { |
|
101 | 3 | return $parameter->getDefaultValue(); |
|
102 | } |
||
103 | 14 | return $container->new($class->getName()); |
|
104 | } |
||
105 | |||
106 | 21 | protected function getFromParameter( |
|
107 | IContainer $container, |
||
108 | ReflectionParameter $parameter, |
||
109 | mixed $value): mixed |
||
110 | { |
||
111 | try { |
||
112 | 21 | $type = ($parameter->getType() ?? $parameter)->getName(); |
|
113 | 1 | } catch (Error) { |
|
114 | // i.e. for ReflectionUnionType, continue with processing |
||
115 | 1 | return $value; |
|
116 | } |
||
117 | |||
118 | 21 | $type = $container->getBinding($type); |
|
119 | 21 | if ($_ = $container->getFromStorage(DIStorage::EXCLUDE, $type)) { |
|
120 | 1 | if (array_intersect($_, array_keys($container->getFromStorage(DIStorage::SINGLETONS)))) { |
|
121 | 1 | return (clone $container)->new($type); |
|
122 | } |
||
123 | } |
||
124 | 21 | if ($_ = $container->getFromStorage(DIStorage::SINGLETONS, $type)) { |
|
125 | 2 | return $_; |
|
126 | } |
||
127 | 21 | if ($_ = $container->getFromStorage(DIStorage::NAMED, $parameter->name)) { |
|
128 | 4 | return $_; |
|
129 | } |
||
130 | |||
131 | try { |
||
132 | 19 | return $value ?? $parameter->getDefaultValue(); |
|
133 | 16 | } catch (ReflectionException $e) { |
|
134 | 16 | if ($parameter->getType()?->isBuiltin()) { |
|
135 | 1 | throw DIException::forMissingArgument($type, $parameter, $e); |
|
136 | } |
||
137 | } |
||
138 | 15 | return null; |
|
139 | } |
||
140 | } |
||
141 |