Completed
Push — PSR-11-2 ( a5ad88...7f5041 )
by Nikolaos
03:59
created

Container   A

Complexity

Total Complexity 38

Size/Duplication

Total Lines 487
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 73
dl 0
loc 487
rs 9.36
c 1
b 0
f 0
ccs 94
cts 94
cp 1
wmc 38
1
<?php
2
3
/**
4
 * This file is part of the Phalcon Framework.
5
 *
6
 * (c) Phalcon Team <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE.txt
9
 * file that was distributed with this source code.
10
 *
11
 * Implementation of this file has been influenced by AuraPHP
12
 *
13
 * @link    https://github.com/auraphp/Aura.Di
14
 * @license https://github.com/auraphp/Aura.Di/blob/4.x/LICENSE
15
 */
16
17
declare(strict_types=1);
18
19
namespace Phalcon;
20
21
use Closure;
22
use Phalcon\Container\Exception;
23
use Phalcon\Container\Exception\ContainerLocked;
24
use Phalcon\Container\Exception\NoSuchProperty;
25
use Phalcon\Container\Exception\ServiceNotFound;
26
use Phalcon\Container\Exception\ServiceNotObject;
27
use Phalcon\Container\Injection\Factory;
28
use Phalcon\Container\Injection\InjectionFactory;
29
use Phalcon\Container\Injection\Lazy;
30
use Phalcon\Container\Injection\LazyArray;
31
use Phalcon\Container\Injection\LazyCallable;
32
use Phalcon\Container\Injection\LazyGet;
33
use Phalcon\Container\Injection\LazyInclude;
34
use Phalcon\Container\Injection\LazyInterface;
35
use Phalcon\Container\Injection\LazyNew;
36
use Phalcon\Container\Injection\LazyRequire;
37
use Phalcon\Container\Injection\LazyValue;
38
use Phalcon\Container\ResolutionHelper;
39
use Phalcon\Container\Resolver\AutoResolver;
40
use Phalcon\Container\Resolver\Blueprint;
41
use Phalcon\Container\Resolver\Resolver;
42
use Phalcon\Container\Resolver\ValueObject;
43
use Psr\Container\ContainerInterface;
44
use ReflectionException;
45
46
/**
47
 * Dependency injection container.
48
 *
49
 * @property ContainerInterface $delegate
50
 * @property InjectionFactory   $factory
51
 * @property array              $instances
52
 * @property bool               $locked
53
 * @property Resolver           $resolver
54
 * @property array              $services
55
 */
56
class Container implements ContainerInterface
57
{
58
    /**
59
     * A container that will be used instead of the main container
60
     * to fetch dependencies.
61
     *
62
     * @var ContainerInterface
63
     */
64
    protected ?ContainerInterface $delegate;
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected '?', expecting T_FUNCTION or T_CONST
Loading history...
65
66
    /**
67
     * A factory to create objects and values for injection.
68
     *
69
     * @var InjectionFactory
70
     */
71
    protected InjectionFactory $factory;
72
73
    /**
74
     * Retains the actual service object instances.
75
     *
76
     * @var array
77
     */
78
    protected array $instances = [];
79
80
    /**
81
     * Is the Container locked?  (When locked, you cannot access configuration
82
     * properties from outside the object, and cannot set services.)
83
     *
84
     * @var bool
85
     */
86
    protected bool $locked = false;
87
88
    /**
89
     * A Resolver obtained from the InjectionFactory.
90
     *
91
     * @var Resolver
92
     */
93
    protected Resolver $resolver;
94
95
    /**
96
     * Retains named service definitions.
97
     *
98
     * @var array
99
     */
100
    protected array $services = [];
101
102
    /**
103
     * Constructor.
104
     *
105
     * @param InjectionFactory   $factory           A factory to create objects
106
     *                                              and values for injection.
107
     * @param ContainerInterface $delegate          An optional container that
108
     *                                              will be used to fetch
109
     *                                              dependencies (i.e. lazy
110
     *                                              gets)
111
     */
112 63
    public function __construct(
113
        InjectionFactory $factory,
114
        ContainerInterface $delegate = null
115
    ) {
116 63
        $this->factory  = $factory;
117 63
        $this->resolver = $this->factory->getResolver();
118 63
        $this->delegate = $delegate;
119 63
    }
120
121
    /**
122
     * Gets a service object by key.
123
     *
124
     * @param string $service The service to get.
125
     *
126
     * @return object
127
     *
128
     * @throws ServiceNotFound when the requested service
129
     * does not exist.
130
     */
131 15
    public function get($service): object
132
    {
133 15
        $this->locked = true;
134
135 15
        if (isset($this->instances[$service])) {
136 4
            return $this->instances[$service];
137
        }
138
139 15
        $this->instances[$service] = $this->getServiceInstance($service);
140 14
        return $this->instances[$service];
141
    }
142
143
    /**
144
     * Returns the secondary delegate container.
145
     *
146
     * @return mixed
147
     */
148 1
    public function getDelegateContainer()
149
    {
150 1
        return $this->delegate;
151
    }
152
153
    /**
154
     * Returns the InjectionFactory.
155
     *
156
     * @return InjectionFactory
157
     */
158 3
    public function getInjectionFactory()
159
    {
160 3
        return $this->factory;
161
    }
162
163
    /**
164
     * Gets the list of instantiated services.
165
     *
166
     * @return array
167
     */
168 1
    public function getInstances(): array
169
    {
170 1
        return array_keys($this->instances);
171
    }
172
173
    /**
174
     * Gets the list of service definitions.
175
     *
176
     * @return array
177
     */
178 1
    public function getServices(): array
179
    {
180 1
        return array_keys($this->services);
181
    }
182
183
    /**
184
     * Does a particular service definition exist?
185
     *
186
     * @param string $service The service key to look up.
187
     *
188
     * @return bool
189
     */
190 15
    public function has($service): bool
191
    {
192 15
        if (isset($this->services[$service])) {
193 13
            return true;
194
        }
195
196 3
        return isset($this->delegate)
197 3
            && $this->delegate->has($service);
198
    }
199
200
    /**
201
     * Is the Container locked?
202
     *
203
     * @return bool
204
     */
205 2
    public function isLocked(): bool
206
    {
207 2
        return $this->locked;
208
    }
209
210
    /**
211
     * Returns a lazy object that calls a callable, optionally with arguments.
212
     *
213
     * @param callable|mixed $callable The callable.
214
     * @param array          $params
215
     *
216
     * @return Lazy
217
     */
218 5
    public function lazy($callable, ...$params): Lazy
219
    {
220 5
        return $this->factory->newLazy($callable, $params);
221
    }
222
223
    /**
224
     * Returns a lazy object that wraps an array that may contain
225
     * (potentially lazy) callables that get invoked at calltime.
226
     *
227
     * @param array $callables The (potentially lazy) array of callables to
228
     *                         invoke.
229
     *
230
     * @return LazyArray
231
     */
232 4
    public function lazyArray(array $callables): LazyArray
233
    {
234 4
        return $this->factory->newLazyArray($callables);
235
    }
236
237
    /**
238
     * Returns a lazy object that invokes a (potentially lazy) callable with
239
     * parameters supplied at calltime.
240
     *
241
     * @param callable $callable The (potentially lazy) callable.
242
     *
243
     * @return LazyCallable
244
     */
245 2
    public function lazyCallable(callable $callable): LazyCallable
246
    {
247 2
        return $this->factory->newLazyCallable($callable);
248
    }
249
250
    /**
251
     * Returns a lazy object that gets a service.
252
     *
253
     * @param string $service The service name; it does not need to exist yet.
254
     *
255
     * @return LazyGet
256
     */
257 4
    public function lazyGet(string $service): LazyGet
258
    {
259 4
        return $this->factory->newLazyGet($this, $service);
260
    }
261
262
    /**
263
     * Returns a lazy object that gets a service and calls a method on it,
264
     * optionally with parameters.
265
     *
266
     * @param string $service   The service name.
267
     * @param string $method    The method to call on the service object.
268
     * @param        ...$params mixed Parameters to use in the method call.
269
     *
270
     * @return Lazy
271
     */
272 1
    public function lazyGetCall(string $service, string $method, ...$params): Lazy
273
    {
274 1
        $callable = [$this->lazyGet($service), $method];
275
276 1
        return $this->factory->newLazy($callable, $params);
277
    }
278
279
    /**
280
     * Returns a lazy that includes a file.
281
     *
282
     * @param string $file The file to include.
283
     *
284
     * @return LazyInclude
285
     */
286 1
    public function lazyInclude(string $file): LazyInclude
287
    {
288 1
        return $this->factory->newLazyInclude($file);
289
    }
290
291
    /**
292
     * Returns a lazy object that creates a new instance.
293
     *
294
     * @param string $class   The type of class of instantiate.
295
     * @param array  $params  Override parameters for the instance.
296
     * @param array  $setters Override setters for the instance.
297
     *
298
     * @return LazyNew
299
     */
300 19
    public function lazyNew(
301
        $class,
302
        array $params = [],
303
        array $setters = []
304
    ): LazyNew {
305 19
        return $this->factory->newLazyNew($class, $params, $setters);
306
    }
307
308
    /**
309
     * Returns a lazy that requires a file.
310
     *
311
     * @param string $file The file to require.
312
     *
313
     * @return LazyRequire
314
     */
315 1
    public function lazyRequire(string $file): LazyRequire
316
    {
317 1
        return $this->factory->newLazyRequire($file);
318
    }
319
320
    /**
321
     * Returns a lazy for an arbitrary value.
322
     *
323
     * @param string $key The arbitrary value key.
324
     *
325
     * @return LazyValue
326
     */
327 3
    public function lazyValue(string $key): LazyValue
328
    {
329 3
        return $this->factory->newLazyValue($key);
330
    }
331
332
    /**
333
     * Locks the Container so that is it read-only.
334
     *
335
     * @return void
336
     */
337 4
    public function lock(): void
338
    {
339 4
        $this->locked = true;
340 4
    }
341
342
    /**
343
     * @return ValueObject
344
     * @throws ContainerLocked
345
     */
346 4
    public function mutations(): ValueObject
347
    {
348 4
        $this->checkLocked();
349
350 4
        return $this->resolver->mutations();
351
    }
352
353
    /**
354
     * Returns a factory that creates an object over and over again (as vs
355
     * creating it one time like the lazyNew() or newInstance() methods).
356
     *
357
     * @param string $class   The factory will create an instance of this class.
358
     * @param array  $params  Override parameters for the instance.
359
     * @param array  $setters Override setters for the instance.
360
     *
361
     * @return Factory
362
     *
363
     */
364 1
    public function newFactory(
365
        string $class,
366
        array $params = [],
367
        array $setters = []
368
    ): Factory {
369 1
        return $this->factory->newFactory(
370 1
            $class,
371
            $params,
372
            $setters
373
        );
374
    }
375
376
    /**
377
     * Creates and returns a new instance of a class using reflection and
378
     * the configuration parameters, optionally with overrides, invoking Lazy
379
     * values along the way.
380
     *
381
     * Note the that container must be locked before creating a new instance.
382
     * This prevents premature resolution of params and setters.
383
     *
384
     * @param string $class        The class to instantiate.
385
     *
386
     * @param array  $mergeParams  An array of override parameters; the key may
387
     *                             be the name *or* the numeric position of the
388
     *                             constructor parameter, and the value is the
389
     *                             parameter value to use.
390
     *
391
     * @param array  $mergeSetters An array of override setters; the key is the
392
     *                             name of the setter method to call and the
393
     *                             value is the value to be passed to the
394
     *                             setter method.
395
     *
396
     * @return object
397
     * @throws NoSuchProperty
398
     * @throws ReflectionException
399
     */
400 23
    public function newInstance(
401
        string $class,
402
        array $mergeParams = [],
403
        array $mergeSetters = []
404
    ): object {
405 23
        $this->locked = true;
406 23
        return $this->factory->newInstance(
407 23
            new Blueprint(
408 23
                $class,
409
                $mergeParams,
410
                $mergeSetters
411
            )
412
        );
413
    }
414
415
    /**
416
     *
417
     * Returns a callable object to resolve a service or new instance of a class
418
     *
419
     * @return ResolutionHelper
420
     */
421 1
    public function newResolutionHelper(): ResolutionHelper
422
    {
423 1
        return new ResolutionHelper($this);
424
    }
425
426
    /**
427
     * @return ValueObject
428
     * @throws ContainerLocked
429
     */
430 14
    public function parameters(): ValueObject
431
    {
432 14
        $this->checkLocked();
433
434 14
        return $this->resolver->parameters();
435
    }
436
437
    /**
438
     * Sets a service definition by name. If you set a service as a Closure,
439
     * it is automatically treated as a Lazy. (Note that is has to be a
440
     * Closure, not just any callable, to be treated as a Lazy; this is
441
     * because the actual service object itself might be callable via an
442
     * __invoke() method.)
443
     *
444
     * @param string          $service The service key.
445
     * @param object|callable $val     The service object; if a Closure, is
446
     *                                 treated as a Lazy.
447
     *
448
     * @return $this
449
     * @throws ServiceNotObject
450
     *
451
     * @throws ContainerLocked when the Container is locked.
452
     */
453 14
    public function set(string $service, object $val): Container
454
    {
455 14
        $this->checkLocked();
456 13
        if ($val instanceof Closure) {
457 1
            $val = $this->factory->newLazy($val);
458
        }
459
460 13
        $this->services[$service] = $val;
461
462 13
        return $this;
463
    }
464
465
    /**
466
     * @return ValueObject
467
     * @throws ContainerLocked
468
     */
469 5
    public function setters(): ValueObject
470
    {
471 5
        $this->checkLocked();
472 5
        return $this->resolver->setters();
473
    }
474
475
    /**
476
     * @return ValueObject|null
477
     * @throws ContainerLocked
478
     */
479 2
    public function types(): ?ValueObject
480
    {
481 2
        if ($this->resolver instanceof AutoResolver) {
482 1
            $this->checkLocked();
483 1
            return $this->resolver->types();
484
        }
485
486 1
        return null;
487
    }
488
489
    /**
490
     * @return ValueObject
491
     * @throws ContainerLocked
492
     */
493 3
    public function values(): ValueObject
494
    {
495 3
        $this->checkLocked();
496
497 3
        return $this->resolver->values();
498
    }
499
500
    /**
501
     * Instantiates a service object by key, lazy-loading it as needed.
502
     *
503
     * @param string $service The service to get.
504
     *
505
     * @return object
506
     *
507
     * @throws ServiceNotFound when the requested service
508
     * does not exist.
509
     */
510 15
    protected function getServiceInstance(string $service): object
511
    {
512
        // does the definition exist?
513 15
        if (!$this->has($service)) {
514 1
            Exception::serviceNotFound($service);
515
        }
516
517
        // is it defined in this container?
518 14
        if (!isset($this->services[$service])) {
519
            // no, get the instance from the delegate container
520 1
            return $this->delegate->get($service);
521
        }
522
523
        // instantiate it from its definition
524 13
        $instance = $this->services[$service];
525
526
        // lazy-load as needed
527 13
        if ($instance instanceof LazyInterface) {
528 8
            $instance = $instance();
529
        }
530
531
        // done
532 13
        return $instance;
533
    }
534
535
    /**
536
     * @throws ContainerLocked
537
     */
538 33
    private function checkLocked(): void
539
    {
540 33
        if ($this->locked) {
541 1
            Exception::containerLocked();
542
        }
543 32
    }
544
}
545