PageRenderTime 60ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Joomla/DI/Container.php

https://github.com/piotr-cz/joomla-framework
PHP | 428 lines | 176 code | 48 blank | 204 comment | 15 complexity | a8c934ee497676c22012df0e7989bd62 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. /**
  3. * Part of the Joomla Framework DI Package
  4. *
  5. * @copyright Copyright (C) 2013 Open Source Matters, Inc. All rights reserved.
  6. * @license GNU General Public License version 2 or later; see LICENSE
  7. */
  8. namespace Joomla\DI;
  9. use Joomla\DI\Exception\DependencyResolutionException;
  10. /**
  11. * The Container class.
  12. *
  13. * @since 1.0
  14. */
  15. class Container
  16. {
  17. /**
  18. * Holds the key aliases.
  19. *
  20. * @var array $aliases
  21. * @since 1.0
  22. */
  23. protected $aliases = array();
  24. /**
  25. * Holds the shared instances.
  26. *
  27. * @var array $instances
  28. * @since 1.0
  29. */
  30. protected $instances = array();
  31. /**
  32. * Holds the keys, their callbacks, and whether or not
  33. * the item is meant to be a shared resource.
  34. *
  35. * @var array $dataStore
  36. * @since 1.0
  37. */
  38. protected $dataStore = array();
  39. /**
  40. * Parent for hierarchical containers.
  41. *
  42. * @var Container
  43. * @since 1.0
  44. */
  45. protected $parent;
  46. /**
  47. * Constructor for the DI Container
  48. *
  49. * @param Container $parent Parent for hierarchical containers.
  50. *
  51. * @since 1.0
  52. */
  53. public function __construct(Container $parent = null)
  54. {
  55. $this->parent = $parent;
  56. }
  57. /**
  58. * Create an alias for a given key for easy access.
  59. *
  60. * @param string $alias The alias name
  61. * @param string $key The key to alias
  62. *
  63. * @return Container This object for chaining.
  64. *
  65. * @since 1.0
  66. */
  67. public function alias($alias, $key)
  68. {
  69. $this->aliases[$alias] = $key;
  70. return $this;
  71. }
  72. /**
  73. * Search the aliases property for a matching alias key.
  74. *
  75. * @param string $key The key to search for.
  76. *
  77. * @return string
  78. *
  79. * @since 1.0
  80. */
  81. protected function resolveAlias($key)
  82. {
  83. if (isset($this->aliases[$key]))
  84. {
  85. return $this->aliases[$key];
  86. }
  87. return $key;
  88. }
  89. /**
  90. * Build an object of class $key;
  91. *
  92. * @param string $key The class name to build.
  93. * @param boolean $shared True to create a shared resource.
  94. *
  95. * @return mixed Instance of class specified by $key with all dependencies injected.
  96. * Returns an object if the class exists and false otherwise
  97. *
  98. * @since 1.0
  99. */
  100. public function buildObject($key, $shared = false)
  101. {
  102. try
  103. {
  104. $reflection = new \ReflectionClass($key);
  105. }
  106. catch (\ReflectionException $e)
  107. {
  108. return false;
  109. }
  110. $constructor = $reflection->getConstructor();
  111. // If there are no parameters, just return a new object.
  112. if (is_null($constructor))
  113. {
  114. $callback = function () use ($key) {
  115. return new $key;
  116. };
  117. }
  118. else
  119. {
  120. $newInstanceArgs = $this->getMethodArgs($constructor);
  121. // Create a callable for the dataStore
  122. $callback = function () use ($reflection, $newInstanceArgs) {
  123. return $reflection->newInstanceArgs($newInstanceArgs);
  124. };
  125. }
  126. return $this->set($key, $callback, $shared)->get($key);
  127. }
  128. /**
  129. * Convenience method for building a shared object.
  130. *
  131. * @param string $key The class name to build.
  132. *
  133. * @return object Instance of class specified by $key with all dependencies injected.
  134. *
  135. * @since 1.0
  136. */
  137. public function buildSharedObject($key)
  138. {
  139. return $this->buildObject($key, true);
  140. }
  141. /**
  142. * Create a child Container with a new property scope that
  143. * that has the ability to access the parent scope when resolving.
  144. *
  145. * @return Container This object for chaining.
  146. *
  147. * @since 1.0
  148. */
  149. public function createChild()
  150. {
  151. return new static($this);
  152. }
  153. /**
  154. * Extend a defined service Closure by wrapping the existing one with a new Closure. This
  155. * works very similar to a decorator pattern. Note that this only works on service Closures
  156. * that have been defined in the current Provider, not parent providers.
  157. *
  158. * @param string $key The unique identifier for the Closure or property.
  159. * @param \Closure $callable A Closure to wrap the original service Closure.
  160. *
  161. * @return void
  162. *
  163. * @since 1.0
  164. * @throws \InvalidArgumentException
  165. */
  166. public function extend($key, \Closure $callable)
  167. {
  168. $raw = $this->getRaw($key);
  169. if (is_null($raw))
  170. {
  171. throw new \InvalidArgumentException(sprintf('The requested key %s does not exist to extend.', $key));
  172. }
  173. $closure = function ($c) use($callable, $raw) {
  174. return $callable($raw['callback']($c), $c);
  175. };
  176. $this->set($key, $closure, $raw['shared']);
  177. }
  178. /**
  179. * Build an array of constructor parameters.
  180. *
  181. * @param \ReflectionMethod $method Method for which to build the argument array.
  182. *
  183. * @return array Array of arguments to pass to the method.
  184. *
  185. * @since 1.0
  186. * @throws DependencyResolutionException
  187. */
  188. protected function getMethodArgs(\ReflectionMethod $method)
  189. {
  190. $methodArgs = array();
  191. foreach ($method->getParameters() as $param)
  192. {
  193. $dependency = $param->getClass();
  194. $dependencyVarName = $param->getName();
  195. // If we have a dependency, that means it has been type-hinted.
  196. if (!is_null($dependency))
  197. {
  198. $dependencyClassName = $dependency->getName();
  199. // If the dependency class name is registered with this container or a parent, use it.
  200. if ($this->getRaw($dependencyClassName) !== null)
  201. {
  202. $depObject = $this->get($dependencyClassName);
  203. }
  204. else
  205. {
  206. $depObject = $this->buildObject($dependencyClassName);
  207. }
  208. if ($depObject instanceof $dependencyClassName)
  209. {
  210. $methodArgs[] = $depObject;
  211. continue;
  212. }
  213. }
  214. // Finally, if there is a default parameter, use it.
  215. if ($param->isOptional())
  216. {
  217. $methodArgs[] = $param->getDefaultValue();
  218. continue;
  219. }
  220. // Couldn't resolve dependency, and no default was provided.
  221. throw new DependencyResolutionException(sprintf('Could not resolve dependency: %s', $dependencyVarName));
  222. }
  223. return $methodArgs;
  224. }
  225. /**
  226. * Method to set the key and callback to the dataStore array.
  227. *
  228. * @param string $key Name of dataStore key to set.
  229. * @param mixed $value Callable function to run or string to retrive when requesting the specified $key.
  230. * @param boolean $shared True to create and store a shared instance.
  231. * @param boolean $protected True to protect this item from being overwritten. Useful for services.
  232. *
  233. * @return Container This object for chaining.
  234. *
  235. * @throws \OutOfBoundsException Thrown if the provided key is already set and is protected.
  236. *
  237. * @since 1.0
  238. */
  239. public function set($key, $value, $shared = false, $protected = false)
  240. {
  241. if (isset($this->dataStore[$key]) && $this->dataStore[$key]['protected'] === true)
  242. {
  243. throw new \OutOfBoundsException(sprintf('Key %s is protected and can\'t be overwritten.', $key));
  244. }
  245. // If the provided $value is not a closure, make it one now for easy resolution.
  246. if (!is_callable($value))
  247. {
  248. $value = function () use ($value) {
  249. return $value;
  250. };
  251. }
  252. $this->dataStore[$key] = array(
  253. 'callback' => $value,
  254. 'shared' => $shared,
  255. 'protected' => $protected
  256. );
  257. return $this;
  258. }
  259. /**
  260. * Convenience method for creating protected keys.
  261. *
  262. * @param string $key Name of dataStore key to set.
  263. * @param callable $callback Callable function to run when requesting the specified $key.
  264. * @param bool $shared True to create and store a shared instance.
  265. *
  266. * @return Container This object for chaining.
  267. *
  268. * @since 1.0
  269. */
  270. public function protect($key, $callback, $shared = false)
  271. {
  272. return $this->set($key, $callback, $shared, true);
  273. }
  274. /**
  275. * Convenience method for creating shared keys.
  276. *
  277. * @param string $key Name of dataStore key to set.
  278. * @param callable $callback Callable function to run when requesting the specified $key.
  279. * @param bool $protected True to create and store a shared instance.
  280. *
  281. * @return Container This object for chaining.
  282. *
  283. * @since 1.0
  284. */
  285. public function share($key, $callback, $protected = false)
  286. {
  287. return $this->set($key, $callback, true, $protected);
  288. }
  289. /**
  290. * Method to retrieve the results of running the $callback for the specified $key;
  291. *
  292. * @param string $key Name of the dataStore key to get.
  293. * @param boolean $forceNew True to force creation and return of a new instance.
  294. *
  295. * @return mixed Results of running the $callback for the specified $key.
  296. *
  297. * @since 1.0
  298. * @throws \InvalidArgumentException
  299. */
  300. public function get($key, $forceNew = false)
  301. {
  302. $raw = $this->getRaw($key);
  303. if (is_null($raw))
  304. {
  305. throw new \InvalidArgumentException(sprintf('Key %s has not been registered with the container.', $key));
  306. }
  307. if ($raw['shared'])
  308. {
  309. if (!isset($this->instances[$key]) || $forceNew)
  310. {
  311. $this->instances[$key] = $raw['callback']($this);
  312. }
  313. return $this->instances[$key];
  314. }
  315. return call_user_func($raw['callback'], $this);
  316. }
  317. /**
  318. * Method to check if specified dataStore key exists.
  319. *
  320. * @param string $key Name of the dataStore key to check.
  321. *
  322. * @return boolean True for success
  323. *
  324. * @since 1.0
  325. */
  326. public function exists($key)
  327. {
  328. return (bool) $this->getRaw($key);
  329. }
  330. /**
  331. * Get the raw data assigned to a key.
  332. *
  333. * @param string $key The key for which to get the stored item.
  334. *
  335. * @return mixed
  336. *
  337. * @since 1.0
  338. */
  339. protected function getRaw($key)
  340. {
  341. $key = $this->resolveAlias($key);
  342. if (isset($this->dataStore[$key]))
  343. {
  344. return $this->dataStore[$key];
  345. }
  346. elseif ($this->parent instanceof Container)
  347. {
  348. return $this->parent->getRaw($key);
  349. }
  350. return null;
  351. }
  352. /**
  353. * Method to force the container to return a new instance
  354. * of the results of the callback for requested $key.
  355. *
  356. * @param string $key Name of the dataStore key to get.
  357. *
  358. * @return mixed Results of running the $callback for the specified $key.
  359. *
  360. * @since 1.0
  361. */
  362. public function getNewInstance($key)
  363. {
  364. return $this->get($key, true);
  365. }
  366. /**
  367. * Register a service provider to the container.
  368. *
  369. * @param ServiceProviderInterface $provider The service provider to register.w
  370. *
  371. * @return Container This object for chaining.
  372. *
  373. * @since 1.0
  374. */
  375. public function registerServiceProvider(ServiceProviderInterface $provider)
  376. {
  377. $provider->register($this);
  378. return $this;
  379. }
  380. }