1<?php 2/** 3 * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) 4 * Copyright (c) Cake Software Foundation, Inc. (https://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. (https://cakefoundation.org) 11 * @link https://cakephp.org CakePHP(tm) Project 12 * @package Cake.Utility 13 * @since CakePHP(tm) v 0.9.2 14 * @license https://opensource.org/licenses/mit-license.php MIT License 15 */ 16 17/** 18 * Included libraries. 19 */ 20App::uses('Model', 'Model'); 21App::uses('AppModel', 'Model'); 22App::uses('ConnectionManager', 'Model'); 23 24/** 25 * Class Collections. 26 * 27 * A repository for class objects, each registered with a key. 28 * If you try to add an object with the same key twice, nothing will come of it. 29 * If you need a second instance of an object, give it another key. 30 * 31 * @package Cake.Utility 32 */ 33class ClassRegistry { 34 35/** 36 * Names of classes with their objects. 37 * 38 * @var array 39 */ 40 protected $_objects = array(); 41 42/** 43 * Names of class names mapped to the object in the registry. 44 * 45 * @var array 46 */ 47 protected $_map = array(); 48 49/** 50 * Default constructor parameter settings, indexed by type 51 * 52 * @var array 53 */ 54 protected $_config = array(); 55 56/** 57 * Return a singleton instance of the ClassRegistry. 58 * 59 * @return ClassRegistry instance 60 */ 61 public static function getInstance() { 62 static $instance = array(); 63 if (!$instance) { 64 $instance[0] = new ClassRegistry(); 65 } 66 return $instance[0]; 67 } 68 69/** 70 * Loads a class, registers the object in the registry and returns instance of the object. ClassRegistry::init() 71 * is used as a factory for models, and handle correct injecting of settings, that assist in testing. 72 * 73 * Examples 74 * Simple Use: Get a Post model instance ```ClassRegistry::init('Post');``` 75 * 76 * Expanded: ```array('class' => 'ClassName', 'alias' => 'AliasNameStoredInTheRegistry');``` 77 * 78 * Model Classes can accept optional ```array('id' => $id, 'table' => $table, 'ds' => $ds, 'alias' => $alias);``` 79 * 80 * When $class is a numeric keyed array, multiple class instances will be stored in the registry, 81 * no instance of the object will be returned 82 * ``` 83 * array( 84 * array('class' => 'ClassName', 'alias' => 'AliasNameStoredInTheRegistry'), 85 * array('class' => 'ClassName', 'alias' => 'AliasNameStoredInTheRegistry'), 86 * array('class' => 'ClassName', 'alias' => 'AliasNameStoredInTheRegistry') 87 * ); 88 * ``` 89 * 90 * @param string|array $class as a string or a single key => value array instance will be created, 91 * stored in the registry and returned. 92 * @param bool $strict if set to true it will return false if the class was not found instead 93 * of trying to create an AppModel 94 * @return bool|object $class instance of ClassName. 95 * @throws CakeException when you try to construct an interface or abstract class. 96 */ 97 public static function init($class, $strict = false) { 98 $_this = ClassRegistry::getInstance(); 99 100 if (is_array($class)) { 101 $objects = $class; 102 if (!isset($class[0])) { 103 $objects = array($class); 104 } 105 } else { 106 $objects = array(array('class' => $class)); 107 } 108 $defaults = array(); 109 if (isset($_this->_config['Model'])) { 110 $defaults = $_this->_config['Model']; 111 } 112 $count = count($objects); 113 $availableDs = null; 114 115 foreach ($objects as $settings) { 116 if (is_numeric($settings)) { 117 trigger_error(__d('cake_dev', '(ClassRegistry::init() Attempted to create instance of a class with a numeric name'), E_USER_WARNING); 118 return false; 119 } 120 121 if (is_array($settings)) { 122 $pluginPath = null; 123 $settings += $defaults; 124 $class = $settings['class']; 125 126 list($plugin, $class) = pluginSplit($class); 127 if ($plugin) { 128 $pluginPath = $plugin . '.'; 129 $settings['plugin'] = $plugin; 130 } 131 132 if (empty($settings['alias'])) { 133 $settings['alias'] = $class; 134 } 135 $alias = $settings['alias']; 136 137 $model = $_this->_duplicate($alias, $class); 138 if ($model) { 139 $_this->map($alias, $class); 140 return $model; 141 } 142 143 App::uses($plugin . 'AppModel', $pluginPath . 'Model'); 144 App::uses($class, $pluginPath . 'Model'); 145 146 if (class_exists($class) || interface_exists($class)) { 147 $reflection = new ReflectionClass($class); 148 if ($reflection->isAbstract() || $reflection->isInterface()) { 149 throw new CakeException(__d('cake_dev', 'Cannot create instance of %s, as it is abstract or is an interface', $class)); 150 } 151 $testing = isset($settings['testing']) ? $settings['testing'] : false; 152 if ($testing) { 153 $settings['ds'] = 'test'; 154 $defaultProperties = $reflection->getDefaultProperties(); 155 if (isset($defaultProperties['useDbConfig'])) { 156 $useDbConfig = $defaultProperties['useDbConfig']; 157 if ($availableDs === null) { 158 $availableDs = array_keys(ConnectionManager::enumConnectionObjects()); 159 } 160 if (in_array('test_' . $useDbConfig, $availableDs)) { 161 $useDbConfig = 'test_' . $useDbConfig; 162 } 163 if (strpos($useDbConfig, 'test') === 0) { 164 $settings['ds'] = $useDbConfig; 165 } 166 } 167 } 168 if ($reflection->getConstructor()) { 169 $instance = $reflection->newInstance($settings); 170 } else { 171 $instance = $reflection->newInstance(); 172 } 173 if ($strict && !$instance instanceof Model) { 174 $instance = null; 175 } 176 } 177 if (!isset($instance)) { 178 $appModel = 'AppModel'; 179 if ($strict) { 180 return false; 181 } elseif ($plugin && class_exists($plugin . 'AppModel')) { 182 $appModel = $plugin . 'AppModel'; 183 } 184 185 $settings['name'] = $class; 186 $instance = new $appModel($settings); 187 } 188 $_this->map($alias, $class); 189 } 190 } 191 192 if ($count > 1) { 193 return true; 194 } 195 return $instance; 196 } 197 198/** 199 * Add $object to the registry, associating it with the name $key. 200 * 201 * @param string $key Key for the object in registry 202 * @param object $object Object to store 203 * @return bool True if the object was written, false if $key already exists 204 */ 205 public static function addObject($key, $object) { 206 $_this = ClassRegistry::getInstance(); 207 $key = Inflector::underscore($key); 208 if (!isset($_this->_objects[$key])) { 209 $_this->_objects[$key] = $object; 210 return true; 211 } 212 return false; 213 } 214 215/** 216 * Remove object which corresponds to given key. 217 * 218 * @param string $key Key of object to remove from registry 219 * @return void 220 */ 221 public static function removeObject($key) { 222 $_this = ClassRegistry::getInstance(); 223 $key = Inflector::underscore($key); 224 if (isset($_this->_objects[$key])) { 225 unset($_this->_objects[$key]); 226 } 227 } 228 229/** 230 * Returns true if given key is present in the ClassRegistry. 231 * 232 * @param string $key Key to look for 233 * @return bool true if key exists in registry, false otherwise 234 */ 235 public static function isKeySet($key) { 236 $_this = ClassRegistry::getInstance(); 237 $key = Inflector::underscore($key); 238 239 return isset($_this->_objects[$key]) || isset($_this->_map[$key]); 240 } 241 242/** 243 * Get all keys from the registry. 244 * 245 * @return array Set of keys stored in registry 246 */ 247 public static function keys() { 248 return array_keys(ClassRegistry::getInstance()->_objects); 249 } 250 251/** 252 * Return object which corresponds to given key. 253 * 254 * @param string $key Key of object to look for 255 * @return mixed Object stored in registry or boolean false if the object does not exist. 256 */ 257 public static function getObject($key) { 258 $_this = ClassRegistry::getInstance(); 259 $key = Inflector::underscore($key); 260 $return = false; 261 if (isset($_this->_objects[$key])) { 262 $return = $_this->_objects[$key]; 263 } else { 264 $key = $_this->_getMap($key); 265 if (isset($_this->_objects[$key])) { 266 $return = $_this->_objects[$key]; 267 } 268 } 269 return $return; 270 } 271 272/** 273 * Sets the default constructor parameter for an object type 274 * 275 * @param string $type Type of object. If this parameter is omitted, defaults to "Model" 276 * @param array $param The parameter that will be passed to object constructors when objects 277 * of $type are created 278 * @return mixed Void if $param is being set. Otherwise, if only $type is passed, returns 279 * the previously-set value of $param, or null if not set. 280 */ 281 public static function config($type, $param = array()) { 282 $_this = ClassRegistry::getInstance(); 283 284 if (empty($param) && is_array($type)) { 285 $param = $type; 286 $type = 'Model'; 287 } elseif ($param === null) { 288 unset($_this->_config[$type]); 289 } elseif (empty($param) && is_string($type)) { 290 return isset($_this->_config[$type]) ? $_this->_config[$type] : null; 291 } 292 if (isset($_this->_config[$type]['testing'])) { 293 $param['testing'] = true; 294 } 295 $_this->_config[$type] = $param; 296 } 297 298/** 299 * Checks to see if $alias is a duplicate $class Object 300 * 301 * @param string $alias Alias to check. 302 * @param string $class Class name. 303 * @return bool|object Object stored in registry or `false` if the object does not exist. 304 */ 305 protected function &_duplicate($alias, $class) { 306 $duplicate = false; 307 if ($this->isKeySet($alias)) { 308 $model = $this->getObject($alias); 309 if (is_object($model) && ($model instanceof $class || $model->alias === $class)) { 310 $duplicate = $model; 311 } 312 unset($model); 313 } 314 return $duplicate; 315 } 316 317/** 318 * Add a key name pair to the registry to map name to class in the registry. 319 * 320 * @param string $key Key to include in map 321 * @param string $name Key that is being mapped 322 * @return void 323 */ 324 public static function map($key, $name) { 325 $_this = ClassRegistry::getInstance(); 326 $key = Inflector::underscore($key); 327 $name = Inflector::underscore($name); 328 if (!isset($_this->_map[$key])) { 329 $_this->_map[$key] = $name; 330 } 331 } 332 333/** 334 * Get all keys from the map in the registry. 335 * 336 * @return array Keys of registry's map 337 */ 338 public static function mapKeys() { 339 return array_keys(ClassRegistry::getInstance()->_map); 340 } 341 342/** 343 * Return the name of a class in the registry. 344 * 345 * @param string $key Key to find in map 346 * @return string Mapped value 347 */ 348 protected function _getMap($key) { 349 if (isset($this->_map[$key])) { 350 return $this->_map[$key]; 351 } 352 } 353 354/** 355 * Flushes all objects from the ClassRegistry. 356 * 357 * @return void 358 */ 359 public static function flush() { 360 $_this = ClassRegistry::getInstance(); 361 $_this->_objects = array(); 362 $_this->_map = array(); 363 } 364 365} 366