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