Behaviors

Behaviors (ou comportamentos) de model são uma maneira de organizar algumas das funcionalidades definidas para models no CakePHP. Behaviors nos permitem separar lógica daquilo que não esteja diretamente relacionado aos models, mas precisa estar neles. Com uma maneira simples e poderosa de estender models, behaviors nos permitem anexar funcionalidades aos models definindo uma simples variável de classe. É assim que os behaviors permitem que os models se livrem de todo o peso extra que pode não ser parte do contrato de negócio que estão modelando ou que também seja necessário em diferentes models e podem ser extrapolados.

Como um exemplo, considere um model que nos dá acesso a uma tabela na base de dados que armazena informação estrutural sobre um árvore. Excluir, adicionar e mover nós na árvore não é tão simples quanto excluir, adicionar e modificar linhas na tabela. Muitos registros podem precisar ser atualizados conforme as coisas sejam movidas. Ao invés de criar estes métodos de manipulação de árvore na camada de model (para cada model que precise dessa funcionalidade), nós poderíamos simplesmente dizer para nosso model usar o TreeBehavior, ou em termos mais formais, diríamos que nosso model se comporta como uma árvore. Isto é conhecido como anexar um comportamento (behavior) a um model. Com apenas uma linha de código, nosso model em CakePHP ganha todo um novo conjunto de métodos que lhe permitem interagir com a estrutura de dados em questão.

O CakePHP já inclui comportamentos para estruturas em árvore, para conteúdos em diversos idiomas, para interação com listas de controle de acesso, e isso sem mencionar os behaviors desenvolvidos pela comunidade e que estão disponíveis no CakePHP Bakery (https://bakery.cakephp.org). Nesta seção iremos cobrir o padrão básico de utilização para adicionar comportamentos aos models, como usar os behaviors que já disponíveis por padrão no CakePHP e como criar os nossos próprios behaviors.

Usando Behaviors

Behaviors são anexados ao modelos através da variável $actAs na classe model

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

Este exemplo mostra como um modelo Categoria pode ser tratado em uma estrutura de árvore usando o TreeBehavior. Uma vez um behavior ter sido especificado, use os métodos adicionados pelo behavior como se eles sempre existiram como parte do modelo original:

// Define o ID
$this->Category->id = 42;

// Usa o método do behavior, children():
$kids = $this->Category->children();

Alguns behaviors podem necessitar ou permitir ajustes para serem definidos quando o behavior é anexado ao modelo. Aqui, dizemos ao nosso TreeBehavior os nomes dos campos da “esquerda” e “direita” na tabela subjacente do banco de dados:

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

Também podemos anexar diversos behaviors a um modelo. Não há razão porque, por exemplo, nosso modelo Categoria apenas agir como uma árvore, ele também pode precisar de suporte de internacionalização:

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

Até agora temos adicionado behaviors a modelos usando uma variável da classe model. Isso significa que seus behavior serão anexados aos nossos modelos por todo tempo de vida do modelo. Contudo, podemos precisar “desanexar” behaviors de nossos modelos em seu tempo de execução. Vamos dizar que em nosso modelo anterior Categoria, que está agindo como um modelo Árvore e um Tradutor, precimaos por alguma razão forçá-lo a parar de agir como um modelo Tradutor:

// Desanexar um behavior de nosso modelo:
$this->Category->Behaviors->detach('Translate');

Isso fará com que nosso modelo Categoria pare de agir como um modelo Tradutor depois disso. Podemos precisar, em vez de, apenas desabilitar o behavior Trandutor de agir sobre nossas operções normal de modelo: nossos finds, nossos saves, etc. De fato, estamos procurando desabilitar o behavior de agir sobre nossos callback de modelo do CakePHP. Em vez de desanexar o behavior, então dizemos ao nosso modelo para parar de informar estes callbacks ao behavior Tradutor:

// Para de deixar o behavior manipular nossos callbacks do modelo
$this->Category->Behaviors->disable('Translate');

Também podemos precisar descobrir se nosso behavior está manipulando aqueles callback do modelo, e se não então restauramos sua abilidade para reagir a eles:

// Se nosso behavior não está manipulando os callbacks do modelo
if (!$this->Category->Behaviors->enabled('Translate')) {
    // Diz a ele para iniciar a fazê-lo
    $this->Category->Behaviors->enable('Translate');
}

Assim como podemos desanexar completamente um behavior de um modelo em tempo de execução, também podemos anexar novos behaviors. Digamos que seu modelo Categoria precisa iniciar a agir como um modelo Natal (Christmas), mas apenas no dia de Natal:

// Se hoje é 25 de Dezembro
if (date('m/d') == '12/25') {
    // Nosso modelo precisa agir como um modelo Natal
    $this->Category->Behaviors->attach('Christmas');
}

Também podemos usar o método de anexar para substituir ajustes de behavior:

// Mudamos um ajuste de nosso behavior já anexado
$this->Category->Behaviors->attach('Tree', array('left' => 'new_left_node'));

Também há um método de obter uma lista de behavior que um modelo tem anexado. Se passarmos o nome de um behavior para o método, ele nos dirá se aquele behavior está anexado ao modelo, caso contrário ele nos dará a lista de behaviors anexados:

// Se o behavior Tradutor não estiver anexado
if (!$this->Category->Behaviors->attached('Translate')) {
    // Pega a lista de todos os behaviors que o modelo tem anexado
    $behaviors = $this->Category->Behaviors->attached();
}

Criando Behaviors

Behaviors que são anexados a Modelos recebem suas chamadas callbacks automaticamente. Os callbacks são similiar aqueles encontrados nos Modelos: beforeFind, afterFind, beforeSave, afterSave, beforeDelete, afterDelete e onError - veja Métodos de Callback.

Geralmente é útil usar um behavior de núcleo como um template ao criar seu próprio behavior. Encontre-os em cake/libs/model/behaviors/.

Todo callback pega uma referência ao modelo, ele está sendo chamado como primeiro parâmetro.

Além de implementar os callbacks, você pode adicionar ajuster por behavior e/ou por anexo de behavior do modelo. Informação sobre especificar ajustes podem ser encontrar nos capítulos sobre behaviors de núcleo e suas configurações.

Um exemplo rápido que ilustra como ajustes de behavior podem ser passados do modelo para o behavior:

class Post extends AppModel {
    var $name = 'Post'
    var $actsAs = array(
        'YourBehavior' => array(
            'option1_key' => 'option1_value'));
}

A partir de 1.2.8004, o CakePHP adiciona aqueles ajustes apenas uma vez por modelo/alias. Para manter seus behavior atualizável você deve respeitar os aliases (ou modelos).

Uma função de atualização amigável de instalação pode parecer algo como isto:

function setup(&$Model, $settings) {
    if (!isset($this->settings[$Model->alias])) {
        $this->settings[$Model->alias] = array(
            'option1_key' => 'option1_default_value',
            'option2_key' => 'option2_default_value',
            'option3_key' => 'option3_default_value',
        );
    }
    $this->settings[$Model->alias] = array_merge(
        $this->settings[$Model->alias], (array)$settings);
}

Criando métodos

Todos os métodos que você criar em seu Behavior estará, automaticamente, disponível em qualquer model em que seu Behavior estiver agindo. Por exemplo:

class Usuario extends AppModel {
    var $name = 'Usuario';
    var $actsAs = array('Motorista');
}

Você poderá chamar qualquer método do MotoristaBehavior se ele estiver no model Usuario.

primeiro parâmetro uma referência do model que está chamando-o. Qualquer outro parâmetro é deslocado uma posição a direita. Por exemplo:

$this->Usuario->dirigir('recife', 'natal');

Embora o método tenha apenas dois parâmetros, o método deve ficar como o abaixo:

function dirigir(&$Model, $from, $to) {
    // fazer algo.
}

Tome cuidado com os métodos chamados internamente no Behavior, eles não terão o primeiro parâmetro (referência do model) passados automaticamente.

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.