Container.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * This file is part of Hyperf.
  5. *
  6. * @link https://www.hyperf.io
  7. * @document https://hyperf.wiki
  8. * @contact group@hyperf.io
  9. * @license https://github.com/hyperf/hyperf/blob/master/LICENSE
  10. */
  11. namespace Hyperf\Di;
  12. use Hyperf\Contract\ContainerInterface as HyperfContainerInterface;
  13. use Hyperf\Di\Definition\DefinitionInterface;
  14. use Hyperf\Di\Definition\ObjectDefinition;
  15. use Hyperf\Di\Exception\InvalidArgumentException;
  16. use Hyperf\Di\Exception\NotFoundException;
  17. use Hyperf\Di\Resolver\ResolverDispatcher;
  18. use Psr\Container\ContainerInterface as PsrContainerInterface;
  19. class Container implements HyperfContainerInterface
  20. {
  21. /**
  22. * Map of entries that are already resolved.
  23. *
  24. * @var array
  25. */
  26. private $resolvedEntries = [];
  27. /**
  28. * Map of definitions that are already fetched (local cache).
  29. */
  30. private $fetchedDefinitions = [];
  31. /**
  32. * @var Definition\DefinitionSourceInterface
  33. */
  34. private $definitionSource;
  35. /**
  36. * @var Resolver\ResolverInterface
  37. */
  38. private $definitionResolver;
  39. /**
  40. * Container constructor.
  41. */
  42. public function __construct(Definition\DefinitionSourceInterface $definitionSource)
  43. {
  44. $this->definitionSource = $definitionSource;
  45. $this->definitionResolver = new ResolverDispatcher($this);
  46. // Auto-register the container.
  47. $this->resolvedEntries = [
  48. self::class => $this,
  49. PsrContainerInterface::class => $this,
  50. HyperfContainerInterface::class => $this,
  51. ];
  52. }
  53. /**
  54. * Build an entry of the container by its name.
  55. * This method behave like get() except resolves the entry again every time.
  56. * For example if the entry is a class then a new instance will be created each time.
  57. * This method makes the container behave like a factory.
  58. *
  59. * @param string $name entry name or a class name
  60. * @param array $parameters Optional parameters to use to build the entry. Use this to force specific parameters
  61. * to specific values. Parameters not defined in this array will be resolved using
  62. * the container.
  63. * @throws NotFoundException no entry found for the given name
  64. * @throws InvalidArgumentException the name parameter must be of type string
  65. */
  66. public function make(string $name, array $parameters = [])
  67. {
  68. $definition = $this->getDefinition($name);
  69. if (! $definition) {
  70. throw new NotFoundException("No entry or class found for '{$name}'");
  71. }
  72. return $this->resolveDefinition($definition, $parameters);
  73. }
  74. /**
  75. * Bind an arbitrary resolved entry to an identifier.
  76. * Useful for testing 'get'.
  77. * @param mixed $entry
  78. */
  79. public function set(string $name, $entry)
  80. {
  81. $this->resolvedEntries[$name] = $entry;
  82. }
  83. /**
  84. * Unbind an arbitrary resolved entry.
  85. */
  86. public function unbind(string $name)
  87. {
  88. if ($this->has($name)) {
  89. unset($this->resolvedEntries[$name]);
  90. }
  91. }
  92. /**
  93. * Bind an arbitrary definition to an identifier.
  94. * Useful for testing 'make'.
  95. *
  96. * @param array|callable|string $definition
  97. */
  98. public function define(string $name, $definition)
  99. {
  100. $this->setDefinition($name, $definition);
  101. }
  102. /**
  103. * Finds an entry of the container by its identifier and returns it.
  104. *
  105. * @param string $name identifier of the entry to look for
  106. */
  107. public function get($name)
  108. {
  109. // If the entry is already resolved we return it
  110. if (isset($this->resolvedEntries[$name]) || array_key_exists($name, $this->resolvedEntries)) {
  111. return $this->resolvedEntries[$name];
  112. }
  113. return $this->resolvedEntries[$name] = $this->make($name);
  114. }
  115. /**
  116. * Returns true if the container can return an entry for the given identifier.
  117. * Returns false otherwise.
  118. * `has($name)` returning true does not mean that `get($name)` will not throw an exception.
  119. * It does however mean that `get($name)` will not throw a `NotFoundExceptionInterface`.
  120. *
  121. * @param mixed|string $name identifier of the entry to look for
  122. */
  123. public function has($name): bool
  124. {
  125. if (! is_string($name)) {
  126. throw new InvalidArgumentException(sprintf('The name parameter must be of type string, %s given', is_object($name) ? get_class($name) : gettype($name)));
  127. }
  128. if (array_key_exists($name, $this->resolvedEntries)) {
  129. return true;
  130. }
  131. $definition = $this->getDefinition($name);
  132. if ($definition === null) {
  133. return false;
  134. }
  135. if ($definition instanceof ObjectDefinition) {
  136. return $definition->isInstantiable();
  137. }
  138. return true;
  139. }
  140. public function getDefinitionSource(): Definition\DefinitionSourceInterface
  141. {
  142. return $this->definitionSource;
  143. }
  144. /**
  145. * @param array|callable|string $definition
  146. */
  147. private function setDefinition(string $name, $definition): void
  148. {
  149. // Clear existing entry if it exists
  150. if (array_key_exists($name, $this->resolvedEntries)) {
  151. unset($this->resolvedEntries[$name]);
  152. }
  153. $this->fetchedDefinitions = []; // Completely clear this local cache
  154. $this->definitionSource->addDefinition($name, $definition);
  155. }
  156. private function getDefinition(string $name): ?DefinitionInterface
  157. {
  158. // Local cache that avoids fetching the same definition twice
  159. if (! array_key_exists($name, $this->fetchedDefinitions)) {
  160. $this->fetchedDefinitions[$name] = $this->definitionSource->getDefinition($name);
  161. }
  162. return $this->fetchedDefinitions[$name];
  163. }
  164. /**
  165. * Resolves a definition.
  166. */
  167. private function resolveDefinition(DefinitionInterface $definition, array $parameters = [])
  168. {
  169. return $this->definitionResolver->resolve($definition, $parameters);
  170. }
  171. }