PageRenderTime 39ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Pimple/Container.php

http://github.com/fabpot/Pimple
PHP | 281 lines | 121 code | 36 blank | 124 comment | 21 complexity | cb9de613241475e892161982ea89ee99 MD5 | raw file
  1. <?php
  2. /*
  3. * This file is part of Pimple.
  4. *
  5. * Copyright (c) 2009 Fabien Potencier
  6. *
  7. * Permission is hereby granted, free of charge, to any person obtaining a copy
  8. * of this software and associated documentation files (the "Software"), to deal
  9. * in the Software without restriction, including without limitation the rights
  10. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. * copies of the Software, and to permit persons to whom the Software is furnished
  12. * to do so, subject to the following conditions:
  13. *
  14. * The above copyright notice and this permission notice shall be included in all
  15. * copies or substantial portions of the Software.
  16. *
  17. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  23. * THE SOFTWARE.
  24. */
  25. namespace Pimple;
  26. /**
  27. * Container main class.
  28. *
  29. * @author Fabien Potencier
  30. */
  31. class Container implements \ArrayAccess
  32. {
  33. private $values = array();
  34. private $factories;
  35. private $protected;
  36. private $frozen = array();
  37. private $raw = array();
  38. private $keys = array();
  39. /**
  40. * Instantiate the container.
  41. *
  42. * Objects and parameters can be passed as argument to the constructor.
  43. *
  44. * @param array $values The parameters or objects.
  45. */
  46. public function __construct(array $values = array())
  47. {
  48. $this->factories = new \SplObjectStorage();
  49. $this->protected = new \SplObjectStorage();
  50. foreach ($values as $key => $value) {
  51. $this->offsetSet($key, $value);
  52. }
  53. }
  54. /**
  55. * Sets a parameter or an object.
  56. *
  57. * Objects must be defined as Closures.
  58. *
  59. * Allowing any PHP callable leads to difficult to debug problems
  60. * as function names (strings) are callable (creating a function with
  61. * the same name as an existing parameter would break your container).
  62. *
  63. * @param string $id The unique identifier for the parameter or object
  64. * @param mixed $value The value of the parameter or a closure to define an object
  65. * @throws \RuntimeException Prevent override of a frozen service
  66. */
  67. public function offsetSet($id, $value)
  68. {
  69. if (isset($this->frozen[$id])) {
  70. throw new \RuntimeException(sprintf('Cannot override frozen service "%s".', $id));
  71. }
  72. $this->values[$id] = $value;
  73. $this->keys[$id] = true;
  74. }
  75. /**
  76. * Gets a parameter or an object.
  77. *
  78. * @param string $id The unique identifier for the parameter or object
  79. *
  80. * @return mixed The value of the parameter or an object
  81. *
  82. * @throws \InvalidArgumentException if the identifier is not defined
  83. */
  84. public function offsetGet($id)
  85. {
  86. if (!isset($this->keys[$id])) {
  87. throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id));
  88. }
  89. if (
  90. isset($this->raw[$id])
  91. || !is_object($this->values[$id])
  92. || isset($this->protected[$this->values[$id]])
  93. || !method_exists($this->values[$id], '__invoke')
  94. ) {
  95. return $this->values[$id];
  96. }
  97. if (isset($this->factories[$this->values[$id]])) {
  98. return $this->values[$id]($this);
  99. }
  100. $raw = $this->values[$id];
  101. $val = $this->values[$id] = $raw($this);
  102. $this->raw[$id] = $raw;
  103. $this->frozen[$id] = true;
  104. return $val;
  105. }
  106. /**
  107. * Checks if a parameter or an object is set.
  108. *
  109. * @param string $id The unique identifier for the parameter or object
  110. *
  111. * @return bool
  112. */
  113. public function offsetExists($id)
  114. {
  115. return isset($this->keys[$id]);
  116. }
  117. /**
  118. * Unsets a parameter or an object.
  119. *
  120. * @param string $id The unique identifier for the parameter or object
  121. */
  122. public function offsetUnset($id)
  123. {
  124. if (isset($this->keys[$id])) {
  125. if (is_object($this->values[$id])) {
  126. unset($this->factories[$this->values[$id]], $this->protected[$this->values[$id]]);
  127. }
  128. unset($this->values[$id], $this->frozen[$id], $this->raw[$id], $this->keys[$id]);
  129. }
  130. }
  131. /**
  132. * Marks a callable as being a factory service.
  133. *
  134. * @param callable $callable A service definition to be used as a factory
  135. *
  136. * @return callable The passed callable
  137. *
  138. * @throws \InvalidArgumentException Service definition has to be a closure of an invokable object
  139. */
  140. public function factory($callable)
  141. {
  142. if (!is_object($callable) || !method_exists($callable, '__invoke')) {
  143. throw new \InvalidArgumentException('Service definition is not a Closure or invokable object.');
  144. }
  145. $this->factories->attach($callable);
  146. return $callable;
  147. }
  148. /**
  149. * Protects a callable from being interpreted as a service.
  150. *
  151. * This is useful when you want to store a callable as a parameter.
  152. *
  153. * @param callable $callable A callable to protect from being evaluated
  154. *
  155. * @return callable The passed callable
  156. *
  157. * @throws \InvalidArgumentException Service definition has to be a closure of an invokable object
  158. */
  159. public function protect($callable)
  160. {
  161. if (!is_object($callable) || !method_exists($callable, '__invoke')) {
  162. throw new \InvalidArgumentException('Callable is not a Closure or invokable object.');
  163. }
  164. $this->protected->attach($callable);
  165. return $callable;
  166. }
  167. /**
  168. * Gets a parameter or the closure defining an object.
  169. *
  170. * @param string $id The unique identifier for the parameter or object
  171. *
  172. * @return mixed The value of the parameter or the closure defining an object
  173. *
  174. * @throws \InvalidArgumentException if the identifier is not defined
  175. */
  176. public function raw($id)
  177. {
  178. if (!isset($this->keys[$id])) {
  179. throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id));
  180. }
  181. if (isset($this->raw[$id])) {
  182. return $this->raw[$id];
  183. }
  184. return $this->values[$id];
  185. }
  186. /**
  187. * Extends an object definition.
  188. *
  189. * Useful when you want to extend an existing object definition,
  190. * without necessarily loading that object.
  191. *
  192. * @param string $id The unique identifier for the object
  193. * @param callable $callable A service definition to extend the original
  194. *
  195. * @return callable The wrapped callable
  196. *
  197. * @throws \InvalidArgumentException if the identifier is not defined or not a service definition
  198. */
  199. public function extend($id, $callable)
  200. {
  201. if (!isset($this->keys[$id])) {
  202. throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id));
  203. }
  204. if (!is_object($this->values[$id]) || !method_exists($this->values[$id], '__invoke')) {
  205. throw new \InvalidArgumentException(sprintf('Identifier "%s" does not contain an object definition.', $id));
  206. }
  207. if (!is_object($callable) || !method_exists($callable, '__invoke')) {
  208. throw new \InvalidArgumentException('Extension service definition is not a Closure or invokable object.');
  209. }
  210. $factory = $this->values[$id];
  211. $extended = function ($c) use ($callable, $factory) {
  212. return $callable($factory($c), $c);
  213. };
  214. if (isset($this->factories[$factory])) {
  215. $this->factories->detach($factory);
  216. $this->factories->attach($extended);
  217. }
  218. return $this[$id] = $extended;
  219. }
  220. /**
  221. * Returns all defined value names.
  222. *
  223. * @return array An array of value names
  224. */
  225. public function keys()
  226. {
  227. return array_keys($this->values);
  228. }
  229. /**
  230. * Registers a service provider.
  231. *
  232. * @param ServiceProviderInterface $provider A ServiceProviderInterface instance
  233. * @param array $values An array of values that customizes the provider
  234. *
  235. * @return static
  236. */
  237. public function register(ServiceProviderInterface $provider, array $values = array())
  238. {
  239. $provider->register($this);
  240. foreach ($values as $key => $value) {
  241. $this[$key] = $value;
  242. }
  243. return $this;
  244. }
  245. }