Container.php 5.7 KB

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