Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
78 / 78 |
|
100.00% |
25 / 25 |
CRAP | |
100.00% |
1 / 1 |
Locator | |
100.00% |
78 / 78 |
|
100.00% |
25 / 25 |
50 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
4 | |||
setServices | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
set | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
5 | |||
get | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
5 | |||
getCall | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
hasParams | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
getParams | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setCall | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
setParams | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
addParam | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
removeParam | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
5 | |||
isAvailable | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isLoaded | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
reload | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
remove | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
__set | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
__get | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
__isset | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
__unset | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
offsetSet | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
offsetGet | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
offsetExists | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
offsetUnset | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
count | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getIterator | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | /** |
3 | * Pop PHP Framework (https://www.popphp.org/) |
4 | * |
5 | * @link https://github.com/popphp/popphp-framework |
6 | * @author Nick Sagona, III <dev@noladev.com> |
7 | * @copyright Copyright (c) 2009-2025 NOLA Interactive, LLC. |
8 | * @license https://www.popphp.org/license New BSD License |
9 | */ |
10 | |
11 | /** |
12 | * @namespace |
13 | */ |
14 | namespace Pop\Service; |
15 | |
16 | use Pop\Utils\CallableObject; |
17 | use ArrayAccess; |
18 | use ArrayIterator; |
19 | use Countable; |
20 | use IteratorAggregate; |
21 | |
22 | /** |
23 | * Service locator class |
24 | * |
25 | * @category Pop |
26 | * @package Pop\Service |
27 | * @author Nick Sagona, III <dev@noladev.com> |
28 | * @copyright Copyright (c) 2009-2025 NOLA Interactive, LLC. |
29 | * @license https://www.popphp.org/license New BSD License |
30 | * @version 4.3.5 |
31 | */ |
32 | class Locator implements ArrayAccess, Countable, IteratorAggregate |
33 | { |
34 | |
35 | /** |
36 | * Recursion depth level tracker |
37 | * @var int |
38 | */ |
39 | private static int $depth = 0; |
40 | |
41 | /** |
42 | * Recursion called service name tracker |
43 | * @var array |
44 | */ |
45 | private static array $called = []; |
46 | |
47 | /** |
48 | * Services |
49 | * @var array |
50 | */ |
51 | protected array $services = []; |
52 | |
53 | /** |
54 | * Services that are loaded/instantiated |
55 | * @var array |
56 | */ |
57 | protected array $loaded = []; |
58 | |
59 | /** |
60 | * Constructor |
61 | * |
62 | * Instantiate the service locator object. |
63 | * |
64 | * @param ?array $services |
65 | * @param bool $default |
66 | * @throws Exception |
67 | */ |
68 | public function __construct(?array $services = null, bool $default = true) |
69 | { |
70 | if ($services !== null) { |
71 | $this->setServices($services); |
72 | } |
73 | |
74 | if (($default) && !(Container::has('default'))) { |
75 | Container::set('default', $this); |
76 | } |
77 | } |
78 | |
79 | /** |
80 | * Set service objects from an array of services |
81 | * |
82 | * @param array $services |
83 | * @throws Exception |
84 | * @return static |
85 | */ |
86 | public function setServices(array $services): static |
87 | { |
88 | foreach ($services as $name => $service) { |
89 | $this->set($name, $service); |
90 | } |
91 | |
92 | return $this; |
93 | } |
94 | |
95 | /** |
96 | * Set a service. It will overwrite any previous service with the same name. |
97 | * |
98 | * A service can be a CallableObject, callable string, or an array that |
99 | * contains a 'call' key and an optional 'params' key. |
100 | * Valid callable strings are: |
101 | * |
102 | * 'someFunction' |
103 | * 'SomeClass' |
104 | * 'SomeClass->foo' |
105 | * 'SomeClass::bar' |
106 | * |
107 | * @param string $name |
108 | * @param mixed $service |
109 | * @throws Exception |
110 | * @return static |
111 | */ |
112 | public function set(string $name, mixed $service): static |
113 | { |
114 | if (!($service instanceof CallableObject)) { |
115 | $call = null; |
116 | $params = null; |
117 | |
118 | if (!is_array($service)) { |
119 | $call = $service; |
120 | } else if (isset($service['call'])) { |
121 | $call = $service['call']; |
122 | $params = $service['params'] ?? null; |
123 | } |
124 | |
125 | if ($call === null) { |
126 | throw new Exception('Error: A callable service was not passed'); |
127 | } |
128 | |
129 | $this->services[$name] = new CallableObject($call, $params); |
130 | } else { |
131 | $this->services[$name] = $service; |
132 | } |
133 | |
134 | return $this; |
135 | } |
136 | |
137 | /** |
138 | * Get/load a service |
139 | * |
140 | * @param string $name |
141 | * @throws Exception |
142 | * @return mixed |
143 | */ |
144 | public function get(string $name): mixed |
145 | { |
146 | if (!isset($this->services[$name])) { |
147 | throw new Exception("Error: The service '" . $name . "' has not been added to the service locator"); |
148 | } |
149 | if (!isset($this->loaded[$name])) { |
150 | if (self::$depth > 40) { |
151 | throw new Exception( |
152 | 'Error: Possible recursion loop detected when attempting to load these services: ' . |
153 | implode(', ', self::$called) |
154 | ); |
155 | } |
156 | |
157 | // Keep track of the called services |
158 | self::$depth++; |
159 | if (!in_array($name, self::$called)) { |
160 | self::$called[] = $name; |
161 | } |
162 | |
163 | $this->loaded[$name] = $this->services[$name]->call(); |
164 | self::$depth--; |
165 | } |
166 | |
167 | return $this->loaded[$name]; |
168 | } |
169 | |
170 | /** |
171 | * Get a service's callable string or object |
172 | * |
173 | * @param string $name |
174 | * @return mixed |
175 | */ |
176 | public function getCall(string $name): mixed |
177 | { |
178 | return $this->services[$name]?->getCallable(); |
179 | } |
180 | |
181 | /** |
182 | * Check if a service has parameters |
183 | * |
184 | * @param string $name |
185 | * @return bool |
186 | */ |
187 | public function hasParams(string $name): bool |
188 | { |
189 | return (isset($this->services[$name]) && $this->services[$name]->hasParameters()); |
190 | } |
191 | |
192 | /** |
193 | * Get a service's parameters |
194 | * |
195 | * @param string $name |
196 | * @return mixed |
197 | */ |
198 | public function getParams(string $name): mixed |
199 | { |
200 | return $this->services[$name]?->getParameters(); |
201 | } |
202 | |
203 | /** |
204 | * Set a service's callable string or object |
205 | * |
206 | * @param string $name |
207 | * @param mixed $call |
208 | * @return static |
209 | */ |
210 | public function setCall(string $name, mixed $call): static |
211 | { |
212 | if (isset($this->services[$name])) { |
213 | $this->services[$name]->setCallable($call); |
214 | } |
215 | return $this; |
216 | } |
217 | |
218 | /** |
219 | * Set a service's parameters |
220 | * |
221 | * @param string $name |
222 | * @param mixed $params |
223 | * @return static |
224 | */ |
225 | public function setParams(string $name, mixed $params): static |
226 | { |
227 | if (isset($this->services[$name])) { |
228 | if (is_array($params)) { |
229 | $this->services[$name]->setParameters($params); |
230 | } else { |
231 | $this->services[$name]->setParameters([$params]); |
232 | } |
233 | } |
234 | return $this; |
235 | } |
236 | |
237 | /** |
238 | * Add to a service's parameters |
239 | * |
240 | * @param string $name |
241 | * @param mixed $param |
242 | * @param mixed $key |
243 | * @return static |
244 | */ |
245 | public function addParam(string $name, mixed $param, mixed $key = null): static |
246 | { |
247 | if (isset($this->services[$name])) { |
248 | if ($key !== null) { |
249 | $this->services[$name]->addNamedParameter($key, $param); |
250 | } else { |
251 | $this->services[$name]->addParameter($param); |
252 | } |
253 | } |
254 | |
255 | return $this; |
256 | } |
257 | |
258 | /** |
259 | * Remove a service's parameters |
260 | * |
261 | * @param string $name |
262 | * @param mixed $param |
263 | * @param mixed $key |
264 | * @return static |
265 | */ |
266 | public function removeParam(string $name, mixed $param, mixed $key = null): static |
267 | { |
268 | if ($this->hasParams($name)) { |
269 | if ($key !== null) { |
270 | $this->services[$name]->removeParameter($key); |
271 | } else { |
272 | foreach ($this->services[$name]->getParameters() as $key => $value) { |
273 | if ($value == $param) { |
274 | $this->services[$name]->removeParameter($key); |
275 | break; |
276 | } |
277 | } |
278 | } |
279 | } |
280 | |
281 | return $this; |
282 | } |
283 | |
284 | /** |
285 | * Determine of a service object is available (but not loaded) |
286 | * |
287 | * @param string $name |
288 | * @return bool |
289 | */ |
290 | public function isAvailable(string $name): bool |
291 | { |
292 | return isset($this->services[$name]); |
293 | } |
294 | |
295 | /** |
296 | * Determine of a service object is loaded |
297 | * |
298 | * @param string $name |
299 | * @return bool |
300 | */ |
301 | public function isLoaded(string $name): bool |
302 | { |
303 | return isset($this->loaded[$name]); |
304 | } |
305 | |
306 | /** |
307 | * Re-load a service object |
308 | * |
309 | * @param string $name |
310 | * @return mixed |
311 | */ |
312 | public function reload(string $name): mixed |
313 | { |
314 | if (isset($this->loaded[$name])) { |
315 | unset($this->loaded[$name]); |
316 | } |
317 | |
318 | return $this->get($name); |
319 | } |
320 | |
321 | /** |
322 | * Remove a service |
323 | * |
324 | * @param string $name |
325 | * @return static |
326 | */ |
327 | public function remove(string $name): static |
328 | { |
329 | if (isset($this->services[$name])) { |
330 | unset($this->services[$name]); |
331 | } |
332 | if (isset($this->loaded[$name])) { |
333 | unset($this->loaded[$name]); |
334 | } |
335 | return $this; |
336 | } |
337 | |
338 | /** |
339 | * Set a service |
340 | * |
341 | * @param string $name |
342 | * @param mixed $value |
343 | * @throws Exception |
344 | * @return void |
345 | */ |
346 | public function __set(string $name, mixed $value): void |
347 | { |
348 | $this->set($name, $value); |
349 | } |
350 | |
351 | /** |
352 | * Get a service |
353 | * |
354 | * @param string $name |
355 | * @throws Exception |
356 | * @return mixed |
357 | */ |
358 | public function __get(string $name): mixed |
359 | { |
360 | return $this->get($name); |
361 | } |
362 | |
363 | /** |
364 | * Determine if a service is available |
365 | * |
366 | * @param string $name |
367 | * @return bool |
368 | */ |
369 | public function __isset(string $name): bool |
370 | { |
371 | return isset($this->services[$name]); |
372 | } |
373 | |
374 | /** |
375 | * Unset a service |
376 | * |
377 | * @param string $name |
378 | * @return void |
379 | */ |
380 | public function __unset(string $name): void |
381 | { |
382 | $this->remove($name); |
383 | } |
384 | |
385 | /** |
386 | * Set a service |
387 | * |
388 | * @param mixed $offset |
389 | * @param mixed $value |
390 | * @throws Exception |
391 | * @return void |
392 | */ |
393 | public function offsetSet(mixed $offset, mixed $value): void |
394 | { |
395 | $this->set($offset, $value); |
396 | } |
397 | |
398 | /** |
399 | * Get a service |
400 | * |
401 | * @param mixed $offset |
402 | * @throws Exception |
403 | * @return mixed |
404 | */ |
405 | public function offsetGet(mixed $offset): mixed |
406 | { |
407 | return $this->get($offset); |
408 | } |
409 | |
410 | /** |
411 | * Determine if a service is available |
412 | * |
413 | * @param mixed $offset |
414 | * @return bool |
415 | */ |
416 | public function offsetExists(mixed$offset): bool |
417 | { |
418 | return isset($this->services[$offset]); |
419 | } |
420 | |
421 | /** |
422 | * Unset a service |
423 | * |
424 | * @param mixed $offset |
425 | * @return void |
426 | */ |
427 | public function offsetUnset(mixed $offset): void |
428 | { |
429 | $this->remove($offset); |
430 | } |
431 | |
432 | /** |
433 | * Return count |
434 | * |
435 | * @return int |
436 | */ |
437 | public function count(): int |
438 | { |
439 | return count($this->services); |
440 | } |
441 | |
442 | /** |
443 | * Get iterator |
444 | * |
445 | * @return ArrayIterator |
446 | */ |
447 | public function getIterator(): ArrayIterator |
448 | { |
449 | return new ArrayIterator($this->services); |
450 | } |
451 | |
452 | } |