cakephp2-php8/cake/libs/model/model_behavior.php
mark_story 6db3dbc680 Starting to create HelperCollection which will be responsible for loading and constructing helpers.
Making an abstract class that will be the base of all object collections.
Adding test cases.
2010-08-10 23:18:36 -04:00

494 lines
14 KiB
PHP

<?php
/**
* Model behaviors base class.
*
* Adds methods and automagic functionality to Cake Models.
*
* PHP versions 4 and 5
*
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @package cake
* @subpackage cake.cake.libs.model
* @since CakePHP(tm) v 1.2.0.0
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
App::import('Core', 'ObjectCollection');
/**
* Model behavior base class.
*
* Defines the Behavior interface, and contains common model interaction functionality.
*
* @package cake
* @subpackage cake.cake.libs.model
*/
class ModelBehavior extends Object {
/**
* Contains configuration settings for use with individual model objects. This
* is used because if multiple models use this Behavior, each will use the same
* object instance. Individual model settings should be stored as an
* associative array, keyed off of the model name.
*
* @var array
* @access public
* @see Model::$alias
*/
public $settings = array();
/**
* Allows the mapping of preg-compatible regular expressions to public or
* private methods in this class, where the array key is a /-delimited regular
* expression, and the value is a class method. Similar to the functionality of
* the findBy* / findAllBy* magic methods.
*
* @var array
* @access public
*/
public $mapMethods = array();
/**
* Setup this behavior with the specified configuration settings.
*
* @param object $model Model using this behavior
* @param array $config Configuration settings for $model
*/
public function setup(&$model, $config = array()) { }
/**
* Clean up any initialization this behavior has done on a model. Called when a behavior is dynamically
* detached from a model using Model::detach().
*
* @param object $model Model using this behavior
* @access public
* @see BehaviorCollection::detach()
*/
function cleanup(&$model) {
if (isset($this->settings[$model->alias])) {
unset($this->settings[$model->alias]);
}
}
/**
* Before find callback
*
* @param object $model Model using this behavior
* @param array $queryData Data used to execute this query, i.e. conditions, order, etc.
* @return mixed False if the operation should abort. An array will replace the value of $query.
* @access public
*/
public function beforeFind(&$model, $query) { }
/**
* After find callback. Can be used to modify any results returned by find and findAll.
*
* @param object $model Model using this behavior
* @param mixed $results The results of the find operation
* @param boolean $primary Whether this model is being queried directly (vs. being queried as an association)
* @return mixed An array value will replace the value of $results - any other value will be ignored.
* @access public
*/
public function afterFind(&$model, $results, $primary) { }
/**
* Before validate callback
*
* @param object $model Model using this behavior
* @return mixed False if the operation should abort. Any other result will continue.
* @access public
*/
public function beforeValidate(&$model) { }
/**
* Before save callback
*
* @param object $model Model using this behavior
* @return mixed False if the operation should abort. Any other result will continue.
* @access public
*/
public function beforeSave(&$model) { }
/**
* After save callback
*
* @param object $model Model using this behavior
* @param boolean $created True if this save created a new record
*/
public function afterSave(&$model, $created) { }
/**
* Before delete callback
*
* @param object $model Model using this behavior
* @param boolean $cascade If true records that depend on this record will also be deleted
* @return mixed False if the operation should abort. Any other result will continue.
* @access public
*/
public function beforeDelete(&$model, $cascade = true) { }
/**
* After delete callback
*
* @param object $model Model using this behavior
*/
public function afterDelete(&$model) { }
/**
* DataSource error callback
*
* @param object $model Model using this behavior
* @param string $error Error generated in DataSource
*/
public function onError(&$model, $error) { }
/**
* Overrides Object::dispatchMethod to account for PHP4's broken reference support
*
* @see Object::dispatchMethod
* @access public
* @return mixed
*/
function dispatchMethod(&$model, $method, $params = array()) {
if (empty($params)) {
return $this->{$method}($model);
}
$params = array_values($params);
switch (count($params)) {
case 1:
return $this->{$method}($model, $params[0]);
case 2:
return $this->{$method}($model, $params[0], $params[1]);
case 3:
return $this->{$method}($model, $params[0], $params[1], $params[2]);
case 4:
return $this->{$method}($model, $params[0], $params[1], $params[2], $params[3]);
case 5:
return $this->{$method}($model, $params[0], $params[1], $params[2], $params[3], $params[4]);
default:
$params = array_merge(array(&$model), $params);
return call_user_func_array(array(&$this, $method), $params);
break;
}
}
/**
* If $model's whitelist property is non-empty, $field will be added to it.
* Note: this method should *only* be used in beforeValidate or beforeSave to ensure
* that it only modifies the whitelist for the current save operation. Also make sure
* you explicitly set the value of the field which you are allowing.
*
* @param object $model Model using this behavior
* @param string $field Field to be added to $model's whitelist
* @access protected
* @return void
*/
function _addToWhitelist(&$model, $field) {
if (is_array($field)) {
foreach ($field as $f) {
$this->_addToWhitelist($model, $f);
}
return;
}
if (!empty($model->whitelist) && !in_array($field, $model->whitelist)) {
$model->whitelist[] = $field;
}
}
}
/**
* Model behavior collection class.
*
* Defines the Behavior interface, and contains common model interaction functionality.
*
* @package cake
* @subpackage cake.cake.libs.model
*/
class BehaviorCollection extends ObjectCollection {
/**
* Stores a reference to the attached name
*
* @var string
* @access public
*/
public $modelName = null;
/**
* Keeps a list of all methods of attached behaviors
*
* @var array
*/
private $__methods = array();
/**
* Keeps a list of all methods which have been mapped with regular expressions
*
* @var array
*/
private $__mappedMethods = array();
/**
* Attaches a model object and loads a list of behaviors
*
* @access public
* @return void
*/
function init($modelName, $behaviors = array()) {
$this->modelName = $modelName;
if (!empty($behaviors)) {
foreach (Set::normalize($behaviors) as $behavior => $config) {
$this->attach($behavior, $config);
}
}
}
/**
* Attaches a behavior to a model
*
* @param string $behavior CamelCased name of the behavior to load
* @param array $config Behavior configuration parameters
* @return boolean True on success, false on failure
*/
public function attach($behavior, $config = array()) {
list($plugin, $name) = pluginSplit($behavior);
$class = $name . 'Behavior';
if (!App::import('Behavior', $behavior)) {
$this->cakeError('missingBehaviorFile', array(array(
'behavior' => $behavior,
'file' => Inflector::underscore($behavior) . '.php',
'code' => 500,
'base' => '/'
)));
return false;
}
if (!class_exists($class)) {
$this->cakeError('missingBehaviorClass', array(array(
'behavior' => $class,
'file' => Inflector::underscore($class) . '.php',
'code' => 500,
'base' => '/'
)));
return false;
}
if (!isset($this->{$name})) {
if (ClassRegistry::isKeySet($class)) {
$this->{$name} = ClassRegistry::getObject($class);
} else {
$this->{$name} = new $class;
ClassRegistry::addObject($class, $this->{$name});
if (!empty($plugin)) {
ClassRegistry::addObject($plugin.'.'.$class, $this->{$name});
}
}
} elseif (isset($this->{$name}->settings) && isset($this->{$name}->settings[$this->modelName])) {
if ($config !== null && $config !== false) {
$config = array_merge($this->{$name}->settings[$this->modelName], $config);
} else {
$config = array();
}
}
if (empty($config)) {
$config = array();
}
$this->{$name}->setup(ClassRegistry::getObject($this->modelName), $config);
foreach ($this->{$name}->mapMethods as $method => $alias) {
$this->__mappedMethods[$method] = array($alias, $name);
}
$methods = get_class_methods($this->{$name});
$parentMethods = array_flip(get_class_methods('ModelBehavior'));
$callbacks = array(
'setup', 'cleanup', 'beforeFind', 'afterFind', 'beforeSave', 'afterSave',
'beforeDelete', 'afterDelete', 'afterError'
);
foreach ($methods as $m) {
if (!isset($parentMethods[$m])) {
$methodAllowed = (
$m[0] != '_' && !array_key_exists($m, $this->__methods) &&
!in_array($m, $callbacks)
);
if ($methodAllowed) {
$this->__methods[$m] = array($m, $name);
}
}
}
if (!in_array($name, $this->_attached)) {
$this->_attached[] = $name;
}
if (in_array($name, $this->_disabled) && !(isset($config['enabled']) && $config['enabled'] === false)) {
$this->enable($name);
} elseif (isset($config['enabled']) && $config['enabled'] === false) {
$this->disable($name);
}
return true;
}
/**
* Detaches a behavior from a model
*
* @param string $name CamelCased name of the behavior to unload
* @return void
*/
public function detach($name) {
list($plugin, $name) = pluginSplit($name);
if (isset($this->{$name})) {
$this->{$name}->cleanup(ClassRegistry::getObject($this->modelName));
unset($this->{$name});
}
foreach ($this->__methods as $m => $callback) {
if (is_array($callback) && $callback[1] == $name) {
unset($this->__methods[$m]);
}
}
$this->_attached = array_values(array_diff($this->_attached, (array)$name));
}
/**
* Enables callbacks on a behavior or array of behaviors
*
* @param mixed $name CamelCased name of the behavior(s) to enable (string or array)
* @return void
*/
public function enable($name) {
$this->_disabled = array_diff($this->_disabled, (array)$name);
}
/**
* Disables callbacks on a behavior or array of behaviors. Public behavior methods are still
* callable as normal.
*
* @param mixed $name CamelCased name of the behavior(s) to disable (string or array)
* @return void
*/
public function disable($name) {
foreach ((array)$name as $behavior) {
if (in_array($behavior, $this->_attached) && !in_array($behavior, $this->_disabled)) {
$this->_disabled[] = $behavior;
}
}
}
/**
* Gets the list of currently-enabled behaviors, or, the current status of a single behavior
*
* @param string $name Optional. The name of the behavior to check the status of. If omitted,
* returns an array of currently-enabled behaviors
* @return mixed If $name is specified, returns the boolean status of the corresponding behavior.
* Otherwise, returns an array of all enabled behaviors.
*/
public function enabled($name = null) {
if (!empty($name)) {
return (in_array($name, $this->_attached) && !in_array($name, $this->_disabled));
}
return array_diff($this->_attached, $this->_disabled);
}
/**
* Dispatches a behavior method
*
* @return array All methods for all behaviors attached to this object
*/
public function dispatchMethod(&$model, $method, $params = array(), $strict = false) {
$methods = array_keys($this->__methods);
$check = array_flip($methods);
$found = isset($check[$method]);
$call = null;
if ($strict && !$found) {
trigger_error(sprintf(__("BehaviorCollection::dispatchMethod() - Method %s not found in any attached behavior"), $method), E_USER_WARNING);
return null;
} elseif ($found) {
$methods = array_combine($methods, array_values($this->__methods));
$call = $methods[$method];
} else {
$count = count($this->__mappedMethods);
$mapped = array_keys($this->__mappedMethods);
for ($i = 0; $i < $count; $i++) {
if (preg_match($mapped[$i] . 'i', $method)) {
$call = $this->__mappedMethods[$mapped[$i]];
array_unshift($params, $method);
break;
}
}
}
if (!empty($call)) {
return $this->{$call[1]}->dispatchMethod($model, $call[0], $params);
}
return array('unhandled');
}
/**
* Dispatches a behavior callback on all attached behavior objects
*
* @param model $model
* @param string $callback
* @param array $params
* @param array $options
* @return mixed
*/
public function trigger(&$model, $callback, $params = array(), $options = array()) {
if (empty($this->_attached)) {
return true;
}
$options = array_merge(array('break' => false, 'breakOn' => array(null, false), 'modParams' => false), $options);
$count = count($this->_attached);
for ($i = 0; $i < $count; $i++) {
$name = $this->_attached[$i];
if (in_array($name, $this->_disabled)) {
continue;
}
$result = $this->{$name}->dispatchMethod($model, $callback, $params);
if ($options['break'] && ($result === $options['breakOn'] || (is_array($options['breakOn']) && in_array($result, $options['breakOn'], true)))) {
return $result;
} elseif ($options['modParams'] && is_array($result)) {
$params[0] = $result;
}
}
if ($options['modParams'] && isset($params[0])) {
return $params[0];
}
return true;
}
/**
* Gets the method list for attached behaviors, i.e. all public, non-callback methods
*
* @return array All public methods for all behaviors attached to this collection
*/
public function methods() {
return $this->__methods;
}
/**
* Gets the list of attached behaviors, or, whether the given behavior is attached
*
* @param string $name Optional. The name of the behavior to check the status of. If omitted,
* returns an array of currently-attached behaviors
* @return mixed If $name is specified, returns the boolean status of the corresponding behavior.
* Otherwise, returns an array of all attached behaviors.
*/
public function attached($name = null) {
if (!empty($name)) {
return (in_array($name, $this->_attached));
}
return $this->_attached;
}
}