Models

Models representam dados e são usados nas aplicações CakePHP para acesso aos dados. Um Model normalmente representa uma tabela de um banco de dados, mas pode ser usado para acessar qualquer coisa que guarde dados como arquivos, registros do LDAP, eventos do iCal, ou linhas em um arquivo CSV.

Um model pode ser associado a outros models. Um modelo pode ser associado a outros modelos. Por exemplo, uma receita pode ser associado com o autor da receita, bem como os ingredientes na receita.

Esta seção irá explicar quais as características de um model que podem ser automatizadas, como substituir essas características, e quais métodos e propriedades um model pode ter. Esta seção vai descrever a forma de encontrar, guardar e apagar dados. Por último, vamos olhar para os Datasources.

Introdução

Um Model representa seu modelo de dados e em programação orientada a objetos é um objeto que representa uma “coisa”, como um carro, uma pessoa ou uma casa. Um blog, por exemplo, deve ter vários posts e cada post deve ter vários comentários. O Blog, o Post e os Comentários são todos exemplos de models, cada um associado ao outro.

Aqui vai um exeplo de definição de model no CakePHP:

<?php
class Ingrediente extends AppModel {
    var $name = 'Ingrediente';
}
?>

Com apenas esta simples declaração, o model Ingrediente é agraciado com todas as funcionalidades que você precisa para criar consultas (queries), salvar e excluir dados. Este método mágico vem da classe Model do CakePHP pela magia da herança. O model Ingrediente estende o application model, AppModel, que estende a classe interna do CakePHP Model. É esta classe Model de núcleo (core Model class) que agracia as funcionalidades para seu model Ingrediente.

Esta classe intermediária, AppModel, é vazia e se você não criou sua própria é pega de dentro da pasta /cake/. Substituindo a AppModel permite a você definir funcionalidades que devem ser feitas disponível a todos o models dentro da aplicação. Para fazer isso, você precisa criar seu próprio arquivo app_model.php que deve se localizar na raiz da pasta /app/. Criando um projeto usando Bake irá automaticamente gerar este arquivo para você.

Crie seu aruqivo model PHP no diretório /app/models/ ou em um subdiretório de /app/model. O CakePHP o encontrará em qualquer lugar no diretório. Por convenção ele deve ter o mesmo nome que a classe, por exemplo ingrediente.php.

CakePHP criará dinamicamente um objeto model para você se ele não puder encontrar o arquivo correspondente em /app/models. Isso também significa que se seu arquivo model não estiver nomeado corretamente (i.e. Ingrediente.php ou ingredientes.php) o CakePHP usará uma instância do AppModel em vez de seu arquivo model incorreto (ao modo de ver do CakePHP). Se você estiver tentando usar um método você deve o definir em seu model, ou um behavior (comportamento) anexado ao seu modelo e você terá erros de SQL que serão o nome do método que você está chamando - é um um sinal seguro que o CakePHP não pode encontrar seu model e você também deve checar os nomes dos arquivos, limpar seus arquivos temporários, ou ambos.

Veja também Behaviors para mais informações sobre como aplicar lógica similar a múltiplos models.

A propriedade $name é necessária para o PHP4 mas opcional para o PHP5

Com seu model definido, ele pode ser acessado de dentro de seu Controller. O CakePHP automaticamente fará o model disponível para acesso quando seu nome corresponde ao do controller. Por exemplo, um controller nomeado IngredientesController automaticamente iniciará o model Ingrediente e o anexará ao controller em $this->Ingrediente.

<?php
class IngredientesController extends AppController {
    function index() {
        //pega todos os ingredientes e os passa para a view
        $ingredientes = $this->Ingrediente->find('all');
        $this->set('ingredientes', $ingredientes);
    }
}

?>

Models associados estão diponíveis através do módel principal. No exemplo a seguir, Receita tem uma associação com o model Ingrediente.

<?php
class ReceitasController extends AppController {
    function index() {
        $ingredientes = $this->Receita->Ingrediente->find('all');
        $this->set('ingredientes', $ingredientes);
    }
}
?>

Se os models NÃO ESTÃO absolutamente associados entre eles, você pode usar Controller::loadModel() para pegar o model.

<?php
class ReceitasController extends AppController {
    function index() {
       $receitas = $this->Receita->find('all');

       $this->loadModel('Carro');
       $carros = $this->Carro->find('all');

       $this->set(compact('receitas', 'carros'));
    }
}
?>

Criando Tabelas de Banco de Dados

Enquanto o CakePHP pode ter datasources que não são dirigidos a banco de dados, a maioria das vezes, eles são. O CakePHP é desenvolvido para ser agnóstico e funcionará com MySQL, MSSQL, Oracle, PostgreSQL e outros. Você pode criar suas tabelas de banco de dados como normalmente faria. Quando você cria suas classes Model, elas automaticamente mapearão às tabelas que você criou.

Nomes de tabelas são por convenção em minúsculo e no plural com nomes de tabelas com múltiplas palavras separadas por sublinhado. Por exemplo, um Model chamado Ingrediente espera a tabela chamada ingredientes. Um Model chamado de RegistroDeEvento espera uma tabela chamada de registro_de_eventos. O CakePHP inspecionará suas tabelas para determinar o tipo de dados de cada um dos campos e usa a informação para automatizar vários recursos como saídas de campos de formulário na view.

Nomes de campos são por convenção minusculos e separados por sublinhado.

As associações de model para nome de tabela podem ser substituídas com o atributo useTable do model explicado mais a frente neste capítulo.

NO restante deta sessão, você verá como CakePHP mapea os tipos de campo do banco de dados para os tipos de dados do PHP e como o CakePHP pode automatizar tarefas baseadas em como seus campos são definidos.

Tipos de dados associados à cada SGBD

Cada SGBD define os tipos de dados de forma ligeiramente diferente. Dentro da classe de dados para cada sistema de base de dados, o CakePHP possui mapas, esses tipos de coisa que reconhece e cria uma interface unificada, não importa qual sistema de base de dados que você irá usar.

Esta seção descreve como cada tipo de dado está mapeado para cada SGBD.

MySQL

Tipos do CakePHP Propriedades do Campo
chave primária NOT NULL auto_increment
string varchar(255)
texto text
inteiro int(11)
número flutuante float
data e horário datetime
timestamp datetime
horário time
data date
binário blob
boleano tinyint(1)

Um campo tinyint(1) é considerado um booleano pelo CakePHP.

MySQLi

Tipo do CakePHP Propriedades do Campo
chave primária DEFAULT NULL auto_increment
string varchar(255)
texto text
inteiro int(11)
número flutuante float
data e horário datetime
timestamp datetime
horário time
data date
binário blob
boleano tinyint(1)

ADOdb

Tipo do CakePHP Propriedades do Campo
chave primária R(11)
string C(255)
texto X
inteiro I(11)
número flutuante N
data e horário T (Y-m-d H:i:s)
timestamp T (Y-m-d H:i:s)
horário T (H:i:s)
data T (Y-m-d)
binário B
booleano L(1)

DB2

Tipo do CakePHP Propriedades do Campo
chave primária not null gerado por padrão como identificador (começa com 1, incrementando em 1)
string varchar(255)
texto clob
inteiro integer(10)
número flutuante double
data e horário timestamp (Y-m-d-H.i.s)
timestamp timestamp (Y-m-d-H.i.s)
horário time (H.i.s)
data date (Y-m-d)
binário blob
booleano smallint(1)

Firebird/Interbase

Tipo do CakePHP Propriedades do Campo
chave primária IDENTITY (1, 1) NOT NULL
string varchar(255)
texto BLOB SUB_TYPE 1 SEGMENT SIZE 100 CHARACTER SET NONE
inteiro integer
número flutuante float
data e horário timestamp (d.m.Y H:i:s)
timestamp timestamp (d.m.Y H:i:s)
horário time (H:i:s)
data date (d.m.Y)
binário blob
booleano smallint

MS SQL

Tipo do CakePHP Propriedades do Campo
chave primária IDENTITY (1, 1) NOT NULL
string varchar(255)
text text
inteiro int
número flutuante numeric
data e horário datetime (Y-m-d H:i:s)
timestamp timestamp (Y-m-d H:i:s)
horário datetime (H:i:s)
data datetime (Y-m-d)
binário image
booleano bit

Oracle

Tipo do CakePHP Propriedades do Campo
chave primária number NOT NULL
string varchar2(255)
texto varchar2
intiro numeric
númeroo flutuante float
data e horário date (Y-m-d H:i:s)
timestamp date (Y-m-d H:i:s)
horário date (H:i:s)
data date (Y-m-d)
binário bytea
booleano boolean
número numeric
inet inet

PostgreSQL

Tipo do CakePHP Propriedade do Campo
chave primária serial NOT NULL
string varchar(255)
texto text
inteiro integer
número flutuante float
date e horário timestamp (Y-m-d H:i:s)
timestamp timestamp (Y-m-d H:i:s)
horário time (H:i:s)
data date (Y-m-d)
binário bytea
booleano boolean
número numeric
inet inet

SQLite

Tipo do CakePHP Propriedades do Campo
chave primária integer primary key
string varchar(255)
texto text
inteiro integer
número flutuante float
data e horário datetime (Y-m-d H:i:s)
timestamp timestamp (Y-m-d H:i:s)
horário time (H:i:s)
data date (Y-m-d)
binário blob
booleano boolean

Sybase

Tipo do CakePHP Propriedade do Campo
chave primária numeric(9,0) IDENTITY PRIMARY KEY
string varchar(255)
texto text
inteiro int(11)
número flutuante float
data e horário datetime (Y-m-d H:i:s)
timestamp timestamp (Y-m-d H:i:s)
horário datetime (H:i:s)
data datetime (Y-m-d)
binário image
booleano bit

Titles

Um objeto, no sentido físico, muitas vezes tem um nome ou um título para se referir a ele. Uma pessoa tem um nome como João ou Mac ou Buddy. Um blog tem um título. A categoria tem um nome.

Ao especificar um campo com o nome de

title ou `` name `` o CakePHP irá automaticamente usar esse rótulo, em várias circunstâncias:

  • Scaffolding - títulos das páginas, rótulos dos fieldset’s
  • Lists - normalmente utilizadas para <select> drop-downs
  • TreeBehavior - reordenação, visualização em árvore

Se você tiver um title e um campo name em sua tabela, o título será usado.

created & modified (ou updated)

Estes dois campos são automaticamente preenchidos pelo CakePHP quando você chama a função save(). O campo created será preenchido apenas quando estiver inserindo um novo registro no banco de dados, já modified será preenchida em cada alteração que você faça. O campo chamado updated terá o mesmo comportamento que o modified.

Ambos os campos devem ser do tipo datetime e o valor padrão (default) setado para NULL.

Usando UUIDs como Chaves Primárias

Chaves primárias normalmente são definidas como campos INT. O banco de dados irá automaticamente incrementar o campo, iniciando em 1, para cada novo registro adicionado. Alternativamente, se você especificar sua chave primária como CHAR(36) ou BINARY(36), CakePHP irá automaticamento gerar UUIDs quando novos registros forem criados.

Uma UUID é uma string de 32 byte separada por quatro hífens, com um total de 36 caracteres. Por exemplo:

550e8400-e29b-41d4-a716-446655440000

UUIDs foram desenvolvidas para serem únicas, não apenas dentro de uma só tabela, mas também entre tabelas e banco de dados. Se você requer um campo que permaneça único entre sistemas, então UUIDs são uma boa pedida.

Recuperando seus dados

findAll(string $conditions, array $fields, string $order, int $limit, int $page, int $recursive);

Retorna no máximo $limite registros com as condições definidas em $conditions (se houver), começando da página $page (o padrão é 1). Se não houver registros, retornará um array vazio.

O $conditions deve ser formada apenas como eles são usados em SQL, por exemplo:

$conditions = "Pastry.type LIKE '%cake%' AND Pastry.created_on > ‘2007-01-01’"

Prefixando os campos com o nome dos models (‘Pastry.type’ ao invés de ‘type’) é uma boa prática, principalmente quando associa-se dados é usado nas queries.

Configurando o parâmetro $recursive para um inteiro, força o findAll() a buscar os dados de acordo com os behaviors descritos em Atributos anteriormente.

Os dados de findAll() são retornados em um array, seguindo o modelo básico abaixo:

Array
(
    [0] => Array
        (
            [ModelName] => Array
                (
                    [id] => 83
                    [field1] => value1
                    [field2] => value2
                    [field3] => value3
                )

            [AssociatedModelName] => Array
                (
                    [id] => 1
                    [field1] => value1
                    [field2] => value2
                    [field3] => value3
                )
        )
    [1] => Array
        (
            [ModelName] => Array
                (
                    [id] => 85
                    [field1] => value1
                    [field2] => value2
                    [field3] => value3
                )

            [AssociatedModelName] => Array
                (
                    [id] => 2
                    [field1] => value1
                    [field2] => value2
                    [field3] => value3
                )
        )
)

find(string $conditions, array $fields, string $order, int $recursive)

Assim como em findAll(), exceto que esta busca retorna o primeiro registro, seguindo as regras de $conditions.

findAllBy<fieldName>(string $value)

findBy<fieldName>(string $value);

Estas funções mágicas podem ser usadas como atalhos para procurar dados nos campos especificados nas suas tabelas. Basta adicionar o nome do campo, no formato CamelCased, no fim das funções e informar os critérios de busca como primeiro parâmetro.

Exemplos de findAllBy<x> em PHP5 Fragmento SQL correspondente
$this->Product->findAllByOrderStatus(‘3’); Product.order_status = 3
$this->Recipe->findAllByType(‘Cookie’); Recipe.type = ‘Cookie’
$this->User->findAllByLastName(‘Anderson’); User.last_name = ‘Anderson’
$this->Cake->findById(7); Cake.id = 7
$this->User->findByUserName(‘psychic’); User.user_name = ‘psychic’

Usuários de PHP4 devem usar essa função com uma pequena diferença:

Exemplos de findAllBy<x> em PHP4 Fragmento SQL correspondente
$this->Product->findAllByOrder_status(‘3’); Product.order_status = 3
$this->Recipe->findAllByType(‘Cookie’); Recipe.type = ‘Cookie’
$this->User->findAllByLast_name(‘Anderson’); User.last_name = ‘Anderson’
$this->Cake->findById(7); Cake.id = 7
$this->User->findByUser_name(‘psychic’); User.user_name = ‘psychic’

O resultado retornado é um array formatado como descrito em find() e findAll().

findNeighbours(string $conditions, array $field, string $value)

Retorna um array contendo os models vizinhos (com apenas os campos especificados), especificados por $field e $value, filtrando pelas condições de SQL em $conditions.

Isto é um atalho para criar links de ‘Anterior’ e ‘Próximo’ que os usuários percorrem em alguma seqüência através das entradas do seu model. Apenas funciona para campos baseados em números ou datas.

class ImagesController extends AppController {
    function view($id) {
        // Diz o que é necessário para mostrar a imagem...
        $this->set('image', $this->Image->findById($id);

        // Mas também fala os links para a imagem anterior e próxima...
        $this->set(
            'vizinhos',
            $this->Image->findNeighbours(null, 'id', $id);
        )
    }
}

Isso nos retorna um array completo com $image[‘Image’], mas também com $vizinhos[‘prev’][‘Image’][‘id’] e $vizinhos[‘next’][‘Image’][‘id’] para ser usada na view.

field(string $name, string $conditions, string $order)

Retorna o valor de um simples campo, especificado em $name, para o primeiro registro filtrado por $conditions e ordenado por $order.

findCount(string $conditions, int $recursive)

Retorna o número de registros que seguem a $conditions. Use o parâmetro $recursive para que o CakePHP busque mais (ou menos) níveis dos models associados.

generateList(string $conditions, string $order, int $limit, string $keyPath, string $valuePath)

Esta função é um atalho para pegar a lista de chaves e seus valores – principalmente utilizada para criar select de HTML com a lista dos seus models. Use os parâmetros $conditions, $order e $limit, assim como você usa nas requisições com findAll().

Se $primaryKey e $displayField estiverem configuradas no seu model, você não precisa configurar os dois últimos parâmetros, pois eles agem como $keyPath e $valuePath, respectivamente. Além disso, se você não definir $keyPath e $valuePath, CakePHP tentará carregar as informações de ‘title’ ou ‘name’.

Os parâmetros $keyPath e $valuePath especifica qual será o campo para as chaves e qual o campo dos valores para gerar a lista. Por exemplo, se você deseja gerar a lista de roles baseada no model Role, chaveado com suas id’s inteiras, uma chamada completa pode ser:

$this->Role->generateList(
    null,
    'role_name ASC',
    null,
    '{n}.Role.id',
    '{n}.Role.role_name'
);

// Isto vai retornar algo como:
array(
    '1' => 'Head Honcho',
    '2' => 'Marketing',
    '3' => 'Department Head',
    '4' => 'Grunt'
);

Muitos estão perplexos pela sintaxe ‘{n}’ usada em generateList(). Fique frio, isto serve de suporte para alterar os models do DataSource, explicado mais adiante neste capítulo.

query(string $query), execute(string $query)

Chamadas personalizadas de SQLs podem ser feitas usando os métodos query() ou execute(). A diferença entre os dois é que query() é usada para fazer queries SQL personalizadas (o resultado será retornado) e execute() é usado para fazer comandos SQL personalizados (não retornará valor).

Se você está usando queries SQL na sua aplicação, lembre-se de usar a biblioteca de Limpeza de dados (descrita mais além deste manual), que ajuda na limpeza dos dados fornecidos pelo usuário, como injection code e ataques de scripts cross-site.

find

find($tipo, $parâmetros)

Find (significa “encontrar” em português) é a função 10-em-1 de todas as funções que retornam dados de um model. $tipo pode ser tanto 'all', 'first', 'count', 'list', 'neighbors' or 'threaded'. O tipo de find padrão é 'first'.

$parâmetros são usados para passar todos os parâmetros para vários tipos de buscas, e têm os seguintes chaves por padrão - o qual todos são opcionais:

array(
    'conditions' => array('Model.campo' => $algumValor), //array com condições
    'recursive' => 1, //int, recursividade
    'fields' => array('Model.campo1', 'DISTINCT Model.campo2'), //array com os nomes dos campos
    'order' => array('Model.criado', 'Model.campo3 DESC'), //string ou array definindo a ordem
    'group' => array('Model.campo'), //campos do GROUP BY
    'limit' => n, //int, limite de resultados do SQL
    'page' => n, //int, página atual para buscas por paginação
    'callbacks' => true //outros possíveis valores são false, 'before', 'after'
)

Também é possível adicionar e usar outros parâmetros, assim como ser feito uso de alguns tipos, behaviors e é claro criar seus próprios métodos nos models.

Mais informação sobre callbacks em models está disponível aqui

find(‘first’)

find('first', $parâmetros)

‘first’ é o tipo de find padrão, e retornará um resultado. Você usaria este tipo para qualquer circunstância aonde você espera somente um resultado. A seguir estão alguns exemplos simples (código de um controller):

// Article significa Artigo em inglês

function alguma_function() {
   ...
   $this->Article->order = null; // resetando se tiver algum valor
   $semiRandomArticle = $this->Article->find();
   $this->Article->order = 'Article.created DESC'; // simulando que o model tenha uma ordem padrão
   $ultimoCriado = $this->Article->find();
   $tambemUltimoCriado = $this->Article->find('first', array('order' => array('Article.created DESC')));
   $especificamenteEste = $this->Article->find('first', array('conditions' => array('Article.id' => 1)));
   ...
}

No primeiro exemplo, nenhum parâmetro foi passado para o find - tampouco condições ou ordenação foi usado. O formato retornado quando chamamos find('first') é da seguinte forma:

Array
(
    [NomeDoModel] => Array
        (
            [id] => 83
            [campo1] => valor1
            [campo2] => valor2
            [campo3] => valor3
        )

    [NomeDoModelAssociado] => Array
        (
            [id] => 1
            [campo1] => valor1
            [campo2] => valor2
            [campo3] => valor3
        )
)

Não há parâmetro adicionais usados por find('first').

find(‘count’)

find('count', $params)

find('count', $params) retorna um valor inteiro. Abaixo dois exemplo (código do controlador) simples:

function some_function() {
   ...
   $total = $this->Article->find('count');
   $pending = $this->Article->find('count', array('conditions' => array('Article.status' => 'pending')));
   $authors = $this->Article->User->find('count');
   $publishedAuthors = $this->Article->find('count', array(
      'fields' => 'DISTINCT Article.user_id',
      'conditions' => array('Article.status !=' => 'pending')
   ));
   ...
}

Não passe fields como um vetor para find('count'). Você somente precisará especificar campos para um DISTINCT count (caso contrário, o count é sempre o mesmo - ditado pelas condições).

Não existem parâmetro adicionais usados pelo find('count').

find(‘all’)

find('all', $params)

find('all') retorna um vetor (potencialmente múltiplo) de resultados. Este é de fato o mecanismo usado por todos as variantes do find(), como o paginate. Abaixo dois exemplos simples (código do controlador):

function some_function() {
   ...
   $allArticles = $this->Article->find('all');
   $pending = $this->Article->find('all', array('conditions' => array('Article.status' => 'pending')));
   $allAuthors = $this->Article->User->find('all');
   $allPublishedAuthors = $this->Article->User->find('all', array('conditions' => array('Article.status !=' => 'pending')));
   ...
}

No exemplo acima $allAuthors vai conter todo usuário da tabela usuários, não serão aplicadas condições se nenhuma foi passada.

O resultado de uma chamada de find('all') vai ser da seguinte forma:

Array
(
    [0] => Array
        (
            [NomeModelo] => Array
                (
                    [id] => 83
                    [campo1] => valor1
                    [campo2] => valor2
                    [campo3] => valor3
                )

            [NomeModeloAssociado] => Array
                (
                    [id] => 1
                    [campo1] => valor1
                    [campo2] => valor2
                    [campo3] => valor3
                )

        )
)

Não existem parâmetros adicionais usados por find('all').

find(‘list’)

find('list', $params)

find('list', $params) retorna um array indexado, útil em qualquer situação em que você precise de uma lista, como por exemplo para valores de listas em elementos select. Abaixo estão alguns exemplos simples (código de controller):

function some_function() {
   ...
   $allArticles = $this->Article->find('list');
   $pending = $this->Article->find('list', array('conditions' => array('Article.status' => 'pending')));
   $allAuthors = $this->Article->User->find('list');
   $allPublishedAuthors = $this->Article->User->find('list', array('conditions' => array('Article.status !=' => 'pending')));
   ...
}

No exemplo acima, $allAuthors irá conter cada usuário na tabela users e nenhuma condição será aplicada ao conjunto dos resultados, já que nenhuma foi passada.

Os resultados da chamada a find('list') estarão no seguinte formato:

Array
(
    //[id] => 'valor de exibição',
    [1] => 'displayValue1',
    [2] => 'displayValue2',
    [4] => 'displayValue4',
    [5] => 'displayValue5',
    [6] => 'displayValue6',
    [3] => 'displayValue3',
)

Ao chamar find('list') os campos passados em fields serão usados para determinar o que deveria ser usado como as chaves, os valores e os valores do array, e opcionalmente como agrupar os resultados. O padrão é que a chave primária do model seja usada como chave e que o campo displayField seja usado como valor. Seguem alguns exemplos para ilustrar:

function some_function() {
   ...
   $justusernames = $this->Article->User->find('list', array('fields' => array('User.username')));
   $usernameMap = $this->Article->User->find('list', array('fields' => array('User.username', 'User.first_name')));
   $usernameGroups = $this->Article->User->find('list', array('fields' => array('User.username', 'User.first_name', 'User.group')));
   ...
}

Com o código do exemplo acima, as variáveis resultantes devem ficar parecidas com:

$justusernames = Array
(
    //[id] => 'username',
    [213] => 'AD7six',
    [25] => '_psychic_',
    [1] => 'PHPNut',
    [2] => 'gwoo',
    [400] => 'jperras',
)

$usernameMap = Array
(
    //[username] => 'firstname',
    ['AD7six'] => 'Andy',
    ['_psychic_'] => 'John',
    ['PHPNut'] => 'Larry',
    ['gwoo'] => 'Gwoo',
    ['jperras'] => 'Joël',
)

$usernameGroups = Array
(
    ['Uber'] => Array
        (
        ['PHPNut'] => 'Larry',
        ['gwoo'] => 'Gwoo',
        )

    ['Admin'] => Array
        (
        ['_psychic_'] => 'John',
        ['AD7six'] => 'Andy',
        ['jperras'] => 'Joël',
        )

)

find(‘threaded’)

find('threaded', $params)

find('threaded', $params) retorna um array de dados aninhados, apropriados se você quiser usar o campo parent_id dos dados de seu model para construir resultados aninhados. Abaixo estão alguns exemplos (código de controller):

function some_function() {
   ...
   $allCategories = $this->Category->find('threaded');
   $aCategory = $this->Category->find('first', array('conditions' => array('parent_id' => 42)); // desconsidera a raiz
   $someCategories = $this->Category->find('threaded', array(
    'conditions' => array(
        'Article.lft >=' => $aCategory['Category']['lft'],
        'Article.rght <=' => $aCategory['Category']['rght']
    )
   ));
   ...
}

Não é necessário utilizar o Tree behavior para fazer uso deste método - mas é possível obter todos os resultados desejados em uma única consulta.

No código de exemplo acima, $allCategories conterá um array aninhado representando toda uma estrutura de categorias. O segundo exemplo utiliza a estrutura de dados usada pelo Tree behavior e retorna um resultado parcial para $aCategory e tudo o que esteja abaixo dela. Os resultados de uma chamada a find('threaded') estarão no seguinte formato:

Array
(
    [0] => Array
        (
            [ModelName] => Array
                (
                    [id] => 83
                    [parent_id] => null
                    [field1] => value1
                    [field2] => value2
                    [field3] => value3
                )

            [AssociatedModelName] => Array
                (
                    [id] => 1
                    [field1] => value1
                    [field2] => value2
                    [field3] => value3
                )
            [children] => Array
                (
            [0] => Array
            (
                [ModelName] => Array
                (
                    [id] => 42
                            [parent_id] => 83
                    [field1] => value1
                    [field2] => value2
                    [field3] => value3
                )

                [AssociatedModelName] => Array
                (
                    [id] => 2
                    [field1] => value1
                    [field2] => value2
                    [field3] => value3
                )
                    [children] => Array
                (
                )
                    )
            ...
                )
        )
)

A ordem em que os resultados aparecem pode ser modificada uma vez que depende da ordem de processamento. Por exemplo, se 'order' => 'name ASC' for passado como parâmetro para find('threaded'), os resultados irão aparecer ordenados pelo campo name. Qualquer campo pode ser usado para ordenação, e não há qualquer requisito predefinido para que o resultado de mais alto nível seja retornado primeiro.

Não existem parâmetros a serem usados pelo find('threaded').

find(‘neighbors’)

find('neighbors', $params)

‘neighbors’ (vizinhos em ingles) vai executar um find similar ao ‘first’, mas vai retornar a linha anterior e posterior a que você requisitou. Abaixo um exemplo simples (código do controlador):

function some_function() {
   $neighbors = $this->Article->find('neighbors', array('field' => 'id', 'value' => 3));
}

Você pode ver neste exemplo os dois elementos requiridos no vetor $params: field e value. Outros elementos ainda são permitidos como em outros find’s (Ex: se o seu modelo funciona como limitador você pode especificar ‘contain’ em $params). O resultado de uma chamada de find('neighbors') vai ser da seguinte forma:

Array
(
    [prev] => Array
        (
            [ModeloNome] => Array
                (
                    [id] => 2
                    [campo1] => valor1
                    [campo2] => valor2
                    ...
                )
            [ModeloNomeAssociado] => Array
                (
                    [id] => 151
                    [campo1] => valor1
                    [campo2] => valor2
                    ...
                )
        )
    [next] => Array
        (
            [ModeloNome] => Array
                (
                    [id] => 4
                    [campo1] => valor1
                    [campo2] => valor2
                    ...
                )
            [ModeloNomeAssociado] => Array
                (
                    [id] => 122
                    [campo1] => valor1
                    [campo2] => valor2
                    ...
                )
        )
)

Note como o resultado sempre contém dois elementos: prev e next.

findAllBy

findAllBy<fieldName>(string $value)

Estes métodos mágicos podem ser usados como atalho para fazer buscas em suas tabelas por um determinado campo. Apenas adicione o nome do campo (no formato CamelCase) ao final destes métodos, e informe o critério para este campo como primeiro parâmetro.

Exemplo de findAllBy<x> em PHP5 Trecho SQL Correspondente
$this->Product->findAllByOrderStatus(‘3’); Product.order_status = 3
$this->Recipe->findAllByType(‘Cookie’); Recipe.type = ‘Cookie’
$this->User->findAllByLastName(‘Anderson’); User.last_name = ‘Anderson’
$this->Cake->findById(7); Cake.id = 7
$this->User->findByUserName(‘psychic’); User.user_name = ‘psychic’

Usuários de PHP4 precisam utilizar esta função de um jeito um pouco diferente devido alguma indiferenciação de maiúsculas e minúsculas no PHP4:

Exemplo de findAllBy<x> em PHP4 Trecho SQL Correspondente
$this->Product->findAllByOrder_status(‘3’); Product.order_status = 3
$this->Recipe->findAllByType(‘Cookie’); Recipe.type = ‘Cookie’
$this->User->findAllByLast_name(‘Anderson’); User.last_name = ‘Anderson’
$this->Cake->findById(7); Cake.id = 7
$this->User->findByUser_name(‘psychic’); User.user_name = ‘psychic’

Os métodos findBy() funcionam como find(‘first’,...), enquanto que os métodos findAllBy() funcionam como find(‘all’,...).

Em qualquer caso, o resultado é um array formatado tal como se tivesse sido retornado pelos métodos find() ou findAll(), respectivamente.

findBy

findBy<fieldName>(string $value)

Estes métodos mágicos podem ser usados como um atalho para buscar suas tabeças por um dado campo. Apenas adicione o nome do campo (no formato CamelCase) ao final destes métodos e informe o critério de busca para esse campo como primeiro parâmetros.

Exemplo de findBy<x> em PHP5 Trecho SQL Correspondente
$this->Product->findAllByOrderStatus(‘3’); Product.order_status = 3
$this->Recipe->findAllByType(‘Cookie’); Recipe.type = ‘Cookie’
$this->User->findAllByLastName(‘Anderson’); User.last_name = ‘Anderson’
$this->Cake->findById(7); Cake.id = 7
$this->User->findByUserName(‘psychic’); User.user_name = ‘psychic’

Usuários de PHP4 precisam usar esta função de um jeito um pouco diferente devido a alguma indiferenciação de maiúsculas/minúsculas no PHP4:

Exemplo de findBy<x> em PHP4 Trecho SQL Correspondente
$this->Product->findAllByOrder_status(‘3’); Product.order_status = 3
$this->Recipe->findAllByType(‘Cookie’); Recipe.type = ‘Cookie’
$this->User->findAllByLast_name(‘Anderson’); User.last_name = ‘Anderson’
$this->Cake->findById(7); Cake.id = 7
$this->User->findByUser_name(‘psychic’); User.user_name = ‘psychic’

Métodos findBy() funcionam como find(‘first’,...), enquanto que os métodos findAllBy() funcionam como find(‘all’,...).

De qualquer maneira, o resultado é um array formatado tal como se fosse retornado por find() ou findAll(), respectivamente.

query

query(string $query)

Comandos SQL que você não possa ou não queira fazer por meio dos outros métodos de model (atenção - serão poucas as circunstâncias que isso pode acontecer) podem ser chamados usando-se o método query() do model.

Se você já estiver usando este método em sua aplicação, certifique-se de olhar antes a biblioteca Sanitize, que lhe ajuda a fazer uma limpeza nos dados informados pelo usuário e livrá-los de ataques de injeção de SQL ou cross-site script.

O método query() não segue o $Model->cachequeries já que sua funcionalidade é inerentemente disconexa daquela do model que o chama. Para evitar de fazer cache das chamadas na consulta, passe false para o segundo argumento, i.e.: query($query, $cachequeries = false)

query() usa o nome da tabela na consulta como chave do array para os dados retornados ao invés do nome do nome do model. Por exemplo,

$this->Picture->query("SELECT * FROM pictures LIMIT 2;");

deve retornar

Array
(
    [0] => Array
        (
            [pictures] => Array
                (
                    [id] => 1304
                    [user_id] => 759
                )
        )

    [1] => Array
        (
            [pictures] => Array
                (
                    [id] => 1305
                    [user_id] => 759
                )
        )
)

Para fazer com que o nome do model seja chave do array, e obter resultados consistentes com aqueles retornados pelos métodos find, a consulta deve ser reescrita:

$this->Picture->query("SELECT * FROM pictures AS Picture LIMIT 2;");

que retorna

Array
(
    [0] => Array
        (
            [Picture] => Array
                (
                    [id] => 1304
                    [user_id] => 759
                )
        )

    [1] => Array
        (
            [Picture] => Array
                (
                    [id] => 1305
                    [user_id] => 759
                )
        )
)

Esta sintaxe e a correspondente estrutura em array é válida para o MySQL apenas. O Cake não dispõe de qualquer abstração de dados quando manipulando consultas diretamente, de forma que os resultados podem diferir entre sistemas de bancos de dados diferentes.

field

field(string $name, array $conditions = null, string $order = null)

Retorna o valor de um campo simples, especificado como $name, do primeiro registro correspondendo as $conditions e ordenado por $order. Se não são passadas condições e a identificação do modelo está definida, vai retornar o valor do campo para o modelo corrente. Se não encontrar registros correspondentes retorna false.

$model->id = 22;
echo $model->field('name'); // mostra o nome para a linha com identificação 22

echo $model->field('name', array('created <' => date('Y-m-d H:i:s')), 'created DESC'); // mostra o nome da última instância criada

read()

read($fields, $id)

read() é um método usado para definir os dados do modelo atual (Model::$data)–como durante as edições–mas também pode ser usado em outras circunstâncias para recuperar um simples registro da base de dados.

$fields é usado para passar um único nome de campo, como uma string, ou um array de nomes de campos; se estiver vazio, todos os campos serão buscados.

$id especifica o ID do registro para ser lido. Por padrão, o registro selecionado atualmente, como especificado por Model::$id, é usado. Passando um valor diferente para $id irá fazer que o registro seja selecionado.

function beforeDelete($cascade) {
   ...
   $rating = $this->read('rating'); // recebe a classificação do registro que está sendo excluído
   $name = $this->read('name', $id2); // recebe o nome do segundo registro.
   $rating = $this->read('rating'); // recebe a classificação do segundo registro.
   $this->id = $id3; //
   $this->Article->read(); // le o terceiro registro
   $record = $this->data // armazena o terceiro registro em $record
   ...
}

Analise que a terceira chamada para read() busca a classificação do mesmo registro antes de ler. Isso porque read() muda Model::$id para qualquer valor passado como $id. Linhas 6-8 demonstram como read() muda o dado do modelo atual.

Condições de Busca Complexa

Muitas das buscas de model envolvem passar definições de condições de uma maneira ou de outra. A simples proposta disto é usar um snippet (cola) da condição WHERE do SQL. Se você precisar de mais controle, você pode usar arrays.

Usar arrays é claro e facil de se ler, e também faz deixa muito fácil contruir queries (consultas). Esta sintaxe também quebra os elementos de sua query (campos, valores, operadores, ect.) em parte discretas e manipuláveis. Isto permito o CakePHP gerar a query mais eficiente possível, mantendo a sintaxe SQL adequada e adequadamente escapa cada parte individual da query.

Basicamente, uma query baseada em array se parece com isto:

$conditions = array("Post.title" => "This is a post");
//Exemplo usado com o model:
$this->Post->find($conditions);

A estrutura aqui é auto-explicativa: ela encontrará qualquer post que o título seja igual a “This is a post”. Note que poderiamos ter usado apenas “title” como o nome do campo, mas ao construir queries, é uma boa prática sempre especifica o nome do model, melhorando assim a clareza do código, e ajuda a prevenir colisões no futuro, você de deveria escolher mudar seu esquema.

Que tal outros tipos de combinações? Estas são igualmente simples. Vamos dizer que queremos encontrar todos os posts que o título não é “This is a post”:

array("Post.title <>" => "This is a post")

Veja o ‘<>’ que segue o nome do campo. O CakePHP consegue analizar qualquer operador de comparação SQL, incluindo expressões de combinação usando LIKE, BETWEEN ou REGEX, enquanto você deixar um espaço entre o nome do campo e o operador. A única exceção aqui é IN (...)-combinação de estilo. Vamos dizer que você quer encontrar um post onde o título estava em uma lista passada de valores:

array(
    "Post.title" => array("First post", "Second post", "Third post")
)

Para fazer uma combinação NOT IN(...) para encontrar posts que o título não está na lista de valores passada:

array(
    "NOT" => array( "Post.title" => array("First post", "Second post", "Third post") )
)

Adicionar outros filtros à condição é tão simples quanto adicionar outras chaves/valores no array:

array (
    "Post.title" => array("First post", "Second post", "Third post"),
    "Post.created >" => date('Y-m-d', strtotime("-2 weeks"))
)

Você pode também criar buscas que comparam dois campos no banco de dados

array("Post.created = Post.modified"

O exemplo acima retornará os posts que a data de criação é igual a data de modificação (i.e. retornará os posts que nunca foram modificados).

Se lembre que se você não conseguir formar uma condição WHERE neste método (ex. operadores booleanos), você pode sempre a especificar como uma string:

array(
    'Model.field & 8 = 1',
    //outra condição como o normal
)

Por padrão o CakePHP junta múltiplas condições com booleano AND; o que significa, o snippet acima apenas deverá combinar posts que foram criados nas duas semanas passadas, e tenha um título que combine com um listado. De qualquer maneira, poderiamos apenas simplesmente encontrar posts que combinem com cada condição:

array( "or" => array (
    "Post.title" => array("First post", "Second post", "Third post"),
    "Post.created >" => date('Y-m-d', strtotime("-2 weeks"))
    )
)

O CakePHP aceita todos os operados booleanos válidos do SQL, incluindo AND, OR, NOT, XOR, etc., e eles podem estar em letras maiúsculas ou minúsculas, como preferir. Estas condições também são infinitamente aninhadas. Vamos dizer que você tem um relacionamento belongsTo (pertence a) entre Posts e Autores. Vamos dizer que você quer encontrar todos os posts que contem uma certa palavra chave (“magic”) ou que foram criados nas duas útlimas semanas, mas você quer restringir sua busca aos posts criados por Bob:

array (
    "Author.name" => "Bob",
    "or" => array (
        "Post.title LIKE" => "%magic%",
        "Post.created >" => date('Y-m-d', strtotime("-2 weeks"))
    )
)

O CakePHP também pode procurar por campos null (nulos). Neste exemplo, a query retornará registros que o título do post não é nulo:

array ("not" => array (
        "Post.title" => null
    )
)

Para manipular queries BETWEEN (entre), você pode usar o seguinte:

array('Post.id BETWEEN ? AND ?' => array(1,10))

Nota: O CakePHP citará os valores numéricos dependendo do tipo de campo do seu banco de dados.

E sobre GROUP BY (agrupar por)?:

array('fields'=>array('Product.type','MIN(Product.price) as price'), 'group' => 'Product.type');

Um exemplo rápido de fazer uma query DISTINCT. Você pode usar outros operadores, como MIN(), MAX(), etc., de um jeito similar

array('fields'=>array('DISTINCT (User.name) AS my_column_name'), 'order'=>array('User.id DESC'));

Você pode criar condições muito complexas, aninhando arrays de múltiplas condições:

array(
   'OR' => array(
      array('Company.name' => 'Future Holdings'),
      array('Company.name' => 'Steel Mega Works')
   ),
   'AND' => array(
      array(
         'OR'=>array(
            array('Company.status' => 'active'),
            'NOT'=>array(
               array('Company.status'=> array('inactive', 'suspended'))
            )
         )
     )
   )
);

O qual produz a seguinte SQL:

SELECT `Company`.`id`, `Company`.`name`,
`Company`.`description`, `Company`.`location`,
`Company`.`created`, `Company`.`status`, `Company`.`size`

FROM
   `companies` AS `Company`
WHERE
   ((`Company`.`name` = 'Future Holdings')
   OR
   (`Company`.`name` = 'Steel Mega Works'))
AND
   ((`Company`.`status` = 'active')
   OR (NOT (`Company`.`status` IN ('inactive', 'suspended'))))

Salvando Seus Dados

O CakePHP faz com da tarefa de salvar registros uma moleza. Os dados que já estejam prontos para ser salvos podem ser passados para o método save() do model usando o seguinte formato básico:

Array
(
    [ModelName] => Array
        (
            [fieldname1] => 'value'
            [fieldname2] => 'value'
        )
)

Na maioria das vezes você sequer precisa se preocupar com este formato: os helper HtmlHelper, FormHelper e os métodos find do CakePHP todos trabalham com dados neste formato. Se você estiver usando esses helpers, os dados já estarão convenientemente disponíveis em $this->data para pronta utilização.

Aqui está um breve exemplo de uma action de um controller que utiliza um model do CakePHP para salvar dados para uma tabela de uma base de dados:

function edit($id) {
    // algum dado foi POSTado pela view?
    if(!empty($this->data)) {
        // se os dados do formulário puderam ser validados e salvos...
        if($this->Recipe->save($this->data)) {
            // define uma mensagem de flash na sessão e redireciona.
            $this->Session->setFlash("Recipe Saved!");
            $this->redirect('/recipes');
        }
    }

    // se não há nenhum dado vindo do formulário, encontre a receita a ser editada
    // e passe-a para a view.
    $this->set('recipe', $this->Recipe->findById($id));
}

Um detalhe adicional: quando o método save é chamado, os dados passados para ele como primeiro parâmetro são validados a partir do mecanismo de validação do CakePHP (veja o capítulo sobre Validação de Dados para mais informações). Se, por alguma razão, seus dados não tiverem sido salvos, certifique-se de conferir se alguma regra de validação não tenha sido quebrada.

Há poucos outros métodos relacionados a salvamento de dados que você poderá achar úteis:

set($one, $two = null)

Model::set() pode ser usado para definir um ou mais campos de dadps do array de dados para um model. Isto pode ser útil ao usar os recursos de ActiveRecord disponibilizados pelo Model.

$this->Post->read(null, 1);
$this->Post->set('title', 'New title for the article');
$this->Post->save();

É um exemplo de como você pode usar set() para atualizar e salvar um único campo na abordagem ActiveRecord. Você também pode usar o método set() para atribuir novos valores para múltiplos campos.

$this->Post->read(null, 1);
$this->Post->set(array(
    'title' => 'New title',
    'published' => false
));
$this->Post->save();

O trecho acima deve atualizar os campos title e published, e salvá-los para a base de dados.

save(array $data = null, boolean $validate = true, array $fieldList = array())

Como mencionado acima, este método faz o salvamento a partir de dados num formato em array. O segundo parâmetro permite desconsiderar a etapa de validação e o terceiro parâmetro permite que você informa uma lista de campos a serem salvos no model. Para aumentar a segurança, você pode limitar os campos salvos à apenas aqueles listados em $fieldList.

Se o parâmetro $fieldList não for informado, um usuário malicioso pode incluir campos adicionais nos dados de formulário, e assim modificar campos que não se pretendiam modificar.

O método save também possui uma sintaxe alternativa:

save(array $data = null, array $params = array())

O array $params pode ter qualquer uma das seguintes opções como chaves:

array(
    'validate' => true,
    'fieldList' => array(),
    'callbacks' => true // outros valores possíveis são false, 'before', 'after'
)

Mais informações sobre callbacks de model estão aqui

Quando o método save tenha terminado, o ID do objeto pode ser obtido no atributo $id do objeto model - algo especialmente útil ao se criar novos objetos.

$this->Ingredient->save($newData);

$newIngredientId = $this->Ingredient->id;

A criação ou atualização é controlada pelo campo id do model. Se o $Model->id já estiver definido, o registro com esta chave primária será atualizado. Caso contrário, um novo registro será criado.

// Criação: id não está definido ou é null
$this->Recipe->create();
$this->Recipe->save($this->data);

// Atualização: id está definido para um valor numérico
$this->Recipe->id = 2;
$this->Recipe->save($this->data);

Ao chamar o método save em um laço, não se esqueça de chamar o método create().

create(array $data = array())

Este método reinicializa o estado do model para salvar novos dados.

Se o parâmetro $data (usando o formato de array apresentado acima) for passado, a instância do model estará pronta para fazer o salvamento daqueles dados (acessível via $this->data).

Se o valor false for passado ao invés de um array, a instância do model não vai inicializar os campos que já não estavam definidos, a partir do esquema do model, e ainda vai deixar os demais campos indefinidos. Utilize isto para previnir que campos sejam atualizados na base de dados que já estavam definidos e que se pretendia atualizar.

saveField(string $fieldName, string $fieldValue, $validate = false)

Utilizado para salvar um único campo. Defina o ID do model ($this->ModelName->id = $id) logo antes de chamar o método saveField(). Ao utilizar este método, $fieldName deveria conter apenas o nome do campo, e não o nome do model e do campo juntos.

Por exemplo, para atualizar o título de um post do blog, a chamada a saveField a partir do controller deveria ser algo parecido com:

$this->Post->saveField('title', 'A New Title for a New Day');

updateAll(array $fields, array $conditions)

Atualiza muitos registros numa única chamada. Os registros a serem atualizados são identificados pelo array $conditions, e os campos a serem atualizados, juntamente com seus nomes, são identificados pelo array $fields array.

Por exemplo, para aprovar todos os cozinheiros que sejam membros há mais de um ano, a chamada ao método deveria ser algo parecido com:

$this_year = date('Y-m-d h:i:s', strtotime('-1 year'));

$this->Baker->updateAll(
    array('Baker.approved' => true),
    array('Baker.created <=' => "$this_year")
);

O array $fields aceita expressões SQL. Valores literais devem ser colocados entre aspas manualmente.

Por exemplo, para fechar todos os tíquetes que pertençam a um determinado cliente:

$this->Ticket->updateAll(
    array('Ticket.status' => "'closed'"),
    array('Ticket.customer_id' => 453)
);

saveAll(array $data = null, array $options = array())

Usado para salvar (a) múltiplos registros individuais para um único model ou (b) este registro, bem como os registros associados.

As seguintes opções podem ser utilizadas:

validate: defina para false para desabilitar a validação e true para validar cada registro antes de salvar, ‘first’ para validar *todos* os registros antes de salvar ou ‘only’ para apenas validar os registros sem salvá-los.

atomic: se definido para true (o valor padrão), o método tentará salvar todos os registros numa única transação. Deve ser definido para false se a base de dados/tabela não suportar transações. Se estiver com o valor false, vamos retornar um array parecido com o array $data informado, mas os valores serão definidos para true/false dependendo se cada registro foi ou não salvo com sucesso.

fieldList: equivalente ao parâmetro $fieldList em Model::save()

Para salvar múltiplos registros de um único model, $data precisa ser um array de registros indexado numericamente, semelhante a:

Array
(
    [Article] => Array(
            [0] => Array
                (
                            [title] => title 1
                        )
            [1] => Array
                (
                            [title] => title 2
                        )
                )
)

O comando para salvar o array $data acima deve ser parecido com isto:

$this->Article->saveAll($data['Article']);

Para salvar um registro juntamente com seus registros relacionados por meio de uma associação hasOne ou belongsTo, o array $data deve ser algo parecido com:

Array
(
    [User] => Array
        (
            [username] => billy
        )
    [Profile] => Array
        (
            [sex] => Male
        [occupation] => Programmer
        )
)

O comando para salvar o array $data acima deve ser parecido com isto:

$this->Article->saveAll($data);

Para salvar um registro juntamente com seus registros relacionados por meio de uma associação hasMany, o array $data deve ser algo parecido com:

Array
(
    [Article] => Array
        (
            [title] => My first article
        )
    [Comment] => Array
        (
            [0] => Array
                (
                    [comment] => Comment 1
            [user_id] => 1
                )
        [1] => Array
                (
                    [comment] => Comment 2
            [user_id] => 2
                )
        )
)

O comando para salvar o array $data acima deve ser parecido com isto:

$this->Article->saveAll($data);

O salvamento de dados relacionados com saveAll() só irá funcionar com models que estejam diretamente associados.

Salvando dados em tabelas relacionadas (hasOne, hasMany, belongsTo)

Ao trabalhar com models associados, é importante perceber que salvar dados em tabelas relacionadas sempre será feito pelo model do CakePHP correspondente. Se você estiver salvando um novo Post e seus Comentários associados, então vcoê deverá usar ambos model, Post e Comentario durante a operação de salvar.

Se ainda nenhum dos registros de models associados exisiter no sistema (por exemplo, você quer salvar os registros de um novo Usuario e seu Perfil relacionado ao mesmo tempo), você precisará salvar primeiro o model primário, ou pai.

Para ter uma idéia de como isto funciona, vamos imaginar que temos uma action em seu UsuariosController que manipula o salvamento de um novo Usuário e um Perfil relacionado. A action de exemplo mostrada a seguir assume que você enviou via POST dados suficientes (usando o FormHelper) para criar um único Usuário e um único Perfil.

<?php
function add() {
    if (!empty($this->data)) {
        // Nós podemos salvar os dados do usuário:
        // deve ser assim $this->data['Usuario']

        $usuario = $this->Usuario->save($this->data);

        // Se o usuário for salvo, agora nós adicionamos estes dados de informação
        // e salva o Perfil.

        if (!empty($usuario)) {
            // O ID do recem adicionado foi definido
            // como $this->Usuario->id.
            $this->data['Perfil']['usuario_id'] = $this->Usuario->id;

            // Porque nosso Usuário hasOne (tem um) Perfil, podemos acessar
            // o model Perfil através do model Usuário:
            $this->Usuario->Perfil->save($this->data);
        }
    }
}
?>

Como regra geral, ao trabalhar com associações hasOne (tem um), hasMany (tem muitos) e belongsTo (pertence a), é tudo sobre chaveamento. A idéia básica é pegar a chave de um model e colocá-la no compo de chave estrangeira no outro model. As vezes isso deve envolver usar o atributo $id da classe model depois de um save(), mas outras vezes deve apenas envolve coletar o ID de um campo oculto em um formulário que acabou de ser POSTado para uma action do controller.

Para completar a aproximação básica acima, CakePHP também oferece um método muito útil saveAll(), o que lhe permite validar e salvar múltiplos models de uma vez. Em adição, salveAll() fornece suporte operacional para assegurar a integridade dos dados no banco de dados (i.e. se um model falhar ao salvar, o outro model também não será salvo).

Para operações funcionarem corretamento no MySQL suas tabelsa devem usar InnoDB engine. Lembre que tabelas MyISAM não suportam operações.

Vamos ver como podemos usar saveAll() para salvar os models Empresa e Conta ao mesmo tempo.

Primeiro, você precisa construir seu formulário para ambos os models, Empresa e Conta (assumimos que Empresas hasMany (tem muitas) Conta).

echo $form->create('Empresa', array('action'=>'add'));
echo $form->input('Empresa.name', array('label'=>'Nome da Empresa'));
echo $form->input('Empresa.descrição');
echo $form->input('Company.local');

echo $form->input('Conta.0.name', array('label'=>'Nome da Conta'));
echo $form->input('Conta.0.usuario');
echo $form->input('Conta.0.email');

echo $form->end('Add');

Dê uma olhada na forma que nomeamos os campos do formulário para o model Conta. Se Empresa é nosso model principal o saveAll() esperará os dados do model relacionado (Conta) para chegar a um formato específico. E tendo Conta.0.nomeDoCampo é exatamente o que precisamos.

Os nomeamentos de campos acima são necessários para associações hasMany. Se a associação entre os models é hasOne, você deve usar a notação NomeDoModel.nomeDoCampo para o model associado.

Agora, em nosso empresas_controller podemos criar uma action add():

function add() {
   if(!empty($this->data)) {
      $this->Empresa->saveAll($this->data, array('validate'=>'first'));
   }
}

Isso é tudo que precisa. Agora suas models Empresa e Conta serão validados e salvados tudo ao mesmo tempo. Um coisa a se notar aqui é o uso do array('validate' => 'first'); esta opção assegura que ambos os models serão validadeos.

counterCache - Armazena seu count()

Esta função o ajuda a armazenar o count (contagem) de dados relacionados. Ao invés de contar os registros manualmente usando find('count'), o model traça qualquer adição/exlcusão relativa ao model associado $hasMany e aumenta/diminui um campo inteiro dedicado dentro da tabela do model pai.

O nome dos campos consiste no nomde do model no singular seguido por um sublinhado e a palavra “count”.

my_model_count

Vamos dizer que você tem um model chamado ImageComment e um model chamado Image, você deve adicionar um novo compo INT na tablea image_comments e nomea-lo image_count

Mais alguns exemplos:

Model Associated Model Example
User Image users.image_count
Image ImageComment image.image_comment_count
BlogEntry BlogEntryComment blog_entries.blog_entry_comment_count

Uma vez adicionado o campo contador está pronto para seguir. Ative o counter-chache em sua associação adicionando uma chave counterCache e defina o valor para true.

class Image extends AppModel {
    var $belongsTo = array(
        'ImageAlbum' => array('counterCache' => true)
    );
}

A partir de agora, toda vez que você adicionar ou remover uma Image associada ao ImageAlbum, o número dentro do image_count é ajustado automaticamente.

Você também pode especificar counterScope Ele permite a você especificar uma condição simples que diz ao model quando atualizar (ou quando não atualizar, dependendo de como você vê) o valor do counter.

Usando nosso exemplo do model Image, podemos especificar como a seguir:

class Image extends AppModel {
    var $belongsTo = array(
        'ImageAlbum' => array(
            'counterCache' => true,
            'counterScope' => array('active' => 1) // apenas conta se "Image" está com active = 1
    ));
}

Salvando dados em tabelas relacionadas (HABTM)

Salvar models que são associados por hasOne, blongsTo e hasMany é muito simples: você apenas popula seu campo de chave estrangeira com o ID do model associado. Uma vez isso feito, você apenas chama o método save() no model, e tudo se linka corretamento.

Com HABTM (has_and_belong_to_many), você precisa definir o ID do model associado em seu array de dados. Nós contruiremos um formulário que cria uma nova tag e a associa “na mosca”com alguma receita.

O formulário simples parece com algum como isto (assumimos que $recipe_id já está definido a algo):

<?php echo $form->create('Tag');?>
<?php echo $form->input(
    'Recipe.id',  // Recipe significa receita
     array('type'=>'hidden', 'value' => $recipe_id)); ?>
<?php echo $form->input('Tag.name'); ?>
<?php echo $form->end('Add Tag'); ?>

Neste exemplo, você pode ver o campo oculto Recipe.id o qual o valor é definido para o ID da receita que queremos linkar a tag.

Quando o método save() é invocado dentro do controller, ele automaticamente salva o os dados HABTM no banco de dados.

function add() {

    //Salva a associação
    if ($this->Tag->save($this->data)) {
        //faz algo se salvo com sucesso
    }
}

Como o código anterior, nossa nova Tag é criada e associada a Recipe (receita), do qual o ID foi definido em $this  ->data['Recipe']['id'].

Outras formas que podemos querer apresentar nossos dados associados pode incluir uma lista select drop down. Os dados podem ser puxados do model usando o método find('list') e enviado a uma váriavel da view do nome do model. Um campo com o mesmo nome será automaticamente puxado com estes dados dentro de uma <select>.

// no controller:
$this->set('tags', $this->Recipe->Tag->find('list'));

// na view:
$form->input('tags');

Um cenário mais provável com um relacionamento HABTM deve incluir <select> definido para aceitar múltiplas seleções. Por exemplo, uma Receita por ter múltiplas Tags atribuídas a ela. Neste caso, os dados são puxados do model da mesma maneira, mas o campo do formulário é declarado ligeiramente diferente. O nome da tag é definidos usando a convenção ModelName.

// no controller:
$this->set('tags', $this->Recipe->Tag->find('list'));

// na view:
$form->input('Tag');

Usando o código anterior, uma campo select dorp down múltiplo é criado, permitindo que múltiplas escolhas sejam automaticamente salvas às Receitas existentes sendo adicionada ou salva no banco de dados

O que fazer quando HABTM se torna complicado?

Por padrão ao salvar um relacionamento HasAndBelongsToMany, o CakePHP excluirá todas as linhas na tabela juntada antes de salvar novas. Por exemplo se você tem um Clube que tem 10 Crianças associadas. Você depois atualiza o Clube com 2 crianças. O Clube setá apenas 2 Crianças, não 12.

Também note que se você quer adicionar mais campos ao juntamento (quando ele foi criado ou meta informação)isto é possivelmente com tabelas juntadas HABTM, mas é importante entender que uma opção fácil.

HasAndBelongsToMany entre dois models é na verdade um atalho para três models associados entre ambos uma associação hasMany e uma belongsTo.

Considere este exemplo:

Child hasAndBelongsToMany Club

Outra forma de olhar para isto é adicionando um model Membership:

Child hasMany Membership
Membership belongsTo Child, Club
Club hasMany Membership.

Estes dois exemplos são exatamente o mesmo. Eles usam o mesmo valor e nomes de campos no banco de dados e o mesmo valor de models. A diferença importante é que o modelo juntado está nomeado diferentemente e seu comportamento é mais previsível.

Excluindo Dados

Estes métodos podem ser usados para remover dados.

delete

delete(int $id = null, boolean $cascade = true);

Exclui o registro identificado por $id. Por padrão, também deleta registros que dependem do registro especificado para exclusão.

Por exemplo, quando excluindo um registro de Usuário que está relacionado com vário registros de Receitas:

  • Se $cascade está definido como verdadeiro (true), os registros de Receitas também serão excluídos se no modelo o valor dependent está definido como verdadeiro (true).
  • Se $cascade está definido como falso(false), os registros de Receita serão mantidos depois Usuário ser excluído.

deleteAll

deleteAll(mixed $conditions, $cascade = true, $callbacks = false)

Parecido com delete() e remove(), exeto que deleteAll() deleta todos os registros que atendem as condições epecificadas. O parâmetro $conditions pode ser um fraqmento SQL ou um vetor.

Associações: Linkando Modelos Juntos

Um dos recursos mais poderosos do CakePHP é a habilidade de linkar mapeamentos relacionais pelo model. No CakePHP, os links entres os models são manipulados através de associações.

Definindo relações entre objetos em sua aplicação deve ser um processo natural. Por exemplo: em um banco de dados de receita, uma receita deve ter vários comentários, comentários tem um único autor, e autores devem ter várias receitas. Definindo o modo de trabalho destas relações lhe permite aessar seus dados de uma maneira intuitiva e poderosa.

A proposta desta seção é lhe mostrar como planejar para, definir e utilizare utilizar associações entre models no CakePHP

Enquanto dados podem vir de uma variedade de fontes, a forma mais comum de armazenamento em aplicações web é uma tabela relacional. A maioria do que esta seção cobre será nesse contexto.

Para informação sobre associações com Plugin models, veja Plugin Model

Introdução

Um dos mais poderosos recursos do CakePHP é o mapeamento relacional fornecido pelos modelos. No CakePHP, as ligações entre os modelos são manipuladas através de associações.

Definir as relações entre os diversos objetos em sua aplicação deve ser um processo natural. Por exemplo: em uma base de dados de receitas, uma receita pode ter muitas opiniões, opiniões têm um único autor, e os autores podem ter muitas receitas. Definir a forma como estas relações trabalham permite o acesso aos seus dados de uma forma intuitiva e poderosa.

O objetivo desta seção é para mostrar-lhe como planejar, definir, utilizar as associações entre os modelos no CakePHP. Porque a forma mais comum de armazenamento em aplicações web é uma base relacional, a maior parte daquilo que nós vamos cobrir neste manual será, em um banco de dados com um contexto relacionado.

Os quatro tipos de associação no CakePHP são: hasOne, hasMany, belongsTo, e hasAndBelongsToMany (HABTM).

Tipo de associação Exemplo
hasOne Um usuário tem um perfil.
hasMany Usuários em um sistema pode ter várias receitas.
belongsTo Uma receita pertence a um usuário.
hasAndBelongsToMany Receitas tem, e pertencem a muitas tags.

Associações são definidas através da criação de uma classe variável com o nome da associação que você está definindo. A classe variável às vezes pode ser tão simples como uma string, mas pode ser tão completa como um array multidimensional utilizado para definir associações específicas.

<?php

class User extends AppModel {
    var $name = 'User';
    var $hasOne = 'Profile';
    var $hasMany = array(
        'Recipe' => array(
            'className'  => 'Recipe',
            'conditions' => 'Recipe.approved = 1',
            'order'      => 'Recipe.created DESC'
        )
    );
}

?>

No exemplo acima, a primeira instância da palavra ‘Recipe’ é o que é chamado de ‘Alias’. Este é o identificador para o relacionamento e pode ser qualquer coisa que você escolher. Geralmente, você escolherá o mesmo nome da classe que ele referencia. De qualquer forma, o alias deve ser único dentre de um único model e em ambos os lados de um relacionamento belongsTo/hasMany ou um belongsTo/hasOne. Escolhendo nomes não-únicos para um model alias pode causar um comportamento inesperado.

O CakePHP automaticamente criará links entre os objetos dos models associados. Por exemplo, em seu model User você pode acessar o model Recipe como:

$this->User->Recipe->algumaFuncao();

Igualmente em seu controller você pode acessar um model associado simplismente seguindo suas associações de model e sem adicioná-lo no $users array:

$this->User->Recipe->algumaFuncao();

Lembre que associação são definidas como ‘mão única’. Se você definir User hasMany Recipe não há efeito no Model Recipe. Você precisa definir Recipe belongsTo User para poder acessar o model User de seu model Recipe.

hasOne

Vamos criar um modelo User que tenha a relação hasOne com o modelo Profile.

Primeiro, suas tabelas da base de dados devem ser introduzidas corretamente. Para uma relação hasOne trabalhar, uma tabela tem de incluir uma chave estrangeira que aponta para um registro na outra. Neste caso, A tabela profiles deverá conter um campo chamado user_id. O padrão básico é:

Relação Esquema
Apple hasOne Banana bananas.apple_id
User hasOne Profile profiles.user_id
Doctor hasOne Mentor mentors.doctor_id

Table: hasOne: os outros modelos contém a chave estrangeira.

O arquivo do modelo User será salvo em /app/models/user.php. Para definir a associação “User hasOne Profile”, adicione a propriedade $hasOne a classe do modelo. Lembre-se de ter um modelo Profile em /app/models/profile.php, ou a associação não irá funcionar.

<?php

class User extends AppModel {
    var $name = 'User';
    var $hasOne = 'Profile';
}
?>

Existem duas formas de descrever essa relação nos seus modelos. O método mais simples consiste em definir o atributo $hasOne para uma string contendo o nome da classe do modelo associado, como fizemos anteriormente.

Se você precisa de mais controle, você pode definir suas associações usando arrays. Por exemplo, você pode querer limitar a associação para incluir apenas certos registros.

<?php

class User extends AppModel {
    var $name = 'User';
    var $hasOne = array(
        'Profile' => array(
            'className'    => 'Profile',
            'conditions'   => array('Profile.published' => '1'),
            'dependent'    => true
        )
    );
}
?>

As possíveis chaves do array para associações hasOne são:

  • className: o nome da classe do modelo a ser associado ao modelo atual. Se você estiver definindo um relacionamento “User hasOne Profile”, o valor da chave className deve ser igual a ‘Profile’;
  • foreignKey: o nome da chave estrangeira encontrada no outro modelo. Isto é especialmente útil se você precisa definir múltiplos relacionamentos hasOne. O valor padrão para esta chave é sublinhado, nome do model atual no singular, seguido de ‘_id’. No exemplo acima ele deve ser padronizado para ‘user_id’.
  • conditions: Um fragmento SQL utilizado para filtrar registros relacionados do modelo. É uma boa prática a utilização dos nomes dos modelos nos fragmentos SQL: “Profile.approved = 1” é sempre melhor do que apenas “approved = 1”.
  • fields: A lista de campos da tabela a serem recuperados quando os dados do modelo associado são coletados. Retorna todos os campos por padrão.
  • order: Um fragmento SQL que define a ordem de sorteio para as o retorno das linhas associadas.
  • dependent: Quando dependent é true, e o método delete() do modelo é chamado com o parâmetro cascade(cascata) para true, os registros associados do modelo também são apagados. Neste caso, defini-lo true , a exclusão do User também vai apagar o seu Profile associado.

Uma vez que esta associação tenha sido definida, operações de busca sobre o modelo User também vai buscar um registro relacionado de Profile, se existir:

// Exemplo de resultados de uma chamada $this->User->find();

Array
(
    [User] => Array
        (
            [id] => 121
            [name] => Gwoo the Kungwoo
            [created] => 2007-05-01 10:31:01
        )
    [Profile] => Array
        (
            [id] => 12
            [user_id] => 121
            [skill] => Baking Cakes
            [created] => 2007-05-01 10:31:01
        )
)

belongsTo

Agora que temos o acesso aos dados do Profile pelo modelo User, vamos definir uma associação belongsTo no modelo Profile, a fim de obter acesso aos dados relacionados de User. A associação belongsTo é um complemento natural à associações hasOne e hasMany : ele nos permite ver os dados da outra direção.

Quando você estiver fazendo suas tabelas na base de dados, com um relacionamento belongsTo, seguir esta convenção:

Relação Esquema
Banana belongsTo Apple bananas.apple_id
Profile belongsTo User profiles.user_id
Mentor belongsTo Doctor mentors.doctor_id

Table: belongsTo: o model atual contém a chave estrangeira.

Se um model (tabela) contém ma chave estrangeira, ele pertenceA (belongsTo) outro model (tabela).

Podemos definir a associação belongsTo no nosso modelo Profile em /app/models/profile.php usando a sintaxe de string como segue:

<?php

class Profile extends AppModel {
    var $name = 'Profile';
    var $belongsTo = 'User';
}
?>

Nós também podemos definir um relacionamento mais específico, utilizando a sintaxe do array abaixo:

<?php

class Profile extends AppModel {
    var $name = 'Profile';
    var $belongsTo = array(
        'User' => array(
            'className'    => 'User',
            'foreignKey'    => 'user_id'
        )
    );
}
?>

As possíveis chaves do array para associações belongsTo são:

  • className: o nome da classe do modelo a ser associado ao modelo atual. Se você estiver definindo um relacionamento “Profile belongsTo User”, o valor da chave className deve ser igual a ‘User’;
  • foreignKey: O nome da chave estrangeira encontrada no modelo atual. Isto é especialmente útil se você precisa definir múltiplos relacionamentos belongsTo. O valor padrão para esta chave é o singular do nome do outro modelo, seguida de ‘_id’.
  • conditions: Um fragmento SQL utilizado para filtrar registros relacionados do modelo. É uma boa prática a utilização dos nomes dos modelos nos fragmentos SQL: “User.active = 1” é sempre melhor do que apenas “active = 1”.
  • fields: A lista de campos da tabela a serem recuperados quando os dados do modelo associado são coletados. Retorna todos os campos por padrão.
  • order:Um fragmento SQL que define a ordem de sorteio para as linhas associadas retornadas.
  • counterCache: Se definido como true o Modelo associado automaticamente aumentará ou dmininuirá o campo “[nome_do_model_no_singular]_count” na tabela estrangeira sempre que você fizer save() ou delete(). Se é uma string então é o nome do campo a utilizar. O valor no campo counter representa o número de linhas relacionadas.
  • counterScope: Array de condições opcional usado para atualizar o campo counter cache.

Uma vez que esta associação tenha sido definida, As operações de busca sobre o modelo Profile também vai buscar um registro ligado ao registro de User se existir:

// Exemplo de resultados de uma chamada $this->Profile->find().

Array
(
   [Profile] => Array
        (
            [id] => 12
            [user_id] => 121
            [skill] => Baking Cakes
            [created] => 2007-05-01 10:31:01
        )
    [User] => Array
        (
            [id] => 121
            [name] => Gwoo the Kungwoo
            [created] => 2007-05-01 10:31:01
        )
)

hasMany

Próximo passo: definir uma associação “User hasMany Comment”. A associação hasMany nos permitirá buscar comentários de um usuário quando faz uma operação de busca no modelo User.

Quando você estiver fazendo suas tabelas na base de dados, com um relacionamento hasMany, seguir esta convenção:

hasMany: o outro modelo cotém a chave estrangeira.

Relation

Schema

User hasMany Comment

Comment.user_id

Cake hasMany Virtue

Virtue.cake_id

Product hasMany Option

Option.product_id

Podemos definir a associação hasMany no nosso modelo User /app/models/user.php usando a sintaxe string como segue:

<?php

class User extends AppModel {
    var $name = 'User';
    var $hasMany = 'Comment';
}
?>

Nós também podemos definir uma relação mais específica utilizando sintaxe de array:

<?php

class User extends AppModel {
    var $name = 'User';
    var $hasMany = array(
        'Comment' => array(
            'className'     => 'Comment',
            'foreignKey'    => 'user_id',
            'conditions'    => array('Comment.status' => '1'),
            'order'    => 'Comment.created DESC',
            'limit'        => '5',
            'dependent'=> true
        )
    );
}
?>

As possíveis chaves do array para associações hasMany são:

  • className: o nome da classe do modelo a ser associado ao modelo atual. Se você estiver definindo um relacionamento “User hasMany Comment” , a chave className deve ser igual “Comment”.

  • foreignKey: o nome da chave estrangeira encontrada no outro modelo. Isto é especialmente útil se você precisa definir múltiplos relacionamentos hasMany. O valor padrão para esta chave é o nome do modelo atual no singular, seguida de “_id”.

  • conditions: Um fragmento SQL utilizado para filtrar registros no modelo relacionado. É uma boa prática a utilização nome do modelo nos fragmentos SQL: “Comment.status = 1” é sempre melhor do que apenas “status = 1”.

  • fields: A lista de campos a serem recuperados quando os dados do modelo associado são coletados. Retorna todos os campos por padrão.

  • order: Um fragmento SQL que define a classificação para a ordem para o retorno de linhas associadas.

  • limit: O número máximo de linhas associadas que você quer que retorne.

  • offset: O número de linhas associadas para saltar sobre (dadas as atuais condições e ordem), antes de ir buscar e associar.

  • dependent: Quando dependent for true, é possível a eliminação recursiva. Neste exemplo, Os registros de Comment serão apagados quando o seu associado registro de User for excluído.

  • exclusive: Quando exclusive está definido como true, a exclusão recursiva de modelo faz a exclusão com uma chamada deleteAll(), em vez de excluir cada entidade separadamente. Isto melhora a performance, mas pode não ser ideal para todas as circunstancias.

  • finderQuery: Uma completa consulta SQL CakePHP que pode-se usar para buscar registros associados. Isto deve ser utilizado em situações que exijam muito resultado personalizado. Se uma consulta que vocês está constuindo requer uma referência ao ID do modelo associado, use o marcador especial {$__cakeID__$} na consulta. Por exemplo, se seu modelo Apple hasMany Orange, a consulta deve se parecer com isto:

    SELECT Orange.* FROM oranges AS Orange WHERE Orange.apple_id = {$__cakeID__$};
    

Uma vez que esta associação tenha sido definida, As operações de busca sobre o modelo User também vai buscar registros relacionados em Comment se existirem:

// Exemplo de resultados de uma chamada $this->User->find();

Array
(
    [User] => Array
        (
            [id] => 121
            [name] => Gwoo the Kungwoo
            [created] => 2007-05-01 10:31:01
        )
    [Comment] => Array
        (
            [0] => Array
                (
                    [id] => 123
                    [user_id] => 121
                    [title] => On Gwoo the Kungwoo
                    [body] => The Kungwooness is not so Gwooish
                    [created] => 2006-05-01 10:31:01
                )
            [1] => Array
                (
                    [id] => 123
                    [user_id] => 121
                    [title] => More on Gwoo
                    [body] => But what of the ‘Nut?
                    [created] => 2006-05-01 10:41:01
                )
        )
)

Uma coisa para se lembrar é que você precisará de um nova associação “Comment belongsTo User”, a fim de obter os dados de ambos os sentidos. O que temos esboçado nesta seção lhe dá poderes para obter os dados dos comentários do usuário. Adicionando a associação Comment belongsTo User no modelo Comment lhe dá poderes para obter os dados do usuário a partir do modelo Comment - completa a ligação e permitindo que haja o fluxo de informação a partir de qualquer perspectiva de modelo.

hasAndBelongsToMany (HABTM)

Tudo bem. Neste ponto, você já pode chamar você mesmo de profissional em associações de modelos no CakePHP. Você já está bem versado(experiente) nas três associações que ocupam a maior parte das relações entre objetos.

Vamos resolver o último tipo de relacionamento: hasAndBelongsToMany, ou HABTM. Esta associação é usada quando você tem dois modelos que precisam se juntar, várias vezes, muitas vezes, de muitas formas diferentes.

A principal diferença entre hasMany e HABTM é que a ligação entre os modelos em HABTM não é exclusiva. Por exemplo, estamos prestes a juntar nosso modelo Recipe(Receita) com o modelo Tag usando HABTM. Anexando a tag “Italian” a minha receita “grandma’s Gnocci” não “esgota” o registro do Tag. Também posso marcar o meu churrasco Honey Glazed Spaghettio’s com a tag “Italian” se eu quiser.

Ligações entre objetos associados com hasMany são exclusivos. Se User hasMany Comment, um comentário é apenas ligado a um usuário específico. Deixou-se para ganhar.

Avancemos. É preciso criar uma tabela extra no banco de dados para manipular associações HABTM. Esta nova tabela deve juntar o nome das tabelas associadas, incluindo os nomes de ambos os modelos envolvidos, em ordem alfabética. O conteúdo da tabela deve ser de pelo menos dois campos, cada um com uma chave estrangeira (que devem ser inteiros) apontando para ambas as chaves primárias dos modelos envolvidos. Para evitar qualquer dúvida - não defina uma chave primária combianda para estes dois campos, se sua aplicação a requer você pode definir um único índice. Se você planeja adicionar qualquer informação extra a esta tabela, é uma boa idéia adicionar um campo de chave primária adicional (por convenção ‘id’) para fazer agir na tabela tão fácil quanto qualquer outro modelo.

HABTM requer uma tabela de junção separada que inclui ambos nomes de modelo.

Relação Esquema (tabela HABTM em negrito)
Recipe HABTM Tag recipes_tags.id, recipes_tags.recipe_id, recipes_tags.tag_id
Cake HABTM Fan cakes_fans.id, cakes_fans.cake_id, cakes_fans.fan_id
Foo HABTM Bar bars_foos.id, bars_foos.foo_id, bars_foos.bar_id

Nomes de tabela são por convenção em ordem alfabética.

Uma vez que esta nova tabela foi criada, podemos definir a associação HABTM nos arquivos dos modelos. Estamos indo saltar diretamente a sintaxe array desta vez:

<?php

class Recipe extends AppModel {
    var $name = 'Recipe';
    var $hasAndBelongsToMany = array(
        'Tag' =>
            array('className'            => 'Tag',
                'joinTable'              => 'recipes_tags',
                'foreignKey'             => 'recipe_id',
                'associationForeignKey'  => 'tag_id',
                'unique'                 => true,
                'conditions'             => '',
                'fields'                 => '',
                'order'                  => '',
                'limit'                  => '',
                'offset'                 => '',
                'finderQuery'            => '',
                'deleteQuery'            => '',
                'insertQuery'            => ''
            )
        );
}
?>

As possíveis chaves do array para associações hasAndBelongsToMany são:

  • className: o nome da classe do modelo a ser associado ao modelo atual. Se você estiver definindo um relacionamento “Recipe HABTM Tag” , a chave className deve ser igual “Tag”.
  • joinTable: O nome da tabela usada para ingressar nesta associação (se a atual tabela usada para fazer esse tipo de relacionamento não aderir à convenção citada acima para nomear tabelas HABTM).
  • with: Define o nome do modelo para a tabela de junção. Por padrão o CakePHP auto-criará um modelo por você. Usando o exemplo a cima ele se chamaria RecipesTag. Usando esta chave você pode substituir este nome padrão. O modelo da tabela de junção pode ser usado simplesmente como qualquer modelo “regular” para acessar a tabela de junção diretamente.
  • foreignKey: O nome da chave estrangeira encontrada no outro modelo. Isto é especialmente útil se você precisa definir múltiplos relacionamentos HABTM. O valor padrão para esta chave é o nome do outro modelo no singular, seguida de “_id”.
  • associationForeignKey: O nome da chave estrangeira encontrada no modelo atual. Isto é especialmente útil se você precisa definir múltiplos relacionamentos HABTM. O valor padrão para esta chave é o nome do atual modelo no singular, seguida de ‘_id’.
  • unique: Se true (valor padrão) o CakePHP primeiro excluirá os registro do relacionamento existente na tabela com a chave estrangeira antes de inserir novos registros, quando atualizar um registro. Então associações existentes precisam ser passadas novamente quando atualizar.
  • conditions: Um fragmento SQL utilizado para filtrar registros no modelo relacionado. É uma boa prática a utilização nome do modelo nos fragmentos SQL: “Recipe.status = 1” é sempre melhor do que apenas “status = 1”.
  • fields: A lista de campos a serem recuperados quando os dados do modelo associado são coletados. Retorna todos os campos por padrão.
  • order: Um fragmento SQL que define a classificação para a ordem para o retorno de linhas associadas.
  • limit: O número máximo de linhas associadas que você quer que retorne.
  • offset: O número de linhas associadas para saltar sobre (dadas as atuais condições e ordem), antes de ir buscar e associar.
  • finderQuery, deleteQuery, insertQuery: Uma completa consulta SQL CakePHP que pode-se usar para buscar registros associados. Isto deve ser utilizado em situações que exijam muito resultado personalizado.

Uma vez que esta associação tenha sido definida, operações de busca sobre o modelo Recipe(Receita) também vai buscar registros relacionados em Tag caso existam:

// Exemplo de resultados de uma chamada $this->Recipe->find();

Array
(
    [Recipe] => Array
        (
            [id] => 2745
            [name] => Chocolate Frosted Sugar Bombs
            [created] => 2007-05-01 10:31:01
            [user_id] => 2346
        )
    [Tag] => Array
        (
            [0] => Array
                (
                    [id] => 123
                    [name] => Breakfast
                )
           [1] => Array
                (
                    [id] => 124
                    [name] => Dessert
                )
           [2] => Array
                (
                    [id] => 125
                    [name] => Heart Disease
                )
        )
)

Lembre-se de definir uma associação HABTM no modelo Tag se quiser buscar dados do modelo Recipe, quando se utiliza o modelo Tag para pesquisas.

Também é possíve executar consultas personalizadas baseado em relacionamentos HABTM. Considere os exemplos a seguir:

Assumindo a mesma estrutura do exemplo acima (Recipe HABTM Tag), vamos dizer que queremos buscar todas as Receitas com a tag ‘Dessert’ (sobremesa), uma maneira (erro) potencial para realizar isto seria aplicar a condição à prórpia associação:

$this->Recipe->bindModel(array(
    'hasAndBelongsToMany' => array(
        'Tag' => array('conditions'=>array('Tag.name'=>'Dessert'))
)));
$this->Recipe->find('all');
// Dados Retornados
Array
(
    0 => Array
        {
        [Recipe] => Array
            (
                [id] => 2745
                [name] => Chocolate Frosted Sugar Bombs
                [created] => 2007-05-01 10:31:01
                [user_id] => 2346
            )
        [Tag] => Array
            (
               [0] => Array
                    (
                        [id] => 124
                        [name] => Dessert
                    )
            )
    )
    1 => Array
        {
        [Recipe] => Array
            (
                [id] => 2745
                [name] => Crab Cakes
                [created] => 2008-05-01 10:31:01
                [user_id] => 2349
            )
        [Tag] => Array
            (
            }
        }
}

Repare que este exemplo retorna TODAS receitas mas apenas as tags “Dessert”. Para realizar corretamente nossa meta, existem vários modos de fazê-la. Uma opção é buscar o modelo Tag (em vez de Recipe), o que sempre nos dará todas as Receitas associadas.

$this->Recipe->Tag->find('all', array('conditions'=>array('Tag.name'=>'Dessert')));

Também podemos usar o modelo de tabela de junção (que o CakePHP fornece para nós), para pesquisar por um ID dado.

$this->Recipe->bindModel(array('hasOne' => array('RecipesTag')));
$this->Recipe->find('all', array(
        'fields' => array('Recipe.*'),
        'conditions'=>array('RecipesTag.tag_id'=>124) // id de Dessert
));

Também é possível criar uma associação exótica para o propósito de criar tantas junções quanto necessárias para permitir a filtragem, por exemplo:

$this->Recipe->bindModel(array(
    'hasOne' => array(
        'RecipesTag',
        'FilterTag' => array(
            'className' => 'Tag',
            'foreignKey' => false,
            'conditions' => array('FilterTag.id = RecipesTag.tag_id')
))));
$this->Recipe->find('all', array(
        'fields' => array('Recipe.*'),
        'conditions'=>array('FilterTag.name'=>'Dessert')
));

Ambos retornarão os seguintes dados:

//Dados Retornados
Array
(
    0 => Array
        {
        [Recipe] => Array
            (
                [id] => 2745
                [name] => Chocolate Frosted Sugar Bombs
                [created] => 2007-05-01 10:31:01
                [user_id] => 2346
            )
    [Tag] => Array
        (
            [0] => Array
                (
                    [id] => 123
                    [name] => Breakfast
                )
           [1] => Array
                (
                    [id] => 124
                    [name] => Dessert
                )
           [2] => Array
                (
                    [id] => 125
                    [name] => Heart Disease
                )
        )
}

O mesmo truque de ligação pode ser usado para facilmente paginar seus modelos HABTM. Apenas um cuidado: desde que paginar precisar de duas consultas (uma para contar os registros e outra para pegar os dados atuais), teha certeza de fornecer o parâmetro false ao seu bindModel(); que essencialmente diz ao CakePHP para manter a ligação persistente através de múltiplas consultas, em vez de uma como no comportamento padrão. Por favor consulte a API para mais detalhes.

Para mais informações sobre associação de ligação de modelos na mosca veja Creating and destroying associations on the fly

hasMany through (The Join Model)

It is sometimes desirable to store additional data with a many to many association. Consider the following

Student hasAndBelongsToMany Course Course hasAndBelongsToMany Student

In other words, a Student can take many Courses and a Course can be taken my many Students. This is a simple many to many association demanding a table such as this

id | student_id | course_id

Now what if we want to store the number of days that were attended by the student on the course and their final grade? The table we’d want would be

id | student_id | course_id | days_attended | grade

The trouble is, hasAndBelongsToMany will not support this type of scenario because when hasAndBelongsToMany associations are saved, the association is deleted first. You would lose the extra data in the columns as it is not replaced in the new insert.

The way to implement our requirement is to use a join model, otherwise known (in Rails) as a hasMany through association. That is, the association is a model itself. So, we can create a new model CourseMembership. Take a look at the following models.

student.php

class Student extends AppModel
{
    public $hasMany = array(
        'CourseMembership'
    );

    public $validate = array(
        'first_name' => array(
            'rule' => 'notEmpty',
            'message' => 'A first name is required'
        ),
        'last_name' => array(
            'rule' => 'notEmpty',
            'message' => 'A last name is required'
        )
    );
}

course.php

class Course extends AppModel
{
    public $hasMany = array(
        'CourseMembership'
    );

    public $validate = array(
        'name' => array(
            'rule' => 'notEmpty',
            'message' => 'A course name is required'
        )
    );
}

course_membership.php

class CourseMembership extends AppModel
{
    public $belongsTo = array(
        'Student', 'Course'
    );

    public $validate = array(
        'days_attended' => array(
            'rule' => 'numeric',
            'message' => 'Enter the number of days the student attended'
        ),
        'grade' => array(
            'rule' => 'notEmpty',
            'message' => 'Select the grade the student received'
        )
    );
}

The CourseMembership join model uniquely identifies a given Student’s participation on a Course in addition to extra meta-information.

Working with join model data

Now that the models have been defined, let’s see how we can save all of this. Let’s say the Head of Cake School has asked us the developer to write an application that allows him to log a student’s attendance on a course with days attended and grade. Take a look at the following code.

controllers/course_membership_controller.php

class CourseMembershipsController extends AppController
{
    public $uses = array('CourseMembership');

    public function index() {
        $this->set('course_memberships_list', $this->CourseMembership->find('all'));
    }

    public function add() {

        if (! empty($this->data)) {

            if ($this->CourseMembership->saveAll(
                $this->data, array('validate' => 'first'))) {


                $this->redirect(array('action' => 'index'));
            }
        }
    }
}

views/course_memberships/add.ctp

<?php echo $form->create('CourseMembership'); ?>
    <?php echo $form->input('Student.first_name'); ?>
    <?php echo $form->input('Student.last_name'); ?>
    <?php echo $form->input('Course.name'); ?>
    <?php echo $form->input('CourseMembership.days_attended'); ?>
    <?php echo $form->input('CourseMembership.grade'); ?>
    <button type="submit">Save</button>
<?php echo $form->end(); ?>

You can see that the form uses the form helper’s dot notation to build up the data array for the controller’s save which looks a bit like this when submitted.

Array
(
    [Student] => Array
        (
            [first_name] => Joe
            [last_name] => Bloggs
        )

    [Course] => Array
        (
            [name] => Cake
        )

    [CourseMembership] => Array
        (
            [days_attended] => 5
            [grade] => A
        )

)

Cake will happily be able to save the lot together and assigning the foreign keys of the Student and Course into CourseMembership with a saveAll call with this data structure. If we run the index action of our CourseMembershipsController the data structure received now from a find(‘all’) is:

Array
(
    [0] => Array
        (
            [CourseMembership] => Array
                (
                    [id] => 1
                    [student_id] => 1
                    [course_id] => 1
                    [days_attended] => 5
                    [grade] => A
                )

            [Student] => Array
                (
                    [id] => 1
                    [first_name] => Joe
                    [last_name] => Bloggs
                )

            [Course] => Array
                (
                    [id] => 1
                    [name] => Cake
                )

        )

)

There are of course many ways to work with a join model. The version above assumes you want to save everything at-once. There will be cases where you want to create the Student and Course independently and at a later point associate the two together with a CourseMembership. So you might have a form that allows selection of existing students and courses from picklists or ID entry and then the two meta-fields for the CourseMembership, e.g.

views/course_memberships/add.ctp

<?php echo $form->create('CourseMembership'); ?>
    <?php echo $form->input('Student.id', array('type' => 'text', 'label' => 'Student ID', 'default' => 1)); ?>
    <?php echo $form->input('Course.id', array('type' => 'text', 'label' => 'Course ID', 'default' => 1)); ?>
    <?php echo $form->input('CourseMembership.days_attended'); ?>
    <?php echo $form->input('CourseMembership.grade'); ?>
    <button type="submit">Save</button>
<?php echo $form->end(); ?>

And the resultant POST

Array
(
    [Student] => Array
        (
            [id] => 1
        )

    [Course] => Array
        (
            [id] => 1
        )

    [CourseMembership] => Array
        (
            [days_attended] => 10
            [grade] => 5
        )

)

Again Cake is good to us and pulls the Student id and Course id into the CourseMembership with the saveAll.

Join models are pretty useful things to be able to use and Cake makes it easy to do so with its built-in hasMany and belongsTo associations and saveAll feature.

Criando e removendo relações durante execução

Por vezes, torna-se necessário criar e destruir associações no modelo durante a execução. Isso pode acontecer por algum número de razões:

  • Você deseja reduzir a quantidade coletada de dados associados, mas todas as suas associações estão no primeiro nível de recursão.
  • Você quer mudar o modo como uma associação é definida, a fim de ordenar ou filtrar dados associados.

Esta criação e destruição de associação é feita usando os métodos bindModel() e unbindModel() do modelo CakePHP. (Também há um behavior muito útil chamado “Containable”, favor consultar a seção do manual sobre Built-in behaviors para mais informações) Vamos configurar alguns modelos, para que possamos ver como bindModel() e unbindModel() trabalham. Nós vamos começar com dois modelos:

<?php

class Leader extends AppModel {
    var $name = 'Leader';

    var $hasMany = array(
        'Follower' => array(
            'className' => 'Follower',
            'order'     => 'Follower.rank'
        )
    );
}

?>

<?php

class Follower extends AppModel {
    var $name = 'Follower';
}

?>

Agora, no LeadersController, podemos usar o método find() do modelo Leader para buscar um líder e seus seguidores associados. Como você pode ver acima, a associação em sintaxe de array no modelo Leader define um relacionamento “Leader hasMany Follower”. Para fins de demonstração, vamos usar unbindModel() para remover esta associação em uma função do controlador.

function someAction() {
    // Isto recupera Líderes, e seus seguidores associados
    $this->Leader->find('all');

    // Vamos remover o hasMany...
    $this->Leader->unbindModel(
        array('hasMany' => array('Follower'))
    );

    // Agora usando a função find() irá retornar
    // Líderes, sem os seus seguidores
    $this->Leader->find('all');

    // NOTA: unbindModel() afeta somente a mais próxima
    // função find(). Uma nova chamada find() será usada
    // a informação configurada na associação do modelo.

    // Já utilizando find('all'), após unbindModel(),
    // esta vai buscar Líderes com seguidores
    // associados mais uma vez...
    $this->Leader->find('all');
}

Remover ou adicionar associações usando bind- e unbindModel() apenas funciona para a próxima operação do modelo exceto o se o segundo parâmetro seja definido para false. Se o segundo parâmentro foi definido para false, o bind mantém no lugar para o restante da requisição.

Aqui está a base para o uso padrão de unbindModel():

$this->Model->unbindModel(
    array('associationType' => array('associatedModelClassName'))
);

// associationType => O tipo de associação com o outro modelo (associatedModelClassName) que você quer remover.

Agora que removida com êxito uma associação durante a execução, vamos adicionar uma. Como ainda temos Líderes sem princípios necessitamos alguns Princípios associados. O arquivo do modelo para o nosso modelo Principle está vazio, com exceção para a declaração var $name. Vamos associar alguns princípios para ao nosso modelo Leader durante a execução (mas lembre-apenas servirá a próxima operação de busca). Esta função aparece no LeadersController:

function anotherAction() {
    // Não há "Leader hasMany Principles" no
    // arquivo do modelo leader.php,
    // aqui somente vamos encontrar Líderes.
    $this->Leader->find('all');

    // Vamos usar bindModel() para adicionar uma nova associação
    // para o modelo Leader.
    $this->Leader->bindModel(
        array('hasMany' => array(
                'Principle' => array(
                    'className' => 'Principle'
                )
            )
        )
    );

    // Agora que está ligado corretamente,
    // Podemos utilizar uma única função find() para buscar
    // Líderes com seus associados princípios:
    $this->Leader->find('all');
}

Isto é o que você tem. A base para uso do bindModel() é o encapsulamento de uma associação normal dentro de um array cuja chave é nomeada após o tipo de associação que você está tentando criar:

$this->Model->bindModel(
        array('associationName' => array(
                'associatedModelClassName' => array(
                    // Aqui você pode usar normalmente as chaves e/ou opções de associação.
                )
            )
        )
    );

Embora o modelo recém-vinculado não precise de nenhum tipo de associação na definição do modelo dentro do arquivo dele, ele ainda precisa ser introduzido corretamente para que a nova associação funcione corretamente.

Múltiplas relações para o mesmo modelo

Existem casos que um Modelo tem mais de uma relação com outro Modelo. Por exemplo você pode ter um modelo Messagem que tem duas relações ao modelo Usuario. Uma relação com o Usuario que envia a mensagem, e uma segunda ao usuários que recebe a mensagem. A tabela mensagens deve ter um campo usuario_id, mas também um campo destinatario_id. Agora seu modelo Mensagem pode se parecer com algo assim:

<?php
// Message = Mensagem
// Sender = Remente
// User = Usuario
// Recipiente = Destinatario
class Message extends AppModel {
    var $name = 'Message';
    var $belongsTo = array(
        'Sender' => array(
            'className' => 'User',
            'foreignKey' => 'user_id'
        ),
        'Recipient' => array(
            'className' => 'User',
            'foreignKey' => 'recipient_id'
        )
    );
}
?>

Destinatário é um pseudônimo para o modelo Usuário. Agora vamos ver como o modelo Usuário deve se parecer.

<?php
//MessageSent = MensagemEnviado
class User extends AppModel {
    var $name = 'User';
    var $hasMany = array(
        'MessageSent' => array(
            'className' => 'Message',
            'foreignKey' => 'user_id'
        ),
        'MessageReceived' => array(
            'className' => 'Message',
            'foreignKey' => 'recipient_id'
        )
    );
}
?>

Ligando Tabelas

Em SQL você pode combinar tabelas relacionadas usando a declaração JOIN. Isto permite você executar consultas complexas entre múltiplas tabelas(ex.: buscar posts fornecendo várias tags).

No CakePHP algumas associações (belongsTo e hasOne) executam ligações automáticas para retornar dados, assim você pode efetuar consultas para retornar dados de um modelo e de seus relacionamentos.

Mas este não é o caso das associações hasMany e hasAndBelongsToMany. Aqui é onde forçar ligações pode nos salvar. Você só precisa definir as ligações para combinar as tabelas e obter os resultados desejados para sua consulta.

Para forçar uma ligação entre tabelas você precisa usar a sintaxe “moderna” para Model::find(), adicionando a chave ‘joins’ no vetor $options. Por exemplo:

$options['joins'] = array(
    array('table' => 'channels',
        'alias' => 'Channel',
        'type' => 'LEFT',
        'conditions' => array(
            'Channel.id = Item.channel_id',
        )
    )
);

$Item->find('all', $options);

Note que o vetor ‘join’ não tem chave.

No exemplo acima, um modelo chamado Item é ligado pela esquerda (left) com a tabela channels. você pode apelidar a tabela com o nome do Modelo, então os dados retornados ficarão de acordo com a estrutura de dados do CakePHP.

As chaves definidas na ligação são as seguintes:

  • table: A tabela para a ligação.
  • alias: Um apelido para a tabela. O nome do modelo associado com a tabela é a melhor aposta.
  • type: O tipo a ligação: interna (inner), pela esquerda (left) ou pela direita (right).
  • conditions: As condições para executar a ligação.

Com ligações, você poderá adicionar condições baseado nos campos dos modelos relacionados:

$options['joins'] = array(
    array('table' => 'channels',
        'alias' => 'Channel',
        'type' => 'LEFT',
        'conditions' => array(
            'Channel.id = Item.channel_id',
        )
    )
);

$options['conditions'] = array(
    'Channel.private' => 1
);

$privateItems = $Item->find('all', $options);

Você poderá executar quantas ligações forem necessárias para hasBelongsToMany:

Considera a associação Book hasAndBelongsToMany (tem e pertence a muitos) Tag. Esta relação usa a tabela books_tags como tabela de ligação, então vocTe precisa ligar a tabela books com a tabela books_tags, e estas com a tabela tag:

$options['joins'] = array(
    array('table' => 'books_tags',
        'alias' => 'BooksTag',
        'type' => 'inner',
        'conditions' => array(
            'Books.id = BooksTag.books_id'
        )
    ),
    array('table' => 'tags',
        'alias' => 'Tag',
        'type' => 'inner',
        'conditions' => array(
            'BooksTag.tag_id = Tag.id'
        )
    )
);

$options['conditions'] = array(
    'Tag.tag' => 'Novel'
);

$books = $Book->find('all', $options);

Usar ligações com o comportamento Containable poderá condizir a alguns erros SQL (tabela duplicada), então você precisa uar um metodo alternativo de ligação para Containable se seu principal objetivo for executar consultas com base em dados relacionados. Containable é o mais adequado para restringir que a quantidade de dados relacionados agrave um declaração find.

Métodos de Callbacks

Se você quer colocar alguma lógica antes ou depois de uma operação do model, use as funções de callback do model. Estas funções podem ser definidas na classe do model (incluindo seu AppModel). Certifique-se de notar o valor retornado para cada uma dessas funções especiais.

beforeFind

beforeFind(mixed $queryData)

Chamada antes de qualquer operação de busca. A variável $queryData contém informações sobre a busca atual, como: condições (conditions), campos (fields), etc.

Se você não quer que a busca inicie (possivelmente relacionada a alguma decisão tomada em cima da $queryData), retorne false. O valor do retorno dessa função (possivelmente modificado) deve ser o retorno que você deseja para a busca.

Você pode usar este callback para restringir funções de busca baseada em regras de usuários ou guardar informações da busca atual.

afterFind

afterFind(array $results, bool $primary)

Use este callback para modificar os resultados de uma busca realizada ou para executar comandos após a operação busca. O parâmetro $results contém as informações retornadas da busca. por exemplo :

$results = array(
  0 => array(
    'ModelName' => array(
      'field1' => 'value1',
      'field2' => 'value2',
    ),
  ),
);

O valor do retorno dessa função (possivelmente modificado) deve ser o retorno que você deseja para a busca.

Se $primary é false, o conteúdo de $result também é um pouco diferente do esperado; no resultado da operação de busca voce terá algo como :

$results = array(
  'field_1' => 'value',
  'field_2' => 'value2'
);

A variável $primary será true e provavelmente voce receberá “Cannot use string offset as an array” um erro fatal do PHP se uma operação de busca recursiva estiver sendo usada.

No exemplo abaixo é apresentado como utilizar afterfind para formatar data.

function afterFind($results) {
    foreach ($results as $key => $val) {
        if (isset($val['Event']['begindate'])) {
            $results[$key]['Event']['begindate'] = $this->dateFormatAfterFind($val['Event']['begindate']);
        }
    }
    return $results;
}

function dateFormatAfterFind($dateString) {
    return date('d-m-Y', strtotime($dateString));
}

beforeValidate

beforeValidate()

Use este callback para modificar os dados do modelo antes dele ser validado, ou para modifcar as regras de validação se necessário. Esta função deve também retornar true, senão a execução save() atual abortará.

beforeSave

beforeSave()

Coloque qualquer lógica pre-save nesta função. Esta função executa imeditamente depois dos dados do modelo serem validados com sucesso, mas logo antes dos dados serem salvos. Esta função também deve retornar true se você deseja que a operação de salvar continue.

Este callback é especialmente manipulado para qualquer lógica de mensagem de dados que precisa acontecer antes de seus dados serem armazenados. Se seu armazenamento precisa de datas em um formato específico, acesse-a em $this->data e modifique-a.

Abaixo segue um exemplo de como o beforeSave pode ser usado para conversão de data. O código no exemplo é usado por uma aplicação com uma data de início formatada em YYYY-MM-DD no banco de dados e é mostrado como DD-MM-YYYY na aplicação. Claro que isso pode ser mudado facilmente. Use o código a seguir no model apropriado.

function beforeSave() {
    if(!empty($this->data['Event']['begindate']) && !empty($this->data['Event']['enddate'])) {
            $this->data['Event']['begindate'] = $this->dateFormatBeforeSave($this->data['Event']['begindate']);
            $this->data['Event']['enddate'] = $this->dateFormatBeforeSave($this->data['Event']['enddate']);
    }
    return true;
}

function dateFormatBeforeSave($dateString) {
    return date('Y-m-d', strtotime($dateString)); // A direção é de
}

Tenha certeza que beforeSave() retorne true, ou seu salvamento falhará

afterSave

afterSave(boolean $created)

Se você tem alguma lógica que precisa ser executada logo após cada operação de salvamento, coloque-a neste método callback.

O valor de $created será true se um novo objeto foi criado (em vez de atualizado).

beforeDelete

beforeDelete(boolean $cascade)

Local para colocar a lógica para excluir. Essa função deve retornar true se desejar excluir, e false se desejar cancelar.

O valor de $cascade será true se os registros que dependem deste registro também serão excluídos.

afterDelete

afterDelete()

Coloque qualquer lógica que pretende que seja executado depois de cada chamada de callback neste método.

onError

onError()

Chamado se qualquer problema ocorrer.

Atributos de Modelo

Atributos de modelo lhe permite definir propriedades que podem substituir o comportamento padrão do modelo.

Para uma lista completa dos atributos de modelo e suas descriçoes visite a API do CakePHP. Confira https://api.cakephp.org/class/model.

useDbConfig

A propriedade useDbConfig especifica o database relacionado com as tabelas de seu model class. Poderá ser armazenada uma conexão de database dentro do arquivo de configuração em /app/config/database.php.

A propriedade useDbConfig assume uma conexão para database.

Examplo:

class Example extends AppModel {
   var $useDbConfig = 'alternate';
}

useTable

A propriedade useTable especifica o nome da tabela do banco de dados. Por padrão, o modelo usa letras minúsculas e plural do nome da classe do modelo. Defina este atributo ao nome de uma tabela alternativa, ou difina-o false se você não quer que o modelo use uma tabela do banco de dados

Exemplo de uso:

class Example extends AppModel {
   var $useTable = false; // Este modelo não usa uma tabela do banco de dados
}

Alternativamente:

class Example extends AppModel {
   var $useTable = 'exmp'; // Este modelo usa a tabela 'exmp'
}

tablePrefix

O nome do prefixo da tabela usado para o modelo. O prefixo da tabela é inicialmente defindo no arquivo de conexão com o banco de dados em /ap/config/database.php. O padrão é sem prefixo. Você pode substituir o padrão definindo o atributo tablePrefix no modelo.

Exemplo de Uso:

class Example extends AppModel {
   var $tablePrefix = 'alternate_'; // irá procurar por 'alternate_examples'
}

primaryKey

Toda tabela normalmente tem uma chave primária, id. Você pode mudar qual o nome do campo que o modelo usa como sua chave primária. Isto é comum ao definir o CakePHP para usar uma tabela já existente no banco de dados.

Exemplo de Uso:

class Example extends AppModel {
    var $primaryKey = 'example_id'; // example_id é o nome do campo na tabela
}

displayField

O atributo displayField especifica qual campo do banco de dados deve ser usado como um rótulo para o registro. O rótulo é usado em scaffolding e em chamadas find('list'). Por padrão modelo usará name ou title.

Por exemplo, para usar o campo username

class User extends AppModel {
   var $displayField = 'username';
}

Múltiplos nomes de campos não podem ser combinados em um único display field. Por exemplo, você nao pode especificar array('firs_name', 'last_name') como um display field.

recursive

A propriedade recursive define o quão fundo o CakePHP deve ir para buscar dados de modelo associado usando os métodos find(), findAll() e read().

Imagine os recursos Grupos de sua aplicação que pertence a um domínio que tem vários Usuários que por sua vez tem vários Artigos. Você pode deifinir $recursive para diferetes valores baseados na quantidade de dados que você quer retornar de uma chamada $this->Grupo->find().

Profundidade Descrição
-1 O CakePHP busca dados apenas do Grupo, sem junção.
0 O CakePHP busca os dados do Grupo e de seu domínio.
1 O CakePHP busca um Grupo, seu domínio e seus Usuários associados.
2 O CakePHP busca um Grupo, seu domíno, seus Usuários associados e os Artigos associados aos Usuários.

Defina-o não além do que você precisa. Tendo dados buscados pelo CakePHP você não vai querer deixar sua aplicação lenta desnecessariamente.

Se você quer combinar $recursive com as funcionalidades de campos, você terá que adicionar as colunas contendo as chaves estrangeiras necessárias ao array campos manualmente. No exemplo acima, pode significar adicionar domain_id.

order

A ordenação padrão de dados para qualquer operação de busca. Valores possíveis incluem:

$order = "field"
$order = "Model.field";
$order = "Model.field asc";
$order = "Model.field ASC";
$order = "Model.field DESC";
$order = array("Model.field" => "asc", "Model.field2" => "DESC");

data

O recipiente para os dados de busca do modelo. Quando dados são retornados de uma classe de modelo é normalmente usado como retorno de uma chamada find() você pode precisar acesar a informação armazenadas em $data dentro de callbacks do modelo.

_schema

Contem metadados descrevendo os campos de tabelas do banco de dados do modelo. Cada modelo é descrito por:

  • name
  • type (integer, string, datetime, etc.)
  • null
  • default value
  • length

validate

Este atributo detem regras que permitem o modelo tomar decisões de validadação de dados antes de salvar. Chaves nomeadas depois de campos guardam valores de expressão regular permitindo ao modelo tentar fazer combinações.

Não é necessário chamar validate() antes de save(), o método save() irá validar automaticamente seus dados antes de efetivamente salva-los.

Para mais informações sobre validação, veja o capítulo de Validação de Dados mais a frente neste manual.

virtualFields

O model pode ter um Array de campos virtuais (virtual fields). Campos virtuais são aliases de expressões SQL. Os campos adicionados através desta propriedade serão lidos/interpretados como qualquer outro campo do model porém não serão salvos.

Exemplo de uso:

var $virtualFields = array(
    'name' => "CONCAT(User.first_name, ' ', User.last_name)"
);

Nas operações find subsequentes feitas no model User os resultados conterão a chave name com o resultado da concatenação do primeiro com o último nome.

Não é aconselhável criar campos virtuais, com os mesmos nomes das colunas do banco de dados, isso pode causar erros de SQL.

Para mais informações sobre virtualFields, suas propriedades, a sua correta utilização, bem como as limitações, consulte a seção sobre campos virtuais.

name

Como você viu anteriormente neste capítulo, o atributo name é um recurso de compatibilidade para usuários de PHP4 e é definido para o mesmo valor que o nome do modelo.

Exemplo de uso:

class Example extends AppModel {
   var $name = 'Example';
}

cacheQueries

Se definido como true, dados buscados pelo modelo durante uma única requisição são armazenados em cache. Este caching é apenas in-memory, e dura apenas pela duração da requisição. Qualquer requisição duplicada para os mesmos dados é manipulada pelo cache.

Métodos Adicionais e Propriedades

Enquanto as funções de modelo do CakePHP te levam onde você precisa ir, não se esqueça que classes de modelo são apenas: classes que permitem você escrever seus próprios métodos ou definir suas próprias propriedades.

class Example extends AppModel {
   function getRecent() {
      $conditions = array(
         'created BETWEEN (curdate() - interval 7 day) and (curdate() - interval 0 day))'
      );
      return $this->find('all', compact('conditions'));
   }
}

Este método getRecent() agora pode ser usado dentro do controller.

$recent = $this->Example->getRecent();

Usando campos virtuais (virtualFields)

Campos virtuais são um novo recurso no Model do CakePHP 1.3. Campos virtuais lhe permitem criar expressões SQL arbitrárias e associá-las como se fossem campos em um Model. Tais campos não podem ser salvos, mas serão tratados como quaisquer outros campos para operações de leitura. Eles serão indexados como uma chave do model normalmente como outros campos.

Como criar campos virtuais

É fácil criar campos virtuais. Em cada model você pode definir uma propriedade chamada $virtualFields contendo um array de campo => expressao. Um exemplo da definição de um campo virtual poderia ser:

var $virtualFields = array(
    'name' => 'CONCAT(User.first_name, ' ', User.last_name)'
);

Em operações find subsequentes, os dados obtidos do model User deverão conter uma chave chamada name que terá o resultado da concatenação. Não é aconselhável criar campos virtuais com os mesmos nomes de colunas já presente na base de dados pois isto pode causar erros de SQL.

Usando campos virtuais

Já que criar campos virtuais é algo simples e direto, a interação com campos virtuais pode ser feita de algumas maneiras.

``Model::hasField()``

O método Model::hasField() foi atualizado de forma a também retornar verdadeiro levando em consideração os virtualField com o nome dado. Passando-se o segundo parâmetro do método hasField como verdadeiro, o virtualFields também será verificado se o model contiver um campo. Usando o campo virtual de exemplo mostrado acima,

$this->User->hasField('name'); // Vai retornar falso, uma vez que não há um campo concreto chamado name
$this->User->hasField('name', true); // Vai retornar verdadeiro pois está consultando também nos campos virtuais e existe o campo chamado name

``Model::isVirtualField()``

Este método pode ser usado para verificar se um campo/coluna é um campo virtual ou um campo concreto. O retorno será verdadeiro se a coluna for um campo virtual.

$this->User->isVirtualField('name'); // verdadeiro
$this->User->isVirtualField('first_nname'); // falso

``Model::getVirtualField()``

Este método pode ser usado para acessar a expressão SQL que corresponde ao campo virtual. Se nenhum argumento for informado, este método retornará todos os campos virtuais presentes no model.

$this->User->getVirtualField('name'); // retorna 'CONCAT(User.first_name, ' ', User.last_name)'

``Model::find()`` e os campos virtuais

Como já dissemos, Model::find() irá tratar os campos virtuais como quaisquer outros campos no model. O valor de um campo virtual será colocado como uma chave normal no resultset do model. Ao contrário do comportamento de campos calculados no CakePHP 1.2

$results = $this->User->find('first');

// results vai conter o seguinte
array(
    'User' => array(
        'first_name' => 'Mark',
        'last_name' => 'Story',
        'name' => 'Mark Story',
        // e outros campos.
    )
);

Paginação e campos virtuais

Como os campos virtuais se comportam como se fossem campos normais em operações de find, o método Controller::paginate() foi atualizado para permitir ordenação por campos virtuais.

Campos virtuais

Campos virtuais são uma nova característica dos Modelos no CakePHP 1.3. Campos virtuais permitem você criar de forma arbitrária expressões SQL e especificá-las como um campo no Modelo. Estes campo não podem ser salvos, mas vão ser tratados como os outros campos do modelo nas operações de leitura. Eles vão ser indexados sob a chave do modelo, ao lado dos outros campos.

Criando virtual fields

A criação de campos virtuais é fácil. Em cada model você pode definir uma propriedade $virtualFields que contém um array de campo => expressões. Um exemplo de uma definição de campo virtual usando o MySQL seria:

var $virtualFields = array(
    'full_name' => 'CONCAT(User.first_name, " ", User.last_name)'
);

E com o PostgreSQL:

var $virtualFields = array(
    'name' => 'User.first_name || \' \' || User.last_name'
);

Nas operações find, os resultados de User deverá conter uma chave name com o resultado da concatenação. Não é aconselhável criar campos virtuais, com os mesmos nomes que as colunas do banco de dados, isso pode causar erros de SQL.

Usando virtual fields

A criação de campos virtuais é simples e fácil, a interação com os mesmos pode ser feita através de alguns métodos diferentes.

Model::hasField()

Model::hasField() foi atualizado para que ele possa retornar true se o model tem um virtualField com o nome correto. Ao definir o segundo parâmetro do hasField para true, os virtualFields também serão examinados quando for verificar se um model tem um campo. Usando o campo de exemplo acima,

$this->User->hasField('name'); // Irá retornar false, como não há nenhum campo concreto chamado name
$this->User->hasField('name', true); // Retornará true como existe um campo virtual chamado name

Model::isVirtualField()

Este método pode ser usado para verificar se um campo/coluna é um campo virtual ou um campo concreto. Retornará true se a coluna é virtual.

$this->User->isVirtualField('name'); //true
$this->User->isVirtualField('first_name'); //false

Model::getVirtualField()

Este método pode ser usado para acessar a expressão SQL que inclui um campo virtual. Se nenhum argumento for fornecido vai retornar todos os campos virtuais em um Model.

$this->User->getVirtualField('name'); //retorna 'CONCAT(User.first_name, ' ', User.last_name)'

Model::find() and virtual fields

Tal como referido anteriormente Model::find() irá tratar campos virtual muito parecido com qualquer outro campo em um modelo. O valor de um campo virtual será colocado sob a chave do model no conjunto de resultados. Ao contrário do comportamento de campos calculados no 1.2

$results = $this->User->find('first');

// contém o seguinte resultado
array(
    'User' => array(
        'first_name' => 'Mark',
        'last_name' => 'Story',
        'name' => 'Mark Story',
        //mais campos.
    )
);

Virtual fields e paginação

Uma vez que campos virtuais se comportam como campos regulares ao executar find’s, o Controller::paginate() foi atualizado para permitir a classificação por campos virtuais.

Virtual fields and model aliases

When you are using virtualFields and models with aliases that are not the same as their name, you can run into problems as virtualFields do not update to reflect the bound alias. If you are using virtualFields in models that have more than one alias it is best to define the virtualFields in your model’s constructor

function __construct($id = false, $table = null, $ds = null) {
    parent::__construct($id, $table, $ds);
    $this->virtualFields['name'] = sprintf('CONCAT(%s.first_name, " ", %s.last_name)', $this->alias, $this->alias);
}

This will allow your virtualFields to work for any alias you give a model.

Limitations of virtualFields

The implementation of virtualFields in 1.3 has a few limitations. First you cannot use virtualFields on associated models for conditions, order, or fields arrays. Doing so will generally result in an SQL error as the fields are not replaced by the ORM. This is because it’s difficult to estimate the depth at which an associated model might be found.

A common workaround for this implementation issue is to copy virtualFields from one model to another at runtime when you need to access them.

$this->virtualFields['full_name'] = $this->Author->virtualFields['full_name'];

Alternatively, you can define $virtualFields in your model’s constructor, using $this->alias, like so:

public function __construct($id=false,$table=null,$ds=null){
  parent::__construct($id,$table,$ds);
  $this->virtualFields = array(
    'name'=>"CONCAT(`{$this->alias}`.`first_name`,' ',`{$this->alias}`.`last_name`)"
  );
}

Transações

Para efetuar uma transação, a tabela de um model deve ser de um tipo que suporte transações.

Todos os métodos de transações devem ser efetuados no objeto DataSource do model. Para se obter um DataSource do model utilize:

$dataSource = $this->getDataSource();

Você pode, então, utilizar o data source para iniciar a transação, efetuar commits, ou roll backs.

$dataSource->begin($this);

//Executa algumas tarefas

if(/*tudo ok*/) {
    $dataSource->commit($this);
} else {
    $dataSource->rollback($this);
}