Passed
Push — PSR-11-2 ( dfb1a3...d7a3e8 )
by Nikolaos
04:33
created

Container::newFactory()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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