You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

262 lines
9.1KB

  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\di;
  8. use Yii;
  9. use Closure;
  10. use yii\base\Component;
  11. use yii\base\InvalidConfigException;
  12. /**
  13. * ServiceLocator implements a [service locator](http://en.wikipedia.org/wiki/Service_locator_pattern).
  14. *
  15. * To use ServiceLocator, you first need to register component IDs with the corresponding component
  16. * definitions with the locator by calling [[set()]] or [[setComponents()]].
  17. * You can then call [[get()]] to retrieve a component with the specified ID. The locator will automatically
  18. * instantiate and configure the component according to the definition.
  19. *
  20. * For example,
  21. *
  22. * ```php
  23. * $locator = new \yii\di\ServiceLocator;
  24. * $locator->setComponents([
  25. * 'db' => [
  26. * 'class' => 'yii\db\Connection',
  27. * 'dsn' => 'sqlite:path/to/file.db',
  28. * ],
  29. * 'cache' => [
  30. * 'class' => 'yii\caching\DbCache',
  31. * 'db' => 'db',
  32. * ],
  33. * ]);
  34. *
  35. * $db = $locator->get('db'); // or $locator->db
  36. * $cache = $locator->get('cache'); // or $locator->cache
  37. * ```
  38. *
  39. * Because [[\yii\base\Module]] extends from ServiceLocator, modules and the application are all service locators.
  40. *
  41. * @property array $components The list of the component definitions or the loaded component instances (ID =>
  42. * definition or instance).
  43. *
  44. * @author Qiang Xue <qiang.xue@gmail.com>
  45. * @since 2.0
  46. */
  47. class ServiceLocator extends Component
  48. {
  49. /**
  50. * @var array shared component instances indexed by their IDs
  51. */
  52. private $_components = [];
  53. /**
  54. * @var array component definitions indexed by their IDs
  55. */
  56. private $_definitions = [];
  57. /**
  58. * Getter magic method.
  59. * This method is overridden to support accessing components like reading properties.
  60. * @param string $name component or property name
  61. * @return mixed the named property value
  62. */
  63. public function __get($name)
  64. {
  65. if ($this->has($name)) {
  66. return $this->get($name);
  67. } else {
  68. return parent::__get($name);
  69. }
  70. }
  71. /**
  72. * Checks if a property value is null.
  73. * This method overrides the parent implementation by checking if the named component is loaded.
  74. * @param string $name the property name or the event name
  75. * @return boolean whether the property value is null
  76. */
  77. public function __isset($name)
  78. {
  79. if ($this->has($name, true)) {
  80. return true;
  81. } else {
  82. return parent::__isset($name);
  83. }
  84. }
  85. /**
  86. * Returns a value indicating whether the locator has the specified component definition or has instantiated the component.
  87. * This method may return different results depending on the value of `$checkInstance`.
  88. *
  89. * - If `$checkInstance` is false (default), the method will return a value indicating whether the locator has the specified
  90. * component definition.
  91. * - If `$checkInstance` is true, the method will return a value indicating whether the locator has
  92. * instantiated the specified component.
  93. *
  94. * @param string $id component ID (e.g. `db`).
  95. * @param boolean $checkInstance whether the method should check if the component is shared and instantiated.
  96. * @return boolean whether the locator has the specified component definition or has instantiated the component.
  97. * @see set()
  98. */
  99. public function has($id, $checkInstance = false)
  100. {
  101. return $checkInstance ? isset($this->_components[$id]) : isset($this->_definitions[$id]);
  102. }
  103. /**
  104. * Returns the component instance with the specified ID.
  105. *
  106. * @param string $id component ID (e.g. `db`).
  107. * @param boolean $throwException whether to throw an exception if `$id` is not registered with the locator before.
  108. * @return object|null the component of the specified ID. If `$throwException` is false and `$id`
  109. * is not registered before, null will be returned.
  110. * @throws InvalidConfigException if `$id` refers to a nonexistent component ID
  111. * @see has()
  112. * @see set()
  113. */
  114. public function get($id, $throwException = true)
  115. {
  116. if (isset($this->_components[$id])) {
  117. return $this->_components[$id];
  118. }
  119. if (isset($this->_definitions[$id])) {
  120. $definition = $this->_definitions[$id];
  121. if (is_object($definition) && !$definition instanceof Closure) {
  122. return $this->_components[$id] = $definition;
  123. } else {
  124. return $this->_components[$id] = Yii::createObject($definition);
  125. }
  126. } elseif ($throwException) {
  127. throw new InvalidConfigException("Unknown component ID: $id");
  128. } else {
  129. return null;
  130. }
  131. }
  132. /**
  133. * Registers a component definition with this locator.
  134. *
  135. * For example,
  136. *
  137. * ```php
  138. * // a class name
  139. * $locator->set('cache', 'yii\caching\FileCache');
  140. *
  141. * // a configuration array
  142. * $locator->set('db', [
  143. * 'class' => 'yii\db\Connection',
  144. * 'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
  145. * 'username' => 'root',
  146. * 'password' => '',
  147. * 'charset' => 'utf8',
  148. * ]);
  149. *
  150. * // an anonymous function
  151. * $locator->set('cache', function ($params) {
  152. * return new \yii\caching\FileCache;
  153. * });
  154. *
  155. * // an instance
  156. * $locator->set('cache', new \yii\caching\FileCache);
  157. * ```
  158. *
  159. * If a component definition with the same ID already exists, it will be overwritten.
  160. *
  161. * @param string $id component ID (e.g. `db`).
  162. * @param mixed $definition the component definition to be registered with this locator.
  163. * It can be one of the following:
  164. *
  165. * - a class name
  166. * - a configuration array: the array contains name-value pairs that will be used to
  167. * initialize the property values of the newly created object when [[get()]] is called.
  168. * The `class` element is required and stands for the the class of the object to be created.
  169. * - a PHP callable: either an anonymous function or an array representing a class method (e.g. `['Foo', 'bar']`).
  170. * The callable will be called by [[get()]] to return an object associated with the specified component ID.
  171. * - an object: When [[get()]] is called, this object will be returned.
  172. *
  173. * @throws InvalidConfigException if the definition is an invalid configuration array
  174. */
  175. public function set($id, $definition)
  176. {
  177. if ($definition === null) {
  178. unset($this->_components[$id], $this->_definitions[$id]);
  179. return;
  180. }
  181. unset($this->_components[$id]);
  182. if (is_object($definition) || is_callable($definition, true)) {
  183. // an object, a class name, or a PHP callable
  184. $this->_definitions[$id] = $definition;
  185. } elseif (is_array($definition)) {
  186. // a configuration array
  187. if (isset($definition['class'])) {
  188. $this->_definitions[$id] = $definition;
  189. } else {
  190. throw new InvalidConfigException("The configuration for the \"$id\" component must contain a \"class\" element.");
  191. }
  192. } else {
  193. throw new InvalidConfigException("Unexpected configuration type for the \"$id\" component: " . gettype($definition));
  194. }
  195. }
  196. /**
  197. * Removes the component from the locator.
  198. * @param string $id the component ID
  199. */
  200. public function clear($id)
  201. {
  202. unset($this->_definitions[$id], $this->_components[$id]);
  203. }
  204. /**
  205. * Returns the list of the component definitions or the loaded component instances.
  206. * @param boolean $returnDefinitions whether to return component definitions instead of the loaded component instances.
  207. * @return array the list of the component definitions or the loaded component instances (ID => definition or instance).
  208. */
  209. public function getComponents($returnDefinitions = true)
  210. {
  211. return $returnDefinitions ? $this->_definitions : $this->_components;
  212. }
  213. /**
  214. * Registers a set of component definitions in this locator.
  215. *
  216. * This is the bulk version of [[set()]]. The parameter should be an array
  217. * whose keys are component IDs and values the corresponding component definitions.
  218. *
  219. * For more details on how to specify component IDs and definitions, please refer to [[set()]].
  220. *
  221. * If a component definition with the same ID already exists, it will be overwritten.
  222. *
  223. * The following is an example for registering two component definitions:
  224. *
  225. * ```php
  226. * [
  227. * 'db' => [
  228. * 'class' => 'yii\db\Connection',
  229. * 'dsn' => 'sqlite:path/to/file.db',
  230. * ],
  231. * 'cache' => [
  232. * 'class' => 'yii\caching\DbCache',
  233. * 'db' => 'db',
  234. * ],
  235. * ]
  236. * ```
  237. *
  238. * @param array $components component definitions or instances
  239. */
  240. public function setComponents($components)
  241. {
  242. foreach ($components as $id => $component) {
  243. $this->set($id, $component);
  244. }
  245. }
  246. }