BehaviorCollection.php 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. <?php
  2. /**
  3. * BehaviorCollection
  4. *
  5. * Provides managment 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-2010, 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-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
  16. * @link http://cakephp.org CakePHP(tm) Project
  17. * @package cake
  18. * @subpackage cake.cake.libs.model
  19. * @since CakePHP(tm) v 1.2.0.0
  20. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  21. */
  22. App::uses('ObjectCollection', 'Core');
  23. /**
  24. * Model behavior collection class.
  25. *
  26. * Defines the Behavior interface, and contains common model interaction functionality.
  27. *
  28. * @package cake
  29. * @subpackage cake.cake.libs.model
  30. */
  31. class BehaviorCollection extends ObjectCollection {
  32. /**
  33. * Stores a reference to the attached name
  34. *
  35. * @var string
  36. * @access public
  37. */
  38. public $modelName = null;
  39. /**
  40. * Keeps a list of all methods of attached behaviors
  41. *
  42. * @var array
  43. */
  44. private $__methods = array();
  45. /**
  46. * Keeps a list of all methods which have been mapped with regular expressions
  47. *
  48. * @var array
  49. */
  50. private $__mappedMethods = array();
  51. /**
  52. * Attaches a model object and loads a list of behaviors
  53. *
  54. * @todo Make this method a constructor instead..
  55. * @access public
  56. * @return void
  57. */
  58. function init($modelName, $behaviors = array()) {
  59. $this->modelName = $modelName;
  60. if (!empty($behaviors)) {
  61. foreach (BehaviorCollection::normalizeObjectArray($behaviors) as $behavior => $config) {
  62. $this->load($config['class'], $config['settings']);
  63. }
  64. }
  65. }
  66. /**
  67. * Backwards compatible alias for load()
  68. *
  69. * @return void
  70. * @deprecated Replaced with load()
  71. */
  72. public function attach($behavior, $config = array()) {
  73. return $this->load($behavior, $config);
  74. }
  75. /**
  76. * Loads a behavior into the collection. You can use use `$config['enabled'] = false`
  77. * to load a behavior with callbacks disabled. By default callbacks are enabled. Disable behaviors
  78. * can still be used as normal.
  79. *
  80. * @param string $behavior CamelCased name of the behavior to load
  81. * @param array $config Behavior configuration parameters
  82. * @return boolean True on success, false on failure
  83. * @throws MissingBehaviorFileException or MissingBehaviorClassException when a behavior could not be found.
  84. */
  85. public function load($behavior, $config = array()) {
  86. list($plugin, $name) = pluginSplit($behavior);
  87. $class = $name . 'Behavior';
  88. if (!App::import('Behavior', $behavior)) {
  89. throw new MissingBehaviorFileException(array(
  90. 'file' => Inflector::underscore($behavior) . '.php',
  91. 'class' => $class
  92. ));
  93. }
  94. if (!class_exists($class)) {
  95. throw new MissingBehaviorClassException(array(
  96. 'file' => Inflector::underscore($behavior) . '.php',
  97. 'class' => $class
  98. ));
  99. }
  100. if (!isset($this->{$name})) {
  101. if (ClassRegistry::isKeySet($class)) {
  102. $this->_loaded[$name] = ClassRegistry::getObject($class);
  103. } else {
  104. $this->_loaded[$name] = new $class();
  105. ClassRegistry::addObject($class, $this->_loaded[$name]);
  106. if (!empty($plugin)) {
  107. ClassRegistry::addObject($plugin . '.' . $class, $this->_loaded[$name]);
  108. }
  109. }
  110. } elseif (isset($this->_loaded[$name]->settings) && isset($this->_loaded[$name]->settings[$this->modelName])) {
  111. if ($config !== null && $config !== false) {
  112. $config = array_merge($this->_loaded[$name]->settings[$this->modelName], $config);
  113. } else {
  114. $config = array();
  115. }
  116. }
  117. if (empty($config)) {
  118. $config = array();
  119. }
  120. $this->_loaded[$name]->setup(ClassRegistry::getObject($this->modelName), $config);
  121. foreach ($this->_loaded[$name]->mapMethods as $method => $alias) {
  122. $this->__mappedMethods[$method] = array($alias, $name);
  123. }
  124. $methods = get_class_methods($this->_loaded[$name]);
  125. $parentMethods = array_flip(get_class_methods('ModelBehavior'));
  126. $callbacks = array(
  127. 'setup', 'cleanup', 'beforeFind', 'afterFind', 'beforeSave', 'afterSave',
  128. 'beforeDelete', 'afterDelete', 'afterError'
  129. );
  130. foreach ($methods as $m) {
  131. if (!isset($parentMethods[$m])) {
  132. $methodAllowed = (
  133. $m[0] != '_' && !array_key_exists($m, $this->__methods) &&
  134. !in_array($m, $callbacks)
  135. );
  136. if ($methodAllowed) {
  137. $this->__methods[$m] = array($m, $name);
  138. }
  139. }
  140. }
  141. $configDisabled = isset($config['enabled']) && $config['enabled'] === false;
  142. if (!in_array($name, $this->_enabled) && !$configDisabled) {
  143. $this->enable($name);
  144. } elseif ($configDisabled) {
  145. $this->disable($name);
  146. }
  147. return true;
  148. }
  149. /**
  150. * Detaches a behavior from a model
  151. *
  152. * @param string $name CamelCased name of the behavior to unload
  153. * @return void
  154. */
  155. public function unload($name) {
  156. list($plugin, $name) = pluginSplit($name);
  157. if (isset($this->_loaded[$name])) {
  158. $this->_loaded[$name]->cleanup(ClassRegistry::getObject($this->modelName));
  159. unset($this->_loaded[$name]);
  160. }
  161. foreach ($this->__methods as $m => $callback) {
  162. if (is_array($callback) && $callback[1] == $name) {
  163. unset($this->__methods[$m]);
  164. }
  165. }
  166. $this->_enabled = array_values(array_diff($this->_enabled, (array)$name));
  167. }
  168. /**
  169. * Backwards compatible alias for unload()
  170. *
  171. * @param string $name Name of behavior
  172. * @return void
  173. * @deprecated Use unload instead.
  174. */
  175. public function detach($name) {
  176. return $this->unload($name);
  177. }
  178. /**
  179. * Dispatches a behavior method
  180. *
  181. * @return array All methods for all behaviors attached to this object
  182. */
  183. public function dispatchMethod(&$model, $method, $params = array(), $strict = false) {
  184. $methods = array_keys($this->__methods);
  185. $check = array_flip($methods);
  186. $found = isset($check[$method]);
  187. $call = null;
  188. if ($strict && !$found) {
  189. trigger_error(sprintf(__("BehaviorCollection::dispatchMethod() - Method %s not found in any attached behavior"), $method), E_USER_WARNING);
  190. return null;
  191. } elseif ($found) {
  192. $methods = array_combine($methods, array_values($this->__methods));
  193. $call = $methods[$method];
  194. } else {
  195. $count = count($this->__mappedMethods);
  196. $mapped = array_keys($this->__mappedMethods);
  197. for ($i = 0; $i < $count; $i++) {
  198. if (preg_match($mapped[$i] . 'i', $method)) {
  199. $call = $this->__mappedMethods[$mapped[$i]];
  200. array_unshift($params, $method);
  201. break;
  202. }
  203. }
  204. }
  205. if (!empty($call)) {
  206. return call_user_func_array(
  207. array(&$this->_loaded[$call[1]], $call[0]),
  208. array_merge(array(&$model), $params)
  209. );
  210. }
  211. return array('unhandled');
  212. }
  213. /**
  214. * Dispatches a behavior callback on all attached behavior objects
  215. *
  216. * @param model $model
  217. * @param string $callback
  218. * @param array $params
  219. * @param array $options
  220. * @return mixed
  221. */
  222. public function trigger(&$model, $callback, $params = array(), $options = array()) {
  223. if (empty($this->_enabled)) {
  224. return true;
  225. }
  226. $options = array_merge(
  227. array('break' => false, 'breakOn' => array(null, false), 'modParams' => false),
  228. $options
  229. );
  230. foreach ($this->_enabled as $name) {
  231. $result = call_user_func_array(
  232. array(&$this->_loaded[$name], $callback),
  233. array_merge(array(&$model), $params)
  234. );
  235. if (
  236. $options['break'] && ($result === $options['breakOn'] ||
  237. (is_array($options['breakOn']) && in_array($result, $options['breakOn'], true)))
  238. ) {
  239. return $result;
  240. } elseif ($options['modParams'] && is_array($result)) {
  241. $params[0] = $result;
  242. }
  243. }
  244. if ($options['modParams'] && isset($params[0])) {
  245. return $params[0];
  246. }
  247. return true;
  248. }
  249. /**
  250. * Gets the method list for attached behaviors, i.e. all public, non-callback methods
  251. *
  252. * @return array All public methods for all behaviors attached to this collection
  253. */
  254. public function methods() {
  255. return $this->__methods;
  256. }
  257. }