| 1 | efrain | 1 | <?php
 | 
        
           |  |  | 2 |   | 
        
           |  |  | 3 | declare(strict_types=1);
 | 
        
           |  |  | 4 |   | 
        
           |  |  | 5 | namespace DI;
 | 
        
           |  |  | 6 |   | 
        
           |  |  | 7 | use DI\Compiler\RequestedEntryHolder;
 | 
        
           |  |  | 8 | use DI\Definition\Definition;
 | 
        
           |  |  | 9 | use DI\Definition\Exception\InvalidDefinition;
 | 
        
           |  |  | 10 | use DI\Invoker\FactoryParameterResolver;
 | 
        
           |  |  | 11 | use Invoker\Exception\NotCallableException;
 | 
        
           |  |  | 12 | use Invoker\Exception\NotEnoughParametersException;
 | 
        
           |  |  | 13 | use Invoker\Invoker;
 | 
        
           |  |  | 14 | use Invoker\InvokerInterface;
 | 
        
           |  |  | 15 | use Invoker\ParameterResolver\AssociativeArrayResolver;
 | 
        
           |  |  | 16 | use Invoker\ParameterResolver\DefaultValueResolver;
 | 
        
           |  |  | 17 | use Invoker\ParameterResolver\NumericArrayResolver;
 | 
        
           |  |  | 18 | use Invoker\ParameterResolver\ResolverChain;
 | 
        
           |  |  | 19 |   | 
        
           |  |  | 20 | /**
 | 
        
           |  |  | 21 |  * Compiled version of the dependency injection container.
 | 
        
           |  |  | 22 |  *
 | 
        
           |  |  | 23 |  * @author Matthieu Napoli <matthieu@mnapoli.fr>
 | 
        
           |  |  | 24 |  */
 | 
        
           |  |  | 25 | abstract class CompiledContainer extends Container
 | 
        
           |  |  | 26 | {
 | 
        
           |  |  | 27 |     /**
 | 
        
           |  |  | 28 |      * This const is overridden in child classes (compiled containers).
 | 
        
           |  |  | 29 |      * @var array
 | 
        
           |  |  | 30 |      */
 | 
        
           |  |  | 31 |     protected const METHOD_MAPPING = [];
 | 
        
           |  |  | 32 |   | 
        
           |  |  | 33 |     private ?InvokerInterface $factoryInvoker = null;
 | 
        
           |  |  | 34 |   | 
        
           |  |  | 35 |     public function get(string $id) : mixed
 | 
        
           |  |  | 36 |     {
 | 
        
           |  |  | 37 |         // Try to find the entry in the singleton map
 | 
        
           |  |  | 38 |         if (isset($this->resolvedEntries[$id]) || array_key_exists($id, $this->resolvedEntries)) {
 | 
        
           |  |  | 39 |             return $this->resolvedEntries[$id];
 | 
        
           |  |  | 40 |         }
 | 
        
           |  |  | 41 |   | 
        
           |  |  | 42 |         /** @psalm-suppress UndefinedConstant */
 | 
        
           |  |  | 43 |         $method = static::METHOD_MAPPING[$id] ?? null;
 | 
        
           |  |  | 44 |   | 
        
           |  |  | 45 |         // If it's a compiled entry, then there is a method in this class
 | 
        
           |  |  | 46 |         if ($method !== null) {
 | 
        
           |  |  | 47 |             // Check if we are already getting this entry -> circular dependency
 | 
        
           |  |  | 48 |             if (isset($this->entriesBeingResolved[$id])) {
 | 
        
           | 1441 | ariadna | 49 |                 $idList = implode(" -> ", [...array_keys($this->entriesBeingResolved), $id]);
 | 
        
           |  |  | 50 |                 throw new DependencyException("Circular dependency detected while trying to resolve entry '$id': Dependencies: " . $idList);
 | 
        
           | 1 | efrain | 51 |             }
 | 
        
           |  |  | 52 |             $this->entriesBeingResolved[$id] = true;
 | 
        
           |  |  | 53 |   | 
        
           |  |  | 54 |             try {
 | 
        
           |  |  | 55 |                 $value = $this->$method();
 | 
        
           |  |  | 56 |             } finally {
 | 
        
           |  |  | 57 |                 unset($this->entriesBeingResolved[$id]);
 | 
        
           |  |  | 58 |             }
 | 
        
           |  |  | 59 |   | 
        
           |  |  | 60 |             // Store the entry to always return it without recomputing it
 | 
        
           |  |  | 61 |             $this->resolvedEntries[$id] = $value;
 | 
        
           |  |  | 62 |   | 
        
           |  |  | 63 |             return $value;
 | 
        
           |  |  | 64 |         }
 | 
        
           |  |  | 65 |   | 
        
           |  |  | 66 |         return parent::get($id);
 | 
        
           |  |  | 67 |     }
 | 
        
           |  |  | 68 |   | 
        
           |  |  | 69 |     public function has(string $id) : bool
 | 
        
           |  |  | 70 |     {
 | 
        
           |  |  | 71 |         // The parent method is overridden to check in our array, it avoids resolving definitions
 | 
        
           |  |  | 72 |         /** @psalm-suppress UndefinedConstant */
 | 
        
           |  |  | 73 |         if (isset(static::METHOD_MAPPING[$id])) {
 | 
        
           |  |  | 74 |             return true;
 | 
        
           |  |  | 75 |         }
 | 
        
           |  |  | 76 |   | 
        
           |  |  | 77 |         return parent::has($id);
 | 
        
           |  |  | 78 |     }
 | 
        
           |  |  | 79 |   | 
        
           |  |  | 80 |     protected function setDefinition(string $name, Definition $definition) : void
 | 
        
           |  |  | 81 |     {
 | 
        
           |  |  | 82 |         // It needs to be forbidden because that would mean get() must go through the definitions
 | 
        
           |  |  | 83 |         // every time, which kinds of defeats the performance gains of the compiled container
 | 
        
           |  |  | 84 |         throw new \LogicException('You cannot set a definition at runtime on a compiled container. You can either put your definitions in a file, disable compilation or ->set() a raw value directly (PHP object, string, int, ...) instead of a PHP-DI definition.');
 | 
        
           |  |  | 85 |     }
 | 
        
           |  |  | 86 |   | 
        
           |  |  | 87 |     /**
 | 
        
           |  |  | 88 |      * Invoke the given callable.
 | 
        
           |  |  | 89 |      */
 | 
        
           |  |  | 90 |     protected function resolveFactory($callable, $entryName, array $extraParameters = []) : mixed
 | 
        
           |  |  | 91 |     {
 | 
        
           |  |  | 92 |         // Initialize the factory resolver
 | 
        
           |  |  | 93 |         if (! $this->factoryInvoker) {
 | 
        
           |  |  | 94 |             $parameterResolver = new ResolverChain([
 | 
        
           |  |  | 95 |                 new AssociativeArrayResolver,
 | 
        
           |  |  | 96 |                 new FactoryParameterResolver($this->delegateContainer),
 | 
        
           |  |  | 97 |                 new NumericArrayResolver,
 | 
        
           |  |  | 98 |                 new DefaultValueResolver,
 | 
        
           |  |  | 99 |             ]);
 | 
        
           |  |  | 100 |   | 
        
           |  |  | 101 |             $this->factoryInvoker = new Invoker($parameterResolver, $this->delegateContainer);
 | 
        
           |  |  | 102 |         }
 | 
        
           |  |  | 103 |   | 
        
           |  |  | 104 |         $parameters = [$this->delegateContainer, new RequestedEntryHolder($entryName)];
 | 
        
           |  |  | 105 |   | 
        
           |  |  | 106 |         $parameters = array_merge($parameters, $extraParameters);
 | 
        
           |  |  | 107 |   | 
        
           |  |  | 108 |         try {
 | 
        
           |  |  | 109 |             return $this->factoryInvoker->call($callable, $parameters);
 | 
        
           |  |  | 110 |         } catch (NotCallableException $e) {
 | 
        
           |  |  | 111 |             throw new InvalidDefinition("Entry \"$entryName\" cannot be resolved: factory " . $e->getMessage());
 | 
        
           |  |  | 112 |         } catch (NotEnoughParametersException $e) {
 | 
        
           |  |  | 113 |             throw new InvalidDefinition("Entry \"$entryName\" cannot be resolved: " . $e->getMessage());
 | 
        
           |  |  | 114 |         }
 | 
        
           |  |  | 115 |     }
 | 
        
           |  |  | 116 | }
 |