Controllers

Os controllers correspondem ao “C” no padrão MVC. Após o roteamento ter sido aplicado e o controller correto encontrado, a ação do controller é chamada. Seu controller deve lidar com a interpretação dos dados de uma requisição, certificando-se que os models corretos são chamados e a resposta ou view esperada seja exibida. Os controllers podem ser vistos como intermediários entre a camada Model e View. Você vai querer manter seus controllers magros e seus Models gordos. Isso lhe ajudará a reutilizar seu código e testá-los mais facilmente.

Mais comumente, controllers são usados para gerenciar a lógica de um único model. Por exemplo, se você está construindo um site para uma padaria online, você pode ter um RecipesController e um IngredientsController gerenciando suas receitas e seus ingredientes. No CakePHP, controllers são nomeados de acordo com o model que manipulam. É também absolutamente possível ter controllers que usam mais de um model.

Os controllers da sua aplicação são classes que estendem a classe CakePHP AppController, a qual por sua vez estende a classe Controller do CakePHP. A classe AppController pode ser definida em /app/Controller/AppController.php e deve conter métodos que são compartilhados entre todos os seus controllers.

Os controllers fornecem uma série de métodos que são chamados de ações. Ações são métodos em um controller que manipulam requisições. Por padrão, todos os métodos públicos em um controller são ações e acessíveis por urls.

A Classe AppController

Como mencionado anteriormente, a classe AppController é a mãe de todos os outros controllers da sua aplicação. O próprio AppController é estendida da classe Controller que faz parte da biblioteca do CakePHP. Assim sendo, AppController é definido em /app/Controller/AppController.php como:

class AppController extends Controller {
}

Os atributos e métodos criados em AppController vão estar disponíveis para todos os controllers da sua aplicação. Este é o lugar ideal para criar códigos que são comuns para todos os seus controllers. Componentes (que você vai aprender mais tarde) são a melhor alternativa para códigos que são usados por muitos (mas não obrigatoriamente em todos) controllers.

Enquanto regras normais de herança de classes orientadas à objetos são aplicadas, o CakePHP também faz um pequeno trabalho extra quando se trata de atributos especiais do controller. A lista de componentes (components) e helpers usados no controller são tratados diferentemente. Nestes casos, as cadeias de valores do AppController são mescladas com os valores de seus controllers filhos. Os valores dos controllers filhos sempre sobrescreveram os do AppController.

Nota

O CakePHP mescla as seguintes variáveis do AppController em controllers da sua aplicação:

  • $components
  • $helpers
  • $uses

Lembre-se de adicionar os helpers Html e Form padrões se você incluiu o atributo $helpers em seu AppController.

Também lembre de fazer as chamadas de callbacks do AppController nos controllers filhos para obter melhores resultados:

function beforeFilter() {
    parent::beforeFilter();
}

Parâmetros de Requisição

Quando uma requisição é feita para uma aplicação CakePHP, a classe Router e a classe Dispatcher do Cake usa a Routes Configuration para encontrar e criar o controller correto. Os dados da requisição são encapsulados em um objeto de requisição. O CakePHP coloca todas as informações importantes de uma requisição na propriedade $this->request. Veja a seção Objetos de Requisição e Resposta para mais informações sobre o objeto de requisição do CakePHP.

Ações de Controllers

Retornando ao nosso exemplo da padaria online, nosso controller RecipesController poderia conter as ações view(), share() e search() e poderia ser encontrado em /app/Controller/RecipesController.php contendo o código a seguir:

# /app/Controller/RecipesController.php

class RecipesController extends AppController {
    function view($id) {
        // a lógica da ação vai aqui
    }

    function share($customer_id, $recipe_id) {
        // a lógica da ação vai aqui
    }

    function search($query) {
        // a lógica da ação vai aqui
    }
}

Para que você use de forma eficaz os controllers em sua aplicação, nós iremos cobrir alguns dos atributos e métodos inclusos no controller fornecido pelo CakePHP.

Ciclo de Vida dos Callbacks em uma Requisição

class Controller

Os controllers do CakePHP vêm equipados com callbacks que você pode usar para inserir lógicas em torno do ciclo de vida de uma requisição:

Controller::beforeFilter()

Este método é executado antes de cada ação dos controllers. É um ótimo lugar para verificar se há uma sessão ativa ou inspecionar as permissões de um usuário.

Nota

O método beforeFilter() será chamado para ações não encontradas e ações criadas pelo scaffold do Cake.

Controller::beforeRender()

Chamada após a lógica da ação de um controller, mas antes da view ser renderizada. Este callback não é usado com frequência mas pode ser preciso se você chamar o método render() manualmente antes do término de uma ação.

Controller::afterFilter()

Chamada após cada ação dos controllers, e após a completa renderização da view. Este é o último método executado do controller.

Em adição aos callbacks dos controllers, os Componentes também fornecem um conjunto de callbacks similares.

Métodos dos Controllers

Para uma lista completa dos métodos e suas descrições, visite a API do CakePHP. Siga para https://api.cakephp.org.

Interagindo Com as Views

Os controllers interagem com as views de diversas maneiras. Primeiramente eles são capazes de passar dados para as views usando o método set(). Você também pode no seu controller decidir qual classe de View usar e qual arquivo deve ser renderizado.

Controller::set(string $var, mixed $value)

O método set() é a principal maneira de enviar dados do seu controller para a sua view. Após ter usado o método set(), a variável pode ser acessada em sua view:

// Primeiro você passa os dados do controller:

$this->set('color', 'pink');

//Então, na view, você pode utilizar os dados:
?>

Você selecionou a cobertura <?php echo $color; ?> para o bolo.

O método set() também aceita um array associativo como primeiro parâmetro, podendo oferecer uma forma rápida para atribuir uma série de informações para a view.

Alterado na versão 1.3: Chaves de arrays não serão mais flexionados antes de serem atribuídas à view (“underscored_key” não se torna “underscoredKey”, etc.):

$data = array(
    'color' => 'pink',
    'type' => 'sugar',
    'base_price' => 23.95
);

// Torna $color, $type e $base_price
// disponível na view:

$this->set($data);

O atributo $pageTitle não existe mais, use o método set() para definir o título na view:

$this->set('title_for_layout', 'This is the page title');
?>
Controller::render(string $action, string $layout, string $file)

O método render() é chamado automaticamente no fim de cada ação requisitada de um controller. Este método executa toda a lógica da view (usando os dados que você passou usando o método set()), coloca a view dentro do seu layout e serve de volta para o usuário final.

O arquivo view usado pelo método render() é determinado por convenção. Se a ação search() do controller RecipesController é requisitada, o arquivo view encontrado em /app/View/Recipes/search.ctp será renderizado:

class RecipesController extends AppController {
...
    function search() {
        // Renderiza a view em /View/Recipes/search.ctp
        $this->render();
    }
...
}

Embora o CakePHP irá chamar o método render automaticamente (ao menos que você altere o atributo $this->autoRender para false) após cada ação, você pode usá-lo para especificar um arquivo view alternativo alterando o nome de ação no controller usando o parâmetro $action.

Se o parâmetro $action começar com '/' é assumido que o arquivo view ou elemento que você queira usar é relativo ao diretório /app/View. Isto permite a renderização direta de elementos, muito útil em chamadas Ajax.

// Renderiza o elemento presente em /View/Elements/ajaxreturn.ctp
$this->render('/Elements/ajaxreturn');

Você também pode especificar uma arquivo view ou elemento usando o terceiro parâmetro chamado $file. O parâmetro $layout permite você especificar o layout em que a view será inserido.

Renderizando Uma View Específica

Em seu controller você pode querer renderizar uma view diferente do que a convenção proporciona automaticamente. Você pode fazer isso chamando o método render() diretamente. Após ter chamado o método render(), o CakePHP não irá tentar renderizar novamente a view:

class PostsController extends AppController {
    function my_action() {
        $this->render('custom_file');
    }
}

Isto irá renderizar o arquivo app/View/Posts/custom_file.ctp ao invés de app/View/Posts/my_action.ctp

Controle de Fluxo

Controller::redirect(mixed $url, integer $status, boolean $exit)

O método de controle de fluxo que você vai usar na maioria das vezes é o redirect(). Este método recebe seu primeiro parâmetro na forma de uma URL relativa do CakePHP. Quando um usuário executou um pedido que altera dados no servidor, você pode querer redirecioná-lo para uma outra tela de recepção.:

function place_order() {
    // Logic for finalizing order goes here
    if ($success) {
        $this->redirect(array('controller' => 'orders', 'action' => 'thanks'));
    } else {
        $this->redirect(array('controller' => 'orders', 'action' => 'confirm'));
    }
}

Nota

Você pode aprender mais sobre a importância de redirecionar o usuário após um formulário do tipo POST no artigo Post/Redirect/Get (en).

Você também pode usar uma URL relativa ou absoluta como argumento:

$this->redirect('/orders/thanks'));
$this->redirect('http://www.example.com');

Você também pode passar dados para a ação:

// observe o parâmetro $id
$this->redirect(array('action' => 'edit', $id));

O segundo parâmetro passado no redirect() permite você definir um código de status HTTP para acompanhar o redirecionamento. Você pode querer usar o código 301 (movido permanentemente) ou 303 (siga outro), dependendo da natureza do redirecionamento.

O método vai assumir um exit() após o redirecionamento ao menos que você passe o terceiro parâmetro como false.

Se você precisa redirecionar o usuário de volta para a página que fez a requisição, você pode usar:

$this->redirect($this->referer());
Controller::flash(string $message, string $url, integer $pause, string $layout)

Assim como o método redirect(), o método flash() é usado para direcionar o usuário para uma nova página após uma operação. O método flash() é diferente na forma de transição, mostrando uma mensagem antes de transferir o usuário para a URL especificada.

O primeiro parâmetro deve conter a mensagem que será exibida e o segundo parâmetro uma URL relativa do CakePHP. O Cake irá mostrar o conteúdo da variável $message pelos segundos especificados em $pause antes de encaminhar o usuário para a URL especificada em $url.

Se existir um template particular que você queira usar para mostrar a mensagem para o usuário, você deve especificar o nome deste layout passando o parâmetro $layout.

Para mensagens flash exibidas dentro de páginas, de uma olhada no método setFlash() do componente SessionComponent.

Callbacks

Em adição ao Ciclo de Vida dos Callbacks em uma Requisição. O CakePHP também suporta callbacks relacionados a scaffolding.

Controller::beforeScaffold($method)

$method é o nome do método chamado, por exemplo: index, edit, etc.

Controller::scaffoldError($method)

$method é o nome do método chamado, por exemplo: index, edit, etc.

Controller::afterScaffoldSave($method)

$method é o nome do método chamado, podendo ser: edit ou update.

Controller::afterScaffoldSaveError($method)

$method é o nome do método chamado, podendo ser: edit ou update.

Outros Métodos Úteis

Controller::constructClasses()

Este método carrega os models requeridos pelo controller. Este processo de carregamento é feito normalmente pelo CakePHP, mas pode ser útil quando for acessar controllers de outras perspectivas. Se você precisa de um controller num script de linha de comando ou para outros lugares, constructClasses() pode vir a calhar.

Controller::referer(mixed $default = null, boolean $local = false)

Retorna a URL de referência para a requisição atual. O parâmetro $default pode ser usado para fornecer uma URL padrão a ser usada caso o HTTP_REFERER não puder ser lido do cabeçalho da requisição. Então, ao invés de fazer isto:

class UserController extends AppController {
    function delete($id) {
        // delete code goes here, and then...
        if ($this->referer() != '/') {
            $this->redirect($this->referer());
        } else {
            $this->redirect(array('action' => 'index'));
        }
    }
}

Você pode fazer isto:

class UserController extends AppController {
    function delete($id) {
        // delete code goes here, and then...
        $this->redirect($this->referer(array('action' => 'index')));
    }
}

Se o parâmetro $default não for passado, o comportamento padrão é a raiz do seu domínio - '/'.

Se o parâmetro $local for passado como true, o redirecionamento restringe a URL de referência apenas para o servidor local.

Controller::disableCache()

Usado para dizer ao browser do usuário não fazer cache do resultado exibido. Isto é diferente do cache de views, abordado em um capítulo posterior.

O cabeçalho enviado para este efeito são:

Expires: Mon, 26 Jul 1997 05:00:00 GMT
Last-Modified: [data e hora atual] GMT
Cache-Control: no-store, no-cache, must-revalidate
Cache-Control: post-check=0, pre-check=0
Pragma: no-cache
Controller::postConditions(array $data, mixed $op, string $bool, boolean $exclusive)

Use este método para transformar um conjunto de dados POSTados de um model (vindos de inputs compatíveis com o FormHelper) em um conjunto de condições de busca. Este método oferece um atalho rápido no momento de construir operações de busca. Por exemplo, um usuário administrativo pode querer pesquisar pedidos para saber quais itens precisarão ser enviados. Você pode usar o FormHelper do CakePHP para criar um formulário de busca para o model Order. Assim, uma ação de um controller pode usar os dados enviados deste formulário e criar as condições de busca necessárias para completar a tarefa:

function index() {
    $conditions = $this->postConditions($this->request->data);
    $orders = $this->Order->find('all', compact('conditions'));
    $this->set('orders', $orders);
}

Se $this->request->data['Order']['destination'] for igual a «Old Towne Bakery», o método postConditions() converte esta condição em um array compatível para o uso em um método Model->find(). Neste caso, array('Order.destination' => 'Old Towne Bakery').

Se você quiser usar um operador diferente entre os termos, informe-os usando o segundo parâmetro:

/*
Conteúdo do atributo $this->request->data
array(
    'Order' => array(
        'num_items' => '4',
        'referrer' => 'Ye Olde'
    )
)
*/

// Vamos pegar os pedidos que possuem no mínimo 4 itens e que contém 'Ye Olde'
$conditions = $this->postConditions(
    $this->request->data,
    array(
        'num_items' => '>=',
        'referrer' => 'LIKE'
    )
);
$orders = $this->Order->find('all', compact('conditions'));

O terceiro parâmetro permite você dizer ao CakePHP qual operador SQL booleano usar entre as condições de busca. Strings como “AND”, “OR” e “XOR” são todos valores válidos.

Finalmente, se o último parâmetro for passado como true, e a variável $op for um array, os campos não inclusos em $op não serão retornados entre as condições.

Controller::paginate()

Este método é usado para fazer a paginação dos resultados retornados por seus models. Você pode especificar o tamanho da página (quantos resultados serão retornados), as condições de busca e outros parâmetros. Veja a seção pagination para mais detalhes sobre como usar o método paginate()

Controller::requestAction(string $url, array $options)

Este método chama uma ação de um controller de qualquer lugar e retorna os dados da ação requisitada. A $url passada é uma URL relativa do Cake (/controller_name/action_name/params). Para passar dados extras para serem recebidos pela ação do controller, adicione-os no parâmetro options em um formato de array.

Nota

Você pode usar o requestAction() para recuperar uma view totalmente renderizada passando 'return' no array de opções: requestAction($url, array('return'));. É importante notar que fazendo uma requisição usando “return” em um controller podem fazer com que tags javascripts e css não funcionem corretamente.

Aviso

Se o método requestAction() for usado sem fazer cache apropriado do resultado obtido, a performance da ação pode ser bem ruim. É raro o uso apropriado deste método em um controller ou model.

O uso do requestAction é melhor usado em conjunto com elementos (cacheados) como uma maneira de recuperar dados para um elemento antes de renderizá-los. Vamos usar o exemplo de por o elemento «últimos comentários» no layout. Primeiro nós precisamos criar um método no controller que irá retornar os dados:

// Controller/CommentsController.php
class CommentsController extends AppController {
    function latest() {
        return $this->Comment->find('all', array('order' => 'Comment.created DESC', 'limit' => 10));
    }
}

Se agora nós criarmos um elemento simples para chamar este método:

// View/Elements/latest_comments.ctp

$comments = $this->requestAction('/comments/latest');
foreach ($comments as $comment) {
    echo $comment['Comment']['title'];
}

Nós podemos por este elemento em qualquer lugar para ter a saída usando:

echo $this->element('latest_comments');

Fazendo desta maneira, sempre que o elemento for renderizado, uma requisição será feita para nosso controller para pegar os dados, processá-los e retorná-los. Porém, de acordo com o aviso acima, é melhor fazer uso de caching do elemento para evitar um processamento desnecessário. Modificando a chamada do elemento para se parecer com isto:

echo $this->element('latest_comments', array('cache' => '+1 hour'));

A chamada para o requestAction não será feita enquanto o arquivo de cache do elemento existir e for válido.

Além disso, o requestAction pode receber uma URL no formato de array do Cake:

echo $this->requestAction(
    array('controller' => 'articles', 'action' => 'featured'),
    array('return')
);

Isto permite o requestAction contornar o uso do método Router::url() para descobrir o controller e a ação, aumentando a performance. As URLs baseadas em arrays são as mesmas usadas pelo método HtmlHelper::link() com uma diferença, se você está usando parâmetros nomeados ou passados, você deve colocá-los em um segundo array envolvendo elas com a chave correta. Isto deve ser feito porque o requestAction mescla o array de parâmetros nomeados (no segundo parâmetro do requestAction) com o array Controller::params e não coloca explicitamente o array de parâmetros nomeados na chave “named”. Além disso, membros do array $options serão disponibilizados no array Controller::params da ação que for chamada.:

echo $this->requestAction('/articles/featured/limit:3');
echo $this->requestAction('/articles/view/5');

Um array no requestAction poderia ser:

echo $this->requestAction(
    array('controller' => 'articles', 'action' => 'featured'),
    array('named' => array('limit' => 3))
);

echo $this->requestAction(
    array('controller' => 'articles', 'action' => 'view'),
    array('pass' => array(5))
);

Nota

Diferente de outros lugares onde URLs no formato de arrays são análogas as URLs no formato de string, o requestAction tratam elas diferentemente.

Quando for usar URLs no formato de arrays em conjunto com o requestAction() você deve especificar todos os parâmetros que você vai precisar na requisição da ação. Isto inclui parâmetros como $this->request->data. Além disso, todos os parâmetros requeridos, parâmetros nomeados e «passados» devem ser feitos no segundo array como visto acima.

Controller::loadModel(string $modelClass, mixed $id)

O método loadModel vem a calhar quando você precisa usar um model que não é padrão do controller ou o seu model não está associado com este.

$this->loadModel('Article');
$recentArticles = $this->Article->find('all', array('limit' => 5, 'order' => 'Article.created DESC'));

$this->loadModel('User', 2);
$user = $this->User->read();

Atributos do Controller

Para uma completa lista dos atributos dos controllers e suas descrições, visite a API do CakePHP. Siga para https://api.cakephp.org/2.x/class-Controller.html.

property Controller::$name

O atributo $name deve ser definido com o nome do controller. Normalmente é apenas a forma plural do nome do model principal que o controller usa. Esta propriedade não é requerida mas salva o CakePHP de ter que flexionar o nome do model para chegar no valor correto:

# Exemplo de uso do atributo $name do controller

class RecipesController extends AppController {
   public $name = 'Recipes';
}

$components, $helpers e $uses

Os seguintes atributos do controller usados com mais frequência dizem ao CakePHP quais helpers, componentes e models você irá usar em conjunto com o controller corrente. Usar estes atributos faz com que as classes MVC dadas por $components e $uses sejam disponibilizadas como atributos no controller (por exemplo, $this->ModelName) e os dados por $helpers disponibilizados como referências para objetos apropriados ($this->{$helpername}) na view.

Nota

Cada controller possui algumas destas classes disponibilizadas por padrão, então você pode nem ao menos precisar de configurar estes atributos.

property Controller::$uses

Os controllers possuem acesso ao seu model principal por padrão. Nosso controller Recipes terá a classe model Recipe disposta em $this->Recipe e nosso controller Products também apresenta o model Product em $this->Product. Porém, quando for permitir um controller acessar models adicionais pela configuração do atributo $uses, o nome do model do controller atual deve também estar incluso. Este caso é ilustrado no exemplo logo abaixo.

Se você não quiser usar um model em seu controller, defina o atributo como um array vazio (public $uses = array()). Isto lhe permitirá usar um controller sem a necessidade de um arquivo model correspondente.

property Controller::$helpers

Os helpers Html, Form e Session são disponibilizados por padrão como é feito o SessionComponent. Mas se você escolher definir seu próprio array de $helpers no AppController, tenha certeza de incluir o Html e o Form se quiser que eles continuem a estar disponíveis nos seus controllers. Você também pode passar configurações na declaração de seus helpers. Para aprender mais sobre estas classes, visite suas respectivas seções mais tarde neste manual.

Vamos ver como dizer para um controller do Cake que você planeja usar classes MVC adicionais:

class RecipesController extends AppController {
    public $uses = array('Recipe', 'User');
    public $helpers = array('Js');
    public $components = array('RequestHandler');
}

Cada uma destas variáveis são mescladas com seus valores herdados, portanto, não é necessário (por exemplo) redeclarar o FormHelper ou qualquer uma das classes que já foram declaradas no seu AppController.

property Controller::$components

O array de componentes permite que você diga ao CakePHP quais Componentes um controller irá usar. Como o $helpers e o $uses, $components são mesclados com os definidos no AppController. E assim como nos $helpers, você pode passar configurações para os componentes. Veja Configurando Componentes para mais informações.

Outros Atributos

Enquanto você pode conferir todos os detalhes de todos os atributos dos controllers na API, existem outros atributos dos controllers que merecem suas próprias seções neste manual.

property Controller::$cacheAction

O atributo $cacheAction é usado para definir a duração e outras informações sobre o cache completo de páginas. Você pode ler mais sobre o caching completo de páginas na documentação do CacheHelper.

property Controller::$paginate

O atributo $paginate é uma propriedade de compatibilidade obsoleta. Usando este atributo, o componente PaginatorComponent será carregado e configurado, no entanto, é recomendado atualizar seu código para usar as configurações normais de componentes:

class ArticlesController extends AppController {
    public $components = array(
        'Paginator' => array(
            'Article' => array(
                'conditions' => array('published' => 1)
            )
        )
    );
}