Adding model behaviors, model onError callback, and updating FormHelper

git-svn-id: https://svn.cakephp.org/repo/branches/1.2.x.x@3280 3807eeeb-6ff5-0310-8944-8be069107fe0
This commit is contained in:
nate 2006-07-22 14:13:07 +00:00
parent 67d75ba18b
commit 07ec77ef22
10 changed files with 516 additions and 67 deletions

View file

@ -379,6 +379,41 @@
return true;
}
}
/**
* Loads a behavior
*
* @param string $name Name of component
* @return boolean Success
*/
function loadBehavior($name) {
$paths = Configure::getInstance();
if ($name === null) {
return true;
}
if (!class_exists($name . 'Behavior')) {
$name = Inflector::underscore($name);
foreach($paths->behaviorPaths as $path) {
if (file_exists($path . $name . '.php')) {
require($path . $name . '.php');
return true;
}
}
if ($behavior_fn = fileExistsInPath(LIBS . 'model' . DS . 'behaviors' . DS . $name . '.php')) {
if (file_exists($behavior_fn)) {
require($behavior_fn);
return true;
} else {
return false;
}
}
} else {
return true;
}
}
/**
* Returns an array of filenames of PHP files in given directory.
*
@ -482,6 +517,7 @@
}
}
if (!$numeric || $assoc) {
$newList = array();
for ($i = 0; $i < $count; $i++) {
if (is_int($keys[$i])) {
$newList[$list[$keys[$i]]] = null;

View file

@ -50,6 +50,10 @@
* Path to the application's models directory.
*/
define ('MODELS', APP.'models'.DS);
/**
* Path to model behaviors directory.
*/
define ('BEHAVIORS', MODELS.'behaviors'.DS);
/**
* Path to the application's controllers directory.
*/

View file

@ -70,6 +70,13 @@ class Configure extends Object {
* @access public
*/
var $componentPaths = array();
/**
* Enter description here...
*
* @var array
* @access public
*/
var $behaviorPaths = array();
/**
* Return a singleton instance of Configure.
*
@ -160,6 +167,21 @@ class Configure extends Object {
}
}
}
/**
* Sets the var behaviorPaths
*
* @param array $behaviorPaths
* @access private
*/
function __buildBehaviorPaths($behaviorPaths) {
$_this =& Configure::getInstance();
$_this->behaviorPaths[] = BEHAVIORS;
if (isset($behaviorPaths)) {
foreach($behaviorPaths as $value) {
$_this->behaviorPaths[] = $value;
}
}
}
/**
* Loads the app/config/bootstrap.php
* If the alternative paths are set in this file
@ -174,12 +196,14 @@ class Configure extends Object {
$controllerPaths = null;
$helperPaths = null;
$componentPaths = null;
$behaviorPaths = null;
require APP_PATH . 'config' . DS . 'bootstrap.php';
$_this->__buildModelPaths($modelPaths);
$_this->__buildViewPaths($viewPaths);
$_this->__buildControllerPaths($controllerPaths);
$_this->__buildHelperPaths($helperPaths);
$_this->__buildComponentPaths($componentPaths);
$_this->__buildBehaviorPaths($behaviorPaths);
}
}

View file

@ -0,0 +1,47 @@
<?php
/* SVN FILE: $Id$ */
/**
* Model behaviors base class.
*
* Adds methods and automagic functionality to Cake Models.
*
* PHP versions 4 and 5
*
* CakePHP : Rapid Development Framework <http://www.cakephp.org/>
* Copyright (c) 2006, Cake Software Foundation, Inc.
* 1785 E. Sahara Avenue, Suite 490-204
* Las Vegas, Nevada 89104
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @filesource
* @copyright Copyright (c) 2006, Cake Software Foundation, Inc.
* @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
* @package cake
* @subpackage cake.cake.libs.model
* @since CakePHP v 1.2.0.0
* @version $Revision$
* @modifiedby $LastChangedBy$
* @lastmodified $Date$
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
class ModelBehavior extends Object {
var $mapMethods = array();
function setup(&$model, $config = array()) { }
function beforeFind(&$model, &$query) { }
function afterFind(&$model, &$results) { }
function beforeDelete(&$model) { }
function afterDelete(&$model) { }
function onError(&$model, &$error) { }
}
?>

View file

@ -340,6 +340,7 @@ class DboMysql extends DboSource {
if ($data && isset($data[0]['id'])) {
return $data[0]['id'];
}
return null;
}
/**
* Converts database-layer column types to basic types

View file

@ -490,8 +490,10 @@ class DboSource extends DataSource {
if ($this->execute('INSERT INTO ' . $this->fullTableName($model) . ' (' . join(',', $fieldInsert). ') VALUES (' . join(',', $valueInsert) . ')')) {
return true;
} else {
$model->onError();
return false;
}
return false;
}
/**
* The "R" in CRUD
@ -541,6 +543,12 @@ class DboSource extends DataSource {
// Build final query SQL
$query = $this->generateAssociationQuery($model, $null, null, null, null, $queryData, false, $null);
$resultSet = $this->fetchAll($query, $model->cacheQueries, $model->name);
if ($resultSet === false) {
$model->onError();
return false;
}
$filtered = $this->__filterResults($resultSet, $model);
if ($model->recursive > 0) {
@ -1108,7 +1116,11 @@ class DboSource extends DataSource {
$sql = 'UPDATE ' . $this->fullTableName($model);
$sql .= ' SET ' . join(',', $updates);
$sql .= ' WHERE ' . $this->name($model->primaryKey) . ' = ' . $this->value($model->getID(), $model->getColumnType($model->primaryKey));
return $this->execute($sql);
if (!$this->execute($sql)) {
$model->onError();
return false;
}
return true;
}
/**
* Generates and executes an SQL DELETE statement for given id on given model.
@ -1129,12 +1141,11 @@ class DboSource extends DataSource {
foreach($model->id as $id) {
$result = $this->execute('DELETE FROM ' . $this->fullTableName($model) . ' WHERE ' . $this->name($model->primaryKey) . ' = ' . $this->value($id));
if ($result === false) {
$model->onError();
}
}
if ($result) {
return true;
}
return false;
return ($result !== false);
}
/**
* Returns a key formatted like a string Model.fieldname(i.e. Post.title, or Country.name)

View file

@ -28,17 +28,22 @@
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
/**
* Include files
*/
uses('model' . DS . 'behavior');
/**
* Load the model class based on the version of PHP.
*
*/
if (phpversion() < 5) {
require(LIBS . 'model' . DS . 'model_php4.php');
require(LIBS . 'model' . DS . 'model_php4.php');
if (function_exists("overload")) {
overload("Model");
}
if (function_exists('overload')) {
overload('Model');
}
} else {
require(LIBS . 'model' . DS . 'model_php5.php');
require(LIBS . 'model' . DS . 'model_php5.php');
}
?>

View file

@ -247,6 +247,28 @@ class Model extends Object{
*/
var $hasAndBelongsToMany = array();
/**
* List of behaviors to use
*
* @var array
*/
var $actsAs = null;
/**
* Behavior objects
*
* @var array
*/
var $behaviors = array();
/**
* Mapped behavior methods
*
* @var array
* @access private
*/
var $__behaviorMethods = array();
/**
* Depth of recursive association
*
@ -380,6 +402,23 @@ class Model extends Object{
*/
function __call($method, $params, &$return) {
$db =& ConnectionManager::getDataSource($this->useDbConfig);
$methods = array_keys($this->__behaviorMethods);
$call = array_values($this->__behaviorMethods);
$count = count($call);
for($i = 0; $i < $count; $i++) {
if (strpos($methods[$i], '/') === 0) {
if (preg_match($methods[$i], $method)) {
$return = $this->behaviors[$call[$i][1]]->{strtolower($call[$i][0])}($this, $params, $method);
return true;
}
} elseif (strtolower($methods[$i]) == strtolower($method)) {
$return = $this->behaviors[$call[$i][1]]->{strtolower($call[$i][0])}($this, $params);
return true;
}
}
$return = $db->query($method, $params, $this);
return true;
}
@ -552,6 +591,39 @@ class Model extends Object{
}
}
}
if ($this->actsAs !== null && empty($this->behaviors)) {
$callbacks = array('setup', 'beforeFind', 'afterFind', 'beforeSave', 'afterSave', 'beforeDelete', 'afterDelete', 'afterError');
$this->actsAs = normalizeList($this->actsAs);
foreach ($this->actsAs as $behavior => $config) {
if (!loadBehavior($behavior)) {
// Raise an error
} else {
$className = $behavior . 'Behavior';
$this->behaviors[$behavior] =& new $className;
$this->behaviors[$behavior]->setup($this, $config);
$methods = $this->behaviors[$behavior]->mapMethods;
foreach ($methods as $method => $alias) {
if (!array_key_exists($method, $this->__behaviorMethods)) {
$this->__behaviorMethods[$method] = array($alias, $behavior);
}
}
$methods = get_class_methods($this->behaviors[$behavior]);
$parentMethods = get_class_methods('ModelBehavior');
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, $behavior);
}
}
}
}
}
}
}
/**
* Sets a custom table for your controller class. Used by your controller to select a database table.
@ -714,20 +786,6 @@ class Model extends Object{
}
return true;
}
/**
* Deprecated
*
*/
function setId($id) {
$this->id = $id;
}
/**
* Deprecated. Use query() instead.
*
*/
function findBySql($sql) {
return $this->query($sql);
}
/**
* Returns a list of fields from the database
*
@ -1170,11 +1228,29 @@ class Model extends Object{
'order' => $order
);
if (!empty($this->behaviors)) {
$b = array_keys($this->behaviors);
$c = count($b);
for ($i = 0; $i < $c; $i++) {
$this->behaviors[$b[$i]]->beforeFind($this, $queryData);
}
}
if (!$this->beforeFind($queryData)) {
return null;
}
$return = $this->afterFind($db->read($this, $queryData, $recursive));
$results = $db->read($this, $queryData, $recursive);
if (!empty($this->behaviors)) {
$b = array_keys($this->behaviors);
$c = count($b);
for ($i = 0; $i < $c; $i++) {
$this->behaviors[$b[$i]]->afterFind($this, $results);
}
}
$return = $this->afterFind($results);
if (isset($this->__backAssociation)) {
$this->__resetAssociations();
@ -1449,9 +1525,12 @@ class Model extends Object{
* @param unknown_type $field
* @return string The name of the escaped field for this Model (i.e. id becomes `Post`.`id`).
*/
function escapeField($field) {
$db =& ConnectionManager::getDataSource($this->useDbConfig);
return $db->name($this->name) . '.' . $db->name($field);
function escapeField($field, $alias = null) {
if ($alias == null) {
$alias = $this->name;
}
$db =& $this->getDataSource();
return $db->name($alias) . '.' . $db->name($field);
}
/**
* Returns the current record's ID
@ -1535,6 +1614,44 @@ class Model extends Object{
return $this->cakeError('missingConnection', array(array('className' => $this->name)));
}
}
/**
* Gets the DataSource to which this model is bound
*
* @return DataSource A DataSource object
*/
function &getDataSource() {
return ConnectionManager::getDataSource($this->useDbConfig);
}
/**
* Gets all the models with which this model is associated
*
* @return array
*/
function getAssociated($type = null) {
if ($type == null) {
$associated = array();
foreach ($this->__associations as $assoc) {
if (!empty($this->{$assoc})) {
$models = array_keys($this->{$assoc});
foreach ($models as $m) {
$associated[$m] = $assoc;
}
}
}
return $associated;
} elseif (in_array($type, $this->__associations)) {
if (empty($this->{$type})) {
return array();
}
return array_keys($this->{$type});
} else {
$assoc = am($this->hasOne, $this->hasMany, $this->belongsTo, $this->hasAndBelongsToMany);
if (array_key_exists($type, $assoc)) {
return $assoc[$type];
}
return null;
}
}
/**
* Before find callback
*
@ -1588,11 +1705,18 @@ class Model extends Object{
/**
* Before validate callback
*
* @return void
* @return True if validate operation should continue, false to abort
*/
function beforeValidate() {
return true;
}
/**
* DataSource error callback
*
* @return void
*/
function onError() {
}
/**
* Private method. Clears cache for this model
*

View file

@ -247,6 +247,28 @@ class Model extends Object {
*/
var $hasAndBelongsToMany = array();
/**
* List of behaviors to use
*
* @var array
*/
var $actsAs = null;
/**
* Behavior objects
*
* @var array
*/
var $behaviors = array();
/**
* Mapped behavior methods
*
* @var array
* @access private
*/
var $__behaviorMethods = array();
/**
* Depth of recursive association
*
@ -364,6 +386,39 @@ class Model extends Object {
}
}
}
if ($this->actsAs !== null && empty($this->behaviors)) {
$callbacks = array('setup', 'beforeFind', 'afterFind', 'beforeSave', 'afterSave', 'beforeDelete', 'afterDelete', 'afterError');
$this->actsAs = normalizeList($this->actsAs);
foreach ($this->actsAs as $behavior => $config) {
if (!loadBehavior($behavior)) {
// Raise an error
} else {
$className = $behavior . 'Behavior';
$this->behaviors[$behavior] =& new $className;
$this->behaviors[$behavior]->setup($this, $config);
$methods = $this->behaviors[$behavior]->mapMethods;
foreach ($methods as $method => $alias) {
if (!array_key_exists($method, $this->__behaviorMethods)) {
$this->__behaviorMethods[$method] = array($alias, $behavior);
}
}
$methods = get_class_methods($this->behaviors[$behavior]);
$parentMethods = get_class_methods('ModelBehavior');
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, $behavior);
}
}
}
}
}
}
}
/**
* Handles custom method calls, like findBy<field> for DB models,
@ -376,6 +431,21 @@ class Model extends Object {
*/
function __call($method, $params) {
$db =& ConnectionManager::getDataSource($this->useDbConfig);
$methods = array_keys($this->__behaviorMethods);
$call = array_values($this->__behaviorMethods);
$count = count($call);
for($i = 0; $i < $count; $i++) {
if (strpos($methods[$i], '/') === 0) {
if (preg_match($methods[$i], $method)) {
return $this->behaviors[$call[$i][1]]->{$call[$i][0]}($this, $params, $method);
}
} elseif (strtolower($methods[$i]) == strtolower($method)) {
return $this->behaviors[$call[$i][1]]->{$call[$i][0]}($this, $params);
}
}
return $db->query($method, $params, $this);
}
/**
@ -517,7 +587,6 @@ class Model extends Object {
case 'foreignKey':
$data = Inflector::singularize($this->table) . '_id';
if ($type == 'belongsTo') {
$data = Inflector::singularize($this->{$class}->table) . '_id';
}
@ -711,20 +780,6 @@ class Model extends Object {
}
return true;
}
/**
* Deprecated
*
*/
function setId($id) {
$this->id = $id;
}
/**
* Deprecated. Use query() instead.
*
*/
function findBySql($sql) {
return $this->query($sql);
}
/**
* Returns a list of fields from the database
*
@ -1170,11 +1225,29 @@ class Model extends Object {
'order' => $order
);
if (!empty($this->behaviors)) {
$b = array_keys($this->behaviors);
$c = count($b);
for ($i = 0; $i < $c; $i++) {
$this->behaviors[$b[$i]]->beforeFind($this, $queryData);
}
}
if (!$this->beforeFind($queryData)) {
return null;
}
$return = $this->afterFind($db->read($this, $queryData, $recursive));
$results = $db->read($this, $queryData, $recursive);
if (!empty($this->behaviors)) {
$b = array_keys($this->behaviors);
$c = count($b);
for ($i = 0; $i < $c; $i++) {
$this->behaviors[$b[$i]]->afterFind($this, $results);
}
}
$return = $this->afterFind($results);
if (isset($this->__backAssociation)) {
$this->__resetAssociations();
@ -1449,9 +1522,12 @@ class Model extends Object {
* @param unknown_type $field
* @return string The name of the escaped field for this Model (i.e. id becomes `Post`.`id`).
*/
function escapeField($field) {
$db =& ConnectionManager::getDataSource($this->useDbConfig);
return $db->name($this->name) . '.' . $db->name($field);
function escapeField($field, $alias = null) {
if ($alias == null) {
$alias = $this->name;
}
$db =& $this->getDataSource();
return $db->name($alias) . '.' . $db->name($field);
}
/**
* Returns the current record's ID
@ -1535,6 +1611,44 @@ class Model extends Object {
return $this->cakeError('missingConnection', array(array('className' => $this->name)));
}
}
/**
* Gets the DataSource to which this model is bound
*
* @return DataSource A DataSource object
*/
function &getDataSource() {
return ConnectionManager::getDataSource($this->useDbConfig);
}
/**
* Gets all the models with which this model is associated
*
* @return array
*/
function getAssociated($type = null) {
if ($type == null) {
$associated = array();
foreach ($this->__associations as $assoc) {
if (!empty($this->{$assoc})) {
$models = array_keys($this->{$assoc});
foreach ($models as $m) {
$associated[$m] = $assoc;
}
}
}
return $associated;
} elseif (in_array($type, $this->__associations)) {
if (empty($this->{$type})) {
return array();
}
return array_keys($this->{$type});
} else {
$assoc = am($this->hasOne, $this->hasMany, $this->belongsTo, $this->hasAndBelongsToMany);
if (array_key_exists($type, $assoc)) {
return $assoc[$type];
}
return null;
}
}
/**
* Before find callback
*
@ -1588,11 +1702,18 @@ class Model extends Object {
/**
* Before validate callback
*
* @return void
* @return True if validate operation should continue, false to abort
*/
function beforeValidate() {
return true;
}
/**
* DataSource error callback
*
* @return void
*/
function onError() {
}
/**
* Private method. Clears cache for this model
*

View file

@ -26,6 +26,9 @@
* @lastmodified $Date$
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
/* Deprecated */
/**
* Tag template for a div with a class attribute.
*/
@ -50,15 +53,9 @@
* @package cake
* @subpackage cake.cake.libs.view.helpers
*/
class FormHelper extends Helper{
class FormHelper extends Helper {
var $helpers = array('Html');
/**
* Constructor which takes an instance of the HtmlHelper class.
*
* @return void
*/
function FormHelper() {
}
/**
* Returns a formatted error message for given FORM field, NULL if no errors.
*
@ -66,7 +63,7 @@ class FormHelper extends Helper{
* @return bool If there are errors this method returns true, else false.
*/
function isFieldError($field) {
$error=1;
$error = 1;
$this->Html->setFormTag($field);
if ($error == $this->Html->tagIsInvalid($this->Html->model, $this->Html->field)) {
@ -75,6 +72,12 @@ class FormHelper extends Helper{
return false;
}
}
/**
* @deprecated
*/
function labelTag($tagName, $text) {
return sprintf($this->tags['label'], Inflector::camelize(str_replace('/', '_', $tagName)), $text);
}
/**
* Returns a formatted LABEL element for HTML FORMs.
*
@ -82,15 +85,11 @@ class FormHelper extends Helper{
* @param string $text Text that will appear in the label field.
* @return string The formatted LABEL element
*/
function labelTag($tagName, $text) {
return sprintf(TAG_LABEL, strtolower(str_replace('/', '_', $tagName)), $text);
function label($tagName, $text) {
return $this->output(sprintf($this->tags['label'], Inflector::camelize(str_replace('/', '_', $tagName)), $text));
}
/**
* Returns a formatted DIV tag for HTML FORMs.
*
* @param string $class CSS class name of the div element.
* @param string $text String content that will appear inside the div element.
* @return string The formatted DIV element
* @deprecated
*/
function divTag($class, $text) {
return sprintf(TAG_DIV, $class, $text);
@ -105,6 +104,83 @@ class FormHelper extends Helper{
function pTag($class, $text) {
return sprintf(TAG_P_CLASS, $class, $text);
}
/**
* Creates a text input widget.
*
* @param string $fieldNamem Name of a field, like this "Modelname/fieldname"
* @param array $htmlAttributes Array of HTML attributes.
* @return string An HTML text input element
*/
function text($fieldName, $htmlAttributes = null) {
$this->Html->setFormTag($fieldName);
if (!isset($htmlAttributes['value'])) {
$htmlAttributes['value'] = $this->tagValue($fieldName);
}
if (!isset($htmlAttributes['type'])) {
$htmlAttributes['type'] = 'text';
}
if (!isset($htmlAttributes['id'])) {
$htmlAttributes['id'] = $this->Html->model . Inflector::camelize($this->Html->field);
}
if ($this->Html->tagIsInvalid($this->Html->model, $this->Html->field)) {
if (isset($htmlAttributes['class']) && trim($htmlAttributes['class']) != "") {
$htmlAttributes['class'] .= ' form_error';
} else {
$htmlAttributes['class'] = 'form_error';
}
}
return sprintf($this->tags['input'], $this->Html->model, $this->Html->field, $this->Html->_parseAttributes($htmlAttributes, null, ' ', ' '));
}
/**
* Creates a button tag.
*
* @param mixed $params Array of params [content, type, options] or the
* content of the button.
* @param string $type Type of the button (button, submit or reset).
* @param array $options Array of options.
* @return string A HTML button tag.
*/
function button($params, $type = 'button', $options = array()) {
trigger_error('Don\'t use me yet', E_USER_ERROR);
if (isset($options['name'])) {
if (strpos($options['name'], "/") !== false) {
if ($this->fieldValue($options['name'])) {
$options['checked'] = 'checked';
}
$this->setFieldName($options['name']);
$options['name'] = 'data['.$this->_model.']['.$this->_field.']';
}
}
$options['type'] = $type;
$values = array(
'options' => $this->_parseOptions($options),
'tagValue' => $content
);
return $this->_assign('button', $values);
}
/**
* Creates an image input widget.
*
* @param string $path Path to the image file, relative to the webroot/img/ directory.
* @param array $htmlAttributes Array of HTML attributes.
* @return string HTML submit image element
*/
function submitImage($path, $htmlAttributes = null) {
if (strpos($path, '://')) {
$url = $path;
} else {
$url = $this->webroot . IMAGES_URL . $this->themeWeb . $path;
}
return sprintf($this->tags['submitimage'], $url, $this->_parseAttributes($htmlAttributes, null, '', ' '));
}
/**
* Returns a formatted INPUT tag for HTML FORMs.
*