1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
|
<?php /** * Resolves ids (string, class names or mixed values) to values with auto-wiring. * * @package lucatume\DI52\Builders */
namespace lucatume\DI52\Builders;
use lucatume\DI52\NotFoundException;
/** * Class Resolver * * @package lucatume\DI52\Builders */ class Resolver { /** * A map from ids bound in the container to their builder or resolved value. * * @var array<string,BuilderInterface|mixed> */ protected $bindings = [];
/** * A flag property to indicate whether implicit bindings, those discovered during auto-wiring resolution, should * be bound as prototype or singleton bindings. * * @var bool */ protected $resolveUnboundAsSingletons = false;
/** * A map from ids bound in the container to their singleton nature. * * @var array<string,bool> */ protected $singletons = [];
/** * A map of when-needs-give specifications. * @var array<string,array<string,BuilderInterface>> */ protected $whenNeedsGive = []; /** * The current build line, a list from the trunk to the leaf of the current resolution. * * @var array<string> */ protected $buildLine = [];
/** * Resolver constructor. * * @param false $resolveUnboundAsSingletons Whether implicit bindings, those discovered during auto-wiring * resolution, should be bound as prototype or singleton bindings. */ public function __construct($resolveUnboundAsSingletons = false) { $this->resolveUnboundAsSingletons = $resolveUnboundAsSingletons; }
/** * Binds an implementation for an id, or class name, as prototype (build new each time). * * @param string|class-string $id The id to register the implementation for. * @param BuilderInterface $implementation The builder that will provide the implementation for the id. * * @return void This method does not return any value. */ public function bind($id, BuilderInterface $implementation) { unset($this->singletons[$id]); $this->bindings[$id] = $implementation; }
/** * Registers an implementation for an id, or class name, as singleton (build at most once). * * @param string|class-string $id The id to register the implementation for. * @param BuilderInterface $implementation The builder that will provide the implementation for * the id. * * @return void This method does not return any value. */ public function singleton($id, BuilderInterface $implementation) { $this->singletons[$id] = true; $this->bindings[$id] = $implementation; }
/** * Returns whether an implementation was registered for the id in the resolver or not. * * @param string $id The id to check the implementation for. * * @return bool Whether an implementation was registered for the id in the resolver or not. */ public function isBound($id) { return isset($this->bindings[$id]); }
/** * Removes the relation between an id and a bound implementation from the resolver. * * @param string|class-string $id The id to unregister the implementation for. * * @return void This method does not return any value. */ public function unbind($id) { unset($this->bindings[$id]); }
/** * Returns whether a specific id is bound as singleton (build at most once), or not. * * @param string|class-string $id The id to check. * * @return bool Whether a specific id is bound as singleton (build at most once), or not. */ public function isSingleton($id) { return isset($this->singletons[$id]); }
/** * Transform the canonical class to the class part of a when-needs-give specification, if required. * * @param string|class-string $id The ID to resolve the when-needs-give case for. * @param string $paramClass The class of the parameter to solve the when-needs-give case for. * * @return BuilderInterface|string Either the builder for the when-needs-give replacement, or the input parameter * class if not found. */ public function whenNeedsGive($id, $paramClass) { return isset($this->whenNeedsGive[$id][$paramClass]) ? $this->whenNeedsGive[$id][$paramClass] : $paramClass; }
/** * Sets an entry in the when->needs->give chain. * * @param string|class-string $whenClass The "when" part of the chain, a class name or id. * @param string|class-string $needsClass The "needs" part of the chain, a class name or id. * @param BuilderInterface $builder The Builder instance that should be returned when a class needs the * specified id. * * @return void This method does not return any value. */ public function setWhenNeedsGive($whenClass, $needsClass, BuilderInterface $builder) { $this->whenNeedsGive[$whenClass][$needsClass] = $builder; }
/** * Resolves an ide to an implementation with the input arguments. * * @param string|class-string|mixed $id The id, class name or built value to resolve. * @param string[]|null $afterBuildMethods A list of methods that should run on the built * instance. * @param mixed ...$buildArgs A set of build arguments that will be passed to * the implementation constructor. * * @return BuilderInterface|ReinitializableBuilderInterface|mixed The builder, set up to use the specified set of * build arguments. * @throws NotFoundException If the id is a string that does not resolve to an existing, concrete, class. */ public function resolveWithArgs($id, array $afterBuildMethods = null, ...$buildArgs) { if (! is_string($id)) { return $id; }
if (empty($afterBuildMethods) && empty($buildArgs)) { return $this->resolve($id); } return $this->cloneBuilder($id, $afterBuildMethods, ...$buildArgs)->build(); }
/** * Resolves an id or input value to a value or object instance. * * @template T * * @param string|class-string<T>|mixed $id Either the id of a bound implementation, a class name or an * object to resolve. * @param string[]|null $buildLine The build line to append the resolution leafs to, or `null` to * use the current one. * * @return T|mixed The resolved value or instance. * @phpstan-return ($id is class-string ? T : mixed) * * @throws NotFoundException If the id is a string that is not bound and is not an existing, concrete, class. */ public function resolve($id, array $buildLine = null) { if ($buildLine !== null) { $this->buildLine = $buildLine; }
if (! is_string($id)) { return $id; }
if (!isset($this->bindings[$id])) { return $this->resolveUnbound($id); }
if ($this->bindings[$id] instanceof BuilderInterface) { $built = $this->resolveBound($id); } else { $built = $this->bindings[$id]; }
return $built; }
/** * Builds, with auto-wiring, an instance of a not bound class. * * @param string|class-string $id The class name to build an instance of. * * @return object The built class instance. * * @throws NotFoundException If the id cannot be resolved to an existing, concrete class. */ private function resolveUnbound($id) { $built = (new ClassBuilder($id, $this, $id))->build();
if ($this->resolveUnboundAsSingletons) { $this->singletons[$id] = true; $this->bindings[$id] = $built; }
return $built; }
/** * Resolves a bound implementation to a value or object. * * @param string|class-string $id The id to resolve the implementation for. * * @return mixed The resolved instance. */ private function resolveBound($id) { // @phpstan-ignore-next-line $built = $this->bindings[$id]->build(); if (isset($this->singletons[$id])) { $this->bindings[$id] = $built; } return $built; }
/** * Clones the builder assigned to an id and re-initializes it. * The clone operation leverages the already resolved dependencies of a builder to create an up-to-date instance. * * @param string|class-string $id The id to clone the builder of. * @param string[]|null $afterBuildMethods A set of methods to run on the built instance. * @param mixed ...$buildArgs An optional set of arguments that will be passed to the instance * constructor. * * @return BuilderInterface A new instance of the builder currently related to the id. * @throws NotFoundException If trying to clone the builder for a non existing id or an id that does not map to a * concrete class name. */ private function cloneBuilder($id, array $afterBuildMethods = null, ...$buildArgs) { if (isset($this->bindings[$id]) && $this->bindings[$id] instanceof BuilderInterface) { $builder = clone $this->bindings[$id]; if ($builder instanceof ReinitializableBuilderInterface) { $builder->reinit($afterBuildMethods, ...$buildArgs); } } else { $builder = new ClassBuilder($id, $this, $id, $afterBuildMethods, ...$buildArgs); }
return $builder; }
/** * Adds an entry to the build line. * * @param string $type The type of parameter the Resolver is currently attempting to resolve. * @param string $parameterName The name of the parameter in the method signature, if any. * * @return void This method does not return any value. */ public function addToBuildLine($type, $parameterName) { $this->buildLine[] = trim("$type \$$parameterName"); }
/** * Returns the current build line. * * The build line will return a straight path from the current resolution root to the leaf * currently being resolved. Used for error logging and formatting. * * @return string[] A set of consecutive items the resolver is currently trying to build. */ public function getBuildLine() { return $this->buildLine; }
/** * Removes the last element from the build line, if any. * * @return void The method does not return any value. */ public function buildLinePop() { array_pop($this->buildLine); } }
|