Comportamientos

Los comportamientos del modelo (Model behaviors) son una manera de organizar parte de la funcionalidad definida en los modelos de CakePHP. Nos permiten separar la lógica que puede no estar relacionada directamente con un modelo, pero que necesita estar ahí. Proveyendo una manera simple pero potente manera de extender modelos, los comportamientos nos permiten atar funcionalidad a los modelos definiendo una simple variable de clase. Así es como los comportamientos permiten a los modelos deshacerse de todo el peso extra que no debería ser parte de contrato de negocio que están modelando, o al menos es necesario en diferentes modelos y puede, entonces, ser extrapolado.

Como ejemplo, considera un modelo que nos da acceso a una tabla de una base de datos la cual almacena información estructural de un árbol. Eliminando, añadiendo y migrando nodos en el árbol no es tan simple como borrar, insertar y editar filas en una tabla. Muchos registros deberán ser actualizados según las cosas se mueven. En vez de crear esos métodos de manipulación del árbol en cada base de modelo (para todo modelo que necesita dicha funcionalidad), podríamos simplemente decirle a nuestro modelo que utilize el TreeBehavior (ArbolComportamiento), o en términos más formales, decirle a nuestro modelo que se comporte como un Árbol. Esto es conocido como atar un comportamiento a un modelo. Con sólo una línea de código, nuestro modelo de CakePHP toma todo un nuevo conjunto de métodos que le permiten interactuar con la estructura subyacente.

CakePHP ya incluye comportamientos para estructuras de árbol, contenido traducido, interacción con listas de control de acceso, sin comentar los comportamientos aportados por la comunidad disponibles en CakePHP Bakery. En esta sección cubriremos el patrón básico de uso para añadir comportamientos a modelos, cómo utilizar los comportamientos de CakePHP incorporados y cómo crear uno nosotros mismos.

Utilizando Comportamientos

Los comportamientos son atados a modelos mediante la variable de clase del modelo $actsAs:

<?php

class Category extends AppModel {
    var $name   = 'Category';
    var $actsAs = array('Tree');
}

?>

Este ejemplo muestra cómo un modelo de Category puede ser manejado en una estructura de árbol utilizando el comportamiento de árbol (TreeBehavior). Una vez que un comportamiento ha sido especificado, utiliza los métodos añadidos por el comportamiento como si siempre existiesen como parte del modelo original:

// Set ID
$this->Category->id = 42;

// Utilizar un método de comportamiento, children():
$kids = $this->Category->children();

Algunos comportamientos pueden requerir o permitir opciones a ser definidas cuando el comportamiento es atado al modelo. Aquí, decimos a nuestro TreeBehavior (comportamiento de árbol) los nombres de los campos left (izquierdo) y right (derecho) en la tabla de base de datos subyacente:

<?php

class Category extends AppModel {
    var $name   = 'Category';
    var $actsAs = array(
        'Tree' => array(
            'left'  => 'left_node',
            'right' => 'right_node'
        )
    );
}

?>

Podemos también atar varios comportamientos a un modelo. No hay razón por la que, por ejemplo, nuestro modelo Category deba sólo comportarse como un árbol, también puede necesitar soporte de internacionalización:

<?php

class Category extends AppModel {
    var $name   = 'Category';
    var $actsAs = array(
        'Tree' => array(
          'left'  => 'left_node',
          'right' => 'right_node'
        ),
        'Translate'
    );
}

?>

Hasta el momento hemos estado añadiendo comportamientos a modelos utilizando una variable de la clase. Eso significa que nuestros comportamientos serán atados a muestros modelos durante todo el tiempo de vida del modelo. Sin embargo, puede ser que necesitemos “desatar” comportamientos de nuestros modelos en tiempo de ejecución. Digamos que en nuestro modelo Category anterior, el cual está actuando como un modelo Tree y Translate, necesitamos por alguna razón forzar a que deje de actuar como un modelo Translate:

// Desata un comportamiento de nuestro modelo:
$this->Category->Behaviors->detach('Translate');

Eso hará que nuestro modelo Category deje de comportarse como un modelo Translate desde ese momento. Tal vez necesitemos, en cambio, simplemente desactivar el comportamiento Translate de actuar sobre operaciones normales del modelo: nuestras búsquedas, grabados, etc. De hecho, estamos buscando desactivar el comportamiento de actuar sobre nuestros callbacks del modelo de CakePHP. En vez de desatar el comportamiento, le decimos a nuestro modelo que pare de informar sobre dichos callbacks al comportamiento Translate:

// Parar de dejar al comportamiento manejar los callbacks de nuestro modelo
$this->Category->Behaviors->disable('Translate');

También necesitaremos saber si nuestro comportamiento está manejando los callbacks del modelo, y si no, restauramos su habilidad para reaccionar a ellos:

// Si nuestro comportamiento no está manejando los callbacks del modelo
if (!$this->Category->Behaviors->enabled('Translate')) {
    // Decirle que comience a hacerlo
    $this->Category->Behaviors->enable('Translate');
}

Tal como podemos desatar completamente un comportamiento de un modelo en tiempo de ejecución, también podemos atar nuevos comportamientos. Digamos que nuestro modelo Category necesita empezar a comportarse como un modelo Christmas (Navidad), pero sólo en el día de Navidad:

// Si hoy es 25 de diciembre
if (date('m/d') == '12/25') {
    // Nuestro modelo necesita comportarse como un modelo Christmas
    $this->Category->Behaviors->attach('Christmas');
}

Podemos también utilizar el método attach() para sobreescribir las opciones de comportamiento:

// Cambiaremos una opción de nuestro ya atado comportamiento
$this->Category->Behaviors->attach('Tree', array('left' => 'new_left_node'));

También hay un método para obtener la lista de comportamientos atados a un modelo: attached(). Si pasamos el nombre de un comportamiento al método, nos dirá si dicho comportamiento está atado al modelo; de cualquier otra manera nos dará una lista de los comportamientos atados.

// Si el comportamiento "Translate" no está atado al modelo
if (!$this->Category->Behaviors->attached('Translate')) {
    // Obtener la lista de todos los comportamientos atados al modelo
    $behaviors = $this->Category->Behaviors->attached();
}

Creando Comportamientos Personalizados

Este es el contenedor del contenido.

Creating behavior methods

Behavior methods are automatically available on any model acting as the behavior. For example if you had:

class Duck extends AppModel {
    var $name = 'Duck';
    var $actsAs = array('Flying');
}

You would be able to call FlyingBehavior methods as if they were methods on your Duck model. When creating behavior methods you automatically get passed a reference of the calling model as the first parameter. All other supplied parameters are shifted one place to the right. For example

$this->Duck->fly('toronto', 'montreal');

Although this method takes two parameters, the method signature should look like:

function fly(&$Model, $from, $to) {
    // Do some flying.
}

Keep in mind that methods called in a $this->doIt() fashion from inside a behavior method will not get the $model parameter automatically appended.

Behavior callbacks

Model Behaviors can define a number of callbacks that are triggered before/after the model callbacks of the same name. Behavior callbacks allow your behaviors to capture events in attached models and augment the parameters or splice in additional behavior.

The available callbacks are:

  • beforeValidate is fired before a model’s beforeValidate
  • beforeFind is fired before a model’s beforeFind
  • afterFind is fired before a model’s afterFind
  • beforeSave is fired before a model’s beforeSave
  • afterSave is fired before a model’s afterSave
  • beforeDelete is fired after a model’s beforeDelete
  • afterDelete is fired before a model’s afterDelete

Creating a behavior callback

Model behavior callbacks are defined as simple methods in your behavior class. Much like regular behavior methods, they receive a $Model parameter as the first argument. This parameter is the model that the behavior method was invoked on.

function beforeFind(&$model, $query)

If a behavior’s beforeFind returns false it will abort the find(). Returning an array will augment the query parameters used for the find operation.

afterFind(&$model, $results, $primary)

You can use the afterFind to augment the results of a find. The return value will be passed on as the results to either the next behavior in the chain or the model’s afterFind.

beforeDelete(&$model, $cascade = true)

You can return false from a behavior’s beforeDelete to abort the delete. Return true to allow it continue.

afterDelete(&$model)

You can use afterDelete to perform clean up operations related to your behavior.

beforeSave(&$model)

You can return false from a behavior’s beforeSave to abort the save. Return true to allow it continue.

afterSave(&$model, $created)

You can use afterSave to perform clean up operations related to your behavior. $created will be true when a record is created, and false when a record is updated.

beforeValidate(&$model)

You can use beforeValidate to modify a model’s validate array or handle any other pre-validation logic. Returning false from a beforeValidate callback will abort the validation and cause it to fail.