Issues (99)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/support/Container.php (6 issues)

Labels

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
namespace Childish\support;
3
4
use Closure;
5
use ArrayAccess;
6
use LogicException;
7
use ReflectionClass;
8
use ReflectionParameter;
9
use Exception;
10
11
/**
12
 * Container
13
 *
14
 * @author    Pu ShaoWei <[email protected]>
15
 * @date      2017/12/7
16
 * @version   1.0
17
 */
18
class Container implements ArrayAccess
19
{
20
    /**
21
     * The current globally available container (if any).
22
     *
23
     * @var static
24
     */
25
    protected static $instance;
26
27
    /**
28
     * An array of the types that have been resolved.
29
     *
30
     * @var array
31
     */
32
    protected $resolved = [];
33
34
    /**
35
     * The container's bindings.
36
     *
37
     * @var array
38
     */
39
    protected $bindings = [];
40
41
    /**
42
     * The container's method bindings.
43
     *
44
     * @var array
45
     */
46
    protected $methodBindings = [];
47
48
    /**
49
     * The container's shared instances.
50
     *
51
     * @var array
52
     */
53
    protected $instances = [];
54
55
    /**
56
     * The registered type aliases.
57
     *
58
     * @var array
59
     */
60
    protected $aliases = [];
61
62
    /**
63
     * The registered aliases keyed by the abstract name.
64
     *
65
     * @var array
66
     */
67
    protected $abstractAliases = [];
68
69
    /**
70
     * The extension closures for services.
71
     *
72
     * @var array
73
     */
74
    protected $extenders = [];
75
76
    /**
77
     * All of the registered tags.
78
     *
79
     * @var array
80
     */
81
    protected $tags = [];
82
83
    /**
84
     * The stack of concretions currently being built.
85
     *
86
     * @var array
87
     */
88
    protected $buildStack = [];
89
90
    /**
91
     * The parameter override stack.
92
     *
93
     * @var array
94
     */
95
    protected $with = [];
96
97
    /**
98
     * The contextual binding map.
99
     *
100
     * @var array
101
     */
102
    public $contextual = [];
103
104
    /**
105
     * All of the registered rebound callbacks.
106
     *
107
     * @var array
108
     */
109
    protected $reboundCallbacks = [];
110
111
    /**
112
     * All of the global resolving callbacks.
113
     *
114
     * @var array
115
     */
116
    protected $globalResolvingCallbacks = [];
117
118
    /**
119
     * All of the global after resolving callbacks.
120
     *
121
     * @var array
122
     */
123
    protected $globalAfterResolvingCallbacks = [];
124
125
    /**
126
     * All of the resolving callbacks by class type.
127
     *
128
     * @var array
129
     */
130
    protected $resolvingCallbacks = [];
131
132
    /**
133
     * All of the after resolving callbacks by class type.
134
     *
135
     * @var array
136
     */
137
    protected $afterResolvingCallbacks = [];
138
139
140
    /**
141
     * Determine if the given abstract type has been bound.
142
     *
143
     * @param  string $abstract
144
     * @return bool
145
     */
146
    public function bound($abstract)
147
    {
148
        return isset($this->bindings[$abstract]) ||
149
               isset($this->instances[$abstract]) ||
150
               $this->isAlias($abstract);
151
    }
152
153
    /**
154
     *  {@inheritdoc}
155
     */
156
    public function has($id)
157
    {
158
        return $this->bound($id);
159
    }
160
161
    /**
162
     * Determine if the given abstract type has been resolved.
163
     *
164
     * @param  string $abstract
165
     * @return bool
166
     */
167
    public function resolved($abstract)
168
    {
169
        if ($this->isAlias($abstract)) {
170
            $abstract = $this->getAlias($abstract);
171
        }
172
173
        return isset($this->resolved[$abstract]) ||
174
               isset($this->instances[$abstract]);
175
    }
176
177
    /**
178
     * Determine if a given type is shared.
179
     *
180
     * @param  string $abstract
181
     * @return bool
182
     */
183
    public function isShared($abstract)
184
    {
185
        return isset($this->instances[$abstract]) ||
186
               (isset($this->bindings[$abstract]['shared']) &&
187
                $this->bindings[$abstract]['shared'] === true);
188
    }
189
190
    /**
191
     * Determine if a given string is an alias.
192
     *
193
     * @param  string $name
194
     * @return bool
195
     */
196
    public function isAlias($name)
197
    {
198
        return isset($this->aliases[$name]);
199
    }
200
201
    /**
202
     * Register a binding with the container.
203
     *
204
     * @param  string|array         $abstract
205
     * @param  \Closure|string|null $concrete
206
     * @param  bool                 $shared
207
     * @return void
208
     */
209
    public function bind($abstract, $concrete = null, $shared = false)
210
    {
211
        // If no concrete type was given, we will simply set the concrete type to the
212
        // abstract type. After that, the concrete type to be registered as shared
213
        // without being forced to state their classes in both of the parameters.
214
        $this->dropStaleInstances($abstract);
0 ignored issues
show
It seems like $abstract defined by parameter $abstract on line 209 can also be of type array; however, Childish\support\Container::dropStaleInstances() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
215
216
        if (is_null($concrete)) {
217
            $concrete = $abstract;
218
        }
219
220
        // If the factory is not a Closure, it means it is just a class name which is
221
        // bound into this container to the abstract type and we will just wrap it
222
        // up inside its own Closure to give us more convenience when extending.
223
        if (!$concrete instanceof Closure) {
224
            $concrete = $this->getClosure($abstract, $concrete);
0 ignored issues
show
It seems like $abstract defined by parameter $abstract on line 209 can also be of type array; however, Childish\support\Container::getClosure() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
It seems like $concrete defined by $this->getClosure($abstract, $concrete) on line 224 can also be of type array; however, Childish\support\Container::getClosure() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
225
        }
226
227
        $this->bindings[$abstract] = compact('concrete', 'shared');
228
229
        // If the abstract type was already resolved in this container we'll fire the
230
        // rebound listener so that any objects which have already gotten resolved
231
        // can have their copy of the object updated via the listener callbacks.
232
        if ($this->resolved($abstract)) {
0 ignored issues
show
It seems like $abstract defined by parameter $abstract on line 209 can also be of type array; however, Childish\support\Container::resolved() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
233
            $this->rebound($abstract);
0 ignored issues
show
It seems like $abstract defined by parameter $abstract on line 209 can also be of type array; however, Childish\support\Container::rebound() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
234
        }
235
    }
236
237
    /**
238
     * Get the Closure to be used when building a type.
239
     *
240
     * @param  string $abstract
241
     * @param  string $concrete
242
     * @return \Closure
243
     */
244
    protected function getClosure($abstract, $concrete)
245
    {
246
        return function ($container, $parameters = []) use ($abstract, $concrete) {
247
            if ($abstract == $concrete) {
248
                return $container->build($concrete);
249
            }
250
251
            return $container->make($concrete, $parameters);
252
        };
253
    }
254
255
    /**
256
     * Determine if the container has a method binding.
257
     *
258
     * @param  string $method
259
     * @return bool
260
     */
261
    public function hasMethodBinding($method)
262
    {
263
        return isset($this->methodBindings[$method]);
264
    }
265
266
    /**
267
     * Bind a callback to resolve with Container::call.
268
     *
269
     * @param  string   $method
270
     * @param  \Closure $callback
271
     * @return void
272
     */
273
    public function bindMethod($method, $callback)
274
    {
275
        $this->methodBindings[$method] = $callback;
276
    }
277
278
    /**
279
     * Get the method binding for the given method.
280
     *
281
     * @param  string $method
282
     * @param  mixed  $instance
283
     * @return mixed
284
     */
285
    public function callMethodBinding($method, $instance)
286
    {
287
        return call_user_func($this->methodBindings[$method], $instance, $this);
288
    }
289
290
    /**
291
     * Add a contextual binding to the container.
292
     *
293
     * @param  string          $concrete
294
     * @param  string          $abstract
295
     * @param  \Closure|string $implementation
296
     * @return void
297
     */
298
    public function addContextualBinding($concrete, $abstract, $implementation)
299
    {
300
        $this->contextual[$concrete][$this->getAlias($abstract)] = $implementation;
301
    }
302
303
    /**
304
     * Register a binding if it hasn't already been registered.
305
     *
306
     * @param  string               $abstract
307
     * @param  \Closure|string|null $concrete
308
     * @param  bool                 $shared
309
     * @return void
310
     */
311
    public function bindIf($abstract, $concrete = null, $shared = false)
312
    {
313
        if (!$this->bound($abstract)) {
314
            $this->bind($abstract, $concrete, $shared);
315
        }
316
    }
317
318
    /**
319
     * Register a shared binding in the container.
320
     *
321
     * @param  string|array         $abstract
322
     * @param  \Closure|string|null $concrete
323
     * @return void
324
     */
325
    public function singleton($abstract, $concrete = null)
326
    {
327
        $this->bind($abstract, $concrete, true);
328
    }
329
330
    /**
331
     * "Extend" an abstract type in the container.
332
     *
333
     * @param  string   $abstract
334
     * @param  \Closure $closure
335
     * @return void
336
     * @throws \InvalidArgumentException
337
     */
338
    public function extend($abstract, Closure $closure)
339
    {
340
        $abstract = $this->getAlias($abstract);
341
342
        if (isset($this->instances[$abstract])) {
343
            $this->instances[$abstract] = $closure($this->instances[$abstract], $this);
344
345
            $this->rebound($abstract);
346
        } else {
347
            $this->extenders[$abstract][] = $closure;
348
349
            if ($this->resolved($abstract)) {
350
                $this->rebound($abstract);
351
            }
352
        }
353
    }
354
355
    /**
356
     * Register an existing instance as shared in the container.
357
     *
358
     * @param  string $abstract
359
     * @param  mixed  $instance
360
     * @return mixed
361
     */
362
    public function instance($abstract, $instance)
363
    {
364
        $this->removeAbstractAlias($abstract);
365
366
        $isBound = $this->bound($abstract);
367
368
        unset($this->aliases[$abstract]);
369
370
        // We'll check to determine if this type has been bound before, and if it has
371
        // we will fire the rebound callbacks registered with the container and it
372
        // can be updated with consuming classes that have gotten resolved here.
373
        $this->instances[$abstract] = $instance;
374
375
        if ($isBound) {
376
            $this->rebound($abstract);
377
        }
378
379
        return $instance;
380
    }
381
382
    /**
383
     * Remove an alias from the contextual binding alias cache.
384
     *
385
     * @param  string $searched
386
     * @return void
387
     */
388
    protected function removeAbstractAlias($searched)
389
    {
390
        if (!isset($this->aliases[$searched])) {
391
            return;
392
        }
393
394
        foreach ($this->abstractAliases as $abstract => $aliases) {
395
            foreach ($aliases as $index => $alias) {
396
                if ($alias == $searched) {
397
                    unset($this->abstractAliases[$abstract][$index]);
398
                }
399
            }
400
        }
401
    }
402
403
    /**
404
     * Assign a set of tags to a given binding.
405
     *
406
     * @param  array|string $abstracts
407
     * @param  array|mixed  ...$tags
408
     * @return void
409
     */
410
    public function tag($abstracts, $tags)
411
    {
412
        $tags = is_array($tags) ? $tags : array_slice(func_get_args(), 1);
413
414
        foreach ($tags as $tag) {
415
            if (!isset($this->tags[$tag])) {
416
                $this->tags[$tag] = [];
417
            }
418
419
            foreach ((array)$abstracts as $abstract) {
420
                $this->tags[$tag][] = $abstract;
421
            }
422
        }
423
    }
424
425
    /**
426
     * Resolve all of the bindings for a given tag.
427
     *
428
     * @param  string $tag
429
     * @return array
430
     */
431
    public function tagged($tag)
432
    {
433
        $results = [];
434
435
        if (isset($this->tags[$tag])) {
436
            foreach ($this->tags[$tag] as $abstract) {
437
                $results[] = $this->make($abstract);
438
            }
439
        }
440
441
        return $results;
442
    }
443
444
    /**
445
     * Alias a type to a different name.
446
     *
447
     * @param  string $abstract
448
     * @param  string $alias
449
     * @return void
450
     */
451
    public function alias($abstract, $alias)
452
    {
453
        $this->aliases[$alias] = $abstract;
454
455
        $this->abstractAliases[$abstract][] = $alias;
456
    }
457
458
    /**
459
     * Bind a new callback to an abstract's rebind event.
460
     *
461
     * @param  string   $abstract
462
     * @param  \Closure $callback
463
     * @return mixed
464
     */
465
    public function rebinding($abstract, Closure $callback)
466
    {
467
        $this->reboundCallbacks[$abstract = $this->getAlias($abstract)][] = $callback;
468
469
        if ($this->bound($abstract)) {
470
            return $this->make($abstract);
471
        }
472
    }
473
474
    /**
475
     * Refresh an instance on the given target and method.
476
     *
477
     * @param  string $abstract
478
     * @param  mixed  $target
479
     * @param  string $method
480
     * @return mixed
481
     */
482
    public function refresh($abstract, $target, $method)
483
    {
484
        return $this->rebinding($abstract, function ($app, $instance) use ($target, $method) {
485
            $target->{$method}($instance);
486
        });
487
    }
488
489
    /**
490
     * Fire the "rebound" callbacks for the given abstract type.
491
     *
492
     * @param  string $abstract
493
     * @return void
494
     */
495
    protected function rebound($abstract)
496
    {
497
        $instance = $this->make($abstract);
498
499
        foreach ($this->getReboundCallbacks($abstract) as $callback) {
500
            call_user_func($callback, $this, $instance);
501
        }
502
    }
503
504
    /**
505
     * Get the rebound callbacks for a given type.
506
     *
507
     * @param  string $abstract
508
     * @return array
509
     */
510
    protected function getReboundCallbacks($abstract)
511
    {
512
        if (isset($this->reboundCallbacks[$abstract])) {
513
            return $this->reboundCallbacks[$abstract];
514
        }
515
516
        return [];
517
    }
518
519
    /**
520
     * Get a closure to resolve the given type from the container.
521
     *
522
     * @param  string $abstract
523
     * @return \Closure
524
     */
525
    public function factory($abstract)
526
    {
527
        return function () use ($abstract) {
528
            return $this->make($abstract);
529
        };
530
    }
531
532
    /**
533
     * An alias function name for make().
534
     *
535
     * @param  string $abstract
536
     * @param  array  $parameters
537
     * @return mixed
538
     */
539
    public function makeWith($abstract, array $parameters = [])
540
    {
541
        return $this->make($abstract, $parameters);
542
    }
543
544
    /**
545
     * Resolve the given type from the container.
546
     *
547
     * @param  string $abstract
548
     * @param  array  $parameters
549
     * @return mixed
550
     */
551
    public function make($abstract, array $parameters = [])
552
    {
553
        return $this->resolve($abstract, $parameters);
554
    }
555
556
    /**
557
     *  {@inheritdoc}
558
     */
559
    public function get($id)
560
    {
561
        if ($this->has($id)) {
562
            return $this->resolve($id);
563
        }
564
565
        throw new Exception;
566
    }
567
568
    /**
569
     * Resolve the given type from the container.
570
     *
571
     * @param  string $abstract
572
     * @param  array  $parameters
573
     * @return mixed
574
     */
575
    protected function resolve($abstract, $parameters = [])
576
    {
577
        $abstract = $this->getAlias($abstract);
578
579
        $needsContextualBuild = !empty($parameters) || !is_null(
580
                $this->getContextualConcrete($abstract)
581
            );
582
583
        // If an instance of the type is currently being managed as a singleton we'll
584
        // just return an existing instance instead of instantiating new instances
585
        // so the developer can keep using the same objects instance every time.
586
        if (isset($this->instances[$abstract]) && !$needsContextualBuild) {
587
            return $this->instances[$abstract];
588
        }
589
590
        $this->with[] = $parameters;
591
592
        $concrete = $this->getConcrete($abstract);
593
594
        // We're ready to instantiate an instance of the concrete type registered for
595
        // the binding. This will instantiate the types, as well as resolve any of
596
        // its "nested" dependencies recursively until all have gotten resolved.
597
        if ($this->isBuildable($concrete, $abstract)) {
598
            $object = $this->build($concrete);
599
        } else {
600
            $object = $this->make($concrete);
601
        }
602
603
        // If we defined any extenders for this type, we'll need to spin through them
604
        // and apply them to the object being built. This allows for the extension
605
        // of services, such as changing configuration or decorating the object.
606
        foreach ($this->getExtenders($abstract) as $extender) {
607
            $object = $extender($object, $this);
608
        }
609
610
        // If the requested type is registered as a singleton we'll want to cache off
611
        // the instances in "memory" so we can return it later without creating an
612
        // entirely new instance of an object on each subsequent request for it.
613
        if ($this->isShared($abstract) && !$needsContextualBuild) {
614
            $this->instances[$abstract] = $object;
615
        }
616
617
        $this->fireResolvingCallbacks($abstract, $object);
618
619
        // Before returning, we will also set the resolved flag to "true" and pop off
620
        // the parameter overrides for this build. After those two things are done
621
        // we will be ready to return back the fully constructed class instance.
622
        $this->resolved[$abstract] = true;
623
624
        array_pop($this->with);
625
626
        return $object;
627
    }
628
629
    /**
630
     * Get the concrete type for a given abstract.
631
     *
632
     * @param  string $abstract
633
     * @return mixed   $concrete
634
     */
635
    protected function getConcrete($abstract)
636
    {
637
        if (!is_null($concrete = $this->getContextualConcrete($abstract))) {
638
            return $concrete;
639
        }
640
641
        // If we don't have a registered resolver or concrete for the type, we'll just
642
        // assume each type is a concrete name and will attempt to resolve it as is
643
        // since the container should be able to resolve concretes automatically.
644
        if (isset($this->bindings[$abstract])) {
645
            return $this->bindings[$abstract]['concrete'];
646
        }
647
648
        return $abstract;
649
    }
650
651
    /**
652
     * Get the contextual concrete binding for the given abstract.
653
     *
654
     * @param  string $abstract
655
     * @return string|null
656
     */
657
    protected function getContextualConcrete($abstract)
658
    {
659
        if (!is_null($binding = $this->findInContextualBindings($abstract))) {
660
            return $binding;
661
        }
662
663
        // Next we need to see if a contextual binding might be bound under an alias of the
664
        // given abstract type. So, we will need to check if any aliases exist with this
665
        // type and then spin through them and check for contextual bindings on these.
666
        if (empty($this->abstractAliases[$abstract])) {
667
            return;
668
        }
669
670
        foreach ($this->abstractAliases[$abstract] as $alias) {
671
            if (!is_null($binding = $this->findInContextualBindings($alias))) {
672
                return $binding;
673
            }
674
        }
675
    }
676
677
    /**
678
     * Find the concrete binding for the given abstract in the contextual binding array.
679
     *
680
     * @param  string $abstract
681
     * @return string|null
682
     */
683
    protected function findInContextualBindings($abstract)
684
    {
685
        if (isset($this->contextual[end($this->buildStack)][$abstract])) {
686
            return $this->contextual[end($this->buildStack)][$abstract];
687
        }
688
    }
689
690
    /**
691
     * Determine if the given concrete is buildable.
692
     *
693
     * @param  mixed  $concrete
694
     * @param  string $abstract
695
     * @return bool
696
     */
697
    protected function isBuildable($concrete, $abstract)
698
    {
699
        return $concrete === $abstract || $concrete instanceof Closure;
700
    }
701
702
    /**
703
     * Instantiate a concrete instance of the given type.
704
     *
705
     * @param $concrete
706
     * @return mixed|object|void
707
     */
708
    public function build($concrete)
709
    {
710
        // If the concrete type is actually a Closure, we will just execute it and
711
        // hand back the results of the functions, which allows functions to be
712
        // used as resolvers for more fine-tuned resolution of these objects.
713
        if ($concrete instanceof Closure) {
714
            return $concrete($this, $this->getLastParameterOverride());
715
        }
716
717
        $reflector = new ReflectionClass($concrete);
718
719
        // If the type is not instantiable, the developer is attempting to resolve
720
        // an abstract type such as an Interface of Abstract Class and there is
721
        // no binding registered for the abstractions so we need to bail out.
722
        if (!$reflector->isInstantiable()) {
723
            return $this->notInstantiable($concrete);
724
        }
725
726
        $this->buildStack[] = $concrete;
727
728
        $constructor = $reflector->getConstructor();
729
730
        // If there are no constructors, that means there are no dependencies then
731
        // we can just resolve the instances of the objects right away, without
732
        // resolving any other types or dependencies out of these containers.
733
        if (is_null($constructor)) {
734
            array_pop($this->buildStack);
735
736
            return new $concrete;
737
        }
738
739
        $dependencies = $constructor->getParameters();
740
741
        // Once we have all the constructor's parameters we can create each of the
742
        // dependency instances and then use the reflection instances to make a
743
        // new instance of this class, injecting the created dependencies in.
744
        $instances = $this->resolveDependencies(
745
            $dependencies
746
        );
747
748
        array_pop($this->buildStack);
749
750
        return $reflector->newInstanceArgs($instances);
751
    }
752
753
    /**
754
     * Resolve all of the dependencies from the ReflectionParameters.
755
     *
756
     * @param  array $dependencies
757
     * @return array
758
     */
759
    protected function resolveDependencies(array $dependencies)
760
    {
761
        $results = [];
762
763
        foreach ($dependencies as $dependency) {
764
            // If this dependency has a override for this particular build we will use
765
            // that instead as the value. Otherwise, we will continue with this run
766
            // of resolutions and let reflection attempt to determine the result.
767
            if ($this->hasParameterOverride($dependency)) {
768
                $results[] = $this->getParameterOverride($dependency);
769
770
                continue;
771
            }
772
773
            // If the class is null, it means the dependency is a string or some other
774
            // primitive type which we can not resolve since it is not a class and
775
            // we will just bomb out with an error since we have no-where to go.
776
            $results[] = is_null($dependency->getClass())
777
                ? $this->resolvePrimitive($dependency)
778
                : $this->resolveClass($dependency);
779
        }
780
781
        return $results;
782
    }
783
784
    /**
785
     * Determine if the given dependency has a parameter override.
786
     *
787
     * @param  \ReflectionParameter $dependency
788
     * @return bool
789
     */
790
    protected function hasParameterOverride($dependency)
791
    {
792
        return array_key_exists(
793
            $dependency->name, $this->getLastParameterOverride()
794
        );
795
    }
796
797
    /**
798
     * Get a parameter override for a dependency.
799
     *
800
     * @param  \ReflectionParameter $dependency
801
     * @return mixed
802
     */
803
    protected function getParameterOverride($dependency)
804
    {
805
        return $this->getLastParameterOverride()[$dependency->name];
806
    }
807
808
    /**
809
     * Get the last parameter override.
810
     *
811
     * @return array
812
     */
813
    protected function getLastParameterOverride()
814
    {
815
        return count($this->with) ? end($this->with) : [];
816
    }
817
818
    /**
819
     * Resolve a non-class hinted primitive dependency.
820
     *
821
     * @param  \ReflectionParameter $parameter
822
     * @return mixed|null|string
823
     */
824
    protected function resolvePrimitive(ReflectionParameter $parameter)
825
    {
826
        if (!is_null($concrete = $this->getContextualConcrete('$' . $parameter->name))) {
827
            return $concrete instanceof Closure ? $concrete($this) : $concrete;
828
        }
829
830
        if ($parameter->isDefaultValueAvailable()) {
831
            return $parameter->getDefaultValue();
832
        }
833
834
        $this->unresolvablePrimitive($parameter);
835
    }
836
837
    /**
838
     * Resolve a class based dependency from the container.
839
     *
840
     * @param \ReflectionParameter $parameter
841
     * @return mixed
842
     * @throws \Exception
843
     */
844
    protected function resolveClass(ReflectionParameter $parameter)
845
    {
846
        try {
847
            return $this->make($parameter->getClass()->name);
848
        }
849
850
            // If we can not resolve the class instance, we will check to see if the value
851
            // is optional, and if it is we will return the optional parameter value as
852
            // the value of the dependency, similarly to how we do this with scalars.
853
        catch (Exception $e) {
854
            if ($parameter->isOptional()) {
855
                return $parameter->getDefaultValue();
856
            }
857
858
            throw $e;
859
        }
860
    }
861
862
    /**
863
     * Throw an exception that the concrete is not instantiable.
864
     *
865
     * @param $concrete
866
     * @throws \Exception
867
     */
868
    protected function notInstantiable($concrete)
869
    {
870
        if (!empty($this->buildStack)) {
871
            $previous = implode(', ', $this->buildStack);
872
873
            $message = "Target [$concrete] is not instantiable while building [$previous].";
874
        } else {
875
            $message = "Target [$concrete] is not instantiable.";
876
        }
877
878
        throw new Exception($message);
879
    }
880
881
    /**
882
     * Throw an exception for an unresolvable primitive.
883
     *
884
     * @param \ReflectionParameter $parameter
885
     * @throws \Exception
886
     */
887
    protected function unresolvablePrimitive(ReflectionParameter $parameter)
888
    {
889
        $message = "Unresolvable dependency resolving [$parameter] in class {$parameter->getDeclaringClass()->getName()}";
0 ignored issues
show
Consider using $parameter->getDeclaringClass()->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
890
891
        throw new Exception($message);
892
    }
893
894
    /**
895
     * Register a new resolving callback.
896
     *
897
     * @param  string        $abstract
898
     * @param  \Closure|null $callback
899
     * @return void
900
     */
901
    public function resolving($abstract, Closure $callback = null)
902
    {
903
        if (is_string($abstract)) {
904
            $abstract = $this->getAlias($abstract);
905
        }
906
907
        if (is_null($callback) && $abstract instanceof Closure) {
908
            $this->globalResolvingCallbacks[] = $abstract;
909
        } else {
910
            $this->resolvingCallbacks[$abstract][] = $callback;
911
        }
912
    }
913
914
    /**
915
     * Register a new after resolving callback for all types.
916
     *
917
     * @param  string        $abstract
918
     * @param  \Closure|null $callback
919
     * @return void
920
     */
921
    public function afterResolving($abstract, Closure $callback = null)
922
    {
923
        if (is_string($abstract)) {
924
            $abstract = $this->getAlias($abstract);
925
        }
926
927
        if ($abstract instanceof Closure && is_null($callback)) {
928
            $this->globalAfterResolvingCallbacks[] = $abstract;
929
        } else {
930
            $this->afterResolvingCallbacks[$abstract][] = $callback;
931
        }
932
    }
933
934
    /**
935
     * Fire all of the resolving callbacks.
936
     *
937
     * @param  string $abstract
938
     * @param  mixed  $object
939
     * @return void
940
     */
941
    protected function fireResolvingCallbacks($abstract, $object)
942
    {
943
        $this->fireCallbackArray($object, $this->globalResolvingCallbacks);
944
945
        $this->fireCallbackArray(
946
            $object, $this->getCallbacksForType($abstract, $object, $this->resolvingCallbacks)
947
        );
948
949
        $this->fireAfterResolvingCallbacks($abstract, $object);
950
    }
951
952
    /**
953
     * Fire all of the after resolving callbacks.
954
     *
955
     * @param  string $abstract
956
     * @param  mixed  $object
957
     * @return void
958
     */
959
    protected function fireAfterResolvingCallbacks($abstract, $object)
960
    {
961
        $this->fireCallbackArray($object, $this->globalAfterResolvingCallbacks);
962
963
        $this->fireCallbackArray(
964
            $object, $this->getCallbacksForType($abstract, $object, $this->afterResolvingCallbacks)
965
        );
966
    }
967
968
    /**
969
     * Get all callbacks for a given type.
970
     *
971
     * @param  string $abstract
972
     * @param  object $object
973
     * @param  array  $callbacksPerType
974
     * @return array
975
     */
976
    protected function getCallbacksForType($abstract, $object, array $callbacksPerType)
977
    {
978
        $results = [];
979
980
        foreach ($callbacksPerType as $type => $callbacks) {
981
            if ($type === $abstract || $object instanceof $type) {
982
                $results = array_merge($results, $callbacks);
983
            }
984
        }
985
986
        return $results;
987
    }
988
989
    /**
990
     * Fire an array of callbacks with an object.
991
     *
992
     * @param  mixed $object
993
     * @param  array $callbacks
994
     * @return void
995
     */
996
    protected function fireCallbackArray($object, array $callbacks)
997
    {
998
        foreach ($callbacks as $callback) {
999
            $callback($object, $this);
1000
        }
1001
    }
1002
1003
    /**
1004
     * Get the container's bindings.
1005
     *
1006
     * @return array
1007
     */
1008
    public function getBindings()
1009
    {
1010
        return $this->bindings;
1011
    }
1012
1013
    /**
1014
     * Get the alias for an abstract if available.
1015
     *
1016
     * @param  string $abstract
1017
     * @return string
1018
     * @throws \LogicException
1019
     */
1020
    public function getAlias($abstract)
1021
    {
1022
        if (!isset($this->aliases[$abstract])) {
1023
            return $abstract;
1024
        }
1025
1026
        if ($this->aliases[$abstract] === $abstract) {
1027
            throw new LogicException("[{$abstract}] is aliased to itself.");
1028
        }
1029
1030
        return $this->getAlias($this->aliases[$abstract]);
1031
    }
1032
1033
    /**
1034
     * Get the extender callbacks for a given type.
1035
     *
1036
     * @param  string $abstract
1037
     * @return array
1038
     */
1039
    protected function getExtenders($abstract)
1040
    {
1041
        $abstract = $this->getAlias($abstract);
1042
1043
        if (isset($this->extenders[$abstract])) {
1044
            return $this->extenders[$abstract];
1045
        }
1046
1047
        return [];
1048
    }
1049
1050
    /**
1051
     * Remove all of the extender callbacks for a given type.
1052
     *
1053
     * @param  string $abstract
1054
     * @return void
1055
     */
1056
    public function forgetExtenders($abstract)
1057
    {
1058
        unset($this->extenders[$this->getAlias($abstract)]);
1059
    }
1060
1061
    /**
1062
     * Drop all of the stale instances and aliases.
1063
     *
1064
     * @param  string $abstract
1065
     * @return void
1066
     */
1067
    protected function dropStaleInstances($abstract)
1068
    {
1069
        unset($this->instances[$abstract], $this->aliases[$abstract]);
1070
    }
1071
1072
    /**
1073
     * Remove a resolved instance from the instance cache.
1074
     *
1075
     * @param  string $abstract
1076
     * @return void
1077
     */
1078
    public function forgetInstance($abstract)
1079
    {
1080
        unset($this->instances[$abstract]);
1081
    }
1082
1083
    /**
1084
     * Clear all of the instances from the container.
1085
     *
1086
     * @return void
1087
     */
1088
    public function forgetInstances()
1089
    {
1090
        $this->instances = [];
1091
    }
1092
1093
    /**
1094
     * Flush the container of all bindings and resolved instances.
1095
     *
1096
     * @return void
1097
     */
1098
    public function flush()
1099
    {
1100
        $this->aliases         = [];
1101
        $this->resolved        = [];
1102
        $this->bindings        = [];
1103
        $this->instances       = [];
1104
        $this->abstractAliases = [];
1105
    }
1106
1107
    /**
1108
     * Set the globally available instance of the container.
1109
     *
1110
     * @return static
1111
     */
1112
    public static function getInstance()
1113
    {
1114
        if (is_null(static::$instance)) {
1115
            static::$instance = new static;
1116
        }
1117
1118
        return static::$instance;
1119
    }
1120
1121
    /**
1122
     * Set the shared instance of the container.
1123
     *
1124
     * @param  $container
1125
     * @return static
1126
     */
1127
    public static function setInstance($container = null)
1128
    {
1129
        return static::$instance = $container;
1130
    }
1131
1132
    /**
1133
     * Determine if a given offset exists.
1134
     *
1135
     * @param  string $key
1136
     * @return bool
1137
     */
1138
    public function offsetExists($key)
1139
    {
1140
        return $this->bound($key);
1141
    }
1142
1143
    /**
1144
     * Get the value at a given offset.
1145
     *
1146
     * @param  string $key
1147
     * @return mixed
1148
     */
1149
    public function offsetGet($key)
1150
    {
1151
        return $this->make($key);
1152
    }
1153
1154
    /**
1155
     * Set the value at a given offset.
1156
     *
1157
     * @param  string $key
1158
     * @param  mixed  $value
1159
     * @return void
1160
     */
1161
    public function offsetSet($key, $value)
1162
    {
1163
        $this->bind($key, $value instanceof Closure ? $value : function () use ($value) {
1164
            return $value;
1165
        });
1166
    }
1167
1168
    /**
1169
     * Unset the value at a given offset.
1170
     *
1171
     * @param  string $key
1172
     * @return void
1173
     */
1174
    public function offsetUnset($key)
1175
    {
1176
        unset($this->bindings[$key], $this->instances[$key], $this->resolved[$key]);
1177
    }
1178
1179
    /**
1180
     * Dynamically access container services.
1181
     *
1182
     * @param  string $key
1183
     * @return mixed
1184
     */
1185
    public function __get($key)
1186
    {
1187
        return $this[$key];
1188
    }
1189
1190
    /**
1191
     * Dynamically set container services.
1192
     *
1193
     * @param  string $key
1194
     * @param  mixed  $value
1195
     * @return void
1196
     */
1197
    public function __set($key, $value)
1198
    {
1199
        $this[$key] = $value;
1200
    }
1201
}
1202