1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
/* |
6
|
|
|
* This file is part of DivineNii opensource projects. |
7
|
|
|
* |
8
|
|
|
* PHP version 7.4 and above required |
9
|
|
|
* |
10
|
|
|
* @author Divine Niiquaye Ibok <[email protected]> |
11
|
|
|
* @copyright 2021 DivineNii (https://divinenii.com/) |
12
|
|
|
* @license https://opensource.org/licenses/BSD-3-Clause License |
13
|
|
|
* |
14
|
|
|
* For the full copyright and license information, please view the LICENSE |
15
|
|
|
* file that was distributed with this source code. |
16
|
|
|
*/ |
17
|
|
|
|
18
|
|
|
namespace Rade\DI\Traits; |
19
|
|
|
|
20
|
|
|
use Nette\Utils\Callback; |
21
|
|
|
use Nette\Utils\Reflection; |
22
|
|
|
use Rade\DI\Exceptions\ContainerResolutionException; |
23
|
|
|
use Rade\DI\Resolvers\AutowireValueResolver; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* The autowiring service used in Container class. |
27
|
|
|
* |
28
|
|
|
* @author Divine Niiquaye Ibok <[email protected]> |
29
|
|
|
*/ |
30
|
|
|
trait AutowireTrait |
31
|
|
|
{ |
32
|
|
|
/** @var array<string,mixed> service name => instance */ |
33
|
|
|
private array $values = []; |
34
|
|
|
|
35
|
|
|
/** @var array<string,bool> service name => bool */ |
36
|
|
|
private array $loading = []; |
37
|
|
|
|
38
|
|
|
/** @var array<string,bool> service name => bool */ |
39
|
|
|
private array $frozen = []; |
40
|
|
|
|
41
|
|
|
/** @var array<string,bool> service name => bool */ |
42
|
|
|
private array $keys = []; |
43
|
|
|
|
44
|
|
|
/** @var string[] alias => service name */ |
45
|
|
|
protected array $aliases = []; |
46
|
|
|
|
47
|
|
|
/** @var array[] tag name => service name => tag value */ |
48
|
|
|
protected array $tags = []; |
49
|
|
|
|
50
|
|
|
/** @var array<string,mixed> service name => instance */ |
51
|
|
|
private array $factories = []; |
52
|
|
|
|
53
|
|
|
/** @var array<string,mixed> service name => instance */ |
54
|
|
|
private array $raw = []; |
55
|
|
|
|
56
|
|
|
private AutowireValueResolver $resolver; |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* Creates new instance from class string or callable using autowiring. |
60
|
|
|
* |
61
|
|
|
* @param string|callable|object $callback |
62
|
|
|
* @param array<int|string,mixed> $args |
63
|
|
|
* |
64
|
|
|
* @throws ContainerResolutionException |
65
|
|
|
* |
66
|
|
|
* @return mixed |
67
|
|
|
*/ |
68
|
44 |
|
public function call($callback, array $args = []) |
69
|
|
|
{ |
70
|
|
|
try { |
71
|
|
|
/** @var callable $callback */ |
72
|
44 |
|
$callable = Callback::toReflection($callback); |
73
|
17 |
|
} catch (\ReflectionException $e) { |
74
|
17 |
|
if (\is_string($callback)) { |
75
|
16 |
|
return $this->autowireClass($callback, $args); |
76
|
|
|
} |
77
|
|
|
|
78
|
1 |
|
throw new ContainerResolutionException($e->getMessage()); |
79
|
|
|
} |
80
|
|
|
|
81
|
43 |
|
return $callback(...$this->autowireArguments($callable, $args)); |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* Add classes/interfaces for autowiring a service. |
86
|
|
|
* |
87
|
|
|
* @param string $id The registered service id |
88
|
|
|
* @param string[] $types |
89
|
|
|
* |
90
|
|
|
* @return static |
91
|
|
|
*/ |
92
|
1 |
|
public function autowire(string $id, array $types) |
93
|
|
|
{ |
94
|
1 |
|
if (!$this->offsetExists($id)) { |
|
|
|
|
95
|
1 |
|
throw new ContainerResolutionException("Service id '{$id}' is not found in container"); |
96
|
|
|
} |
97
|
|
|
|
98
|
1 |
|
$this->resolver->autowire($id, $types); |
99
|
|
|
|
100
|
1 |
|
return $this; |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
/** |
104
|
|
|
* Add a class or interface that should be excluded from autowiring. |
105
|
|
|
* |
106
|
|
|
* @param string ...$types |
107
|
|
|
*/ |
108
|
2 |
|
public function exclude(string ...$types): void |
109
|
|
|
{ |
110
|
2 |
|
foreach ($types as $type) { |
111
|
2 |
|
$this->resolver->exclude($type); |
112
|
|
|
} |
113
|
2 |
|
} |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* @param mixed $definition |
117
|
|
|
* |
118
|
|
|
* @return mixed |
119
|
|
|
*/ |
120
|
44 |
|
private function autowireService(string $id, $definition) |
121
|
|
|
{ |
122
|
|
|
try { |
123
|
44 |
|
$types = Reflection::getReturnTypes(Callback::toReflection($definition)); |
124
|
19 |
|
} catch (\ReflectionException $e) { |
125
|
19 |
|
$types = [\get_class($definition)]; |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
// Resolving wiring so we could call the service parent classes and interfaces. |
129
|
44 |
|
$this->resolver->autowire($id, $types); |
130
|
|
|
|
131
|
44 |
|
return $definition; |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* Resolves arguments for callable |
136
|
|
|
* |
137
|
|
|
* @param \ReflectionFunctionAbstract $function |
138
|
|
|
* @param array<int|string,mixed> $args |
139
|
|
|
* |
140
|
|
|
* @return array<int,mixed> |
141
|
|
|
*/ |
142
|
47 |
|
private function autowireArguments(\ReflectionFunctionAbstract $function, array $args = []): array |
143
|
|
|
{ |
144
|
47 |
|
$resolvedParameters = []; |
145
|
47 |
|
$reflectionParameters = $function->getParameters(); |
146
|
|
|
|
147
|
47 |
|
foreach ($reflectionParameters as $parameter) { |
148
|
33 |
|
$position = $parameter->getPosition(); |
149
|
33 |
|
$resolved = $this->resolver->resolve($parameter, $args); |
150
|
|
|
|
151
|
27 |
|
if ($parameter->isVariadic() && (\is_array($resolved) && \count($resolved) > 1)) { |
152
|
2 |
|
foreach (\array_chunk($resolved, 1) as $index => [$value]) { |
153
|
2 |
|
$resolvedParameters[$index + 1] = $value; |
154
|
|
|
} |
155
|
|
|
|
156
|
2 |
|
continue; |
157
|
|
|
} |
158
|
|
|
|
159
|
27 |
|
$resolvedParameters[$position] = $resolved; |
160
|
|
|
|
161
|
27 |
|
if (empty(\array_diff_key($reflectionParameters, $resolvedParameters))) { |
162
|
|
|
// Stop traversing: all parameters are resolved |
163
|
26 |
|
return $resolvedParameters; |
164
|
|
|
} |
165
|
|
|
} |
166
|
|
|
|
167
|
18 |
|
return $resolvedParameters; |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
/** |
171
|
|
|
* @param string $class |
172
|
|
|
* @param array<int|string,mixed> $args |
173
|
|
|
* |
174
|
|
|
* @return object |
175
|
|
|
*/ |
176
|
22 |
|
private function autowireClass(string $class, array $args) |
177
|
|
|
{ |
178
|
|
|
/** @var class-string $class */ |
179
|
22 |
|
$reflection = new \ReflectionClass($class); |
180
|
|
|
|
181
|
22 |
|
if (!$reflection->isInstantiable()) { |
182
|
2 |
|
throw new ContainerResolutionException("Class $class is not instantiable."); |
183
|
|
|
} |
184
|
|
|
|
185
|
22 |
|
if (null !== $constructor = $reflection->getConstructor()) { |
186
|
19 |
|
return $reflection->newInstanceArgs($this->autowireArguments($constructor, $args)); |
187
|
|
|
} |
188
|
|
|
|
189
|
9 |
|
if (!empty($args)) { |
190
|
1 |
|
throw new ContainerResolutionException("Unable to pass arguments, class $class has no constructor.", \strlen($class)); |
191
|
|
|
} |
192
|
|
|
|
193
|
8 |
|
return new $class(); |
194
|
|
|
} |
195
|
|
|
} |
196
|
|
|
|