ObjectRegistry.php 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Core;
  16. use RuntimeException;
  17. /**
  18. * Acts as a registry/factory for objects.
  19. *
  20. * Provides registry & factory functionality for object types. Used
  21. * as a super class for various composition based re-use features in CakePHP.
  22. *
  23. * Each subclass needs to implement the various abstract methods to complete
  24. * the template method load().
  25. *
  26. * The ObjectRegistry is EventManager aware, but each extending class will need to use
  27. * \Cake\Event\EventManagerTrait to attach and detach on set and bind
  28. *
  29. * @see \Cake\Controller\ComponentRegistry
  30. * @see \Cake\View\HelperRegistry
  31. * @see \Cake\Console\TaskRegistry
  32. */
  33. abstract class ObjectRegistry
  34. {
  35. /**
  36. * Map of loaded objects.
  37. *
  38. * @var array
  39. */
  40. protected $_loaded = [];
  41. /**
  42. * Loads/constructs an object instance.
  43. *
  44. * Will return the instance in the registry if it already exists.
  45. * If a subclass provides event support, you can use `$config['enabled'] = false`
  46. * to exclude constructed objects from being registered for events.
  47. *
  48. * Using Cake\Controller\Controller::$components as an example. You can alias
  49. * an object by setting the 'className' key, i.e.,
  50. *
  51. * ```
  52. * public $components = [
  53. * 'Email' => [
  54. * 'className' => '\App\Controller\Component\AliasedEmailComponent'
  55. * ];
  56. * ];
  57. * ```
  58. *
  59. * All calls to the `Email` component would use `AliasedEmail` instead.
  60. *
  61. * @param string $objectName The name/class of the object to load.
  62. * @param array $config Additional settings to use when loading the object.
  63. * @return mixed
  64. */
  65. public function load($objectName, $config = [])
  66. {
  67. list(, $name) = pluginSplit($objectName);
  68. $loaded = isset($this->_loaded[$name]);
  69. if ($loaded && !empty($config)) {
  70. $this->_checkDuplicate($name, $config);
  71. }
  72. if ($loaded) {
  73. return $this->_loaded[$name];
  74. }
  75. if (is_array($config) && isset($config['className'])) {
  76. $objectName = $config['className'];
  77. }
  78. $className = $this->_resolveClassName($objectName);
  79. if (!$className || (is_string($className) && !class_exists($className))) {
  80. list($plugin, $objectName) = pluginSplit($objectName);
  81. $this->_throwMissingClassError($objectName, $plugin);
  82. }
  83. $instance = $this->_create($className, $name, $config);
  84. $this->_loaded[$name] = $instance;
  85. return $instance;
  86. }
  87. /**
  88. * Check for duplicate object loading.
  89. *
  90. * If a duplicate is being loaded and has different configuration, that is
  91. * bad and an exception will be raised.
  92. *
  93. * An exception is raised, as replacing the object will not update any
  94. * references other objects may have. Additionally, simply updating the runtime
  95. * configuration is not a good option as we may be missing important constructor
  96. * logic dependent on the configuration.
  97. *
  98. * @param string $name The name of the alias in the registry.
  99. * @param array $config The config data for the new instance.
  100. * @return void
  101. * @throws \RuntimeException When a duplicate is found.
  102. */
  103. protected function _checkDuplicate($name, $config)
  104. {
  105. $existing = $this->_loaded[$name];
  106. $msg = sprintf('The "%s" alias has already been loaded', $name);
  107. $hasConfig = false;
  108. if (method_exists($existing, 'config')) {
  109. $hasConfig = true;
  110. }
  111. if (!$hasConfig) {
  112. throw new RuntimeException($msg);
  113. }
  114. if (empty($config)) {
  115. return;
  116. }
  117. $existingConfig = $existing->config();
  118. unset($config['enabled'], $existingConfig['enabled']);
  119. $fail = false;
  120. foreach ($config as $key => $value) {
  121. if (!array_key_exists($key, $existingConfig)) {
  122. $fail = true;
  123. break;
  124. }
  125. if (isset($existingConfig[$key]) && $existingConfig[$key] !== $value) {
  126. $fail = true;
  127. break;
  128. }
  129. }
  130. if ($fail) {
  131. $msg .= ' with the following config: ';
  132. $msg .= var_export($existingConfig, true);
  133. $msg .= ' which differs from ' . var_export($config, true);
  134. throw new RuntimeException($msg);
  135. }
  136. }
  137. /**
  138. * Should resolve the classname for a given object type.
  139. *
  140. * @param string $class The class to resolve.
  141. * @return string|false The resolved name or false for failure.
  142. */
  143. abstract protected function _resolveClassName($class);
  144. /**
  145. * Throw an exception when the requested object name is missing.
  146. *
  147. * @param string $class The class that is missing.
  148. * @param string $plugin The plugin $class is missing from.
  149. * @return void
  150. * @throws \Exception
  151. */
  152. abstract protected function _throwMissingClassError($class, $plugin);
  153. /**
  154. * Create an instance of a given classname.
  155. *
  156. * This method should construct and do any other initialization logic
  157. * required.
  158. *
  159. * @param string $class The class to build.
  160. * @param string $alias The alias of the object.
  161. * @param array $config The Configuration settings for construction
  162. * @return mixed
  163. */
  164. abstract protected function _create($class, $alias, $config);
  165. /**
  166. * Get the list of loaded objects.
  167. *
  168. * @return array List of object names.
  169. */
  170. public function loaded()
  171. {
  172. return array_keys($this->_loaded);
  173. }
  174. /**
  175. * Check whether or not a given object is loaded.
  176. *
  177. * @param string $name The object name to check for.
  178. * @return bool True is object is loaded else false.
  179. */
  180. public function has($name)
  181. {
  182. return isset($this->_loaded[$name]);
  183. }
  184. /**
  185. * Get loaded object instance.
  186. *
  187. * @param string $name Name of object.
  188. * @return object|null Object instance if loaded else null.
  189. */
  190. public function get($name)
  191. {
  192. if (isset($this->_loaded[$name])) {
  193. return $this->_loaded[$name];
  194. }
  195. return null;
  196. }
  197. /**
  198. * Provide public read access to the loaded objects
  199. *
  200. * @param string $name Name of property to read
  201. * @return mixed
  202. */
  203. public function __get($name)
  204. {
  205. return $this->get($name);
  206. }
  207. /**
  208. * Provide isset access to _loaded
  209. *
  210. * @param string $name Name of object being checked.
  211. * @return bool
  212. */
  213. public function __isset($name)
  214. {
  215. return isset($this->_loaded[$name]);
  216. }
  217. /**
  218. * Normalizes an object array, creates an array that makes lazy loading
  219. * easier
  220. *
  221. * @param array $objects Array of child objects to normalize.
  222. * @return array Array of normalized objects.
  223. */
  224. public function normalizeArray($objects)
  225. {
  226. $normal = [];
  227. foreach ($objects as $i => $objectName) {
  228. $config = [];
  229. if (!is_int($i)) {
  230. $config = (array)$objectName;
  231. $objectName = $i;
  232. }
  233. list(, $name) = pluginSplit($objectName);
  234. $normal[$name] = ['class' => $objectName, 'config' => $config];
  235. }
  236. return $normal;
  237. }
  238. /**
  239. * Clear loaded instances in the registry.
  240. *
  241. * If the registry subclass has an event manager, the objects will be detached from events as well.
  242. *
  243. * @return void
  244. */
  245. public function reset()
  246. {
  247. foreach (array_keys($this->_loaded) as $name) {
  248. $this->unload($name);
  249. }
  250. }
  251. /**
  252. * Set an object directly into the registry by name.
  253. *
  254. * If this collection implements events, the passed object will
  255. * be attached into the event manager
  256. *
  257. * @param string $objectName The name of the object to set in the registry.
  258. * @param object $object instance to store in the registry
  259. * @return void
  260. */
  261. public function set($objectName, $object)
  262. {
  263. list(, $name) = pluginSplit($objectName);
  264. $this->unload($objectName);
  265. if (isset($this->_eventManager)) {
  266. $this->eventManager()->attach($object);
  267. }
  268. $this->_loaded[$name] = $object;
  269. }
  270. /**
  271. * Remove an object from the registry.
  272. *
  273. * If this registry has an event manager, the object will be detached from any events as well.
  274. *
  275. * @param string $objectName The name of the object to remove from the registry.
  276. * @return void
  277. */
  278. public function unload($objectName)
  279. {
  280. if (empty($this->_loaded[$objectName])) {
  281. return;
  282. }
  283. $object = $this->_loaded[$objectName];
  284. if (isset($this->_eventManager)) {
  285. $this->eventManager()->off($object);
  286. }
  287. unset($this->_loaded[$objectName]);
  288. }
  289. /**
  290. * Debug friendly object properties.
  291. *
  292. * @return array
  293. */
  294. public function __debugInfo()
  295. {
  296. $properties = get_object_vars($this);
  297. $properties['_loaded'] = array_keys($properties['_loaded']);
  298. return $properties;
  299. }
  300. }