From 0572926d49effbd8223a723ce9a8297698f6cb5d Mon Sep 17 00:00:00 2001 From: nate Date: Thu, 21 Feb 2008 15:36:13 +0000 Subject: [PATCH] Refactoring model behavior handling git-svn-id: https://svn.cakephp.org/repo/branches/1.2.x.x@6469 3807eeeb-6ff5-0310-8944-8be069107fe0 --- cake/libs/model/behavior.php | 277 ++++++++- cake/libs/model/behaviors/translate.php | 62 +- cake/libs/model/datasources/dbo_source.php | 75 ++- cake/libs/model/model.php | 325 ++++------- cake/libs/object.php | 2 +- .../libs/model/behaviors/translate.test.php | 10 +- .../cases/libs/model/behaviors/tree.test.php | 1 + .../model/datasources/dbo_source.test.php | 33 +- cake/tests/cases/libs/model/model.test.php | 546 +----------------- 9 files changed, 497 insertions(+), 834 deletions(-) diff --git a/cake/libs/model/behavior.php b/cake/libs/model/behavior.php index faa5affee..c094f856c 100644 --- a/cake/libs/model/behavior.php +++ b/cake/libs/model/behavior.php @@ -71,9 +71,13 @@ class ModelBehavior extends Object { * * @param object $model Model using this behavior * @access public - * @see Model::detach() + * @see BehaviorCollection::detach() */ - function cleanup(&$model) { } + function cleanup(&$model) { + if (isset($this->settings[$model->alias])) { + unset($this->settings[$model->alias]); + } + } /** * Before find callback * @@ -174,7 +178,274 @@ class ModelBehavior extends Object { * @subpackage cake.cake.libs.model */ class BehaviorCollection extends Object { - + +/** + * Stores a reference to the attached model + * + * @var object + */ + var $model = null; +/** + * Lists the currently-attached behavior objects + * + * @var array + * @access private + */ + var $_attached = array(); +/** + * Lists the currently-attached behavior objects which are disabled + * + * @var array + * @access private + */ + var $_disabled = array(); +/** + * Keeps a list of all methods of attached behaviors + * + * @var array + */ + var $__methods = array(); +/** + * Keeps a list of all methods which have been mapped with regular expressions + * + * @var array + */ + var $__mappedMethods = array(); +/** + * Attaches a model object and loads a list of behaviors + * + * @access public + */ + function __construct(&$model, $behaviors = array()) { + $this->model =& $model; + + 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 + * @access public + */ + function attach($behavior, $config = array()) { + $name = $behavior; + if (strpos($behavior, '.')) { + list($plugin, $name) = explode('.', $behavior, 2); + } + $class = $name . 'Behavior'; + + if (!App::import('Behavior', $behavior)) { + // Raise an error + return false; + } + + if (!isset($this->{$name})) { + if (ClassRegistry::isKeySet($class)) { + if (PHP5) { + $this->{$name} = ClassRegistry::getObject($class); + } else { + $this->{$name} =& ClassRegistry::getObject($class); + } + } else { + if (PHP5) { + $this->{$name} = new $class; + } else { + $this->{$name} =& new $class; + } + ClassRegistry::addObject($class, $this->{$name}); + } + } elseif (isset($this->{$name}->settings) && isset($this->{$name}->settings[$this->model->alias])) { + $config = array_merge($this->{$name}->settings[$this->model->alias], $config); + } + $this->{$name}->setup($this->model, $config); + + foreach ($this->{$name}->mapMethods as $method => $alias) { + $this->__mappedMethods[$method] = array($alias, $name); + } + + $methods = get_class_methods($this->{$name}); + $parentMethods = get_class_methods('ModelBehavior'); + $callbacks = array('setup', 'cleanup', 'beforeFind', 'afterFind', 'beforeSave', 'afterSave', 'beforeDelete', 'afterDelete', 'afterError'); + + foreach ($methods as $m) { + if (!in_array($m, $parentMethods)) { + if (strpos($m, '_') !== 0 && !array_key_exists($m, $this->__methods) && !in_array($m, $callbacks)) { + $this->__methods[$m] = array($m, $name); + } + } + } + + if (!in_array($name, $this->_attached)) { + $this->_attached[] = $name; + } + if (in_array($name, $this->_disabled)) { + $this->enable($name); + } + return true; + } +/** + * Detaches a behavior from a model + * + * @param string $name CamelCased name of the behavior to unload + * @access public + */ + function detach($name) { + if (isset($this->{$name})) { + $this->{$name}->cleanup($this->model); + unset($this->{$name}); + } + foreach ($this->__methods as $m => $callback) { + if (is_array($callback) && $callback[1] == $name) { + unset($this->__methods[$m]); + } + } + $keys = array_combine(array_values($this->_attached), array_keys($this->_attached)); + unset($this->_attached[$keys[$name]]); + $this->_attached = array_values($this->_attached); + } +/** + * 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 + * @access public + */ + function enable($name) { + $keys = array_combine(array_values($this->_disabled), array_keys($this->_disabled)); + foreach ((array)$name as $behavior) { + unset($this->_disabled[$keys[$behavior]]); + } + } +/** + * 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 + * @access 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. + * @access 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 + * @access public + */ + function dispatchMethod($method, $params = array(), $strict = false) { + $methods = array_map('strtolower', array_keys($this->__methods)); + $found = (in_array(strtolower($method), $methods)); + $call = null; + + if ($strict && !$found) { + trigger_error("BehaviorCollection::dispatchMethod() - Method {$method} not found in any attached behavior", E_USER_WARNING); + return null; + } elseif ($found) { + $methods = array_combine($methods, array_values($this->__methods)); + $call = $methods[strtolower($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($call[0], array_merge(array(&$this->model), $params)); + } + return array('unhandled'); + } +/** + * Dispatches a behavior callback on all attached behavior objects + * + * @param string $callback + * @param array $params + * @param array $options + * @return mixed + * @access public + */ + function trigger($callback, $params = array(), $options = array()) { + if (empty($this->_attached)) { + return true; + } + $_params = $params; + $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($callback, array_merge(array(&$this->model), (array)$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 + * @access 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. + * @access public + */ + function attached($name = null) { + if (!empty($name)) { + return (in_array($name, $this->_attached)); + } + return $this->_attached; + } } ?> \ No newline at end of file diff --git a/cake/libs/model/behaviors/translate.php b/cake/libs/model/behaviors/translate.php index f12e7a4e0..2aa710455 100644 --- a/cake/libs/model/behaviors/translate.php +++ b/cake/libs/model/behaviors/translate.php @@ -85,15 +85,17 @@ class TranslateBehavior extends ModelBehavior { $RuntimeModel =& $this->translateModel($model); if (is_string($query['fields']) && 'COUNT(*) AS '.$db->name('count') == $query['fields']) { - $query['fields'] = 'COUNT(DISTINCT('.$db->name($model->alias).'.'.$db->name($model->primaryKey).')) ' . $db->alias . 'count'; + $query['fields'] = 'COUNT(DISTINCT('.$db->name($model->alias . '.' . $model->primaryKey) . ')) ' . $db->alias . 'count'; $query['joins'][] = array( - 'type' => 'INNER', - 'alias' => $RuntimeModel->alias, - 'table' => $db->name($tablePrefix . $RuntimeModel->useTable), - 'conditions' => array( - $model->alias.'.id' => '{$__cakeIdentifier['.$RuntimeModel->alias.'.foreign_key]__$}', - $RuntimeModel->alias.'.model' => $model->alias, - $RuntimeModel->alias.'.locale' => $locale)); + 'type' => 'INNER', + 'alias' => $RuntimeModel->alias, + 'table' => $db->name($tablePrefix . $RuntimeModel->useTable), + 'conditions' => array( + $model->alias.'.id' => '{$__cakeIdentifier['.$RuntimeModel->alias.'.foreign_key]__$}', + $RuntimeModel->alias.'.model' => $model->alias, + $RuntimeModel->alias.'.locale' => $locale + ) + ); return $query; } @@ -142,25 +144,29 @@ class TranslateBehavior extends ModelBehavior { foreach ($locale as $_locale) { $query['fields'][] = 'I18n__'.$field.'__'.$_locale.'.content'; $query['joins'][] = array( - 'type' => 'LEFT', - 'alias' => 'I18n__'.$field.'__'.$_locale, - 'table' => $db->name($tablePrefix . $RuntimeModel->useTable), - 'conditions' => array( - $model->alias.'.id' => '{$__cakeIdentifier[I18n__'.$field.'__'.$_locale.'.foreign_key]__$}', - 'I18n__'.$field.'__'.$_locale.'.model' => $model->alias, - 'I18n__'.$field.'__'.$_locale.'.'.$RuntimeModel->displayField => $field, - 'I18n__'.$field.'__'.$_locale.'.locale' => $_locale)); + 'type' => 'LEFT', + 'alias' => 'I18n__'.$field.'__'.$_locale, + 'table' => $db->name($tablePrefix . $RuntimeModel->useTable), + 'conditions' => array( + $model->alias.'.id' => '{$__cakeIdentifier[I18n__'.$field.'__'.$_locale.'.foreign_key]__$}', + 'I18n__'.$field.'__'.$_locale.'.model' => $model->alias, + 'I18n__'.$field.'__'.$_locale.'.'.$RuntimeModel->displayField => $field, + 'I18n__'.$field.'__'.$_locale.'.locale' => $_locale + ) + ); } } else { $query['fields'][] = 'I18n__'.$field.'.content'; $query['joins'][] = array( - 'type' => 'LEFT', - 'alias' => 'I18n__'.$field, - 'table' => $db->name($tablePrefix . $RuntimeModel->useTable), - 'conditions' => array( - $model->alias.'.id' => '{$__cakeIdentifier[I18n__'.$field.'.foreign_key]__$}', - 'I18n__'.$field.'.model' => $model->alias, - 'I18n__'.$field.'.'.$RuntimeModel->displayField => $field)); + 'type' => 'LEFT', + 'alias' => 'I18n__'.$field, + 'table' => $db->name($tablePrefix . $RuntimeModel->useTable), + 'conditions' => array( + $model->alias.'.id' => '{$__cakeIdentifier[I18n__'.$field.'.foreign_key]__$}', + 'I18n__'.$field.'.model' => $model->alias, + 'I18n__'.$field.'.'.$RuntimeModel->displayField => $field + ) + ); $query['conditions'][$db->name('I18n__'.$field.'.locale')] = $locale; } } @@ -285,7 +291,7 @@ class TranslateBehavior extends ModelBehavior { function _getLocale(&$model) { if (!isset($model->locale) || is_null($model->locale)) { if (!class_exists('I18n')) { - uses('i18n'); + App::import('Core', 'i18n'); } $I18n =& I18n::getInstance(); $model->locale = $I18n->l10n->locale; @@ -311,15 +317,14 @@ class TranslateBehavior extends ModelBehavior { $this->runtime[$model->alias]['model'] =& ClassRegistry::init($className, 'Model'); } } - $useTable = 'i18n'; + if (!empty($model->translateTable)) { $useTable = $model->translateTable; } if ($useTable !== $this->runtime[$model->alias]['model']->useTable) { $this->runtime[$model->alias]['model']->setSource($model->translateTable); } - return $this->runtime[$model->alias]['model']; } /** @@ -383,8 +388,9 @@ class TranslateBehavior extends ModelBehavior { } } $associations[$association] = array_merge($default, array('conditions' => array( - 'model' => $model->alias, - $RuntimeModel->displayField => $field))); + 'model' => $model->alias, + $RuntimeModel->displayField => $field + ))); } } diff --git a/cake/libs/model/datasources/dbo_source.php b/cake/libs/model/datasources/dbo_source.php index 3550b8a64..3c182bff5 100644 --- a/cake/libs/model/datasources/dbo_source.php +++ b/cake/libs/model/datasources/dbo_source.php @@ -48,7 +48,7 @@ class DboSource extends DataSource { * * @var array */ - var $index = array('PRI'=> 'primary', 'MUL'=> 'index', 'UNI'=>'unique'); + var $index = array('PRI' => 'primary', 'MUL' => 'index', 'UNI' => 'unique'); /** * Enter description here... * @@ -68,11 +68,11 @@ class DboSource extends DataSource { */ var $alias = 'AS '; /** - * Enter description here... + * Caches fields quoted in DboSource::name() * - * @var unknown_type + * @var array */ - var $goofyLimit = false; + var $fieldCache = array(); /** * Enter description here... * @@ -336,28 +336,45 @@ class DboSource extends DataSource { * @return string SQL field */ function name($data) { - if (preg_match_all('/([^(]*)\((.*)\)(.*)/', $data, $fields)) { - $fields = Set::extract($fields, '{n}.0'); - if (!empty($fields[1])) { - if (!empty($fields[2])) { - return $fields[1] . '(' . $this->name($fields[2]) . ')' . $fields[3]; - } else { - return $fields[1] . '()' . $fields[3]; - } - } - } if ($data == '*') { return '*'; } - $data = $this->startQuote . str_replace('.', $this->endQuote . '.' . $this->startQuote, $data) . $this->endQuote; - $data = str_replace($this->startQuote . $this->startQuote, $this->startQuote, $data); + $array = is_array($data); - if (!empty($this->endQuote) && $this->endQuote == $this->startQuote) { - if (substr_count($data, $this->endQuote) % 2 == 1) { - $data = trim($data, $this->endQuote); + $data = (array)$data; + $count = count($data); + + for($i = 0; $i < $count; $i++) { + if ($data[$i] == '*') { + continue; } + if (strpos($data[$i], '(') !== false && preg_match_all('/([^(]*)\((.*)\)(.*)/', $data[$i], $fields)) { + $fields = Set::extract($fields, '{n}.0'); + if (!empty($fields[1])) { + if (!empty($fields[2])) { + $data[$i] = $fields[1] . '(' . $this->name($fields[2]) . ')' . $fields[3]; + } else { + $data[$i] = $fields[1] . '()' . $fields[3]; + } + } + } + $data[$i] = $this->startQuote . str_replace('.', $this->endQuote . '.' . $this->startQuote, $data[$i]) . $this->endQuote; + $data[$i] = str_replace($this->startQuote . $this->startQuote, $this->startQuote, $data[$i]); + + if (!empty($this->endQuote) && $this->endQuote == $this->startQuote) { + if (substr_count($data[$i], $this->endQuote) % 2 == 1) { + $data[$i] = trim($data[$i], $this->endQuote); + } + } + if (strpos($data[$i], '*')) { + $data[$i] = str_replace($this->endQuote . '*' . $this->endQuote, '*', $data[$i]); + } + $data[$i] = str_replace($this->endQuote . $this->endQuote, $this->endQuote, $data[$i]); } - return str_replace($this->endQuote . $this->endQuote, $this->endQuote, $data); + if (!$array) { + return $data[0]; + } + return $data; } /** * Checks if it's connected to the database @@ -472,8 +489,6 @@ class DboSource extends DataSource { * @return boolean Success */ function create(&$model, $fields = null, $values = null) { - $fieldInsert = array(); - $valueInsert = array(); $id = null; if ($fields == null) { @@ -483,17 +498,15 @@ class DboSource extends DataSource { } $count = count($fields); + for ($i = 0; $i < $count; $i++) { + $valueInsert[] = $this->value($values[$i], $model->getColumnType($fields[$i])); + } for ($i = 0; $i < $count; $i++) { $fieldInsert[] = $this->name($fields[$i]); if ($fields[$i] == $model->primaryKey) { $id = $values[$i]; } } - $count = count($values); - - for ($i = 0; $i < $count; $i++) { - $valueInsert[] = $this->value($values[$i], $model->getColumnType($fields[$i])); - } if ($this->execute('INSERT INTO ' . $this->fullTableName($model) . ' (' . join(',', $fieldInsert). ') VALUES (' . join(',', $valueInsert) . ')')) { if (empty($id)) { @@ -1588,14 +1601,14 @@ class DboSource extends DataSource { $dot = strpos($fields[$i], '.'); if ($dot === false) { - $fields[$i] = $prepend . $this->name($alias) . '.' . $this->name($fields[$i]); + $fields[$i] = $prepend . $this->name($alias . '.' . $fields[$i]); } else { $value = array(); $comma = strpos($fields[$i], ','); if ($comma === false) { $build = explode('.', $fields[$i]); if (!Set::numeric($build)) { - $fields[$i] = $prepend . $this->name($build[0]) . '.' . $this->name($build[1]); + $fields[$i] = $prepend . $this->name($build[0] . '.' . $build[1]); } $comma = String::tokenize($fields[$i]); foreach ($comma as $string) { @@ -1603,7 +1616,7 @@ class DboSource extends DataSource { $value[] = $string; } else { $build = explode('.', $string); - $value[] = $prepend . $this->name(trim($build[0])) . '.' . $this->name(trim($build[1])); + $value[] = $prepend . $this->name(trim($build[0]) . '.' . trim($build[1])); } } $fields[$i] = implode(', ', $value); @@ -1612,7 +1625,7 @@ class DboSource extends DataSource { } elseif (preg_match('/\(([\.\w]+)\)/', $fields[$i], $field)) { if (isset($field[1])) { if (strpos($field[1], '.') === false) { - $field[1] = $this->name($alias) . '.' . $this->name($field[1]); + $field[1] = $this->name($alias . '.' . $field[1]); } else { $field[0] = explode('.', $field[1]); if (!Set::numeric($field[0])) { diff --git a/cake/libs/model/model.php b/cake/libs/model/model.php index baa757231..6ed304265 100644 --- a/cake/libs/model/model.php +++ b/cake/libs/model/model.php @@ -203,12 +203,12 @@ class Model extends Overloadable { */ var $actsAs = null; /** - * Holds the Behavior objects currently bound to this model, keyed by behavior name + * Holds the Behavior objects currently bound to this model * - * @var array + * @var object * @access public */ - var $behaviors = array(); + var $Behaviors = null; /** * Whitelist of fields allowed to be saved * @@ -230,13 +230,6 @@ class Model extends Overloadable { * @access public */ var $findQueryType = null; -/** - * Mapped behavior methods - * - * @var array - * @access private - */ - var $__behaviorMethods = array(); /** * Depth of recursive association * @@ -395,90 +388,7 @@ class Model extends Overloadable { } } } - - if ($this->actsAs !== null && empty($this->behaviors)) { - $this->actsAs = Set::normalize($this->actsAs); - foreach ($this->actsAs 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 - * @access public - */ - function attach($behavior, $config = array()) { - $name = $behavior; - if (strpos($behavior, '.')) { - list($plugin, $name) = explode('.', $behavior, 2); - } - $class = $name . 'Behavior'; - - if (!App::import('Behavior', $behavior)) { - // Raise an error - return false; - } - - if (!isset($this->behaviors[$name])) { - if (ClassRegistry::isKeySet($class)) { - if (PHP5) { - $this->behaviors[$name] = ClassRegistry::getObject($class); - } else { - $this->behaviors[$name] =& ClassRegistry::getObject($class); - } - } else { - if (PHP5) { - $this->behaviors[$name] = new $class; - } else { - $this->behaviors[$name] =& new $class; - } - ClassRegistry::addObject($class, $this->behaviors[$name]); - } - } elseif (isset($this->behaviors[$name]->settings) && isset($this->behaviors[$name]->settings[$this->alias])) { - $config = array_merge($this->behaviors[$name]->settings[$this->alias], $config); - } - $this->behaviors[$name]->setup($this, $config); - $methods = $this->behaviors[$name]->mapMethods; - - foreach ($methods as $method => $alias) { - if (!array_key_exists($method, $this->__behaviorMethods)) { - $this->__behaviorMethods[$method] = array($alias, $name); - } - } - - $methods = get_class_methods($this->behaviors[$name]); - $parentMethods = get_class_methods('ModelBehavior'); - $callbacks = array('setup', 'cleanup', 'beforeFind', 'afterFind', 'beforeSave', 'afterSave', 'beforeDelete', 'afterDelete', 'afterError'); - - foreach ($methods as $m) { - if (!in_array($m, $parentMethods)) { - if (strpos($m, '_') !== 0 && !array_key_exists($m, $this->__behaviorMethods) && !in_array($m, $callbacks)) { - $this->__behaviorMethods[$m] = array($m, $name); - } - } - } - return true; - } -/** - * Detaches a behavior from a model - * - * @param string $behavior CamelCased name of the behavior to unload - * @access public - */ - function detach($behavior) { - if (isset($this->behaviors[$behavior])) { - $this->behaviors[$behavior]->cleanup($this); - unset($this->behaviors[$behavior]); - } - foreach ($this->__behaviorMethods as $m => $callback) { - if (is_array($callback) && $callback[1] == $behavior) { - unset($this->__behaviorMethods[$m]); - } - } + $this->Behaviors = new BehaviorCollection($this, $this->actsAs); } /** * Handles custom method calls, like findBy for DB models, @@ -490,34 +400,11 @@ class Model extends Overloadable { * @access protected */ function call__($method, $params) { - $methods = array_map('strtolower', array_keys($this->__behaviorMethods)); - $call = array_values($this->__behaviorMethods); - $map = array(); + $result = $this->Behaviors->dispatchMethod($method, $params); - if (!empty($methods) && !empty($call)) { - $map = array_combine($methods, $call); + if ($result !== array('unhandled')) { + return $result; } - $count = count($call); - $pass = array(&$this); - - if (!in_array(strtolower($method), $methods)) { - $pass[] = $method; - } - foreach ($params as $param) { - $pass[] = $param; - } - - if (in_array(strtolower($method), $methods)) { - $it = $map[strtolower($method)]; - return call_user_func_array(array(&$this->behaviors[$it[1]], $it[0]), $pass); - } - - for ($i = 0; $i < $count; $i++) { - if (strpos($methods[$i], '/') === 0 && preg_match($methods[$i] . 'i', $method)) { - return call_user_func_array(array($this->behaviors[$call[$i][1]], $call[$i][0]), $pass); - } - } - $db =& ConnectionManager::getDataSource($this->useDbConfig); $return = $db->query($method, $params, $this); @@ -1206,18 +1093,7 @@ class Model extends Overloadable { } } - if (!empty($this->behaviors)) { - $behaviors = array_keys($this->behaviors); - $ct = count($behaviors); - for ($i = 0; $i < $ct; $i++) { - if ($this->behaviors[$behaviors[$i]]->beforeSave($this) === false) { - $this->whitelist = $_whitelist; - return false; - } - } - } - - if (!$this->beforeSave()) { + if (!$this->Behaviors->trigger('beforeSave', array(), array('break' => true, 'breakOn' => false)) || !$this->beforeSave()) { $this->whitelist = $_whitelist; return false; } @@ -1292,13 +1168,7 @@ class Model extends Overloadable { if (!empty($this->data)) { $success = $this->data; } - if (!empty($this->behaviors)) { - $behaviors = array_keys($this->behaviors); - $ct = count($behaviors); - for ($i = 0; $i < $ct; $i++) { - $this->behaviors[$behaviors[$i]]->afterSave($this, $created); - } - } + $this->Behaviors->trigger('afterSave', array($created)); $this->afterSave($created); if (!empty($this->data)) { $success = Set::pushDiff($success, $this->data); @@ -1414,10 +1284,9 @@ class Model extends Overloadable { } $db =& ConnectionManager::getDataSource($this->useDbConfig); - $options = array_merge( - array('validate' => true, 'fieldList' => array(), 'atomic' => true), - $options - ); + $options = array_merge(array('validate' => true, 'fieldList' => array(), 'atomic' => true), $options); + $validationErrors = array(); + $validates = true; $return = array(); if ($options['atomic']) { @@ -1425,12 +1294,18 @@ class Model extends Overloadable { } if (Set::numeric(array_keys($data))) { - if ($options['validate'] === 'first') { + if ($options['validate'] === 'first' || $options['validate'] === 'only') { + $this->validationErrors = array(); + foreach ($data as $record) { - if (!($this->create($record) && $result = $this->validates())) { - return false; + if (!($this->create($record) && $this->validates())) { + $validationErrors[] = $this->validationErrors; + $validates = false; } - $return[] = $result[$this->alias]; + } + if (!$validates) { + $this->validationErrors = $validationErrors; + return false; } } @@ -1446,6 +1321,48 @@ class Model extends Overloadable { } else { $associations = $this->getAssociated(); + if ($options['validate'] === 'first' || $options['validate'] === 'only') { + foreach ($data as $association => $values) { + if (isset($associations[$association]) && ($type = $associations[$association]) == 'belongsTo') { + if (!($this->{$association}->create($values) && $this->{$association}->validates())) { + $validationErrors[$association] = $this->{$association}->validationErrors; + $validates = false; + } + } + } + if (!($this->create($data[$this->alias]) && $this->validates())) { + $validationErrors[$this->alias] = $this->validationErrors; + $validates = false; + } + foreach ($data as $association => $values) { + if (isset($associations[$association])) { + switch ($associations[$association]) { + case 'hasOne': + $type = $associations[$association]; + $this->{$association}->set($this->{$type}[$association]['foreignKey'], $this->id); + if (!($this->{$association}->create($values) && $this->{$association}->validates())) { + $validationErrors[$association] = $this->{$association}->validationErrors; + $validates = false; + } + break; + case 'hasMany': + if (!$this->{$association}->saveAll($values, array('validate' => 'only'))) { + $validationErrors[$association] = $this->{$association}->validationErrors; + $validates = false; + } + break; + } + } + } + if ($options['validate'] === 'only') { + if (empty($validationErrors)) { + return true; + } + $this->validationErrors = $validationErrors; + return $validates; + } + } + foreach ($data as $association => $values) { if (isset($associations[$association]) && ($type = $associations[$association]) == 'belongsTo') { $alias = $this->{$association}->alias; @@ -1542,15 +1459,8 @@ class Model extends Overloadable { if ($this->exists() && $this->beforeDelete($cascade)) { $db =& ConnectionManager::getDataSource($this->useDbConfig); - - if (!empty($this->behaviors)) { - $behaviors = array_keys($this->behaviors); - $ct = count($behaviors); - for ($i = 0; $i < $ct; $i++) { - if ($this->behaviors[$behaviors[$i]]->beforeDelete($this, $cascade) === false) { - return false; - } - } + if (!$this->Behaviors->trigger('beforeDelete', array($cascade), array('break' => true, 'breakOn' => false))) { + return false; } $this->_deleteDependent($id, $cascade); $this->_deleteLinks($id); @@ -1564,11 +1474,7 @@ class Model extends Overloadable { if (!empty($this->belongsTo)) { $this->updateCounterCache($keys[$this->alias]); } - if (!empty($this->behaviors)) { - for ($i = 0; $i < $ct; $i++) { - $this->behaviors[$behaviors[$i]]->afterDelete($this); - } - } + $this->Behaviors->trigger('afterDelete'); $this->afterDelete(); $this->_clearCache(); $this->id = false; @@ -1634,6 +1540,7 @@ class Model extends Overloadable { */ function _deleteLinks($id) { $db =& ConnectionManager::getDataSource($this->useDbConfig); + foreach ($this->hasAndBelongsToMany as $assoc => $data) { if (isset($data['with'])) { $model =& $this->{$data['with']}; @@ -1850,37 +1757,28 @@ class Model extends Overloadable { $query['order'] = array($query['order']); } - if (!empty($this->behaviors)) { - $behaviors = array_keys($this->behaviors); - $ct = count($behaviors); - - for ($i = 0; $i < $ct; $i++) { - $return = $this->behaviors[$behaviors[$i]]->beforeFind($this, $query); - if (is_array($return)) { - $query = $return; - } elseif ($return === false) { - return null; - } - } + $return = $this->Behaviors->trigger('beforeFind', array($query), array('break' => true, 'breakOn' => false, 'modParams' => true)); + $query = ife(is_array($return), $return, $query); + if ($return === false) { + return null; } $return = $this->beforeFind($query); - - if (is_array($return)) { - $query = $return; - } elseif ($return === false) { + $query = ife(is_array($return), $return, $query); + if ($return === false) { return null; } + $results = $db->read($this, $query); $this->__resetAssociations(); $this->findQueryType = null; switch ($type) { case 'all': - return $this->__filterResults($results, true); + return $this->__filterResults($results); break; case 'first': - $results = $this->__filterResults($results, true); + $results = $this->__filterResults($results); if (empty($results[0])) { return false; } @@ -1898,7 +1796,7 @@ class Model extends Overloadable { if (empty($results)) { return array(); } - return Set::combine($this->__filterResults($results, true), $keyPath, $valuePath, $groupPath); + return Set::combine($this->__filterResults($results), $keyPath, $valuePath, $groupPath); break; } } @@ -1929,16 +1827,9 @@ class Model extends Overloadable { * @access private */ function __filterResults($results, $primary = true) { - if (!empty($this->behaviors)) { - $b = array_keys($this->behaviors); - $c = count($b); - - for ($i = 0; $i < $c; $i++) { - $return = $this->behaviors[$b[$i]]->afterFind($this, $results, $primary); - if (is_array($return)) { - $results = $return; - } - } + $return = $this->Behaviors->trigger('afterFind', array($results, $primary), array('modParams' => true)); + if ($return !== true) { + $results = $return; } return $this->afterFind($results, $primary); } @@ -2133,15 +2024,11 @@ class Model extends Overloadable { /** * Returns true if all fields pass validation, otherwise false. * - * @param array $data Parameter usage is deprecated, set Model::$data instead * @return boolean True if there are no errors * @access public */ - function validates($data = array()) { - if (!empty($data)) { - trigger_error(__('(Model::validates) Parameter usage is deprecated, use Model::set() to update your fields first', true), E_USER_WARNING); - } - $errors = $this->invalidFields($data); + function validates() { + $errors = $this->invalidFields(); if (is_array($errors)) { return count($errors) === 0; } @@ -2150,34 +2037,20 @@ class Model extends Overloadable { /** * Returns an array of fields that do not meet validation. * - * @param array $data Parameter usage is deprecated, set Model::$data instead * @return array Array of invalid fields * @access public */ - function invalidFields($data = array()) { - if (!empty($this->behaviors)) { - $behaviors = array_keys($this->behaviors); - $ct = count($behaviors); - for ($i = 0; $i < $ct; $i++) { - if ($this->behaviors[$behaviors[$i]]->beforeValidate($this) === false) { - return $this->validationErrors; - } - } - } - - if (!$this->beforeValidate()) { + function invalidFields() { + if (!$this->Behaviors->trigger('beforeValidate', array(), array('break' => true, 'breakOn' => false)) || $this->beforeValidate() === false) { return $this->validationErrors; } - if (empty($data)) { - $data = $this->data; - } else { - trigger_error(__('(Model::invalidFields) Parameter usage is deprecated, set the $data property instead', true), E_USER_WARNING); - } - if (!isset($this->validate) || empty($this->validate)) { return $this->validationErrors; } + $data = $this->data; + $methods = array_map('strtolower', get_class_methods($this)); + $behaviorMethods = $this->Behaviors->methods(); if (isset($data[$this->alias])) { $data = $data[$this->alias]; @@ -2190,13 +2063,12 @@ class Model extends Overloadable { if (!is_array($ruleSet) || (is_array($ruleSet) && isset($ruleSet['rule']))) { $ruleSet = array($ruleSet); } + $default = array('allowEmpty' => null, 'required' => null, 'rule' => 'blank', 'last' => false, 'on' => null); foreach ($ruleSet as $index => $validator) { if (!is_array($validator)) { $validator = array('rule' => $validator); } - - $default = array('allowEmpty' => null, 'required' => null, 'rule' => 'blank', 'last' => false, 'on' => null); $validator = array_merge($default, $validator); if (isset($validator['message'])) { @@ -2226,12 +2098,16 @@ class Model extends Overloadable { $valid = true; - if (method_exists($this, $rule) || isset($this->__behaviorMethods[$rule]) || isset($this->__behaviorMethods[strtolower($rule)])) { + if (in_array(strtolower($rule), $methods)) { $ruleParams[] = array_diff_key($validator, $default); $ruleParams[0] = array($fieldName => $ruleParams[0]); - $valid = call_user_func_array(array(&$this, $rule), $ruleParams); + $valid = $this->dispatchMethod($rule, $ruleParams); + } elseif (in_array($rule, $behaviorMethods) || in_array(strtolower($rule), $behaviorMethods)) { + $ruleParams[] = array_diff_key($validator, $default); + $ruleParams[0] = array($fieldName => $ruleParams[0]); + $valid = $this->Behaviors->dispatchMethod($rule, $ruleParams); } elseif (method_exists($Validation, $rule)) { - $valid = call_user_func_array(array(&$Validation, $rule), $ruleParams); + $valid = $Validation->dispatchMethod($rule, $ruleParams); } elseif (!is_array($validator['rule'])) { $valid = preg_match($rule, $data[$fieldName]); } @@ -2243,8 +2119,8 @@ class Model extends Overloadable { $validator['message'] = ife(is_numeric($index) && count($ruleSet) > 1, ($index + 1), $message); } } - $this->invalidate($fieldName, $validator['message']); + if ($validator['last']) { break; } @@ -2263,13 +2139,10 @@ class Model extends Overloadable { * @param string $value Name of validation rule that was not met * @access public */ - function invalidate($field, $value = null) { + function invalidate($field, $value = true) { if (!is_array($this->validationErrors)) { $this->validationErrors = array(); } - if (empty($value)) { - $value = true; - } $this->validationErrors[$field] = $value; } /** @@ -2351,7 +2224,7 @@ class Model extends Overloadable { if (strpos($field, $db->name($alias)) === 0) { return $field; } - return $db->name($alias) . '.' . $db->name($field); + return $db->name($alias . '.' . $field); } /** * Returns the current record's ID diff --git a/cake/libs/object.php b/cake/libs/object.php index 5a6a7f19c..d131ab8cb 100644 --- a/cake/libs/object.php +++ b/cake/libs/object.php @@ -145,7 +145,7 @@ class Object { case 5: return $this->{$method}($params[0], $params[1], $params[2], $params[3], $params[4]); default: - call_user_func_array(array(&$this, $method), $params); + return call_user_func_array(array(&$this, $method), $params); break; } } diff --git a/cake/tests/cases/libs/model/behaviors/translate.test.php b/cake/tests/cases/libs/model/behaviors/translate.test.php index 33fec7ff8..4ec18ab86 100644 --- a/cake/tests/cases/libs/model/behaviors/translate.test.php +++ b/cake/tests/cases/libs/model/behaviors/translate.test.php @@ -421,7 +421,7 @@ class TranslateTest extends CakeTestCase { } function testAttachDetach() { - $Behavior =& $this->Model->behaviors['Translate']; + $Behavior =& $this->Model->Behaviors->Translate; $this->Model->unbindTranslation(); $translations = array('title' => 'Title', 'content' => 'Content'); @@ -431,13 +431,13 @@ class TranslateTest extends CakeTestCase { $expected = array('Title', 'Content'); $this->assertEqual($result, $expected); - $this->Model->detach('Translate'); + $this->Model->Behaviors->detach('Translate'); $result = array_keys($this->Model->hasMany); $expected = array(); $this->assertEqual($result, $expected); - $result = isset($this->Model->behaviors['Translate']); + $result = isset($this->Model->Behaviors->Translate); $this->assertFalse($result); $result = isset($Behavior->settings[$this->Model->alias]); @@ -446,12 +446,12 @@ class TranslateTest extends CakeTestCase { $result = isset($Behavior->runtime[$this->Model->alias]); $this->assertFalse($result); - $this->Model->attach('Translate', array('title' => 'Title', 'content' => 'Content')); + $this->Model->Behaviors->attach('Translate', array('title' => 'Title', 'content' => 'Content')); $result = array_keys($this->Model->hasMany); $expected = array('Title', 'Content'); $this->assertEqual($result, $expected); - $result = isset($this->Model->behaviors['Translate']); + $result = isset($this->Model->Behaviors->Translate); $this->assertTrue($result); $result = isset($Behavior->settings[$this->Model->alias]); diff --git a/cake/tests/cases/libs/model/behaviors/tree.test.php b/cake/tests/cases/libs/model/behaviors/tree.test.php index 02fe90c95..4d0f4534b 100644 --- a/cake/tests/cases/libs/model/behaviors/tree.test.php +++ b/cake/tests/cases/libs/model/behaviors/tree.test.php @@ -312,6 +312,7 @@ class NumberTreeCase extends CakeTestCase { $this->NumberTree = & new NumberTree(); $this->NumberTree->__initialize(2, 2); + $this->NumberTree->whitelist = array('name', 'parent_id'); $this->NumberTree->save(array('NumberTree' => array('name' => 'testAddOrphan', 'parent_id' => null))); $result = $this->NumberTree->find(null, array('name', 'parent_id'), 'NumberTree.lft desc'); $expected = array('NumberTree' => array('name' => 'testAddOrphan', 'parent_id' => null)); diff --git a/cake/tests/cases/libs/model/datasources/dbo_source.test.php b/cake/tests/cases/libs/model/datasources/dbo_source.test.php index b2defa5e6..924c54864 100644 --- a/cake/tests/cases/libs/model/datasources/dbo_source.test.php +++ b/cake/tests/cases/libs/model/datasources/dbo_source.test.php @@ -618,7 +618,20 @@ class DboSourceTest extends CakeTestCase { unset($this->debug); } + function testCreateSpeed() { + $model = new TestModel(); + $data = array('client_id' => 3, 'name' => 'Nate', 'login' => 'nate'); + + $model->set($data); + $start = microtime(true); + for ($i = 0; $i < 1000; $i++) { + $this->db->create($model, array_keys($data), array_values($data)); + } + $time = microtime(true) - $start; + } + function testGenerateAssociationQuerySelfJoin() { + $this->startTime = microtime(true); $this->Model = new Article2(); $this->_buildRelatedModels($this->Model); $this->_buildRelatedModels($this->Model->Category2); @@ -1744,6 +1757,10 @@ class DboSourceTest extends CakeTestCase { $result = $this->db->fields($this->Model, null, array('dayofyear(now())')); $expected = array('dayofyear(now())'); $this->assertEqual($result, $expected); + + $result = $this->db->fields($this->Model, null, array('MAX(Model.field) As Max')); + $expected = array('MAX(`Model`.`field`) As Max'); + $this->assertEqual($result, $expected); } function testMergeAssociations() { @@ -1754,9 +1771,7 @@ class DboSourceTest extends CakeTestCase { ); $merge = array( 'Topic' => array ( - array( - 'id' => '1', 'topic' => 'Topic', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' - ) + array('id' => '1', 'topic' => 'Topic', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31') ) ); $expected = array( @@ -1776,10 +1791,8 @@ class DboSourceTest extends CakeTestCase { ) ); $merge = array( - 'User2' => array ( - array( - 'id' => '1', 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' - ) + 'User2' => array( + array('id' => '1', 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31') ) ); $expected = array( @@ -1798,11 +1811,7 @@ class DboSourceTest extends CakeTestCase { 'id' => '1', 'user_id' => '1', 'title' => 'First Article', 'body' => 'First Article Body', 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' ) ); - $merge = array( - array ( - 'Comment' => false - ) - ); + $merge = array(array('Comment' => false)); $expected = array( 'Article2' => array( 'id' => '1', 'user_id' => '1', 'title' => 'First Article', 'body' => 'First Article Body', 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' diff --git a/cake/tests/cases/libs/model/model.test.php b/cake/tests/cases/libs/model/model.test.php index 176d46452..a3ca68624 100644 --- a/cake/tests/cases/libs/model/model.test.php +++ b/cake/tests/cases/libs/model/model.test.php @@ -26,465 +26,10 @@ * @lastmodified $Date$ * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License */ -if (!defined('CAKEPHP_UNIT_TEST_EXECUTION')) { - define('CAKEPHP_UNIT_TEST_EXECUTION', 1); -} -App::import('Core', array('AppModel', 'Model', 'DataSource', 'DboSource', 'DboMysql')); +App::import('Core', array('AppModel', 'Model')); +require_once dirname(__FILE__) . DS . 'models.php'; -/** - * Short description for class. - * - * @package cake.tests - * @subpackage cake.tests.cases.libs.model - */ -class Test extends Model { - var $useTable = false; - var $name = 'Test'; - - var $_schema = array( - 'id'=> array('type' => 'integer', 'null' => '', 'default' => '1', 'length' => '8', 'key'=>'primary'), - 'name'=> array('type' => 'string', 'null' => '', 'default' => '', 'length' => '255'), - 'email'=> array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), - 'notes'=> array('type' => 'text', 'null' => '1', 'default' => 'write some notes here', 'length' => ''), - 'created'=> array('type' => 'date', 'null' => '1', 'default' => '', 'length' => ''), - 'updated'=> array('type' => 'datetime', 'null' => '1', 'default' => '', 'length' => null) - ); -} - -/** - * Short description for class. - * - * @package cake.tests - * @subpackage cake.tests.cases.libs.model - */ -class TestValidate extends Model { - var $useTable = false; - var $name = 'TestValidate'; - - var $_schema = array( - 'id' => array('type' => 'integer', 'null' => '', 'default' => '', 'length' => '8'), - 'title' => array('type' => 'string', 'null' => '', 'default' => '', 'length' => '255'), - 'body' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => ''), - 'number' => array('type' => 'integer', 'null' => '', 'default' => '', 'length' => '8'), - 'created' => array('type' => 'date', 'null' => '1', 'default' => '', 'length' => ''), - 'modified' => array('type' => 'datetime', 'null' => '1', 'default' => '', 'length' => null) - ); - - function validateNumber($value, $options) { - $options = am(array('min' => 0, 'max' => 100), $options); - $valid = ($value['number'] >= $options['min'] && $value['number'] <= $options['max']); - return $valid; - } - - function validateTitle($value) { - return (!empty($value) && strpos(low($value['title']), 'title-') === 0); - } -} -/** - * Short description for class. - * - * @package cake.tests - * @subpackage cake.tests.cases.libs.model - */ -class User extends CakeTestModel { - var $name = 'User'; - var $validate = array('user' => VALID_NOT_EMPTY, 'password' => VALID_NOT_EMPTY); -} -/** - * Short description for class. - * - * @package cake.tests - * @subpackage cake.tests.cases.libs.model - */ -class Article extends CakeTestModel { - var $name = 'Article'; - var $belongsTo = array('User'); - var $hasMany = array('Comment' => array('className'=>'Comment', 'dependent' => true)); - var $hasAndBelongsToMany = array('Tag'); - var $validate = array('user_id' => VALID_NUMBER, 'title' => array('allowEmpty' => false, 'rule' => VALID_NOT_EMPTY), 'body' => VALID_NOT_EMPTY); - - function titleDuplicate ($title) { - if ($title === 'My Article Title') { - return false; - } - return true; - } -} -/** - * Short description for class. - * - * @package cake.tests - * @subpackage cake.tests.cases.libs.model - */ -class Article10 extends CakeTestModel { - var $name = 'Article10'; - var $useTable = 'articles'; - var $hasMany = array('Comment' => array('dependent' => true, 'exclusive' => true)); -} -/** - * Short description for class. - * - * @package cake.tests - * @subpackage cake.tests.cases.libs.model - */ -class ArticleFeatured extends CakeTestModel { - var $name = 'ArticleFeatured'; - var $belongsTo = array('User', 'Category'); - var $hasOne = array('Featured'); - var $hasMany = array('Comment' => array('className'=>'Comment', 'dependent' => true)); - var $hasAndBelongsToMany = array('Tag'); - var $validate = array('user_id' => VALID_NUMBER, 'title' => VALID_NOT_EMPTY, 'body' => VALID_NOT_EMPTY); -} -/** - * Short description for class. - * - * @package cake.tests - * @subpackage cake.tests.cases.libs.model - */ -class Featured extends CakeTestModel { - var $name = 'Featured'; - var $belongsTo = array( - 'ArticleFeatured'=> array('className' => 'ArticleFeatured'), - 'Category'=> array('className' => 'Category') - ); -} - -/** - * Short description for class. - * - * @package cake.tests - * @subpackage cake.tests.cases.libs.model - */ -class Tag extends CakeTestModel { - var $name = 'Tag'; -} -/** - * Short description for class. - * - * @package cake.tests - * @subpackage cake.tests.cases.libs.model - */ -class ArticlesTag extends CakeTestModel { - var $name = 'ArticlesTag'; -} -/** - * Short description for class. - * - * @package cake.tests - * @subpackage cake.tests.cases.libs.model - */ -class ArticleFeaturedsTag extends CakeTestModel { - var $name = 'ArticleFeaturedsTag'; -} -/** - * Short description for class. - * - * @package cake.tests - * @subpackage cake.tests.cases.libs.model - */ -class Comment extends CakeTestModel { - var $name = 'Comment'; - var $belongsTo = array('Article', 'User'); - var $hasOne = array('Attachment' => array('className'=>'Attachment', 'dependent' => true)); -} -/** - * Short description for class. - * - * @package cake.tests - * @subpackage cake.tests.cases.libs.model - */ -class Attachment extends CakeTestModel { - var $name = 'Attachment'; -} -/** - * Short description for class. - * - * @package cake.tests - * @subpackage cake.tests.cases.libs.model - */ -class Category extends CakeTestModel { - var $name = 'Category'; -} -/** - * Short description for class. - * - * @package cake.tests - * @subpackage cake.tests.cases.libs.model - */ -class CategoryThread extends CakeTestModel { - var $name = 'CategoryThread'; - var $belongsTo = array('ParentCategory' => array('className' => 'CategoryThread', 'foreignKey' => 'parent_id')); -} -/** - * Short description for class. - * - * @package cake.tests - * @subpackage cake.tests.cases.libs.model - */ -class Apple extends CakeTestModel { - var $name = 'Apple'; - var $validate = array('name' => VALID_NOT_EMPTY); - var $hasOne = array('Sample'); - var $hasMany = array('Child' => array('className' => 'Apple', 'dependent' => true)); - var $belongsTo = array('Parent' => array('className' => 'Apple', 'foreignKey' => 'apple_id')); -} -/** - * Short description for class. - * - * @package cake.tests - * @subpackage cake.tests.cases.libs.model - */ -class Sample extends CakeTestModel { - var $name = 'Sample'; - var $belongsTo = 'Apple'; -} -/** - * Short description for class. - * - * @package cake.tests - * @subpackage cake.tests.cases.libs.model - */ -class AnotherArticle extends CakeTestModel { - var $name = 'AnotherArticle'; - var $hasMany = 'Home'; -} -/** - * Short description for class. - * - * @package cake.tests - * @subpackage cake.tests.cases.libs.model - */ -class Advertisement extends CakeTestModel { - var $name = 'Advertisement'; - var $hasMany = 'Home'; -} -/** - * Short description for class. - * - * @package cake.tests - * @subpackage cake.tests.cases.libs.model - */ -class Home extends CakeTestModel { - var $name = 'Home'; - var $belongsTo = array('AnotherArticle', 'Advertisement'); -} -/** - * Short description for class. - * - * @package cake.tests - * @subpackage cake.tests.cases.libs.model - */ -class Post extends CakeTestModel { - var $name = 'Post'; - var $belongsTo = array('Author'); -} -/** - * Short description for class. - * - * @package cake.tests - * @subpackage cake.tests.cases.libs.model - */ -class Author extends CakeTestModel { - var $name = 'Author'; - var $hasMany = array('Post'); - - function afterFind($results) { - $results[0]['Author']['test'] = 'working'; - return $results; - } -} -/** - * Short description for class. - * - * @package cake.tests - * @subpackage cake.tests.cases.libs.model - */ -class Project extends CakeTestModel { - var $name = 'Project'; - var $hasMany = array('Thread'); -} -/** - * Short description for class. - * - * @package cake.tests - * @subpackage cake.tests.cases.libs.model - */ -class Thread extends CakeTestModel { - var $name = 'Thread'; - var $hasMany = array('Message'); -} -/** - * Short description for class. - * - * @package cake.tests - * @subpackage cake.tests.cases.libs.model - */ -class Message extends CakeTestModel { - var $name = 'Message'; - var $hasOne = array('Bid'); -} -/** - * Short description for class. - * - * @package cake.tests - * @subpackage cake.tests.cases.libs.model - */ -class Bid extends CakeTestModel { - var $name = 'Bid'; - var $belongsTo = array('Message'); -} -class NodeAfterFind extends CakeTestModel { - var $name = 'NodeAfterFind'; - var $validate = array('name' => VALID_NOT_EMPTY); - var $useTable = 'apples'; - var $hasOne = array('Sample' => array('className' => 'NodeAfterFindSample')); - var $hasMany = array('Child' => array('className' => 'NodeAfterFind', 'dependent' => true)); - var $belongsTo = array('Parent' => array('className' => 'NodeAfterFind', 'foreignKey' => 'apple_id')); - - function afterFind($results) { - return $results; - } -} -class NodeAfterFindSample extends CakeTestModel { - var $name = 'NodeAfterFindSample'; - var $useTable = 'samples'; - var $belongsTo = 'NodeAfterFind'; -} -class NodeNoAfterFind extends CakeTestModel { - var $name = 'NodeAfterFind'; - var $validate = array('name' => VALID_NOT_EMPTY); - var $useTable = 'apples'; - var $hasOne = array('Sample' => array('className' => 'NodeAfterFindSample')); - var $hasMany = array('Child' => array( 'className' => 'NodeAfterFind', 'dependent' => true)); - var $belongsTo = array('Parent' => array('className' => 'NodeAfterFind', 'foreignKey' => 'apple_id')); -} -class ModelA extends CakeTestModel { - var $name = 'ModelA'; - var $useTable = 'apples'; - var $hasMany = array('ModelB', 'ModelC'); -} -class ModelB extends CakeTestModel { - var $name = 'ModelB'; - var $useTable = 'messages'; - var $hasMany = array('ModelD'); -} -class ModelC extends CakeTestModel { - var $name = 'ModelC'; - var $useTable = 'bids'; - var $hasMany = array('ModelD'); -} -class ModelD extends CakeTestModel { - var $name = 'ModelD'; - var $useTable = 'threads'; -} -class Something extends CakeTestModel { - var $name = 'Something'; - var $hasAndBelongsToMany = array('SomethingElse' => array('with' => array('JoinThing' => array('doomed')))); -} -class SomethingElse extends CakeTestModel { - var $name = 'SomethingElse'; - var $hasAndBelongsToMany = array('Something' => array('with' => 'JoinThing')); -} -class JoinThing extends CakeTestModel { - var $name = 'JoinThing'; - var $belongsTo = array('Something', 'SomethingElse'); -} -class Portfolio extends CakeTestModel { - var $name = 'Portfolio'; - var $hasAndBelongsToMany = array('Item'); -} -class Item extends CakeTestModel { - var $name = 'Item'; - var $belongsTo = array('Syfile' => array('counterCache' => true)); - var $hasAndBelongsToMany = array('Portfolio'); -} -class ItemsPortfolio extends CakeTestModel { - var $name = 'ItemsPortfolio'; -} -class Syfile extends CakeTestModel { - var $name = 'Syfile'; - var $belongsTo = array('Image'); -} -class Image extends CakeTestModel { - var $name = 'Image'; -} -class DeviceType extends CakeTestModel { - var $name = 'DeviceType'; - var $order = array('DeviceType.order' => 'ASC'); - var $belongsTo = array( - 'DeviceTypeCategory', 'FeatureSet', 'ExteriorTypeCategory', - 'Image' => array('className' => 'Document'), - 'Extra1' => array('className' => 'Document'), - 'Extra2' => array('className' => 'Document')); - var $hasMany = array('Device' => array('order' => array('Device.id' => 'ASC'))); -} -class DeviceTypeCategory extends CakeTestModel { - var $name = 'DeviceTypeCategory'; -} -class FeatureSet extends CakeTestModel { - var $name = 'FeatureSet'; -} -class ExteriorTypeCategory extends CakeTestModel { - var $name = 'ExteriorTypeCategory'; - var $belongsTo = array('Image' => array('className' => 'Device')); -} -class Document extends CakeTestModel { - var $name = 'Document'; - var $belongsTo = array('DocumentDirectory'); -} -class Device extends CakeTestModel { - var $name = 'Device'; -} -class DocumentDirectory extends CakeTestModel { - var $name = 'DocumentDirectory'; -} -class PrimaryModel extends CakeTestModel { - var $name = 'PrimaryModel'; -} -class SecondaryModel extends CakeTestModel { - var $name = 'SecondaryModel'; -} -class JoinA extends CakeTestModel { - var $name = 'JoinA'; - var $hasAndBelongsToMany = array('JoinB', 'JoinC'); -} -class JoinB extends CakeTestModel { - var $name = 'JoinB'; - var $hasAndBelongsToMany = array('JoinA'); -} -class JoinC extends CakeTestModel { - var $name = 'JoinC'; - var $hasAndBelongsToMany = array('JoinA'); -} -/** - * Short description for class. - * - * @package cake.tests - * @subpackage cake.tests.cases.libs.model - */ -class AssociationTest1 extends CakeTestModel { - var $useTable = 'join_as'; - var $name = 'AssociationTest1'; - - var $hasAndBelongsToMany = array('AssociationTest2' => array( - 'unique' => false, 'joinTable' => 'join_as_join_bs', 'foreignKey' => false - )); -} -/** - * Short description for class. - * - * @package cake.tests - * @subpackage cake.tests.cases.libs.model - */ -class AssociationTest2 extends CakeTestModel { - var $useTable = 'join_bs'; - var $name = 'AssociationTest2'; - - var $hasAndBelongsToMany = array('AssociationTest1' => array( - 'unique' => false, 'joinTable' => 'join_as_join_bs' - )); -} /** * Short description for class. * @@ -499,11 +44,10 @@ class ModelTest extends CakeTestCase { 'core.category', 'core.category_thread', 'core.user', 'core.article', 'core.featured', 'core.article_featureds_tags', 'core.article_featured', 'core.articles', 'core.tag', 'core.articles_tag', 'core.comment', 'core.attachment', 'core.apple', 'core.sample', 'core.another_article', 'core.advertisement', 'core.home', 'core.post', 'core.author', - 'core.project', 'core.thread', 'core.message', 'core.bid', - 'core.portfolio', 'core.item', 'core.items_portfolio', 'core.syfile', 'core.image', - 'core.device_type', 'core.device_type_category', 'core.feature_set', 'core.exterior_type_category', 'core.document', 'core.device', 'core.document_directory', - 'core.primary_model', 'core.secondary_model', 'core.something', 'core.something_else', 'core.join_thing', - 'core.join_a', 'core.join_b', 'core.join_c', 'core.join_a_b', 'core.join_a_c' + 'core.project', 'core.thread', 'core.message', 'core.bid', 'core.portfolio', 'core.item', 'core.items_portfolio', + 'core.syfile', 'core.image', 'core.device_type', 'core.device_type_category', 'core.feature_set', 'core.exterior_type_category', + 'core.document', 'core.device', 'core.document_directory', 'core.primary_model', 'core.secondary_model', 'core.something', + 'core.something_else', 'core.join_thing', 'core.join_a', 'core.join_b', 'core.join_c', 'core.join_a_b', 'core.join_a_c' ); function start() { @@ -3387,7 +2931,7 @@ class ModelTest extends CakeTestCase { $this->model->data = null; $this->model->set($data); - $expected = array('Apple'=> array('mytime'=> '03:04:04')); + $expected = array('Apple' => array('mytime'=> '03:04:04')); $this->assertEqual($this->model->data, $expected); $data = array(); @@ -3404,82 +2948,28 @@ class ModelTest extends CakeTestCase { function testDynamicBehaviorAttachment() { $this->loadFixtures('Apple'); $this->model =& new Apple(); - $this->assertEqual($this->model->behaviors, array()); + $this->assertEqual($this->model->Behaviors->attached(), array()); - $this->model->attach('Tree', array('left' => 'left_field', 'right' => 'right_field')); - $this->assertTrue(is_object($this->model->behaviors['Tree'])); + $this->model->Behaviors->attach('Tree', array('left' => 'left_field', 'right' => 'right_field')); + $this->assertTrue(is_object($this->model->Behaviors->Tree)); + $this->assertEqual($this->model->Behaviors->attached(), array('Tree')); $expected = array('parent' => 'parent_id', 'left' => 'left_field', 'right' => 'right_field', 'scope' => '1 = 1', 'enabled' => true, 'type' => 'nested', '__parentChange' => false); - $this->assertEqual($this->model->behaviors['Tree']->settings['Apple'], $expected); + $this->assertEqual($this->model->Behaviors->Tree->settings['Apple'], $expected); $expected['enabled'] = false; - $this->model->attach('Tree', array('enabled' => false)); - $this->assertEqual($this->model->behaviors['Tree']->settings['Apple'], $expected); + $this->model->Behaviors->attach('Tree', array('enabled' => false)); + $this->assertEqual($this->model->Behaviors->Tree->settings['Apple'], $expected); + $this->assertEqual($this->model->Behaviors->attached(), array('Tree')); - $this->model->detach('Tree'); - $this->assertEqual($this->model->behaviors, array()); + $this->model->Behaviors->detach('Tree'); + $this->assertEqual($this->model->Behaviors->attached(), array()); + $this->assertFalse(isset($this->model->Behaviors->Tree)); } function endTest() { ClassRegistry::flush(); } } -/** - * Short description for class. - * - * @package cake.tests - * @subpackage cake.tests.cases.libs.model - */ -class ValidationTest extends CakeTestModel { - var $name = 'ValidationTest'; - var $useTable = false; - - var $validate = array( - 'title' => VALID_NOT_EMPTY, - 'published' => 'customValidationMethod', - 'body' => array( - VALID_NOT_EMPTY, - '/^.{5,}$/s' => 'no matchy', - '/^[0-9A-Za-z \\.]{1,}$/s' - ) - ); - - function customValidationMethod($data) { - return $data === 1; - } - - function schema() { - return array(); - } -} - -/** - * Short description for class. - * - * @package cake.tests - * @subpackage cake.tests.cases.libs.model - */ -class ValidationTest2 extends CakeTestModel { - var $name = 'ValidationTest2'; - var $useTable = false; - - var $validate = array( - 'title' => VALID_NOT_EMPTY, - 'published' => 'customValidationMethod', - 'body' => array( - VALID_NOT_EMPTY, - '/^.{5,}$/s' => 'no matchy', - '/^[0-9A-Za-z \\.]{1,}$/s' - ) - ); - - function customValidationMethod($data) { - return $data === 1; - } - - function schema() { - return array(); - } -} ?> \ No newline at end of file