PageRenderTime 50ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/cake/libs/model/model_behavior.php

http://github.com/Datawalke/Coordino
PHP | 533 lines | 239 code | 43 blank | 251 comment | 56 complexity | 1efec992092b712d8e2da16ed6db16b1 MD5 | raw file
  1. <?php
  2. /**
  3. * Model behaviors base class.
  4. *
  5. * Adds methods and automagic functionality to Cake Models.
  6. *
  7. * PHP versions 4 and 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
  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. /**
  23. * Model behavior base class.
  24. *
  25. * Defines the Behavior interface, and contains common model interaction functionality.
  26. *
  27. * @package cake
  28. * @subpackage cake.cake.libs.model
  29. */
  30. class ModelBehavior extends Object {
  31. /**
  32. * Contains configuration settings for use with individual model objects. This
  33. * is used because if multiple models use this Behavior, each will use the same
  34. * object instance. Individual model settings should be stored as an
  35. * associative array, keyed off of the model name.
  36. *
  37. * @var array
  38. * @access public
  39. * @see Model::$alias
  40. */
  41. var $settings = array();
  42. /**
  43. * Allows the mapping of preg-compatible regular expressions to public or
  44. * private methods in this class, where the array key is a /-delimited regular
  45. * expression, and the value is a class method. Similar to the functionality of
  46. * the findBy* / findAllBy* magic methods.
  47. *
  48. * @var array
  49. * @access public
  50. */
  51. var $mapMethods = array();
  52. /**
  53. * Setup this behavior with the specified configuration settings.
  54. *
  55. * @param object $model Model using this behavior
  56. * @param array $config Configuration settings for $model
  57. * @access public
  58. */
  59. function setup(&$model, $config = array()) { }
  60. /**
  61. * Clean up any initialization this behavior has done on a model. Called when a behavior is dynamically
  62. * detached from a model using Model::detach().
  63. *
  64. * @param object $model Model using this behavior
  65. * @access public
  66. * @see BehaviorCollection::detach()
  67. */
  68. function cleanup(&$model) {
  69. if (isset($this->settings[$model->alias])) {
  70. unset($this->settings[$model->alias]);
  71. }
  72. }
  73. /**
  74. * Before find callback
  75. *
  76. * @param object $model Model using this behavior
  77. * @param array $queryData Data used to execute this query, i.e. conditions, order, etc.
  78. * @return mixed False if the operation should abort. An array will replace the value of $query.
  79. * @access public
  80. */
  81. function beforeFind(&$model, $query) { }
  82. /**
  83. * After find callback. Can be used to modify any results returned by find and findAll.
  84. *
  85. * @param object $model Model using this behavior
  86. * @param mixed $results The results of the find operation
  87. * @param boolean $primary Whether this model is being queried directly (vs. being queried as an association)
  88. * @return mixed An array value will replace the value of $results - any other value will be ignored.
  89. * @access public
  90. */
  91. function afterFind(&$model, $results, $primary) { }
  92. /**
  93. * Before validate callback
  94. *
  95. * @param object $model Model using this behavior
  96. * @return mixed False if the operation should abort. Any other result will continue.
  97. * @access public
  98. */
  99. function beforeValidate(&$model) { }
  100. /**
  101. * Before save callback
  102. *
  103. * @param object $model Model using this behavior
  104. * @return mixed False if the operation should abort. Any other result will continue.
  105. * @access public
  106. */
  107. function beforeSave(&$model) { }
  108. /**
  109. * After save callback
  110. *
  111. * @param object $model Model using this behavior
  112. * @param boolean $created True if this save created a new record
  113. * @access public
  114. */
  115. function afterSave(&$model, $created) { }
  116. /**
  117. * Before delete callback
  118. *
  119. * @param object $model Model using this behavior
  120. * @param boolean $cascade If true records that depend on this record will also be deleted
  121. * @return mixed False if the operation should abort. Any other result will continue.
  122. * @access public
  123. */
  124. function beforeDelete(&$model, $cascade = true) { }
  125. /**
  126. * After delete callback
  127. *
  128. * @param object $model Model using this behavior
  129. * @access public
  130. */
  131. function afterDelete(&$model) { }
  132. /**
  133. * DataSource error callback
  134. *
  135. * @param object $model Model using this behavior
  136. * @param string $error Error generated in DataSource
  137. * @access public
  138. */
  139. function onError(&$model, $error) { }
  140. /**
  141. * Overrides Object::dispatchMethod to account for PHP4's broken reference support
  142. *
  143. * @see Object::dispatchMethod
  144. * @access public
  145. * @return mixed
  146. */
  147. function dispatchMethod(&$model, $method, $params = array()) {
  148. if (empty($params)) {
  149. return $this->{$method}($model);
  150. }
  151. $params = array_values($params);
  152. switch (count($params)) {
  153. case 1:
  154. return $this->{$method}($model, $params[0]);
  155. case 2:
  156. return $this->{$method}($model, $params[0], $params[1]);
  157. case 3:
  158. return $this->{$method}($model, $params[0], $params[1], $params[2]);
  159. case 4:
  160. return $this->{$method}($model, $params[0], $params[1], $params[2], $params[3]);
  161. case 5:
  162. return $this->{$method}($model, $params[0], $params[1], $params[2], $params[3], $params[4]);
  163. default:
  164. $params = array_merge(array(&$model), $params);
  165. return call_user_func_array(array(&$this, $method), $params);
  166. break;
  167. }
  168. }
  169. /**
  170. * If $model's whitelist property is non-empty, $field will be added to it.
  171. * Note: this method should *only* be used in beforeValidate or beforeSave to ensure
  172. * that it only modifies the whitelist for the current save operation. Also make sure
  173. * you explicitly set the value of the field which you are allowing.
  174. *
  175. * @param object $model Model using this behavior
  176. * @param string $field Field to be added to $model's whitelist
  177. * @access protected
  178. * @return void
  179. */
  180. function _addToWhitelist(&$model, $field) {
  181. if (is_array($field)) {
  182. foreach ($field as $f) {
  183. $this->_addToWhitelist($model, $f);
  184. }
  185. return;
  186. }
  187. if (!empty($model->whitelist) && !in_array($field, $model->whitelist)) {
  188. $model->whitelist[] = $field;
  189. }
  190. }
  191. }
  192. /**
  193. * Model behavior collection class.
  194. *
  195. * Defines the Behavior interface, and contains common model interaction functionality.
  196. *
  197. * @package cake
  198. * @subpackage cake.cake.libs.model
  199. */
  200. class BehaviorCollection extends Object {
  201. /**
  202. * Stores a reference to the attached name
  203. *
  204. * @var string
  205. * @access public
  206. */
  207. var $modelName = null;
  208. /**
  209. * Lists the currently-attached behavior objects
  210. *
  211. * @var array
  212. * @access private
  213. */
  214. var $_attached = array();
  215. /**
  216. * Lists the currently-attached behavior objects which are disabled
  217. *
  218. * @var array
  219. * @access private
  220. */
  221. var $_disabled = array();
  222. /**
  223. * Keeps a list of all methods of attached behaviors
  224. *
  225. * @var array
  226. */
  227. var $__methods = array();
  228. /**
  229. * Keeps a list of all methods which have been mapped with regular expressions
  230. *
  231. * @var array
  232. */
  233. var $__mappedMethods = array();
  234. /**
  235. * Attaches a model object and loads a list of behaviors
  236. *
  237. * @access public
  238. * @return void
  239. */
  240. function init($modelName, $behaviors = array()) {
  241. $this->modelName = $modelName;
  242. if (!empty($behaviors)) {
  243. foreach (Set::normalize($behaviors) as $behavior => $config) {
  244. $this->attach($behavior, $config);
  245. }
  246. }
  247. }
  248. /**
  249. * Attaches a behavior to a model
  250. *
  251. * @param string $behavior CamelCased name of the behavior to load
  252. * @param array $config Behavior configuration parameters
  253. * @return boolean True on success, false on failure
  254. * @access public
  255. */
  256. function attach($behavior, $config = array()) {
  257. list($plugin, $name) = pluginSplit($behavior);
  258. $class = $name . 'Behavior';
  259. if (!App::import('Behavior', $behavior)) {
  260. $this->cakeError('missingBehaviorFile', array(array(
  261. 'behavior' => $behavior,
  262. 'file' => Inflector::underscore($behavior) . '.php',
  263. 'code' => 500,
  264. 'base' => '/'
  265. )));
  266. return false;
  267. }
  268. if (!class_exists($class)) {
  269. $this->cakeError('missingBehaviorClass', array(array(
  270. 'behavior' => $class,
  271. 'file' => Inflector::underscore($class) . '.php',
  272. 'code' => 500,
  273. 'base' => '/'
  274. )));
  275. return false;
  276. }
  277. if (!isset($this->{$name})) {
  278. if (ClassRegistry::isKeySet($class)) {
  279. if (PHP5) {
  280. $this->{$name} = ClassRegistry::getObject($class);
  281. } else {
  282. $this->{$name} =& ClassRegistry::getObject($class);
  283. }
  284. } else {
  285. if (PHP5) {
  286. $this->{$name} = new $class;
  287. } else {
  288. $this->{$name} =& new $class;
  289. }
  290. ClassRegistry::addObject($class, $this->{$name});
  291. if (!empty($plugin)) {
  292. ClassRegistry::addObject($plugin.'.'.$class, $this->{$name});
  293. }
  294. }
  295. } elseif (isset($this->{$name}->settings) && isset($this->{$name}->settings[$this->modelName])) {
  296. if ($config !== null && $config !== false) {
  297. $config = array_merge($this->{$name}->settings[$this->modelName], $config);
  298. } else {
  299. $config = array();
  300. }
  301. }
  302. if (empty($config)) {
  303. $config = array();
  304. }
  305. $this->{$name}->setup(ClassRegistry::getObject($this->modelName), $config);
  306. foreach ($this->{$name}->mapMethods as $method => $alias) {
  307. $this->__mappedMethods[$method] = array($alias, $name);
  308. }
  309. $methods = get_class_methods($this->{$name});
  310. $parentMethods = array_flip(get_class_methods('ModelBehavior'));
  311. $callbacks = array(
  312. 'setup', 'cleanup', 'beforeFind', 'afterFind', 'beforeSave', 'afterSave',
  313. 'beforeDelete', 'afterDelete', 'afterError'
  314. );
  315. foreach ($methods as $m) {
  316. if (!isset($parentMethods[$m])) {
  317. $methodAllowed = (
  318. $m[0] != '_' && !array_key_exists($m, $this->__methods) &&
  319. !in_array($m, $callbacks)
  320. );
  321. if ($methodAllowed) {
  322. $this->__methods[$m] = array($m, $name);
  323. }
  324. }
  325. }
  326. if (!in_array($name, $this->_attached)) {
  327. $this->_attached[] = $name;
  328. }
  329. if (in_array($name, $this->_disabled) && !(isset($config['enabled']) && $config['enabled'] === false)) {
  330. $this->enable($name);
  331. } elseif (isset($config['enabled']) && $config['enabled'] === false) {
  332. $this->disable($name);
  333. }
  334. return true;
  335. }
  336. /**
  337. * Detaches a behavior from a model
  338. *
  339. * @param string $name CamelCased name of the behavior to unload
  340. * @return void
  341. * @access public
  342. */
  343. function detach($name) {
  344. list($plugin, $name) = pluginSplit($name);
  345. if (isset($this->{$name})) {
  346. $this->{$name}->cleanup(ClassRegistry::getObject($this->modelName));
  347. unset($this->{$name});
  348. }
  349. foreach ($this->__methods as $m => $callback) {
  350. if (is_array($callback) && $callback[1] == $name) {
  351. unset($this->__methods[$m]);
  352. }
  353. }
  354. $this->_attached = array_values(array_diff($this->_attached, (array)$name));
  355. }
  356. /**
  357. * Enables callbacks on a behavior or array of behaviors
  358. *
  359. * @param mixed $name CamelCased name of the behavior(s) to enable (string or array)
  360. * @return void
  361. * @access public
  362. */
  363. function enable($name) {
  364. $this->_disabled = array_diff($this->_disabled, (array)$name);
  365. }
  366. /**
  367. * Disables callbacks on a behavior or array of behaviors. Public behavior methods are still
  368. * callable as normal.
  369. *
  370. * @param mixed $name CamelCased name of the behavior(s) to disable (string or array)
  371. * @return void
  372. * @access public
  373. */
  374. function disable($name) {
  375. foreach ((array)$name as $behavior) {
  376. if (in_array($behavior, $this->_attached) && !in_array($behavior, $this->_disabled)) {
  377. $this->_disabled[] = $behavior;
  378. }
  379. }
  380. }
  381. /**
  382. * Gets the list of currently-enabled behaviors, or, the current status of a single behavior
  383. *
  384. * @param string $name Optional. The name of the behavior to check the status of. If omitted,
  385. * returns an array of currently-enabled behaviors
  386. * @return mixed If $name is specified, returns the boolean status of the corresponding behavior.
  387. * Otherwise, returns an array of all enabled behaviors.
  388. * @access public
  389. */
  390. function enabled($name = null) {
  391. if (!empty($name)) {
  392. return (in_array($name, $this->_attached) && !in_array($name, $this->_disabled));
  393. }
  394. return array_diff($this->_attached, $this->_disabled);
  395. }
  396. /**
  397. * Dispatches a behavior method
  398. *
  399. * @return array All methods for all behaviors attached to this object
  400. * @access public
  401. */
  402. function dispatchMethod(&$model, $method, $params = array(), $strict = false) {
  403. $methods = array_keys($this->__methods);
  404. foreach ($methods as $key => $value) {
  405. $methods[$key] = strtolower($value);
  406. }
  407. $method = strtolower($method);
  408. $check = array_flip($methods);
  409. $found = isset($check[$method]);
  410. $call = null;
  411. if ($strict && !$found) {
  412. trigger_error(sprintf(__("BehaviorCollection::dispatchMethod() - Method %s not found in any attached behavior", true), $method), E_USER_WARNING);
  413. return null;
  414. } elseif ($found) {
  415. $methods = array_combine($methods, array_values($this->__methods));
  416. $call = $methods[$method];
  417. } else {
  418. $count = count($this->__mappedMethods);
  419. $mapped = array_keys($this->__mappedMethods);
  420. for ($i = 0; $i < $count; $i++) {
  421. if (preg_match($mapped[$i] . 'i', $method)) {
  422. $call = $this->__mappedMethods[$mapped[$i]];
  423. array_unshift($params, $method);
  424. break;
  425. }
  426. }
  427. }
  428. if (!empty($call)) {
  429. return $this->{$call[1]}->dispatchMethod($model, $call[0], $params);
  430. }
  431. return array('unhandled');
  432. }
  433. /**
  434. * Dispatches a behavior callback on all attached behavior objects
  435. *
  436. * @param model $model
  437. * @param string $callback
  438. * @param array $params
  439. * @param array $options
  440. * @return mixed
  441. * @access public
  442. */
  443. function trigger(&$model, $callback, $params = array(), $options = array()) {
  444. if (empty($this->_attached)) {
  445. return true;
  446. }
  447. $options = array_merge(array('break' => false, 'breakOn' => array(null, false), 'modParams' => false), $options);
  448. $count = count($this->_attached);
  449. for ($i = 0; $i < $count; $i++) {
  450. $name = $this->_attached[$i];
  451. if (in_array($name, $this->_disabled)) {
  452. continue;
  453. }
  454. $result = $this->{$name}->dispatchMethod($model, $callback, $params);
  455. if ($options['break'] && ($result === $options['breakOn'] || (is_array($options['breakOn']) && in_array($result, $options['breakOn'], true)))) {
  456. return $result;
  457. } elseif ($options['modParams'] && is_array($result)) {
  458. $params[0] = $result;
  459. }
  460. }
  461. if ($options['modParams'] && isset($params[0])) {
  462. return $params[0];
  463. }
  464. return true;
  465. }
  466. /**
  467. * Gets the method list for attached behaviors, i.e. all public, non-callback methods
  468. *
  469. * @return array All public methods for all behaviors attached to this collection
  470. * @access public
  471. */
  472. function methods() {
  473. return $this->__methods;
  474. }
  475. /**
  476. * Gets the list of attached behaviors, or, whether the given behavior is attached
  477. *
  478. * @param string $name Optional. The name of the behavior to check the status of. If omitted,
  479. * returns an array of currently-attached behaviors
  480. * @return mixed If $name is specified, returns the boolean status of the corresponding behavior.
  481. * Otherwise, returns an array of all attached behaviors.
  482. * @access public
  483. */
  484. function attached($name = null) {
  485. if (!empty($name)) {
  486. return (in_array($name, $this->_attached));
  487. }
  488. return $this->_attached;
  489. }
  490. }