Cake/Model/BehaviorCollection.php
| 1 | <?php |
|---|---|
| 2 | /** |
| 3 | * BehaviorCollection |
| 4 | * |
| 5 | * Provides management and interface for interacting with collections of behaviors. |
| 6 | * |
| 7 | * PHP 5 |
| 8 | * |
| 9 | * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) |
| 10 | * Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org) |
| 11 | * |
| 12 | * Licensed under The MIT License |
| 13 | * Redistributions of files must retain the above copyright notice. |
| 14 | * |
| 15 | * @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org) |
| 16 | * @link http://cakephp.org CakePHP(tm) Project |
| 17 | * @package Cake.Model |
| 18 | * @since CakePHP(tm) v 1.2.0.0 |
| 19 | * @license MIT License (http://www.opensource.org/licenses/mit-license.php) |
| 20 | */ |
| 21 | |
| 22 | App::uses('ObjectCollection', 'Utility'); |
| 23 | App::uses('CakeEventListener', 'Event'); |
| 24 | |
| 25 | /** |
| 26 | * Model behavior collection class. |
| 27 | * |
| 28 | * Defines the Behavior interface, and contains common model interaction functionality. |
| 29 | * |
| 30 | * @package Cake.Model |
| 31 | */ |
| 32 | class BehaviorCollection extends ObjectCollection implements CakeEventListener { |
| 33 | |
| 34 | /** |
| 35 | * Stores a reference to the attached name |
| 36 | * |
| 37 | * @var string |
| 38 | */ |
| 39 | public $modelName = null; |
| 40 | |
| 41 | /** |
| 42 | * Keeps a list of all methods of attached behaviors |
| 43 | * |
| 44 | * @var array |
| 45 | */ |
| 46 | protected $_methods = array(); |
| 47 | |
| 48 | /** |
| 49 | * Keeps a list of all methods which have been mapped with regular expressions |
| 50 | * |
| 51 | * @var array |
| 52 | */ |
| 53 | protected $_mappedMethods = array(); |
| 54 | |
| 55 | /** |
| 56 | * Attaches a model object and loads a list of behaviors |
| 57 | * |
| 58 | * @todo Make this method a constructor instead.. |
| 59 | * @param string $modelName |
| 60 | * @param array $behaviors |
| 61 | * @return void |
| 62 | */ |
| 63 | public function init($modelName, $behaviors = array()) { |
| 64 | $this->modelName = $modelName; |
| 65 | |
| 66 | if (!empty($behaviors)) { |
| 67 | foreach (BehaviorCollection::normalizeObjectArray($behaviors) as $behavior => $config) { |
| 68 | $this->load($config['class'], $config['settings']); |
| 69 | } |
| 70 | } |
| 71 | } |
| 72 | |
| 73 | /** |
| 74 | * Backwards compatible alias for load() |
| 75 | * |
| 76 | * @param string $behavior |
| 77 | * @param array $config |
| 78 | * @return void |
| 79 | * @deprecated Replaced with load() |
| 80 | */ |
| 81 | public function attach($behavior, $config = array()) { |
| 82 | return $this->load($behavior, $config); |
| 83 | } |
| 84 | |
| 85 | /** |
| 86 | * Loads a behavior into the collection. You can use use `$config['enabled'] = false` |
| 87 | * to load a behavior with callbacks disabled. By default callbacks are enabled. Disable behaviors |
| 88 | * can still be used as normal. |
| 89 | * |
| 90 | * You can alias your behavior as an existing behavior by setting the 'className' key, i.e., |
| 91 | * {{{ |
| 92 | * public $actsAs = array( |
| 93 | * 'Tree' => array( |
| 94 | * 'className' => 'AliasedTree' |
| 95 | * ); |
| 96 | * ); |
| 97 | * }}} |
| 98 | * All calls to the `Tree` behavior would use `AliasedTree` instead. |
| 99 | * |
| 100 | * @param string $behavior CamelCased name of the behavior to load |
| 101 | * @param array $config Behavior configuration parameters |
| 102 | * @return boolean True on success, false on failure |
| 103 | * @throws MissingBehaviorException when a behavior could not be found. |
| 104 | */ |
| 105 | public function load($behavior, $config = array()) { |
| 106 | if (is_array($config) && isset($config['className'])) { |
| 107 | $alias = $behavior; |
| 108 | $behavior = $config['className']; |
| 109 | } |
| 110 | $configDisabled = isset($config['enabled']) && $config['enabled'] === false; |
| 111 | unset($config['enabled'], $config['className']); |
| 112 | |
| 113 | list($plugin, $name) = pluginSplit($behavior, true); |
| 114 | if (!isset($alias)) { |
| 115 | $alias = $name; |
| 116 | } |
| 117 | |
| 118 | $class = $name . 'Behavior'; |
| 119 | |
| 120 | App::uses($class, $plugin . 'Model/Behavior'); |
| 121 | if (!class_exists($class)) { |
| 122 | throw new MissingBehaviorException(array( |
| 123 | 'class' => $class, |
| 124 | 'plugin' => substr($plugin, 0, -1) |
| 125 | )); |
| 126 | } |
| 127 | |
| 128 | if (!isset($this->{$alias})) { |
| 129 | if (ClassRegistry::isKeySet($class)) { |
| 130 | $this->_loaded[$alias] = ClassRegistry::getObject($class); |
| 131 | } else { |
| 132 | $this->_loaded[$alias] = new $class(); |
| 133 | ClassRegistry::addObject($class, $this->_loaded[$alias]); |
| 134 | if (!empty($plugin)) { |
| 135 | ClassRegistry::addObject($plugin . '.' . $class, $this->_loaded[$alias]); |
| 136 | } |
| 137 | } |
| 138 | } elseif (isset($this->_loaded[$alias]->settings) && isset($this->_loaded[$alias]->settings[$this->modelName])) { |
| 139 | if ($config !== null && $config !== false) { |
| 140 | $config = array_merge($this->_loaded[$alias]->settings[$this->modelName], $config); |
| 141 | } else { |
| 142 | $config = array(); |
| 143 | } |
| 144 | } |
| 145 | if (empty($config)) { |
| 146 | $config = array(); |
| 147 | } |
| 148 | $this->_loaded[$alias]->setup(ClassRegistry::getObject($this->modelName), $config); |
| 149 | |
| 150 | foreach ($this->_loaded[$alias]->mapMethods as $method => $methodAlias) { |
| 151 | $this->_mappedMethods[$method] = array($alias, $methodAlias); |
| 152 | } |
| 153 | $methods = get_class_methods($this->_loaded[$alias]); |
| 154 | $parentMethods = array_flip(get_class_methods('ModelBehavior')); |
| 155 | $callbacks = array( |
| 156 | 'setup', 'cleanup', 'beforeFind', 'afterFind', 'beforeSave', 'afterSave', |
| 157 | 'beforeDelete', 'afterDelete', 'onError' |
| 158 | ); |
| 159 | |
| 160 | foreach ($methods as $m) { |
| 161 | if (!isset($parentMethods[$m])) { |
| 162 | $methodAllowed = ( |
| 163 | $m[0] != '_' && !array_key_exists($m, $this->_methods) && |
| 164 | !in_array($m, $callbacks) |
| 165 | ); |
| 166 | if ($methodAllowed) { |
| 167 | $this->_methods[$m] = array($alias, $m); |
| 168 | } |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | if (!in_array($alias, $this->_enabled) && !$configDisabled) { |
| 173 | $this->enable($alias); |
| 174 | } else { |
| 175 | $this->disable($alias); |
| 176 | } |
| 177 | return true; |
| 178 | } |
| 179 | |
| 180 | /** |
| 181 | * Detaches a behavior from a model |
| 182 | * |
| 183 | * @param string $name CamelCased name of the behavior to unload |
| 184 | * @return void |
| 185 | */ |
| 186 | public function unload($name) { |
| 187 | list($plugin, $name) = pluginSplit($name); |
| 188 | if (isset($this->_loaded[$name])) { |
| 189 | $this->_loaded[$name]->cleanup(ClassRegistry::getObject($this->modelName)); |
| 190 | parent::unload($name); |
| 191 | } |
| 192 | foreach ($this->_methods as $m => $callback) { |
| 193 | if (is_array($callback) && $callback[0] == $name) { |
| 194 | unset($this->_methods[$m]); |
| 195 | } |
| 196 | } |
| 197 | } |
| 198 | |
| 199 | /** |
| 200 | * Backwards compatible alias for unload() |
| 201 | * |
| 202 | * @param string $name Name of behavior |
| 203 | * @return void |
| 204 | * @deprecated Use unload instead. |
| 205 | */ |
| 206 | public function detach($name) { |
| 207 | return $this->unload($name); |
| 208 | } |
| 209 | |
| 210 | /** |
| 211 | * Dispatches a behavior method. Will call either normal methods or mapped methods. |
| 212 | * |
| 213 | * If a method is not handled by the BehaviorCollection, and $strict is false, a |
| 214 | * special return of `array('unhandled')` will be returned to signal the method was not found. |
| 215 | * |
| 216 | * @param Model $model The model the method was originally called on. |
| 217 | * @param string $method The method called. |
| 218 | * @param array $params Parameters for the called method. |
| 219 | * @param boolean $strict If methods are not found, trigger an error. |
| 220 | * @return array All methods for all behaviors attached to this object |
| 221 | */ |
| 222 | public function dispatchMethod($model, $method, $params = array(), $strict = false) { |
| 223 | $method = $this->hasMethod($method, true); |
| 224 | |
| 225 | if ($strict && empty($method)) { |
| 226 | trigger_error(__d('cake_dev', "BehaviorCollection::dispatchMethod() - Method %s not found in any attached behavior", $method), E_USER_WARNING); |
| 227 | return null; |
| 228 | } |
| 229 | if (empty($method)) { |
| 230 | return array('unhandled'); |
| 231 | } |
| 232 | if (count($method) === 3) { |
| 233 | array_unshift($params, $method[2]); |
| 234 | unset($method[2]); |
| 235 | } |
| 236 | return call_user_func_array( |
| 237 | array($this->_loaded[$method[0]], $method[1]), |
| 238 | array_merge(array(&$model), $params) |
| 239 | ); |
| 240 | } |
| 241 | |
| 242 | /** |
| 243 | * Gets the method list for attached behaviors, i.e. all public, non-callback methods. |
| 244 | * This does not include mappedMethods. |
| 245 | * |
| 246 | * @return array All public methods for all behaviors attached to this collection |
| 247 | */ |
| 248 | public function methods() { |
| 249 | return $this->_methods; |
| 250 | } |
| 251 | |
| 252 | /** |
| 253 | * Check to see if a behavior in this collection implements the provided method. Will |
| 254 | * also check mappedMethods. |
| 255 | * |
| 256 | * @param string $method The method to find. |
| 257 | * @param boolean $callback Return the callback for the method. |
| 258 | * @return mixed If $callback is false, a boolean will be returned, if its true, an array |
| 259 | * containing callback information will be returned. For mapped methods the array will have 3 elements. |
| 260 | */ |
| 261 | public function hasMethod($method, $callback = false) { |
| 262 | if (isset($this->_methods[$method])) { |
| 263 | return $callback ? $this->_methods[$method] : true; |
| 264 | } |
| 265 | foreach ($this->_mappedMethods as $pattern => $target) { |
| 266 | if (preg_match($pattern . 'i', $method)) { |
| 267 | if ($callback) { |
| 268 | $target[] = $method; |
| 269 | return $target; |
| 270 | } |
| 271 | return true; |
| 272 | } |
| 273 | } |
| 274 | return false; |
| 275 | } |
| 276 | |
| 277 | /** |
| 278 | * Returns the implemented events that will get routed to the trigger function |
| 279 | * in order to dispatch them separately on each behavior |
| 280 | * |
| 281 | * @return array |
| 282 | */ |
| 283 | public function implementedEvents() { |
| 284 | return array( |
| 285 | 'Model.beforeFind' => 'trigger', |
| 286 | 'Model.afterFind' => 'trigger', |
| 287 | 'Model.beforeValidate' => 'trigger', |
| 288 | 'Model.beforeSave' => 'trigger', |
| 289 | 'Model.afterSave' => 'trigger', |
| 290 | 'Model.beforeDelete' => 'trigger', |
| 291 | 'Model.afterDelete' => 'trigger' |
| 292 | ); |
| 293 | } |
| 294 | |
| 295 | } |
| 296 | |
| 297 |
