/var/www/html_us/wp-content/plugins/wp-smtp/vendor/vendor-prefixed/lucatume/di52/src/Container.php


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
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
<?php
/**
 * The Dependency Injection container.
 *
 * @package lucatume\DI52
 *
 * @license GPL-3.0
 * Modified using {@see https://github.com/BrianHenryIE/strauss}.
 */

namespace SolidWP\Mail\lucatume\DI52;

use 
ArrayAccess;
use 
Closure;
use 
Exception;
use 
SolidWP\Mail\lucatume\DI52\Builders\BuilderInterface;
use 
SolidWP\Mail\lucatume\DI52\Builders\ValueBuilder;
use 
SolidWP\Mail\Psr\Container\ContainerInterface;
use 
ReflectionClass;
use 
ReflectionException;
use 
ReflectionMethod;
use 
ReturnTypeWillChange;
use 
Throwable;
use function 
spl_object_hash;

/**
 * Class Container
 *
 * @package lucatume\DI52
 * @implements ArrayAccess<string,object>
 */
class Container implements ArrayAccessContainerInterface
{
    const 
EXCEPTION_MASK_NONE 0;
    const 
EXCEPTION_MASK_MESSAGE 1;
    const 
EXCEPTION_MASK_FILE_LINE 2;

    
/**
     * An array cache to store the results of the class exists checks.
     *
     * @var array<string,bool>
     */
    
protected $classIsInstantiatableCache = [];
    
/**
     * A cache of what methods are static and what are not.
     *
     * @var array<string,bool>
     */
    
protected $isStaticMethodCache = [];
    
/**
     * A list of bound and resolved singletons.
     *
     * @var array<string|class-string,bool>
     */
    
protected $singletons = [];
    
/**
     * @var array<ServiceProvider>
     */
    
protected $deferred = [];
    
/**
     * @var array<string,array<string|object|callable>>
     */
    
protected $tags = [];
    
/**
     * @var array<ServiceProvider>
     */
    
protected $bootable = [];
    
/**
     * @var string
     */
    
protected $whenClass;
    
/**
     * @var string
     */
    
protected $needsClass;
    
/**
     * A map from class name and static methods to the built callback.
     *
     * @var array<string,Closure>
     */
    
protected $callbacks = [];
    
/**
     * @var Builders\Resolver
     */
    
protected $resolver;
    
/**
     * @var Builders\Factory
     */
    
protected $builders;
    
/**
     * What kind of masking should be applied to throwables catched by the container during resolution.
     *
     * @var int
     */
    
private $maskThrowables self::EXCEPTION_MASK_MESSAGE self::EXCEPTION_MASK_FILE_LINE;

    
/**
     * Container constructor.
     *
     * @param false $resolveUnboundAsSingletons Whether unbound classes should be resolved as singletons by default,
     *                                          or not.
     */
    
public function __construct($resolveUnboundAsSingletons false)
    {
        
$this->resolver = new Builders\Resolver($resolveUnboundAsSingletons);
        
$this->builders = new Builders\Factory($this$this->resolver);
        
$this->bindThis();
    }

    
/**
     * Sets a variable on the container.
     *
     * @param string $key   The alias the container will use to reference the variable.
     * @param mixed  $value The variable value.
     *
     * @return void The method does not return any value.
     */
    
public function setVar($key$value)
    {
        
$this->resolver->bind($keyValueBuilder::of($value));
    }

    
/**
     * Sets a variable on the container using the ArrayAccess API.
     *
     * When using the container as an array bindings will be bound as singletons.
     * These are equivalent: `$container->singleton('foo','ClassOne');`, `$container['foo'] = 'ClassOne';`.
     *
     * @param string $offset The alias the container will use to reference the variable.
     * @param mixed  $value  The variable value.
     *
     * @return void This method does not return any value.
     *
     * @throws ContainerException If the closure building fails.
     */
    
#[ReturnTypeWillChange]
    public function 
offsetSet($offset$value)
    {
        
$this->singleton($offset$value);
    }

    
/**
     * Binds an interface a class or a string slug to an implementation and will always return the same instance.
     *
     * @param string             $id                A class or interface fully qualified name or a string slug.
     * @param mixed              $implementation    The implementation that should be bound to the alias(es); can be a
     *                                              class name, an object or a closure.
     * @param array<string>|null $afterBuildMethods An array of methods that should be called on the built
     *                                              implementation after resolving it.
     *
     * @return void This method does not return any value.
     * @throws ContainerException If there's any issue reflecting on the class, interface or the implementation.
     */
    
public function singleton($id$implementation null, array $afterBuildMethods null)
    {
        if (
$implementation === null) {
            
$implementation $id;
        }

        
$this->resolver->singleton($id$this->builders->getBuilder($id$implementation$afterBuildMethods));
    }

    
/**
     * Returns a variable stored in the container.
     *
     * If the variable is a binding then the binding will be resolved before returning it.
     *
     * @param string     $key     The alias of the variable or binding to fetch.
     * @param mixed|null $default A default value to return if the variable is not set in the container.
     *
     * @return mixed The variable value or the resolved binding.
     * @throws ContainerException If there's an issue resolving the variable.
     *
     * @see Container::get()
     */
    
public function getVar($key$default null)
    {
        if (
$this->resolver->isBound($key)) {
            return 
$this->resolver->resolve($key);
        }

        return 
$default;
    }

    
/**
     * Finds an entry of the container by its identifier and returns it.
     *
     * @template T
     *
     * @param string|class-string<T> $offset Identifier of the entry to look for.
     *
     * @return T|mixed The value for the offset.
     * @phpstan-return ($offset is class-string ? T : mixed)
     *
     * @throws ContainerException Error while retrieving the entry.
     * @throws NotFoundException  No entry was found for **this** identifier.
     */
    
#[ReturnTypeWillChange]
    public function 
offsetGet($offset)
    {
        return 
$this->get($offset);
    }

    
/**
     * Finds an entry of the container by its identifier and returns it.
     *
     * @template T
     *
     * @param  string|class-string<T>  $id  A fully qualified class or interface name or an already built object.
     *
     * @return T|mixed The entry for an id.
     * @phpstan-return ($id is class-string ? T : mixed)
     *
     * @throws ContainerException Error while retrieving the entry.
     */
    
public function get($id)
    {
        try {
            return 
$this->resolver->resolve($id, [$id]);
        } catch (
Throwable $throwable) {
            throw 
$this->castThrown($throwable$id);
            
// @codeCoverageIgnoreStart
        
} catch (Exception $exception) { // @phan-suppress-current-line PhanUnreachableCatch @phpstan-ignore-line
            
throw $this->castThrown($exception$id);
            
// @codeCoverageIgnoreEnd
        
}
    }

    
/**
     * Builds an instance of the exception with a pretty message.
     *
     * @param Exception|Throwable $thrown The exception to cast.
     * @param string|object $id The top identifier the containe was attempting to build, or object.
     *
     * @return ContainerException|Exception|Throwable The cast exception.
     */
    
private function castThrown($thrown$id)
    {
        if (
$this->maskThrowables === self::EXCEPTION_MASK_NONE) {
            return 
$thrown;
        }

        return 
ContainerException::fromThrowable(
            
$id,
            
$thrown,
            
$this->maskThrowables,
            
$this->resolver->getBuildLine()
        );
    }

    
/**
     * Returns an instance of the class or object bound to an interface, class  or string slug if any, else it will try
     * to automagically resolve the object to a usable instance.
     *
     * If the implementation has been bound as singleton using the `singleton` method
     * or the ArrayAccess API then the implementation will be resolved just on the first request.
     *
     * @template T
     *
     * @param string|class-string<T> $id A fully qualified class or interface name or an already built object.
     *
     * @return T|mixed
     * @phpstan-return ($id is class-string ? T : mixed)
     *
     * @throws ContainerException If the target of the make is not bound and is not a valid,
     *                                              concrete, class name or there's any issue making the target.
     */
    
public function make($id)
    {
        return 
$this->get($id);
    }

    
/**
     * Returns true if the container can return an entry for the given identifier.
     * Returns false otherwise.
     *
     * `$container[$id]` returning true does not mean that `$container[$id]` will not throw an exception.
     * It does however mean that `$container[$id]` will not throw a `NotFoundExceptionInterface`.
     *
     * @param string|class-string $offset An offset to check for.
     *
     * @return boolean true on success or false on failure.
     */
    
#[ReturnTypeWillChange]
    public function 
offsetExists($offset)
    {
        return 
$this->has($offset);
    }

    
/**
     * Returns true if the container can return an entry for the given identifier.
     * Returns false otherwise.
     *
     * `has($id)` returning true does not mean that `get($id)` will not throw an exception.
     * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
     *
     * @param string|class-string $id Identifier of the entry to look for.
     *
     * @return bool Whether the container contains a binding for an id or not.
     */
    
public function has($id)
    {
        return 
$this->resolver->isBound($id) || class_exists($id);
    }

    
/**
     * Tags an array of implementations bindings for later retrieval.
     *
     * The implementations can also reference interfaces, classes or string slugs.
     * Example:
     *
     *        $container->tag(['Posts', 'Users', 'Comments'], 'endpoints');
     *
     * @param array<string|callable|object> $implementationsArray The ids, class names or objects to apply the tag to.
     * @param string                        $tag                  The tag to apply.
     *
     * @return void This method does not return any value.
     * @see Container::tagged()
     *
     */
    
public function tag(array $implementationsArray$tag)
    {
        
$this->tags[$tag] = $implementationsArray;
    }

    
/**
     * Retrieves an array of bound implementations resolving them.
     *
     * The array of implementations should be bound using the `tag` method:
     *
     *        $container->tag(['Posts', 'Users', 'Comments'], 'endpoints');
     *        foreach($container->tagged('endpoints') as $endpoint){
     *            $endpoint->register();
     *        }
     *
     * @param string $tag The tag to return the tagged values for.
     *
     * @return array<mixed> An array of resolved bound implementations.
     * @throws NotFoundException If nothing is tagged with the tag.
     * @throws ContainerException If one of the bindings is not of the correct type.
     * @see Container::tag()
     */
    
public function tagged($tag)
    {
        if (!
$this->hasTag($tag)) {
            throw new 
NotFoundException("Nothing is tagged as '{$tag}'");
        }

        return 
array_map(
            function (
$id) {
                if (
is_string($id)) {
                    return 
$this->get($id);
                }
                return 
$this->builders->getBuilder($id)->build();
            },
            
$this->tags[$tag]
        );
    }

    
/**
     * Checks whether a tag group exists in the container.
     *
     * @param string $tag
     *
     * @return bool
     * @see Container::tag()
     *
     */
    
public function hasTag($tag)
    {
        return isset(
$this->tags[$tag]);
    }

    
/**
     * A wrapper around the `class_exists` function to capture and handle possible fatal errors on PHP 7.0+.
     *
     * @param string|class-string $class The class name to check.
     *
     * @return bool Whether the class exists or not.
     *
     * @throws ContainerException|ReflectionException If the class has syntax or other errors preventing its load.
     */
    
protected function classIsInstantiable($class)
    {
        if (isset(
$this->classIsInstantiatableCache[$class])) {
            return 
$this->classIsInstantiatableCache[$class];
        }

        
// @codeCoverageIgnoreStart
        
if (PHP_VERSION_ID 70000) {
            
$isInstantiatable $this->checkClassIsInstantiatable($class);
            
$this->classIsInstantiatableCache[$class] = $isInstantiatable;
            return 
$isInstantiatable;
        }
        
// @codeCoverageIgnoreEnd

        // PHP 7.0+ allows handling fatal errors; x_exists will trigger auto-loading, that might result in an error.
        
try {
            
$isInstantiatable $this->checkClassIsInstantiatable($class);
            
$this->classIsInstantiatableCache[$class] = $isInstantiatable;
            return 
$isInstantiatable;
        } catch (
Throwable $e) {
            
$this->classIsInstantiatableCache[$class] = false;
            throw new 
ContainerException($e->getMessage());
        }
    }

    
/**
     * Checks a class, interface or trait exists.
     *
     * @param string|class-string $class The class, interface or trait to check.
     *
     * @return bool Whether the class, interface or trait exists or not.
     * @throws ReflectionException If the class should be checked for concreteness and it does not exist.
     */
    
protected function checkClassIsInstantiatable($class)
    {
        
$exists class_exists($class);
        if (!
$exists) {
            return 
false;
        }
        
$classReflection = new ReflectionClass($class);
        if (
$classReflection->isAbstract()) {
            return 
false;
        }
        
$constructor $classReflection->getConstructor();
        if (
$constructor === null) {
            return 
true;
        }
        return 
$constructor->isPublic();
    }

    
/**
     * Registers a service provider implementation.
     *
     * The `register` method will be called immediately on the service provider.
     *
     * If the provider overloads the  `isDeferred` method returning a truthy value then the `register` method will be
     * called only if one of the implementations provided by the provider is requested. The container defines which
     * implementations is offering overloading the `provides` method; the method should return an array of provided
     * implementations.
     *
     * If a provider overloads the `boot` method that method will be called when the `boot` method is called on the
     * container itself.
     *
     * @param class-string $serviceProviderClass The fully-qualified Service Provider class name.
     * @param string ...$alias             A list of aliases the provider should be registered with.
     * @return void This method does not return any value.
     * @throws ContainerException If the Service Provider is not correctly configured or there's an issue
     *                                     reflecting on it.
     * @see ServiceProvider::register()
     * @see ServiceProvider::isDeferred()
     * @see ServiceProvider::provides()
     * @see Container::getProvider()
     * @see ServiceProvider::boot()
     */
    
public function register($serviceProviderClass, ...$alias)
    {
        
/** @var ServiceProvider $provider */
        
$provider $this->get($serviceProviderClass);
        if (!
$provider->isDeferred()) {
            
$provider->register();
        } else {
            
$provided $provider->provides();
            if (!
is_array($provided) || count($provided) === 0) {
                throw new 
ContainerException(
                    
"Service provider '{$serviceProviderClass}' is marked as deferred" .
                    
" but is not providing any implementation."
                
);
            }
            foreach (
$provided as $id) {
                
$this->resolver->bind(
                    
$id,
                    
$this->builders->getBuilder($this->getDeferredProviderMakeClosure($provider$id))
                );
            }
        }

        try {
            
$bootMethod = new ReflectionMethod($provider'boot');
        } catch (
ReflectionException $e) {
            throw new 
ContainerException('Could not reflect on the provider boot method.');
        }

        
$requiresBoot = ($bootMethod->getDeclaringClass()->getName() === get_class($provider));
        if (
$requiresBoot) {
            
$this->bootable[] = $provider;
        }
        
$this->resolver->singleton($serviceProviderClass, new ValueBuilder($provider));
        foreach (
$alias as $a) {
            
$this->resolver->singleton($a, new ValueBuilder($provider));
        }
    }

    
/**
     * Returns a closure that will build a provider on demand, if an implementation provided by the provider is
     * required.
     *
     * @param ServiceProvider $provider The provider instance to register.
     * @param string          $id       The id of the implementation to bind.
     *
     * @return Closure A Closure ready to be bound to the id as implementation.
     */
    
private function getDeferredProviderMakeClosure(ServiceProvider $provider$id)
    {
        return function () use (
$provider$id) {
            static 
$registered;
            if (
$registered === null) {
                
$provider->register();
                
$registered true;
            }

            return 
$this->get($id);
        };
    }

    
/**
     * Binds an interface, a class or a string slug to an implementation.
     *
     * Existing implementations are replaced.
     *
     * @param  string|class-string  $id                 A class or interface fully qualified name or a string slug.
     * @param  mixed                $implementation     The implementation that should be bound to the alias(es); can
     *                                                  be a class name, an object or a closure.
     * @param  string[]|null        $afterBuildMethods  An array of methods that should be called on the built
     *                                                  implementation after resolving it.
     *
     * @return void The method does not return any value.
     *
     * @throws ContainerException      If there's an issue while trying to bind the implementation.
     */
    
public function bind($id$implementation null, array $afterBuildMethods null)
    {
        if (
$implementation === null) {
            
$implementation $id;
        }
        if (
$implementation === $id && !$this->classIsInstantiable($implementation)) {
            throw new 
NotFoundException("Class {$implementation} does not exist.");
        }
        
$this->resolver->bind($id$this->builders->getBuilder($id$implementation$afterBuildMethods));
    }

    
/**
     * Boots up the application calling the `boot` method of each registered service provider.
     *
     * If there are bootable providers (providers overloading the `boot` method) then the `boot` method will be
     * called on each bootable provider.
     *
     * @return void This method does not return any value.
     *
     * @see ServiceProvider::boot()
     */
    
public function boot()
    {
        if (!empty(
$this->bootable)) {
            foreach (
$this->bootable as $provider) {
                
/** @var ServiceProvider $provider */
                
$provider->boot();
            }
        }
    }

    
/**
     * Binds a class, interface or string slug to a chain of implementations decorating a base
     * object; the chain will be lazily resolved only on the first call.
     * The base decorated object must be the last element of the array.
     *
     * @param  string|class-string            $id                 The class, interface or slug the decorator chain
     *                                                            should be bound to.
     * @param  array<string|object|callable>  $decorators         An array of implementations that decorate an object.
     * @param  string[]|null                  $afterBuildMethods  An array of methods that should be called on the
     *                                                            instance after it has been built; the methods should
     *                                                            not require any argument.
     * @param bool $afterBuildAll                                 Whether to call the after build methods on only the
     *                                                            base instance or all instances of the decorator chain.
     *
     * @return void This method does not return any value.
     * @throws ContainerException
     */
    
public function singletonDecorators($id$decorators, array $afterBuildMethods null$afterBuildAll false)
    {
        
$this->resolver->singleton(
            
$id,
            
$this->getDecoratorBuilder($decorators$id$afterBuildMethods$afterBuildAll)
        );
    }

    
/**
     * Builds and returns a closure that will start building the chain of decorators.
     *
     * @param array<string|object|callable> $decorators        A list of decorators.
     * @param string                        $id                The id to bind the decorator tail to.
     * @param array<string>|null            $afterBuildMethods A set of method to run on the built decorated instance
     *                                                         after it's built.
     * @param bool $afterBuildAll                              Whether to run the after build methods only on the base
     *                                                         instance (default, false) or on all instances of the
     *                                                         decorator chain.
     *
     * @return BuilderInterface The callable or Closure that will start building the decorator chain.
     *
     * @throws ContainerException If there's any issue while trying to register any decorator step.
     */
    
private function getDecoratorBuilder(
        array 
$decorators,
        
$id,
        array 
$afterBuildMethods null,
        
$afterBuildAll false
    
) {
        
$decorator array_pop($decorators);

        if (
$decorator === null) {
            throw new 
ContainerException('The decorator chain cannot be empty.');
        }

        do {
            
$previous = isset($builder) ? $builder null;
            
$builder $this->builders->getBuilder($id$decorator$afterBuildMethods$previous);
            
$decorator array_pop($decorators);
            if (!
$afterBuildAll) {
                
$afterBuildMethods = [];
            }
        } while (
$decorator !== null);

        return 
$builder;
    }

    
/**
     * Binds a class, interface or string slug to a chain of implementations decorating a
     * base object.
     *
     * The base decorated object must be the last element of the array.
     *
     * @param  string|class-string            $id                 The class, interface or slug the decorator chain
     *                                                            should be bound to.
     * @param  array<string|object|callable>  $decorators         An array of implementations that decorate an object.
     * @param  string[]|null                  $afterBuildMethods  An array of methods that should be called on the
     *                                                            base instance after it has been built; the methods
     *                                                            should not require any argument.
     * @param bool $afterBuildAll                                 Whether to call the after build methods on only the
     *                                                            base instance or all instances of the decorator chain.
     *
     * @return void This method does not return any value.
     * @throws ContainerException If there's any issue binding the decorators.
     */
    
public function bindDecorators($id, array $decorators, array $afterBuildMethods null$afterBuildAll false)
    {
        
$this->resolver->bind($id$this->getDecoratorBuilder($decorators$id$afterBuildMethods$afterBuildAll));
    }

    
/**
     * Unsets a binding or tag in the container.
     *
     * @param mixed $offset The offset to unset.
     *
     * @return void The method does not return any value.
     */
    
#[ReturnTypeWillChange]
    public function 
offsetUnset($offset)
    {
        if (!
is_string($offset)) {
            return;
        }
        
$this->resolver->unbind($offset);
        unset(
$this->tags[$offset]);
    }

    
/**
     * Starts the `when->needs->give` chain for a contextual binding.
     *
     * @param string|class-string $class The fully qualified name of the requesting class.
     *
     * Example:
     *
     *      // Any class requesting an implementation of `LoggerInterface` will receive this implementation ...
     *      $container->singleton('LoggerInterface', 'FilesystemLogger');
     *      // But if the requesting class is `Worker` return another implementation
     *      $container->when('Worker')
     *          ->needs('LoggerInterface')
     *          ->give('RemoteLogger');
     *
     * @return Container The container instance, to continue the when/needs/give chain.
     */
    
public function when($class)
    {
        
$this->whenClass $class;

        return 
$this;
    }

    
/**
     * Second step of the `when->needs->give` chain for a contextual binding.
     *
     * Example:
     *
     *      // Any class requesting an implementation of `LoggerInterface` will receive this implementation ...
     *      $container->singleton('LoggerInterface', 'FilesystemLogger');
     *      // But if the requesting class is `Worker` return another implementation.
     *      $container->when('Worker')
     *          ->needs('LoggerInterface')
     *          ->give('RemoteLogger');
     *
     * @param string|class-string $id The class or interface needed by the class.
     *
     * @return Container The container instance, to continue the when/needs/give chain.
     */
    
public function needs($id)
    {
        
$this->needsClass $id;

        return 
$this;
    }

    
/**
     * Third step of the `when->needs->give` chain for a contextual binding.
     *
     * Example:
     *
     *      // any class requesting an implementation of `LoggerInterface` will receive this implementation...
     *      $container->singleton('LoggerInterface', 'FilesystemLogger');
     *      // but if the requesting class is `Worker` return another implementation
     *      $container->when('Worker')
     *          ->needs('LoggerInterface')
     *          ->give('RemoteLogger');
     *
     * @param mixed $implementation The implementation specified
     *
     * @return void This method does not return any value.
     * @throws NotFoundException
     */
    
public function give($implementation)
    {
        
$id "{$this->whenClass}::{$this->needsClass}";
        
$builder $this->builders->getBuilder($id$implementation);
        
$this->resolver->setWhenNeedsGive($this->whenClass$this->needsClass$builder);
        unset(
$this->whenClass$this->needsClass);
    }

    
/**
     * Returns a lambda function suitable to use as a callback; when called the function will build the implementation
     * bound to `$id` and return the value of a call to `$method` method with the call arguments.
     *
     * @param  string|class-string|object  $id      A fully-qualified class name, a bound slug or an object o call the
     *                                              callback on.
     * @param  string                      $method  The method that should be called on the resolved implementation
     *                                              with the specified array arguments.
     *
     * @return callable|Closure The callback function.
     * @throws ContainerException If the id is not a bound implementation or valid class name.
     */
    
public function callback($id$method)
    {
        
$callbackIdPrefix is_object($id) ? spl_object_hash($id) : $id;

        if (!
is_string($callbackIdPrefix)) {
            
$typeOfId gettype($id);
            throw new 
ContainerException(
                
"Callbacks can only be built on ids, class names or objects; '{$typeOfId}' is neither."
            
);
        }

        if (!
is_string($method)) {
            throw new 
ContainerException("Callbacks second argument must be a string method name.");
        }

        
$callbackId $callbackIdPrefix '::' $method;

        if (isset(
$this->callbacks[$callbackId])) {
            return 
$this->callbacks[$callbackId];
        }

        
$callbackClosure = function (...$args) use ($id$method) {
            
$instance is_string($id) ?
                
$this->resolver->resolve($id)
                : 
$this->builders->getBuilder($id)->build();
            return 
$instance->{$method}(...$args);
        };

        if (
is_string($id) && ($this->resolver->isSingleton($id) || $this->isStaticMethod($id$method))) {
            
// If we can know immediately, without actually resolving the binding, then build and cache immediately.
            
$this->callbacks[$callbackId] = $callbackClosure;
        }

        return 
$callbackClosure;
    }

    
/**
     * Whether a method of an id, possibly not a class, is static or not.
     *
     * @param  object|string|class-string  $object  A class name, instance or something that does not map to a class.
     * @param  string                      $method  The method to check.
     *
     * @return bool Whether a method of an id or class is static or not.
     */
    
protected function isStaticMethod($object$method)
    {
        
$key is_string($object) ? $object '::' $method get_class($object) . '::' $method;

        if (!isset(
$this->isStaticMethodCache[$key])) {
            try {
                
$this->isStaticMethodCache[$key] = (new ReflectionMethod($object$method))->isStatic();
            } catch (
ReflectionException $e) {
                return 
false;
            }
        }

        return 
$this->isStaticMethodCache[$key];
    }

    
/**
     * Returns a callable object that will build an instance of the specified class using the
     * specified arguments when called.
     * The callable will be a closure on PHP 5.3+ or a lambda function on PHP 5.2.
     *
     * @param  string|class-string|mixed  $id                 The fully qualified name of a class or an interface.
     * @param  array<mixed>               $buildArgs          An array of arguments that should be used to build the
     *                                                        instance; note that any argument will be resolved using
     *                                                        the container itself and bindings will apply.
     * @param  string[]|null              $afterBuildMethods  An array of methods that should be called on the built
     *                                                        implementation after resolving it.
     *
     * @return callable|Closure  A callable function that will return an instance of the specified class when
     *                   called.
     */
    
public function instance($id, array $buildArgs = [], array $afterBuildMethods null)
    {
        return function () use (
$id$afterBuildMethods$buildArgs) {
            if (
is_string($id)) {
                return 
$this->resolver->resolveWithArgs($id$afterBuildMethods, ...$buildArgs);
            }

            return 
$this->builders->getBuilder($id$id$afterBuildMethods, ...$buildArgs)->build();
        };
    }

    
/**
     * Protects a value to make sure it will not be resolved, if callable or if the name of an existing class.
     *
     * @param mixed $value The value to protect.
     *
     * @return ValueBuilder A protected value instance, its value set to the provided value.
     */
    
public function protect($value)
    {
        return new 
ValueBuilder($value);
    }

    
/**
     * Returns the Service Provider instance registered.
     *
     * @param string|class-string $providerId The Service Provider clas to return the instance for.
     *
     * @return ServiceProvider The service provider instance.
     *
     * @throws NotFoundException|ContainerException If the Service Provider class was never registered in the container
     *                                              or there's an issue retrieving it.
     */
    
public function getProvider($providerId)
    {
        if (!
$this->resolver->isBound($providerId)) {
            throw new 
NotFoundException("Service provider '$providerId' is not registered in the container.");
        }

        
$provider $this->get($providerId);

        if (! 
$provider instanceof ServiceProvider) {
            throw new 
NotFoundException("Bound implementation for '$providerId' is not Service Provider.");
        }

        return 
$provider;
    }

    
/**
     * Returns whether a binding exists in the container or not.
     *
     * `isBound($id)` returning `true` means the a call to `bind($id, $implementaion)` or `singleton($id,
     * $implementation)` (or equivalent ArrayAccess methods) was explicitly made.
     *
     * @param string $id The id to check for bindings in the container.
     *
     * @return bool Whether an explicit binding for the id exists in the container or not.
     */
    
public function isBound($id)
    {
        return 
is_string($id) && $this->resolver->isBound($id);
    }

    
/**
     * Sets the mask for the throwables that should be caught and re-thrown as container exceptions.
     *
     * @param int $maskThrowables The mask for the throwables that should be caught and re-thrown as container
     *
     * @return void
     */
    
public function setExceptionMask($maskThrowables)
    {
        
$this->maskThrowables = (int)$maskThrowables;
    }

    
/**
     * Binds the container to the base class name, the current class name and the container interface.
     *
     * @return void
     */
    
private function bindThis()
    {
        
$this->singleton(ContainerInterface::class, $this);
        
$this->singleton(Container::class, $this);
        if (
get_class($this) !== Container::class) {
            
$this->singleton(get_class($this), $this);
        }
    }

    
/**
     * Upon cloning, clones the resolver and builders instances.
     *
     * @return void
     */
    
public function __clone()
    {
        
$this->resolver = clone $this->resolver;
        
$this->builders = clone $this->builders;
        
$this->builders->setContainer($this);
        
$this->builders->setResolver($this->resolver);
        
$this->bindThis();
    }
}