behavior.php

Go to the documentation of this file.
00001 <?php
00002 /* SVN FILE: $Id: behavior_8php-source.html 580 2008-07-01 14:45:49Z gwoo $ */
00003 
00004 /**
00005  * Model behaviors base class.
00006  *
00007  * Adds methods and automagic functionality to Cake Models.
00008  *
00009  * PHP versions 4 and 5
00010  *
00011  * CakePHP(tm) :  Rapid Development Framework <http://www.cakephp.org/>
00012  * Copyright 2005-2008, Cake Software Foundation, Inc.
00013  *                              1785 E. Sahara Avenue, Suite 490-204
00014  *                              Las Vegas, Nevada 89104
00015  *
00016  * Licensed under The MIT License
00017  * Redistributions of files must retain the above copyright notice.
00018  *
00019  * @filesource
00020  * @copyright       Copyright 2005-2008, Cake Software Foundation, Inc.
00021  * @link                http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
00022  * @package         cake
00023  * @subpackage      cake.cake.libs.model
00024  * @since           CakePHP(tm) v 1.2.0.0
00025  * @version         $Revision: 580 $
00026  * @modifiedby      $LastChangedBy: gwoo $
00027  * @lastmodified    $Date: 2008-07-01 09:45:49 -0500 (Tue, 01 Jul 2008) $
00028  * @license         http://www.opensource.org/licenses/mit-license.php The MIT License
00029  */
00030 /**
00031  * Model behavior base class.
00032  *
00033  * Defines the Behavior interface, and contains common model interaction functionality.
00034  *
00035  * @package     cake
00036  * @subpackage  cake.cake.libs.model
00037  */
00038 class ModelBehavior extends Object {
00039 /**
00040  * Contains configuration settings for use with individual model objects.  This
00041  * is used because if multiple models use this Behavior, each will use the same
00042  * object instance.  Individual model settings should be stored as an
00043  * associative array, keyed off of the model name.
00044  *
00045  * @var array
00046  * @access public
00047  * @see Model::$alias
00048  */
00049     var $settings = array();
00050 /**
00051  * Allows the mapping of preg-compatible regular expressions to public or
00052  * private methods in this class, where the array key is a /-delimited regular
00053  * expression, and the value is a class method.  Similar to the functionality of
00054  * the findBy* / findAllBy* magic methods.
00055  *
00056  * @var array
00057  * @access public
00058  */
00059     var $mapMethods = array();
00060 /**
00061  * Setup this behavior with the specified configuration settings.
00062  *
00063  * @param object $model Model using this behavior
00064  * @param array $config Configuration settings for $model
00065  * @access public
00066  */
00067     function setup(&$model, $config = array()) { }
00068 /**
00069  * Clean up any initialization this behavior has done on a model.  Called when a behavior is dynamically
00070  * detached from a model using Model::detach().
00071  *
00072  * @param object $model Model using this behavior
00073  * @access public
00074  * @see BehaviorCollection::detach()
00075  */
00076     function cleanup(&$model) {
00077         if (isset($this->settings[$model->alias])) {
00078             unset($this->settings[$model->alias]);
00079         }
00080     }
00081 /**
00082  * Before find callback
00083  *
00084  * @param object $model Model using this behavior
00085  * @param array $queryData Data used to execute this query, i.e. conditions, order, etc.
00086  * @return boolean True if the operation should continue, false if it should abort
00087  * @access public
00088  */
00089     function beforeFind(&$model, $query) { }
00090 /**
00091  * After find callback. Can be used to modify any results returned by find and findAll.
00092  *
00093  * @param object $model Model using this behavior
00094  * @param mixed $results The results of the find operation
00095  * @param boolean $primary Whether this model is being queried directly (vs. being queried as an association)
00096  * @return mixed Result of the find operation
00097  * @access public
00098  */
00099     function afterFind(&$model, $results, $primary) { }
00100 /**
00101  * Before validate callback
00102  *
00103  * @param object $model Model using this behavior
00104  * @return boolean True if validate operation should continue, false to abort
00105  * @access public
00106  */
00107     function beforeValidate(&$model) { }
00108 /**
00109  * Before save callback
00110  *
00111  * @param object $model Model using this behavior
00112  * @return boolean True if the operation should continue, false if it should abort
00113  * @access public
00114  */
00115     function beforeSave(&$model) { }
00116 /**
00117  * After save callback
00118  *
00119  * @param object $model Model using this behavior
00120  * @param boolean $created True if this save created a new record
00121  * @access public
00122  */
00123     function afterSave(&$model, $created) { }
00124 /**
00125  * Before delete callback
00126  *
00127  * @param object $model Model using this behavior
00128  * @param boolean $cascade If true records that depend on this record will also be deleted
00129  * @return boolean True if the operation should continue, false if it should abort
00130  * @access public
00131  */
00132     function beforeDelete(&$model, $cascade = true) { }
00133 /**
00134  * After delete callback
00135  *
00136  * @param object $model Model using this behavior
00137  * @access public
00138  */
00139     function afterDelete(&$model) { }
00140 /**
00141  * DataSource error callback
00142  *
00143  * @param object $model Model using this behavior
00144  * @param string $error Error generated in DataSource
00145  * @access public
00146  */
00147     function onError(&$model, $error) { }
00148 /**
00149  * Overrides Object::dispatchMethod to account for PHP4's broken reference support
00150  *
00151  * @see Object::dispatchMethod
00152  * @access public
00153  */
00154     function dispatchMethod(&$model, $method, $params = array()) {
00155         if (empty($params)) {
00156             return $this->{$method}($model);
00157         }
00158         $params = array_values($params);
00159 
00160         switch (count($params)) {
00161             case 1:
00162                 return $this->{$method}($model, $params[0]);
00163             case 2:
00164                 return $this->{$method}($model, $params[0], $params[1]);
00165             case 3:
00166                 return $this->{$method}($model, $params[0], $params[1], $params[2]);
00167             case 4:
00168                 return $this->{$method}($model, $params[0], $params[1], $params[2], $params[3]);
00169             case 5:
00170                 return $this->{$method}($model, $params[0], $params[1], $params[2], $params[3], $params[4]);
00171             default:
00172                 array_unshift($params, $model);
00173                 return call_user_func_array(array(&$this, $method), $params);
00174             break;
00175         }
00176     }
00177 /**
00178  * If $model's whitelist property is non-empty, $field will be added to it.
00179  * Note: this method should *only* be used in beforeValidate or beforeSave to ensure
00180  * that it only modifies the whitelist for the current save operation.  Also make sure
00181  * you explicitly set the value of the field which you are allowing.
00182  *
00183  * @param object $model Model using this behavior
00184  * @param string $field Field to be added to $model's whitelist
00185  * @access protected
00186  * @return void
00187  */
00188     function _addToWhitelist(&$model, $field) {
00189         if (is_array($field)) {
00190             foreach ($field as $f) {
00191                 $this->_addToWhitelist($model, $f);
00192             }
00193             return;
00194         }
00195         if (!empty($model->whitelist) && !in_array($field, $model->whitelist)) {
00196             $model->whitelist[] = $field;
00197         }
00198     }
00199 }
00200 
00201 /**
00202  * Model behavior collection class.
00203  *
00204  * Defines the Behavior interface, and contains common model interaction functionality.
00205  *
00206  * @package     cake
00207  * @subpackage  cake.cake.libs.model
00208  */
00209 class BehaviorCollection extends Object {
00210 
00211 /**
00212  * Stores a reference to the attached name
00213  *
00214  * @var object
00215  */
00216     var $modelName = null;
00217 /**
00218  * Lists the currently-attached behavior objects
00219  *
00220  * @var array
00221  * @access private
00222  */
00223     var $_attached = array();
00224 /**
00225  * Lists the currently-attached behavior objects which are disabled
00226  *
00227  * @var array
00228  * @access private
00229  */
00230     var $_disabled = array();
00231 /**
00232  * Keeps a list of all methods of attached behaviors
00233  *
00234  * @var array
00235  */
00236     var $__methods = array();
00237 /**
00238  * Keeps a list of all methods which have been mapped with regular expressions
00239  *
00240  * @var array
00241  */
00242     var $__mappedMethods = array();
00243 /**
00244  * Attaches a model object and loads a list of behaviors
00245  *
00246  * @access public
00247  */
00248     function init($modelName, $behaviors = array()) {
00249         $this->modelName = $modelName;
00250 
00251         if (!empty($behaviors)) {
00252             foreach (Set::normalize($behaviors) as $behavior => $config) {
00253                 $this->attach($behavior, $config);
00254             }
00255         }
00256     }
00257 /**
00258  * Attaches a behavior to a model
00259  *
00260  * @param string $behavior CamelCased name of the behavior to load
00261  * @param array $config Behavior configuration parameters
00262  * @return boolean True on success, false on failure
00263  * @access public
00264  */
00265     function attach($behavior, $config = array()) {
00266         $name = $behavior;
00267         if (strpos($behavior, '.')) {
00268             list($plugin, $name) = explode('.', $behavior, 2);
00269         }
00270         $class = $name . 'Behavior';
00271 
00272         if (!App::import('Behavior', $behavior)) {
00273             return false;
00274         }
00275 
00276         if (!isset($this->{$name})) {
00277             if (PHP5) {
00278                 $this->{$name} = new $class;
00279             } else {
00280                 $this->{$name} =& new $class;
00281             }
00282         } elseif (isset($this->{$name}->settings) && isset($this->{$name}->settings[$this->modelName])) {
00283             if ($config !== null && $config !== false) {
00284                 $config = array_merge($this->{$name}->settings[$this->modelName], $config);
00285             } else {
00286                 $config = array();
00287             }
00288         }
00289         $this->{$name}->setup(ClassRegistry::getObject($this->modelName), $config);
00290 
00291         foreach ($this->{$name}->mapMethods as $method => $alias) {
00292             $this->__mappedMethods[$method] = array($alias, $name);
00293         }
00294         $methods = get_class_methods($this->{$name});
00295         $parentMethods = get_class_methods('ModelBehavior');
00296         $callbacks = array('setup', 'cleanup', 'beforeFind', 'afterFind', 'beforeSave', 'afterSave', 'beforeDelete', 'afterDelete', 'afterError');
00297 
00298         foreach ($methods as $m) {
00299             if (!in_array($m, $parentMethods)) {
00300                 if ($m[0] != '_' && !array_key_exists($m, $this->__methods) && !in_array($m, $callbacks)) {
00301                     $this->__methods[$m] = array($m, $name);
00302                 }
00303             }
00304         }
00305 
00306         if (!in_array($name, $this->_attached)) {
00307             $this->_attached[] = $name;
00308         }
00309         if (in_array($name, $this->_disabled) && !(isset($config['enabled']) && $config['enabled'] === false)) {
00310             $this->enable($name);
00311         } elseif (isset($config['enabled']) && $config['enabled'] === false) {
00312             $this->disable($name);
00313         }
00314         return true;
00315     }
00316 /**
00317  * Detaches a behavior from a model
00318  *
00319  * @param string $name CamelCased name of the behavior to unload
00320  * @access public
00321  */
00322     function detach($name) {
00323         if (isset($this->{$name})) {
00324             $this->{$name}->cleanup(ClassRegistry::getObject($this->modelName));
00325             unset($this->{$name});
00326         }
00327         foreach ($this->__methods as $m => $callback) {
00328             if (is_array($callback) && $callback[1] == $name) {
00329                 unset($this->__methods[$m]);
00330             }
00331         }
00332         $keys = array_combine(array_values($this->_attached), array_keys($this->_attached));
00333         unset($this->_attached[$keys[$name]]);
00334         $this->_attached = array_values($this->_attached);
00335     }
00336 /**
00337  * Enables callbacks on a behavior or array of behaviors
00338  *
00339  * @param mixed $name CamelCased name of the behavior(s) to enable (string or array)
00340  * @return void
00341  * @access public
00342  */
00343     function enable($name) {
00344         $keys = array_combine(array_values($this->_disabled), array_keys($this->_disabled));
00345         foreach ((array)$name as $behavior) {
00346             unset($this->_disabled[$keys[$behavior]]);
00347         }
00348     }
00349 /**
00350  * Disables callbacks on a behavior or array of behaviors.  Public behavior methods are still
00351  * callable as normal.
00352  *
00353  * @param mixed $name CamelCased name of the behavior(s) to disable (string or array)
00354  * @return void
00355  * @access public
00356  */
00357     function disable($name) {
00358         foreach ((array)$name as $behavior) {
00359             if (in_array($behavior, $this->_attached) && !in_array($behavior, $this->_disabled)) {
00360                 $this->_disabled[] = $behavior;
00361             }
00362         }
00363     }
00364 /**
00365  * Gets the list of currently-enabled behaviors, or, the current status of a single behavior
00366  *
00367  * @param string $name Optional.  The name of the behavior to check the status of.  If omitted,
00368  *                      returns an array of currently-enabled behaviors
00369  * @return mixed If $name is specified, returns the boolean status of the corresponding behavior.
00370  *               Otherwise, returns an array of all enabled behaviors.
00371  * @access public
00372  */
00373     function enabled($name = null) {
00374         if (!empty($name)) {
00375             return (in_array($name, $this->_attached) && !in_array($name, $this->_disabled));
00376         }
00377         return array_diff($this->_attached, $this->_disabled);
00378     }
00379 /**
00380  * Dispatches a behavior method
00381  *
00382  * @return array All methods for all behaviors attached to this object
00383  * @access public
00384  */
00385     function dispatchMethod(&$model, $method, $params = array(), $strict = false) {
00386         $methods = array_map('strtolower', array_keys($this->__methods));
00387         $found = (in_array(strtolower($method), $methods));
00388         $call = null;
00389 
00390         if ($strict && !$found) {
00391             trigger_error("BehaviorCollection::dispatchMethod() - Method {$method} not found in any attached behavior", E_USER_WARNING);
00392             return null;
00393         } elseif ($found) {
00394             $methods = array_combine($methods, array_values($this->__methods));
00395             $call = $methods[strtolower($method)];
00396         } else {
00397             $count = count($this->__mappedMethods);
00398             $mapped = array_keys($this->__mappedMethods);
00399 
00400             for ($i = 0; $i < $count; $i++) {
00401                 if (preg_match($mapped[$i] . 'i', $method)) {
00402                     $call = $this->__mappedMethods[$mapped[$i]];
00403                     array_unshift($params, $method);
00404                     break;
00405                 }
00406             }
00407         }
00408         if (!empty($call)) {
00409             return $this->{$call[1]}->dispatchMethod($model, $call[0], $params);
00410         }
00411         return array('unhandled');
00412     }
00413 /**
00414  * Dispatches a behavior callback on all attached behavior objects
00415  *
00416  * @param model $model
00417  * @param string $callback
00418  * @param array $params
00419  * @param array $options
00420  * @return mixed
00421  * @access public
00422  */
00423     function trigger(&$model, $callback, $params = array(), $options = array()) {
00424         if (empty($this->_attached)) {
00425             return true;
00426         }
00427         $_params = $params;
00428         $options = array_merge(array('break' => false, 'breakOn' => array(null, false), 'modParams' => false), $options);
00429         $count = count($this->_attached);
00430 
00431         for ($i = 0; $i < $count; $i++) {
00432             $name = $this->_attached[$i];
00433             if (in_array($name, $this->_disabled)) {
00434                 continue;
00435             }
00436             $result = $this->{$name}->dispatchMethod($model, $callback, $params);
00437 
00438             if ($options['break'] && ($result === $options['breakOn'] || (is_array($options['breakOn']) && in_array($result, $options['breakOn'], true)))) {
00439                 return $result;
00440             } elseif ($options['modParams'] && is_array($result)) {
00441                 $params[0] = $result;
00442             }
00443         }
00444         if ($options['modParams'] && isset($params[0])) {
00445             return $params[0];
00446         }
00447         return true;
00448     }
00449 /**
00450  * Gets the method list for attached behaviors, i.e. all public, non-callback methods
00451  *
00452  * @return array All public methods for all behaviors attached to this collection
00453  * @access public
00454  */
00455     function methods() {
00456         return $this->__methods;
00457     }
00458 /**
00459  * Gets the list of attached behaviors, or, whether the given behavior is attached
00460  *
00461  * @param string $name Optional.  The name of the behavior to check the status of.  If omitted,
00462  *                      returns an array of currently-attached behaviors
00463  * @return mixed If $name is specified, returns the boolean status of the corresponding behavior.
00464  *               Otherwise, returns an array of all attached behaviors.
00465  * @access public
00466  */
00467     function attached($name = null) {
00468         if (!empty($name)) {
00469             return (in_array($name, $this->_attached));
00470         }
00471         return $this->_attached;
00472     }
00473 }
00474 ?>