CakePHP Cookbook Documentation Release 3.x Cake Software Foundation October 15, 2014 Conteúdo 1 2 3 CakePHP num piscar de olhos Convenções Sobre Configuração . A camada Model . . . . . . . . . A camada View . . . . . . . . . . A camada Controller . . . . . . . Ciclo de Requisições do CakePHP Apenas o Começo . . . . . . . . . Leitura adicional . . . . . . . . . . . . . . . . . . . . . . . Guia de Início Rápido Blog Tutorial . . . . . . . . . . . . . Getting CakePHP . . . . . . . . . . . Directory Permissions on tmp and logs Creating the Blog Database . . . . . . Database Configuration . . . . . . . . Optional Configuration . . . . . . . . A Note on mod_rewrite . . . . . . . . Blog Tutorial - Adding a Layer . . . . Create an Article Model . . . . . . . . Create the Articles Controller . . . . . Creating Article Views . . . . . . . . Adding Articles . . . . . . . . . . . . Data Validation . . . . . . . . . . . . Editing Articles . . . . . . . . . . . . Deleting Articles . . . . . . . . . . . Routes . . . . . . . . . . . . . . . . . Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1 1 2 2 3 4 4 . . . . . . . . . . . . . . . . . 11 11 11 12 13 13 14 14 15 15 15 16 18 20 21 23 24 24 Instalação 27 Requisitos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 i Instalando o CakePHP . . . Permissões . . . . . . . . . Servidor de Desenvolvimento Produção . . . . . . . . . . Aquecendo . . . . . . . . . Reescrita de URL . . . . . . 4 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 28 29 30 30 31 Configuration Configuring your Application . . . . . . . Additional Class Paths . . . . . . . . . . Inflection Configuration . . . . . . . . . . Configure Class . . . . . . . . . . . . . . Reading and writing configuration files . . Creating your Own Configuration Engines Built-in Configuration Engines . . . . . . Bootstrapping CakePHP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 37 39 40 40 42 44 45 46 Routing Quick Tour . . . . . . . . . . . . . . Connecting Routes . . . . . . . . . . Creating RESTful Routes . . . . . . . Passed Arguments . . . . . . . . . . . Generating URLs . . . . . . . . . . . Redirect Routing . . . . . . . . . . . Custom Route Classes . . . . . . . . Handling Named Parameters in URLs RequestActionTrait . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 47 48 57 59 60 61 61 62 62 . . . . . . . . . . . . . . . . . . 6 Request and Response Objects 65 Request . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 Response . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 7 Controllers A Classe AppController . . . . . . . . . . . . . . Parâmetros de Requisição . . . . . . . . . . . . . Ações de Controllers . . . . . . . . . . . . . . . Ciclo de Vida dos Callbacks em uma Requisição . Métodos dos Controllers . . . . . . . . . . . . . Atributos do Controller . . . . . . . . . . . . . . Mais sobre Controllers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 79 80 80 81 81 88 90 Views View Templates . . . . . . . . . . Usando Blocos de Views (Visões) Layouts . . . . . . . . . . . . . . Elements . . . . . . . . . . . . . . View API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141 141 143 145 147 150 8 9 ii Bibliotecas Centrais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153 General Purpose (Uso Geral) Behaviors (Comportamentos) Components (Componentes) Helpers (Auxiliares) . . . . . Utilities (Utilitários) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153 153 153 153 153 10 Plugins Instalando um Plugin . . . . . . . . Usando um Plugin . . . . . . . . . . Criando Seus Próprios Plugins . . . Plugin Controllers . . . . . . . . . . Plugin Models . . . . . . . . . . . . Plugin Views . . . . . . . . . . . . Imagens de Plugin, CSS e Javascript Components, Helpers e Behaviors . Expanda seu Plugin . . . . . . . . . Plugin Dicas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155 155 156 157 158 158 159 160 160 161 161 11 Desenvolvimento Sessions . . . . . . . . . . . Error & Exception Handling Debugging . . . . . . . . . . Testing . . . . . . . . . . . . REST . . . . . . . . . . . . Dispatcher Filters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163 163 169 177 180 202 204 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 Implementação 209 Definindo a Raiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209 Atualizar o core.php . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209 Multiplas aplicações usando o mesmo core do CakePHP . . . . . . . . . . . . . . . . . . . . . . 210 13 Apêndices 211 Guia de Migração para a versão 3.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211 Informações Gerais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245 14 Índices e Tabelas 249 PHP Namespace Index 251 Index 253 iii iv CAPÍTULO 1 CakePHP num piscar de olhos O CakePHP é concebido para tornar tarefas de desenvolvimento web mais simples e fáceis. Por fornecer uma caixa de ferramentas completa para você poder começar, o CakePHP funciona bem em conjunto ou isoladamente. O objetivo desta análise é introduzir os conceitos gerais presentes no CakePHP, e lhe dar uma rápida visão geral de como estes conceitos são implementados. Se você está ávido para começar um projeto, você pode começar com o tutorial, ou mergulhar na documentação. Convenções Sobre Configuração O CakePHP provê uma estrutura organizacional básica que cobre nomenclaturas de classes, nomenclaturas de arquivos, nomenclaturas de banco de dados, e outras convenções. Apesar das convenções levarem algum tempo para serem assimiladas, ao seguí-las o CakePHP evita configuração desnecessário e cria uma estrutura de aplicação uniforme que faz trabalhar com vários projetos uma tarefa suave. O capítulo de convenções cobre as variadas convenções que o CakePHP utiliza. A camada Model A camada Model representa a parte da sua aplicação que implementa a lógica de negócio. Ela é responsável por recuperar dados e convertê-los nos conceitos significativos primários na sua aplicação. Isto inclui processar, validar, associar ou qualquer outra tarefa relacionada à manipulação de dados. No caso de uma rede social, a camada Model deveria tomar cuidado de tarefas como salvar os dados do usuário, salvar as associações entre amigos, salvar e recuperar fotos de usuários, localizar sugestões para novos amigos, etc. Os objetos de modelo podem ser pensados como “Friend”, “User”, “Comment”, ou “Photo”. Se nós quiséssemos carregar alguns dados da nossa tabela users poderiamos fazer: use Cake\ORM\TableRegistry; $users = TableRegistry::get(’Users’); $query = $users->find(); 1 CakePHP Cookbook Documentation, Release 3.x foreach ($query as $row) { echo $row->username; } Você pode notar que não precisamos escrever nenhum código antes de podermos começar a trabalhar com nossos dados. Por usar convenções, o CakePHP irá utilizar classes padrão para tabelas e entidades ainda não definidas. Se nós quiséssemos criar um usuário e salvá-lo (com validação) fariamos algo assim: use Cake\ORM\TableRegistry; $users = TableRegistry::get(’Users’); $user = $users->newEntity([’email’ => ’[email protected]’]); $users->save($user); A camada View A View renderiza uma apresentação de dados modelados. Estando separada dos objetos da Model, é responsável por utilizar a informação que tem disponível para produzir qualquer interface de apresentação que a sua aplicação possa precisar. Por exemplo, a view pode usar dados da model para renderizar uma página HTML que os conhtenha, ou um resultado formatado como XML: // No arquivo view, nós renderizaremos um ’elemento’ para cada usuário. <?php foreach ($users as $user): ?> <div class="user"> <?= $this->element(’user’, [’user’ => $user]) ?> </div> <?php endforeach; ?> A camada View provê alguma variedade de extensões como Elements e /views/cells para permitir que você reutilize sua lógica de apresentação. A camada View não está limitada somente a HTML ou apresentação textual dos dados. Ela pode ser usada para entregar formatos de dado comuns como JSON, XML, e através de uma arquitetura encaixável qualquer outro formato que você venha precisar. A camada Controller A camada Controller manipula requisições dos usuários. É responsável por renderizar uma resposta com o auxílio de ambas as camadas, Model e View respectivamente. Um controller pode ser visto como um gerente que certifica-se que todos os recursos necessários para completar uma tarefa sejam delegados aos trabalhadores corretos. Ele aguarda por petições dos clientes, checa suas validades de acordo com autenticação ou regras de autorização, delega requisições ou processamento de dados da camada Model, selecciona o tipo de dados de apresentação que os clientes estão aceitando, e 2 Capítulo 1. CakePHP num piscar de olhos CakePHP Cookbook Documentation, Release 3.x finalmente delega o processo de renderização para a camada View. Um exemplo de controller para registro de usuário seria: public function add() { $user = $this->Users->newEntity(); if ($this->request->is(’post’)) { $user = $this->Users->patchEntity($user, $this->request->data); if ($this->Users->save($user, [’validate’ => ’registration’])) { $this->Flash->success(__(’Você está registrado.’)); } else { $this->Flash->error(__(’Houve algum problema.’)); } } $this->set(’user’, $user); } Você pode perceber que nós nunca renderizamos uma view explicitamente. As convenções do CakePHP tomarão cuidado de selecionar a view correta e renderizá-la como os dados definidos com set(). Ciclo de Requisições do CakePHP Agora que você é familiar com as diferentes camadas no CakePHP, vamos revisar como um cíclo de requisição funciona no CakePHP: O cíclo de requisição típico do CakePHP começa com um usuário solicitando uma página ou recurso na sua Ciclo de Requisições do CakePHP 3 CakePHP Cookbook Documentation, Release 3.x aplicação. Em alto nível cada requisição vai através dos seguintes passos: 1. A requisição é primeiramente processada pela suas rotas. 2. Depois da requisição ter sido roteada, o despachante irá selecionar o objeto de controller correto para manipulá-la. 3. A action do controller é chamada e o controller interage com os models e components requisitados. 4. O controller delega a criação de resposta à view para gerar os dados de saída resultantes dos dados do model. Apenas o Começo Esperamos que essa rápida visão geral tenha despertado seu interesse. Alguns outros grandes recursos no CakePHP são: • Framework de cache que integra com Memcache, Redis e outros backends. • Poderosas ferramentas de geração de código para você sair em disparada. • Framework de teste integrado para você assegurar-se que seu código funciona perfeitamente. Os próximos passos óbvios são baixar o CakePHP, ler o tutorial e construir algo fantástico. Leitura adicional Onde Conseguir Ajuda O website oficial do CakePHP http://www.cakephp.org O website oficial do CakePHP é sempre um ótimo lugar para visitar. Ele provê links para ferramentas comunmente utilizadas por desenvolvedores, screencasts, oportunidades de doação e downloads. O Cookbook http://book.cakephp.org Esse manual deveria ser o primeiro lugar para onde você iria afim de conseguir respostas. Assim como muitos outros projetos de código aberto, nós conseguimos novos colaboradores regularmente. Tente o seu melhor para responder suas questões por si só. Respostas vão vir lentamente, e provavelmente continuarão longas. Você pode suavizar nossa carga de suporte. Tanto o manual quanto a API possuem um componente online. 4 Capítulo 1. CakePHP num piscar de olhos CakePHP Cookbook Documentation, Release 3.x A Bakery http://bakery.cakephp.org A “padaria” do CakePHP é um local para todas as coisas relacionadas ao CakePHP. Visite-a para tutoriais, estudos de caso e exemplos de código. Uma vez que você tenha se familiarizado com o CakePHP, autentique-se e compartilhe seu conhecimento com a comunidade, ganhe instantaneamente fama e fortuna. A API http://api.cakephp.org/ Diretamente ao ponto, dos desenvolvedores do núcleo do CakePHP, a API (Application Programming Interface) do CakePHP é a mais compreensiva documentação sobre os detalhes técnicos e minuciosos sobre do funcionamento interno do framework. Os Testes de Caso Se você sente que a informação provida pela API não é suficiente, verifique os códigos de testes de caso do CakePHP. Eles podem servir como exemplos práticos para funções e e utilização de dados referentes a uma classe.: tests/TestCase/ O canal de IRC Canal de IRC na irc.freenode.net: • #cakephp – Discussão geral • #cakephp-docs – Documentação • #cakephp-bakery – Bakery • #cakephp-fr – Canal francês. Se você está travado, nos faça uma visita no canal de IRC do CakePHP. Alguém do time de desenvolvimento1 normalmente está conectado, especiamente nos horários diurnos da América do Sul e América do Norte. Nós apreciaríamos ouví-lo se você precisar de ajuda, se quiser encontrar usuários da sua área ou ainda se quiser doar seu novo carro esporte. Grupo oficial de discussão do CakePHP Grupo de discussão do Google2 1 2 https://github.com/cakephp?tab=members http://groups.google.com/group/cake-php Leitura adicional 5 CakePHP Cookbook Documentation, Release 3.x O CakePHP também possui seu grupo de discussão oficial no Google Grupos. Existem milhares de pessoas discutindo projetos CakePHP, ajudando uns aos outros, resolvendo problemas, construindo projetos e compartilhando idéias. Pode ser uma grande fonte para encontrar respostas arquivadas, perguntas frequentes e conseguir respostas para problemas imediatos. Junte-se a outros usuários do CakePHP e comece a conversar. Stackoverflow http://stackoverflow.com/3 Marque suas questões com a tag cakephp e especifique a versão que você está utilizando para permitir que usuários do stackoverflow achem suas questões. Onde conseguir ajuda em sua língua Francês • Comunidade CakePHP francesa4 Convenções do CakePHP Nós somos grandes fãs de convenção sobre configuração. Apesar de levar um pouco de tempo para aprender as convenções do CakePHP, você economiza tempo a longo prazo. Ao seguir as convenções, você ganha funcionalidades instantaneamente e liberta-se do pesadelo de manutenção e rastreamento de arquivos de configuração. Convenções também prezam por uma experiência de desenvolvimento uniforme, permitindo que outros desenvolvedores ajudem mais facilmente. Convenções para Controllers Os nomes das classes de Controllers são pluralizados, CamelCased, e terminam em Controller. PeopleController e LatestArticlesController são exemplos de nomes convencionais para controllers. Métodos públicos nos Controllers são frequentemente referenciados como ‘actions’ acessíveis através de um navegador web. Por exemplo, o /articles/view mapeia para o método view() do ArticlesController sem nenhum esforço. Métodos privados ou protegidos não podem ser acessados pelo roteamento. Considerações de URL para nomes de Controller Como você acabou de ver, controllers singulares mapeiam facilmente um caminho simples, todo em minúsculo. Por exemplo, ApplesController (o qual deveria ser definido no arquivo de nome ‘ApplesController.php’) é acessado por http://example.com/apples. 3 4 6 http://stackoverflow.com/questions/tagged/cakephp/ http://cakephp-fr.org Capítulo 1. CakePHP num piscar de olhos CakePHP Cookbook Documentation, Release 3.x Controllers com múltiplas palavras podem estar em qualquer forma ‘flexionada’ igual ao nome do controller, então: • /redApples • /RedApples • /Red_apples • /red_apples Todos resolverão para o index do controller RedApples. Porém, a forma correta é que suas URLs sejam minúsculas e separadas por sublinhado, portanto /red_apples/go_pick é a forma correta de acessar a action RedApplesController::go_pick. Para mais informações sobre o manuseio de URLs e parâmetros do CakePHP, veja Connecting Routes. Convenções para nomes de Classes e seus nomes de arquivos No geral, nomes de arquivos correspondem aos nomes das classes, e seguem os padrões PSR-0 ou PSR-4 para auto-carregamento. A seguir seguem exemplos de nomes de classes e de seus arquivos: • A classe de Controller KissesAndHugsController deveria ser encontrada em um arquivo nomeado KissesAndHugsController.php • A classe de Component MyHandyComponent deveria ser encontrada em um arquivo nomeado MyHandyComponent.php • A classe de Table OptionValuesTable deveria ser encontrada em um arquivo nomeado OptionValuesTable.php. • A classe de Entity OptionValue deveria ser encontrada em um arquivo nomeado OptionValue.php. • A classe de Behavior EspeciallyFunkableBehavior deveria ser encontrada em um arquivo nomeado EspeciallyFunkableBehavior.php • A classe de View SuperSimpleView deveria ser encontrada em um arquivo nomeado SuperSimpleView.php • A classe de Helper BestEverHelper deveria ser encontrada em um arquivo nomeado BestEverHelper.php Cada arquivo deveria estar localizado no diretório/namespace apropriado de sua aplicação. Convenções para Models e Databases Os nomes de classe de Tables são pluralizadas e CamelCased. People, BigPeople, and ReallyBigPeople são todos exemplos convencionais de models. Os nomes de Tables correspondentes aos models do CakePHP são pluralizadas e separadas por sublinhado. As tables sublinhadas para os models mencionados acima seriam people, big_people, e really_big_people, respectively. Leitura adicional 7 CakePHP Cookbook Documentation, Release 3.x Você pode utilizar a biblioteca utility Cake\Utility\Inflector para checar o singular/plural de palavras. Veja o /core-utility-libraries/inflector para mais informações. Recomenda-se que as tables sejam criadas e mantidas na língua inglesa. Campos com duas ou mais palavras são separados por sublinhado: first_name. Chaves estrangeiras nos relacionamentos hasMany, belongsTo ou hasOne são reconhecidas por padrão como o nome (singular) da table relacionada seguida por _id. Então se Bakers hasMany Cakes, a table cakes irá referenciar-se para a table bakers através da chave estrangeira baker_id. Para uma tabela como category_types a qual o nome contém mais palavras, a chave estrangeira seria a category_type_id. tables de união, usadas no relacionamento BelongsToMany entre models, devem ser nomeadas depois das tables que ela está unindo, ordenadas em ordem alfabética (apples_zebras ao invés de zebras_apples). Convenções para Views Arquivos de template views são nomeadas seguindo as funções que a exibem do controller, separadas por sublinhado. A função getReady() da classe PeopleController buscará por um template view em src/Template/People/get\_ready.ctp. O padrão é src/Template/Controller/underscored\_function\_name.ctp. Por nomear as partes de sua aplicação utilizando as convenções do CakePHP, você ganha funcionalidades sem luta e sem amarras de configuração. Aqui está um exemplo final que enlaça as convenções juntas: • Table: “people” • Classe Table: “PeopleTable”, encontrada em src/Model/Table/PeopleTable.php • Classe Entity: “Person”, encontrada em src/Model/Entity/Person.php • Classe Controller: “PeopleController”, encontrada em src/Controller/PeopleController.php • View template, encontrado em src/Template/People/index.ctp Utilizando estas convenções, o CakePHP sabe que uma requisição para http://example.com/people/ mapeia para uma chamada da função index() do PeopleController, onde o model Person é automaticamente disponbilizado (e automaticamente amarrado à table ‘people’ no banco de dados), e então renderiza-se um arquivo view template. Nenhuma destes relacionamentos foi configurado de qualquer forma se não por criar classes e arquivos que você precisaria criar de qualquer forma. Agora que você foi introduzido aos fundamentos do CakePHP, você pode tentar seguir através do /tutorials-and-examples/blog/blog para ver como as coisas se encaixam juntas. Estrutura de pastas do CakePHP Depois de você ter baixado e extraído o CakePHP, aí estão os arquivos e pastas que você deve ver: • src • config • tests • plugins 8 Capítulo 1. CakePHP num piscar de olhos CakePHP Cookbook Documentation, Release 3.x • tmp • vendor • webroot • .htaccess • composer.json • index.php • README.md Você notará alguns diretórios principais: • O diretório src será onde você fará sua mágica: é onde os arquivos da sua aplicação serão colocados. • O diretório config contem os (poucos) Configuration arquivos de configuração que o CakePHP utiliza. Detalhes de conexão com banco de dados, inicialização, arquivos de configuração do núcleo da aplicação, e relacionados devem ser postos aqui. • O diretório tests será onde você colocará os testes de caso para sua aplicação. • O diretório plugins será onde Plugins que sua aplicação utiliza serão armazenados. • O diretório vendor será onde o CakePHP e outras dependências da aplicação serão instalados. Faça uma nota pessoal para não editar arquivos deste diretório. Nós não podemos ajudar se você tivé-lo feito. • O diretório webroot será a raíz pública de documentos da sua aplicação. Ele contem todos os arquivos que você gostaria que fossem públicos. • O diretório tmp será onde o CakePHP armazenará dados temporários. O modo como os dados serão armazenados depende da configuração do CakePHP, mas esse diretório é comunmente usado para armazenar descrições de modelos e algumas vezes informação de sessão. • O diretório logs será normalmente onde seus arquivos de log ficarão, dependendo das suas configurações. Certifique-se que os diretórios tmp e logs existem e são passíveis de escrita, senão a performance de sua aplicação será severamente impactada. Em modo de debug, o CakePHP irá alertá-lo se este for o caso. O diretório src O diretório src do CakePHP é onde você fará a maior parte do desenvolvimento de sua aplicação. Vamos ver mais de perto a estrutura de pastas dentro de src. Console Contém os comandos e tarefas de console para sua aplicação. Para mais informações veja <no title>. Controller Contém os controllers de sua aplicação e seus componentes. Locale Armazena arquivos textuais para internacionalização. Model Contém as tables, entities e behaviors de sua aplicação. Leitura adicional 9 CakePHP Cookbook Documentation, Release 3.x View Classes de apresentação são alocadas aqui: cells, helpers, e arquivos view. Template Arquivos de apresentação são alocados aqui: elements, páginas de erro, layouts, e templates view. 10 Capítulo 1. CakePHP num piscar de olhos CAPÍTULO 2 Guia de Início Rápido A melhor forma de viver experiências e aprender sobre CakePHP é sentar e construir algo. Para começar nós iremos construir uma aplicação simples de blog. Blog Tutorial This tutorial will walk you through the creation of a simple blog application. We’ll be installing CakePHP, creating a database, and creating enough application logic to list, add, edit, and delete blog posts. Here’s what you’ll need: 1. A running web server. We’re going to assume you’re using Apache, though the instructions for using other servers should be very similar. We might have to play a little with the server configuration, but most folks can get CakePHP up and running without any configuration at all. Make sure you have PHP 5.4.16 or greater, and that the mbstring, intl and mcrypt extensions are enabled in PHP. 2. A database server. We’re going to be using MySQL server in this tutorial. You’ll need to know enough about SQL in order to create a database: CakePHP will be taking the reins from there. Since we’re using MySQL, also make sure that you have pdo_mysql enabled in PHP. 3. Basic PHP knowledge. Let’s get started! Getting CakePHP The easiest way to install CakePHP is to use Composer. Composer is a simple way of installing CakePHP from your terminal or command line prompt. First, you’ll need to download and install Composer if you haven’t done so already. If you have cURL installed, it’s as easy as running the following: curl -s https://getcomposer.org/installer | php 11 CakePHP Cookbook Documentation, Release 3.x Or, you can download composer.phar from the Composer website1 . Then simply type the following line in your terminal from your installation directory to install the CakePHP application skeleton in the [app_name] directory.: php composer.phar create-project --prefer-dist -s dev cakephp/app [app_name] The advantage to using Composer is that it will automatically complete some important set up tasks, such as setting the correct file permissions and creating your config/app.php file for you. There are other ways to install CakePHP. If you cannot or don’t want to use Composer, check out the Instalação section. Regardless of how you downloaded and installed CakePHP, once your set up is completed, your directory setup should look something like the following: /cake_install /config /logs /plugins /src /tests /tmp /vendor /webroot .gitignore .htaccess .travis.yml composer.json index.php phpunit.xml.dist README.md Now might be a good time to learn a bit about how CakePHP’s directory structure works: check out the Estrutura de pastas do CakePHP section. Directory Permissions on tmp and logs The tmp and logs directories need to have proper permissions to be writable by your webserver. If you used Composer for the install, this should have been done for you and confirmed with a “Permissions set on <folder>” message. If you instead got an error message or want to do it manually, the best way would be to find out what user your webserver runs as (<?= ‘whoami‘; ?>) and change the ownership of these two directories to that user. The final command you run (in *nix) might look something like this: chown -R www-data tmp chown -R www-data logs If for some reason CakePHP can’t write to these directories, you’ll be informed by a warning while not in production mode. 1 12 https://getcomposer.org/download/ Capítulo 2. Guia de Início Rápido CakePHP Cookbook Documentation, Release 3.x While not recommended, if you are unable to set the permissions to the same as your webserver, you can simply set write permissions on the folder by running a command such as: chmod 777 -R tmp chmod 777 -R logs Creating the Blog Database Next, let’s set up the underlying MySQL database for our blog. If you haven’t already done so, create an empty database for use in this tutorial, with a name of your choice, e.g. cake_blog. Right now, we’ll just create a single table to store our articles. We’ll also throw in a few articles to use for testing purposes. Execute the following SQL statements into your database: /* First, create our articles table: */ CREATE TABLE articles ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, title VARCHAR(50), body TEXT, created DATETIME DEFAULT NULL, modified DATETIME DEFAULT NULL ); /* Then insert some articles for testing: */ INSERT INTO articles (title,body,created) VALUES (’The title’, ’This is the article body.’, NOW()); INSERT INTO articles (title,body,created) VALUES (’A title once again’, ’And the article body follows.’, NOW()); INSERT INTO articles (title,body,created) VALUES (’Title strikes back’, ’This is really exciting! Not.’, NOW()); The choices on table and column names are not arbitrary. If you follow CakePHP’s database naming conventions, and CakePHP’s class naming conventions (both outlined in Convenções do CakePHP), you’ll be able to take advantage of a lot of free functionality and avoid configuration. CakePHP is flexible enough to accommodate even inconsistent legacy database schemas, but adhering to the conventions will save you time. Check out Convenções do CakePHP for more information, but it’s suffice to say that naming our table ‘articles’ automatically hooks it to our Articles model, and having fields called ‘modified’ and ‘created’ will be automatically managed by CakePHP. Database Configuration Next, let’s tell CakePHP where our database is and how to connect to it. For many, this will be the first and last time you will need to configure anything. The configuration should be pretty straightforward: just replace the values in the Datasources.default array in the config/app.php file with those that apply to your setup. A sample completed configuration array might look something like the following: Creating the Blog Database 13 CakePHP Cookbook Documentation, Release 3.x $config = [ // More configuration above. ’Datasources’ => [ ’default’ => [ ’className’ => ’Cake\Database\Connection’, ’driver’ => ’Cake\Database\Driver\Mysql’, ’persistent’ => false, ’host’ => ’localhost’, ’login’ => ’cake_blog’, ’password’ => ’AngelF00dC4k3~’, ’database’ => ’cake_blog’, ’prefix’ => false, ’encoding’ => ’utf8’, ’timezone’ => ’UTC’ ], ], // More configuration below. ]; Once you’ve saved your config/app.php file, you should be able to open your browser and see the CakePHP welcome page. It should also tell you that your database connection file was found, and that CakePHP can successfully connect to the database. Note: A copy of CakePHP’s default configuration file is found in config/app.default.php. Optional Configuration There are a few other items that can be configured. Most developers complete these laundry-list items, but they’re not required for this tutorial. One is defining a custom string (or “salt”) for use in security hashes. The security salt is used for generating hashes. If you used Composer this too is taken care of for you during the install. Else you’d need to change the default salt value by editing config/app.php. It doesn’t matter much what the new value is, as long as it’s not easily guessed: ’Security’ => [ ’salt’ => ’something long and containing lots of different values.’, ], A Note on mod_rewrite Occasionally new users will run into mod_rewrite issues. For example if the CakePHP welcome page looks a little funny (no images or CSS styles). This probably means mod_rewrite is not functioning on your system. Please refer to the Reescrita de URL section to help resolve any issues you are having. Now continue to /tutorials-and-examples/blog/part-two to start building your first CakePHP application. 14 Capítulo 2. Guia de Início Rápido CakePHP Cookbook Documentation, Release 3.x Blog Tutorial - Adding a Layer Create an Article Model Models are the bread and butter of CakePHP applications. By creating a CakePHP model that will interact with our database, we’ll have the foundation in place needed to do our view, add, edit, and delete operations later. CakePHP’s model class files are split between Table and Entity objects. Table objects provide access to the collection of entities stored in a specific table and go in src/Model/Table. The file we’ll be creating will be saved to src/Model/Table/ArticlesTable.php. The completed file should look like this: namespace App\Model\Table; use Cake\ORM\Table; class ArticlesTable extends Table { public function initialize(array $config) { $this->addBehavior(’Timestamp’); } } Naming conventions are very important in CakePHP. By naming our Table object ArticlesTable, CakePHP can automatically infer that this Table object will be used in the ArticlesController, and will be tied to a database table called articles. Note: CakePHP will dynamically create a model object for you if it cannot find a corresponding file in src/Model/Table. This also means that if you accidentally name your file wrong (i.e. articlestable.php or ArticleTable.php), CakePHP will not recognize any of your settings and will use the a generated model instead. For more on models, such as callbacks, and validation, check out the <no title> chapter of the Manual. Create the Articles Controller Next, we’ll create a controller for our articles. The controller is where all interaction with articles will happen. In a nutshell, it’s the place where you play with the business logic contained in the models and get work related to articles done. We’ll place this new controller in a file called ArticlesController.php inside the src/Controller directory. Here’s what the basic controller should look like: namespace App\Controller; class ArticlesController extends AppController { } Now, let’s add an action to our controller. Actions often represent a single function or interface in an application. For example, when users request www.example.com/articles/index (which is also the same as Blog Tutorial - Adding a Layer 15 CakePHP Cookbook Documentation, Release 3.x www.example.com/articles/), they might expect to see a listing of articles. The code for that action would look like this: namespace App\Controller; class ArticlesController extends AppController { public function index() { $articles = $this->Articles->find(’all’); $this->set(compact(’articles’)); } } By defining function index() in our ArticlesController, users can now access the logic there by requesting www.example.com/articles/index. Similarly, if we were to define a function called foobar(), users would be able to access that at www.example.com/articles/foobar. Warning: You may be tempted to name your controllers and actions a certain way to obtain a certain URL. Resist that temptation. Follow CakePHP conventions (capitalization, plural names, etc.) and create readable, understandable action names. You can map URLs to your code using “routes” covered later on. The single instruction in the action uses set() to pass data from the controller to the view (which we’ll create next). The line sets the view variable called ‘articles’ equal to the return value of the find(’all’) method of the Articles table object. To learn more about CakePHP’s controllers, check out the Controllers chapter. Creating Article Views Now that we have our data flowing from our model, and our application logic is defined by our controller, let’s create a view for the index action we created above. CakePHP views are just presentation-flavored fragments that fit inside an application’s layout. For most applications, they’re HTML mixed with PHP, but they may end up as XML, CSV, or even binary data. A layout is presentation code that is wrapped around a view. Multiple layouts can be defined, and you can switch between them, but for now, let’s just use the default. Remember in the last section how we assigned the ‘articles’ variable to the view using the set() method? That would hand down the query object to the view to be invoked with a foreach iteration. CakePHP’s view files are stored in src/Template inside a folder named after the controller they correspond to (we’ll have to create a folder named ‘Articles’ in this case). To format this article data in a nice table, our view code might look something like this: <!-- File: src/Template/Articles/index.ctp --> <h1>Blog articles</h1> <table> <tr> 16 Capítulo 2. Guia de Início Rápido CakePHP Cookbook Documentation, Release 3.x <th>Id</th> <th>Title</th> <th>Created</th> </tr> <!-- Here is where we iterate through our $articles query object, printing out article <?php foreach ($articles as $article): ?> <tr> <td><?= $article->id ?></td> <td> <?= $this->Html->link($article->title, [’controller’ => ’Articles’, ’action’ => ’view’, $article->id]) ?> </td> <td><?= $article->created->format(DATE_RFC850) ?></td> </tr> <?php endforeach; ?> </table> Hopefully this should look somewhat simple. You might have noticed the use of an object called $this->Html. This is an instance of the CakePHP Cake\View\Helper\HtmlHelper class. CakePHP comes with a set of view helpers that make things like linking, form output a snap. You can learn more about how to use them in /views/helpers, but what’s important to note here is that the link() method will generate an HTML link with the given title (the first parameter) and URL (the second parameter). When specifying URLs in CakePHP, it is recommended that you use the array format. This is explained in more detail in the section on Routes. Using the array format for URLs allows you to take advantage of CakePHP’s reverse routing capabilities. You can also specify URLs relative to the base of the application in the form of /controller/action/param1/param2 or use Using Named Routes. At this point, you should be able to point your browser to http://www.example.com/articles/index. You should see your view, correctly formatted with the title and table listing of the articles. If you happened to have clicked on one of the links we created in this view (that link a article’s title to a URL /articles/view/some\_id), you were probably informed by CakePHP that the action hasn’t yet been defined. If you were not so informed, either something has gone wrong, or you actually did define it already, in which case you are very sneaky. Otherwise, we’ll create it in the ArticlesController now: namespace App\Controller; use Cake\Network\Exception\NotFoundException; class ArticlesController extends AppController { public function index() { $this->set(’articles’, $this->Articles->find(’all’)); } public function view($id = null) { if (!$id) { throw new NotFoundException(__(’Invalid article’)); Creating Article Views 17 CakePHP Cookbook Documentation, Release 3.x } $article = $this->Articles->get($id); $this->set(compact(’article’)); } } The set() call should look familiar. Notice we’re using get() rather than find(’all’) because we only really want a single article’s information. Notice that our view action takes a parameter: the ID of the article we’d like to see. This parameter is handed to the action through the requested URL. If a user requests /articles/view/3, then the value ‘3’ is passed as $id. We also do a bit of error checking to ensure a user is actually accessing a record. If a user requests /articles/view, we will throw a NotFoundException and let the ErrorHandler take over. By using the get() function in the Articles table, we also perform a similar check to make sure the user has accessed a record that exists. In case the requested article is not present in the database, the get() function will throw a NotFoundException. Now let’s create the view for src/Template/Articles/view.ctp our new ‘view’ action and place it in <!-- File: src/Template/Articles/view.ctp --> <h1><?= h($article->title) ?></h1> <p><?= h($article->body) ?></p> <p><small>Created: <?= $article->created->format(DATE_RFC850) ?></small></p> Verify that this is working by trying the links at /articles/index or manually requesting an article by accessing /articles/view/1. Adding Articles Reading from the database and showing us the articles is a great start, but let’s allow for the adding of new articles. First, start by creating an add() action in the ArticlesController: namespace App\Controller; use Cake\Network\Exception\NotFoundException; class ArticlesController extends AppController { public $components = [’Flash’]; public function index() { $this->set(’articles’, $this->Articles->find(’all’)); } public function view($id) { if (!$id) { throw new NotFoundException(__(’Invalid article’)); } 18 Capítulo 2. Guia de Início Rápido CakePHP Cookbook Documentation, Release 3.x $article = $this->Articles->get($id); $this->set(compact(’article’)); } public function add() { $article = $this->Articles->newEntity($this->request->data); if ($this->request->is(’post’)) { if ($this->Articles->save($article)) { $this->Flash->success(__(’Your article has been saved.’)); return $this->redirect([’action’ => ’index’]); } $this->Flash->error(__(’Unable to add your article.’)); } $this->set(’article’, $article); } } Note: You need to include the FlashComponent in any controller where you will use it. If necessary, include it in your AppController. Here’s what the add() action does: if the HTTP method of the request was POST, try to save the data using the Articles model. If for some reason it doesn’t save, just render the view. This gives us a chance to show the user validation errors or other warnings. Every CakePHP request includes a Request object which is accessible using $this->request. The request object contains useful information regarding the request that was just received, and can be used to control the flow of your application. In this case, we use the Cake\Network\Request::is() method to check that the request is a HTTP POST request. When a user uses a form to POST data to your application, that information is available in $this->request->data. You can use the pr() or debug() functions to print it out if you want to see what it looks like. We use FlashComponent’s magic __call method to set a message to a session variable, which will be displayed on the page after redirection. In the layout we have <?= $this->Flash->render() ?> which displays the message and clears the corresponding session variable. The controller’s Cake\Controller\Controller::redirect function redirects to another URL. The param [’action’ => ’index’] translates to URL /articles i.e the index action of the articles controller. You can refer to Cake\Routing\Router::url() function on the API2 to see the formats in which you can specify a URL for various CakePHP functions. Calling the save() method will check for validation errors and abort the save if any occur. We’ll discuss how those errors are handled in the following sections. 2 http://api.cakephp.org Adding Articles 19 CakePHP Cookbook Documentation, Release 3.x Data Validation CakePHP goes a long way toward taking the monotony out of form input validation. Everyone hates coding up endless forms and their validation routines. CakePHP makes it easier and faster. To take advantage of the validation features, you’ll need to use CakePHP’s FormHelper in your views. The Cake\View\Helper\FormHelper is available by default to all views at $this->Form. Here’s our add view: <!-- File: src/Template/Articles/add.ctp --> <h1>Add Article</h1> <?php echo $this->Form->create($article); echo $this->Form->input(’title’); echo $this->Form->input(’body’, [’rows’ => ’3’]); echo $this->Form->button(__(’Save Article’)); echo $this->Form->end(); ?> We use the FormHelper to generate the opening tag for an HTML form. $this->Form->create() generates: Here’s the HTML that <form method="post" action="/articles/add"> If create() is called with no parameters supplied, it assumes you are building a form that submits via POST to the current controller’s add() action (or edit() action when id is included in the form data). The $this->Form->input() method is used to create form elements of the same name. The first parameter tells CakePHP which field they correspond to, and the second parameter allows you to specify a wide array of options - in this case, the number of rows for the textarea. There’s a bit of introspection and automagic here: input() will output different form elements based on the model field specified. The $this->Form->end() call ends the form. Outputting hidden inputs if CSRF/Form Tampering prevention is enabled. Now let’s go back and update our src/Template/Articles/index.ctp view to include a new “Add Article” link. Before the <table>, add the following line: <?= $this->Html->link( ’Add Article’, [’controller’ => ’Articles’, ’action’ => ’add’] ) ?> You may be wondering: how do I tell CakePHP about my validation requirements? Validation rules are defined in the model. Let’s look back at our Articles model and make a few adjustments: namespace App\Model\Table; use Cake\ORM\Table; use Cake\Validation\Validator; class ArticlesTable extends Table { 20 Capítulo 2. Guia de Início Rápido CakePHP Cookbook Documentation, Release 3.x public function initialize(array $config) { $this->addBehavior(’Timestamp’); } public function validationDefault(Validator $validator) { $validator ->notEmpty(’title’) ->notEmpty(’body’); return $validator; } } The validationDefault() method tells CakePHP how to validate your data when the save() method is called. Here, we’ve specified that both the body and title fields must not be empty. CakePHP’s validation engine is strong, with a number of pre-built rules (credit card numbers, email addresses, etc.) and flexibility for adding your own validation rules. For more information on that setup, check the /core-libraries/validation documentation. Now that your validation rules are in place, use the app to try to add an article with an empty title or body to see how it works. Since we’ve used the Cake\View\Helper\FormHelper::input() method of the FormHelper to create our form elements, our validation error messages will be shown automatically. Editing Articles Post editing: here we go. You’re a CakePHP pro by now, so you should have picked up a pattern. Make the action, then the view. Here’s what the edit() action of the ArticlesController would look like: public function edit($id = null) { if (!$id) { throw new NotFoundException(__(’Invalid article’)); } $article = $this->Articles->get($id); if ($this->request->is([’post’, ’put’])) { $this->Articles->patchEntity($article, $this->request->data); if ($this->Articles->save($article)) { $this->Flash->success(__(’Your article has been updated.’)); return $this->redirect([’action’ => ’index’]); } $this->Flash->error(__(’Unable to update your article.’)); } $this->set(’article’, $article); } This action first ensures that the user has tried to access an existing record. If they haven’t passed in an $id parameter, or the article does not exist, we throw a NotFoundException for the CakePHP ErrorHandler to take care of. Next the action checks whether the request is either a POST or a PUT request. If it is, then we use the POST Editing Articles 21 CakePHP Cookbook Documentation, Release 3.x data to update our article entity by using the ‘patchEntity’ method. Finally we use the table object to save the entity back or kick back and show the user validation errors. The edit view might look something like this: <!-- File: src/Template/Articles/edit.ctp --> <h1>Edit <?php echo echo echo echo echo ?> Article</h1> $this->Form->create($article); $this->Form->input(’title’); $this->Form->input(’body’, [’rows’ => ’3’]); $this->Form->button(__(’Save Article’)); $this->Form->end(); This view outputs the edit form (with the values populated), along with any necessary validation error messages. CakePHP will use the result of $article->isNew() to determine whether or not a save() should insert a new record, or update an existing one. You can now update your index view with links to edit specific articles: <!-- File: src/Template/Articles/index.ctp (edit links added) --> <h1>Blog articles</h1> <p><?= $this->Html->link("Add Article", [’action’ => ’add’]) ?></p> <table> <tr> <th>Id</th> <th>Title</th> <th>Created</th> <th>Action</th> </tr> <!-- Here’s where we iterate through our $articles query object, printing out article info <?php foreach ($articles as $article): ?> <tr> <td><?= $article->id ?></td> <td> <?= $this->Html->link($article->title, [’action’ => ’view’, $article->id]) ?> </td> <td> <?= $article->created->format(DATE_RFC850) ?> </td> <td> <?= $this->Html->link(’Edit’, [’action’ => ’edit’, $article->id]) ?> </td> </tr> <?php endforeach; ?> </table> 22 Capítulo 2. Guia de Início Rápido CakePHP Cookbook Documentation, Release 3.x Deleting Articles Next, let’s make a way for users to delete articles. Start with a delete() action in the ArticlesController: public function delete($id) { $this->request->allowMethod([’post’, ’delete’]); $article = $this->Articles->get($id); if ($this->Articles->delete($article)) { $this->Flash->success(__(’The article with id: {0} has been deleted.’, h($id))); return $this->redirect([’action’ => ’index’]); } } This logic deletes the article specified by $id, and uses $this->Flash->success() to show the user a confirmation message after redirecting them on to /articles. If the user attempts to do a delete using a GET request, the ‘allowMethod’ will throw an Exception. Uncaught exceptions are captured by CakePHP’s exception handler, and a nice error page is displayed. There are many built-in Exceptions that can be used to indicate the various HTTP errors your application might need to generate. Because we’re just executing some logic and redirecting, this action has no view. You might want to update your index view with links that allow users to delete articles, however: <!-- File: src/Template/Articles/index.ctp --> <h1>Blog articles</h1> <p><?= $this->Html->link(’Add Article’, [’action’ => ’add’]) ?></p> <table> <tr> <th>Id</th> <th>Title</th> <th>Created</th> <th>Actions</th> </tr> <!-- Here’s where we loop through our $articles query object, printing out article info --> <?php foreach ($articles as $article): ?> <tr> <td><?= $article->id ?></td> <td> <?= $this->Html->link($article->title, [’action’ => ’view’, $article->id]) ?> </td> <td> <?= $article->created->format(DATE_RFC850) ?> </td> <td> <?= $this->Form->postLink( ’Delete’, [’action’ => ’delete’, $article->id], [’confirm’ => ’Are you sure?’]) ?> <?= $this->Html->link(’Edit’, [’action’ => ’edit’, $article->id]) ?> Deleting Articles 23 CakePHP Cookbook Documentation, Release 3.x </td> </tr> <?php endforeach; ?> </table> Using View\Helper\FormHelper::postLink() will create a link that uses JavaScript to do a POST request deleting our article. Allowing content to be deleted using GET requests is dangerous, as web crawlers could accidentally delete all your content. Note: This view code also uses the FormHelper to prompt the user with a JavaScript confirmation dialog before they attempt to delete an article. Routes For some, CakePHP’s default routing works well enough. Developers who are sensitive to user-friendliness and general search engine compatibility will appreciate the way that CakePHP’s URLs map to specific actions. So we’ll just make a quick change to routes in this tutorial. For more information on advanced routing techniques, see Connecting Routes. By default, CakePHP responds to a request for the root of your site (e.g., http://www.example.com) using its PagesController, rendering a view called “home”. Instead, we’ll replace this with our ArticlesController by creating a routing rule. CakePHP’s routing is found in config/routes.php. You’ll want to comment out or remove the line that defines the default root route. It looks like this: Router::connect(’/’, [’controller’ => ’Pages’, ’action’ => ’display’, ’home’]); This line connects the URL ‘/’ with the default CakePHP home page. We want it to connect with our own controller, so replace that line with this one: Router::connect(’/’, [’controller’ => ’Articles’, ’action’ => ’index’]); This should connect users requesting ‘/’ to the index() action of our ArticlesController. Note: CakePHP also makes use of ‘reverse routing’. If, with the above route defined, you pass [’controller’ => ’Articles’, ’action’ => ’index’] to a function expecting an array, the resulting URL used will be ‘/’. It’s therefore a good idea to always use arrays for URLs as this means your routes define where a URL goes, and also ensures that links point to the same place. Conclusion Creating applications this way will win you peace, honor, love, and money beyond even your wildest fantasies. Simple, isn’t it? Keep in mind that this tutorial was very basic. CakePHP has many more features to 24 Capítulo 2. Guia de Início Rápido CakePHP Cookbook Documentation, Release 3.x offer, and is flexible in ways we didn’t wish to cover here for simplicity’s sake. Use the rest of this manual as a guide for building more feature-rich applications. Now that you’ve created a basic CakePHP application, you’re ready for the real thing. Start your own project and read the rest of the Cookbook and API3 . If you need help, there are many ways to get the help you need - please see the Onde Conseguir Ajuda page. Welcome to CakePHP! Suggested Follow-up Reading These are common tasks people learning CakePHP usually want to study next: 1. Layouts: Customizing your website layout 2. Elements: Including and reusing view snippets 3. /console-and-shells/code-generation-with-bake: Generating basic CRUD code 4. /tutorials-and-examples/blog-auth-example/auth: User authentication and authorization tutorial 3 http://api.cakephp.org Conclusion 25 CakePHP Cookbook Documentation, Release 3.x 26 Capítulo 2. Guia de Início Rápido CAPÍTULO 3 Instalação O CakePHP é rápido e fácil de instalar. Os requisitos mínimos são um servidor web e uma cópia do CakePHP, só isso! Apesar deste manual focar principalmente na configuração do Apache (porquê ele é o mais simples de instalar e configurar), o CakePHP vai ser executado em uma série de servidores web como nginx, LightHTTPD, ou Microsoft IIS. Requisitos • HTTP Server. Por exemplo: Apache. De preferência com mod_rewrite ativo, mas não é obrigatório. • PHP 5.4.16 ou superior. • extensão mbstring • extensão mcrypt • extensão intl Apesar de um mecanismo de banco de dados não ser exigido, nós imaginamos que a maioria das aplicações irá utilizar um. O CakePHP suporta uma variedade de mecanismos de armazenamento de banco de dados: • MySQL (5.1.10 ou superior) • PostgreSQL • Microsoft SQL Server (2008 ou superior) • SQLite 3 Note: Todos os drivers inclusos internamente requerem PDO. Você deve assegurar-se que possui a extensão PDO correta instalada. 27 CakePHP Cookbook Documentation, Release 3.x Instalando o CakePHP O CakePHP utiliza Composer1 , uma ferramenta de gerenciamento de dependências para PHP 5.3+, como o método suportado oficial para instalação. Primeiramente, você precisará baixar e instalar o Composer se não o fez anteriormente. Se você tem cURL instalada, é tão fácil quanto executar o seguinte: curl -s https://getcomposer.org/installer | php Ou, você pode baixar composer.phar do Site oficial do Composer2 . Para sistemas Windows, você pode baixar o instalador aqui3 . Mais instruções para o instalador Windows do Composer podem ser encontradas dentro do LEIA-ME aqui4 . Agora que você baixou e instalou o Composer, você pode receber uma nova aplicação CakePHP executando: php composer.phar create-project --prefer-dist -s dev cakephp/app [app_name] Ou se o Composer estiver instalado globalmente: composer create-project --prefer-dist -s dev cakephp/app [app_name] Uma vez que o Composer terminar de baixar o esqueleto da aplicação e o núcleo da biblioteca CakePHP, você deve ter uma aplicação funcional instalada via Composer. Esteja certo de manter os arquivos composer.json e composer.lock com o restante do seu código fonte. You can now visit the path to where you installed your CakePHP application and see the setup traffic lights. Mantendo sincronização com as últimas alterações no CakePHP Se você quer se manter atualizado com as últimas mudanças no CakePHP, você pode adicionar o seguinte ao composer.json de sua aplicação: "require": { "cakephp/cakephp": "3.0.*-dev" } Onde <branch> é o nome do branch que você segue. Toda vez que você executar php composer.phar update você receberá as últimas atualizações do branch escolhido. Permissões O CakePHP utiliza o diretório tmp para diversas operações. Descrição de models, views armazenadas em cache e informações de sessão são apenas alguns exemplos. O diretório logs é utilizado para escrever arquivos de log pelo mecanismo padrão FileLog. 1 http://getcomposer.org https://getcomposer.org/download/ 3 https://github.com/composer/windows-setup/releases/ 4 https://github.com/composer/windows-setup 2 28 Capítulo 3. Instalação CakePHP Cookbook Documentation, Release 3.x Como tal, certifique-se que os diretórios logs, tmp e todos os seus sub-diretórios em sua instalação CakePHP são graváveis pelo usuário relacionado ao servidor web. O processo de instalação do Composer faz tmp e seus sub-diretórios globalmente graváveis para obter as coisas funcionando rapidamente, mas você pode atualizar as permissões para melhor segurança e mantê-los graváveis apenas para o usuário relacionado ao servidor web. Um problema comum é que os diretórios e sub-diretórios de logs e tmp devem ser graváveis tanto pelo servidor quanto pelo usuário da linha de comando. Em um sistema UNIX, se seu usuário relacionado ao servidor web é diferente do seu usuário da linha de comando, você pode executar somente uma vez os seguintes comandos a partir do diretório da sua aplicação para assegurar que as permissões serão configuradas corretamente: HTTPDUSER=‘ps aux | grep -E ’[a]pache|[h]ttpd|[_]www|[w]ww-data|[n]ginx’ | grep -v root | h setfacl -R -m u: $ {HTTPDUSER}:rwx tmp setfacl -R -d -m u: $ {HTTPDUSER}:rwx tmp setfacl -R -m u: $ {HTTPDUSER}:rwx logs setfacl -R -d -m u: $ {HTTPDUSER}:rwx logs Servidor de Desenvolvimento Uma instalação de desenvolvimento é o método mais rápido de configurar o CakePHP. Neste exemplo, nós vamos utilizar o console CakePHP para executar o servidor integrado do PHP que vai tornar sua aplicação disponível em http://host:port. A partir do diretório da aplicação, execute: bin/cake server Por padrão, sem nenhum argumento fornecido, isso vai disponibilizar a sua aplicação em http://localhost:8765/. Se você tem algo conflitando com A development installation is the fastest method to setup CakePHP. In this example, we will be using CakePHP’s console to run PHP’s built-in web server which will make your application available at http://host:port. From the app directory, execute: bin/cake server Por padrão, sem nenhum argumento fornecido, isso vai disponibilizar a sua aplicação em http://localhost:8765/. Se você tem algo conflitante com localhost ou porta 8765, você pode dizer ao console CakePHP para executar o servidor web em um host e/ou porta específica utilizando os seguintes argumentos: bin/cake server -H 192.168.13.37 -p 5673 Isto irá disponibilizar sua aplicação em http://192.168.13.37:5673/. É isso aí! Sua aplicação CakePHP está instalada e funcionando sem ter que configurar um servidor web. Servidor de Desenvolvimento 29 CakePHP Cookbook Documentation, Release 3.x Warning: O servidor de desenvolvimento nunca deve ser usado em um ambiente de produção. Destinase apenas como um servidor de desenvolvimento básico. Se você preferir usar um servidor web real, você deve ser capaz de mover a instalação do CakePHP (incluindo os arquivos ocultos) para dentro do diretório raiz do seu servidor web. Você deve, então, ser capaz de apontar seu navegador para o diretório que você moveu os arquivos para dentro e ver a aplicação em ação. Produção Uma instalação de produção é uma forma mais flexível de configurar o CakePHP. Usar este método permite total domínio para agir como uma aplicação CakePHP singular. Este exemplo o ajudará a instalar o CakePHP em qualquer lugar em seu sistema de arquivos e torná-lo disponível em http://www.example.com. Note que esta instalação pode exigir os direitos de alterar o DocumentRoot em servidores web Apache. Depois de instalar a aplicação usando um dos métodos acima no diretório de sua escolha - vamos supor que você escolheu /cake_install - sua configuração de produção será parecida com esta no sistema de arquivos: /cake_install/ bin/ config/ logs/ plugins/ src/ tests/ tmp/ vendor/ webroot/ (esse diret ó rio é definido como DocumentRoot) .gitignore .htaccess .travis.yml composer.json index.php phpunit.xml.dist README.md Desenvolvedores utilizando Apache devem definir a diretiva DocumentRoot pelo domínio para: DocumentRoot /cake_install/webroot Se o seu servidor web está configurado corretamente, agora você deve encontrar sua aplicação CakePHP acessível em http://www.example.com. Aquecendo Tudo bem, vamos ver o CakePHP em ação. Dependendo de qual configuração você usou, você deve apontar seu navegador para http://example.com/ ou http://localhost:8765/. Nesse ponto, você será apresentado à página home padrão do CakePHP e uma mensagem que diz a você o estado da sua conexão atual com o banco de dados. 30 Capítulo 3. Instalação CakePHP Cookbook Documentation, Release 3.x Parabéns! Você está pronto para create your first CakePHP application. Reescrita de URL Apache Apesar do CakePHP ser construído para trabalhar com mod_rewrite fora da caixa, e normalmente o faz, nos atentamos que aluns usuários lutam para conseguir fazer tudo funcionar bem em seus sistemas. Aqui estão algumas coisas que você poderia tentar para conseguir tudo rodando corretamente. Primeiramente observe seu httpd.conf. (Tenha certeza que você está editando o httpd.conf do sistema ao invés de um usuário, ou site específico.) Esses arquivos podem variar entre diferentes distribuições e versões do Apache. Você também pode pesquisar em http://wiki.apache.org/httpd/DistrosDefaultLayout para maiores informações. 1. Tenha certeza que a sobreescrita do .htaccess está permitida e que AllowOverride está definido para All no correto DocumentRoot. Você deve ver algo similar a: # Cada diretório ao qual o Apache tenha acesso pode ser configurado com respeito # a quais serviços e recursos estão permitidos e/ou desabilitados neste # diretório (e seus sub-diretórios). # # Primeiro, nós configuramos o "default" para ser um conjunto bem restrito de # recursos. <Directory /> Options FollowSymLinks AllowOverride All # Order deny,allow # Deny from all </Directory> 2. Certifique-se que o mod_rewrite está sendo carregado corretamente. Você deve ver algo como: LoadModule rewrite_module libexec/apache2/mod_rewrite.so Em muitos sistemas estará comentado por padrão, então você pode apenas remover os símbolos #. Depois de fazer as mudanças, reinicie o Apache para certificar-se que as configurações estão ativas. Verifique se os seus arquivos .htaccess estão realmente nos diretórios corretos. Alguns sistemas operacionais tratam arquivos iniciados com ‘.’ como ocultos e portanto, não os copia. 3. Certifique-se de sua cópia do CakePHP vir da seção de downloads do site ou do nosso repositório Git, e que foi descompactado corretamente, verificando os arquivos .htaccess. O diretório app do CakePHP (será copiado para o diretório mais alto de sua aplicação através do bake): <IfModule mod_rewrite.c> RewriteEngine on RewriteRule ^$ webroot/ Reescrita de URL [L] 31 CakePHP Cookbook Documentation, Release 3.x RewriteRule </IfModule> (.*) webroot/ $ 1 [L] O diretório webroot do CakePHP (será copiado para a raíz de sua aplicação através do bake): <IfModule mod_rewrite.c> RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^ index.php [L] </IfModule> Se o seu site CakePHP ainda possuir problemas com mod_rewrite, você pode tentar modificar as configurações para Virtual Hosts. No Ubuntu, edita o arquivo /etc/apache2/sites-available/default (a localização depende da distribuição). Nesse arquivo, certifique-se que AllowOverride None seja modificado para AllowOverride All, então você terá: <Directory /> Options FollowSymLinks AllowOverride All </Directory> <Directory /var/www> Options Indexes FollowSymLinks MultiViews AllowOverride All Order Allow,Deny Allow from all </Directory> No Mac OSX, outra solução é usar a ferramenta virtualhostx5 para fazer um Virtual Host apontar para o seu diretório. Para muitos serviços de hospedagem (GoDaddy, land1), seu servidor web é na verdade oferecido a partir de um diretório de usuário que já utiliza mod_rewrite. Se você está instalando o CakePHP em um diretório de usuário (http://example.com/~username/cakephp/), ou qualquer outra estrutura URL que já utilize mod_rewrite, você precisará adicionar declarações RewriteBase para os arquivos .htaccess que o CakePHP utiliza. (.htaccess, webroot/.htaccess). Isso pode ser adicionado na mesma seção com a diretiva RewriteEngine, por exemplo, seu arquivo webroot/.htaccess This can be added to the same section with the RewriteEngine directive, so for example, your webroot .htaccess ficaria como: <IfModule mod_rewrite.c> RewriteEngine On RewriteBase /path/to/app RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^ index.php [L] </IfModule> Os detalhes dessas mudanças vão depender da sua configuração, e podem incluir coisas adicionais que não estão relacionadas ao CakePHP. Por favor, busque pela documentação online do Apache para mais informações. 5 32 http://clickontyler.com/virtualhostx/ Capítulo 3. Instalação CakePHP Cookbook Documentation, Release 3.x 4. (Opcional) Para melhorar a configuração de produção, você deve prevenir conteúdos inváidos de serem analisados pelo CakePHP. Modifique seu webroot/.htaccess para algo como: <IfModule mod_rewrite.c> RewriteEngine On RewriteBase /path/to/app/ RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_URI} !^/(webroot/)?(img|css|js)/(.*) $ RewriteRule ^ index.php [L] </IfModule> Isto irá simplesmente prevenir conteúdo incorreto de ser enviado para o index.php e então exibir sua página de erro 404 do servidor web. Adicionalmente você pode criar uma página HTML de erro 404 correspondente, ou utilizar a padrão do CakePHP ao adicionar uma diretiva ErrorDocument: ErrorDocument 404 /404-not-found nginx nginx não utiliza arquivos .htaccess como o Apache, então é necessário criar as reescritas de URL na configuração de sites disponíveis. Dependendo da sua configuração, você precisará modificar isso, mas pelo menos, você vai precisar do PHP rodando como uma instância FastCGI: server { listen 80; server_name www.example.com; rewrite ^(.*) http://example.com $ 1 permanent; } server { listen 80; server_name example.com; # root directive should be global root /var/www/example.com/public/webroot/; index index.php; access_log /var/www/example.com/log/access.log; error_log /var/www/example.com/log/error.log; location / { try_files $uri $uri/ /index.php?$args; } location ~ \.php $ { try_files $uri =404; include /etc/nginx/fastcgi_params; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; Reescrita de URL 33 CakePHP Cookbook Documentation, Release 3.x } } IIS7 (Windows hosts) IIS7 não suporta nativamente arquivos .htaccess. Mesmo existindo add-ons que adicionam esse suporte, você também pode importar as regras .htaccess no IIS para utilizar as reescritas nativas do CakePHP. Para isso, siga os seguintes passos: #. Utilize o ‘Microsoft’s Web Platform Installer <http://www.microsoft.com/web/downloads/pl Rewrite Module 2.06 ou baixe-o diretamente (32-bit7 / 64-bit8 ). 1. Crie um novo arquivo chamado web.config em seu diretório raiz do CakePHP. 2. Utilize o Notepad ou qualquer editor seguro XML para copiar o seguinte código em seu novo arquivo web.config: <?xml version="1.0" encoding="UTF-8"?> <configuration> <system.webServer> <rewrite> <rules> <rule name="Exclude direct access to webroot/*" stopProcessing="true"> <match url="^webroot/(.*)$" ignoreCase="false" /> <action type="None" /> </rule> <rule name="Rewrite routed access to assets(img, css, files, js, favic stopProcessing="true"> <match url="^(img|css|files|js|favicon.ico)(.*)$" /> <action type="Rewrite" url="webroot/{R:1}{R:2}" appendQueryString="false" /> </rule> <rule name="Rewrite requested file/folder to index.php" stopProcessing="true"> <match url="^(.*)$" ignoreCase="false" /> <action type="Rewrite" url="index.php" appendQueryString="true" /> </rule> </rules> </rewrite> </system.webServer> </configuration> Uma vez que o arquivo web.config é criado com as regras amigáveis de reescrita do IIS, os links, CSS, JavaScript, e roteamento do CakePHP agora devem funcionar corretamente. 6 http://www.iis.net/downloads/microsoft/url-rewrite http://www.microsoft.com/en-us/download/details.aspx?id=5747 8 http://www.microsoft.com/en-us/download/details.aspx?id=7435 7 34 Capítulo 3. Instalação CakePHP Cookbook Documentation, Release 3.x Não posso utilizar Reescrita de URL Se você não quer ou não pode ter mod_rewrite (ou algum outro módulo compatível) funcionando no seu servidor, você precisará utilizar as URLs amigáveis natívas do CakePHP. No config/app.php, descomente a linha que se parece como: ’App’ => [ // ... // ’baseUrl’ => env(’SCRIPT_NAME’), ] Também remova esses arquivos .htaccess: /.htaccess webroot/.htaccess Isso fará suas URLs parecem como www.example.com/index.php/controllername/actionname/param ao invés de www.example.com/controllername/actionname/param. Reescrita de URL 35 CakePHP Cookbook Documentation, Release 3.x 36 Capítulo 3. Instalação CAPÍTULO 4 Configuration While conventions remove the need to configure all of CakePHP, you’ll still need to configure a few things like your database credentials. Additionally, there are optional configuration options that allow you to swap out default values & implementations with ones tailored to your application. Configuring your Application Configuration is generally stored in either PHP or INI files, and loaded during the application bootstrap. CakePHP comes with one configuration file by default, but if required you can add additional configuration files and load them in config/bootstrap.php. Cake\Core\Configure is used for general configuration, and the adapter based classes provide config() methods to make configuration simple and transparent. Loading Additional Configuration Files If your application has many configuration options it can be helpful to split configuration into multiple files. After creating each of the files in your config/ directory you can load them in bootstrap.php: use Cake\Core\Configure; use Cake\Core\Configure\Engine\PhpConfig; Configure::config(’default’, new PhpConfig()); Configure::load(’app.php’, ’default’, false); Configure::load(’other_config.php’, ’default’); You can also use additional configuration files to provide environment specific overrides. Each file loaded after app.php can redefine previously declared values allowing you to customize configuration for development or staging environments. 37 CakePHP Cookbook Documentation, Release 3.x General Configuration Below is a description of the variables and how they affect your CakePHP application. debug Changes CakePHP debugging output. false = Production mode. No error messages, errors, or warnings shown. true = Errors and warnings shown. App.namespace The namespace to find app classes under. Note: When changing the namespace in your configuration, you will also need to update your composer.json file to use this namespace as well. Additionally, create a new autoloader by running php composer.phar dumpautoload. App.baseUrl Un-comment this definition if you don’t plan to use Apache’s mod_rewrite with CakePHP. Don’t forget to remove your .htaccess files too. App.base The base directory the app resides in. If false this will be auto detected. App.encoding Define what encoding your application uses. This encoding is used to generate the charset in the layout, and encode entities. It should match the encoding values specified for your database. App.webroot The webroot directory. App.www_root The file path to webroot. App.fullBaseUrl The fully qualified domain name (including protocol) to your application’s root. This is used when generating absolute URLs. By default this value is generated using the $_SERVER environment. However, you should define it manually to optimize performance or if you are concerned about people manipulating the Host header. App.imageBaseUrl Web path to the public images directory under webroot. If you are using a CDN you should set this value to the CDN’s location. App.cssBaseUrl Web path to the public css directory under webroot. If you are using a CDN you should set this value to the CDN’s location. App.jsBaseUrl Web path to the public js directory under webroot. If you are using a CDN you should set this value to the CDN’s location. Security.salt A random string used in hashing. This value is also used as the HMAC salt when doing symetric encryption. Asset.timestamp Appends a timestamp which is last modified time of the particular file at the end of asset files URLs (CSS, JavaScript, Image) when using proper helpers. Valid values: • (bool) false - Doesn’t do anything (default) • (bool) true - Appends the timestamp when debug is false • (string) ‘force’ - Always appends the timestamp. Database Configuration See the Database Configuration for information on configuring your database connections. 38 Capítulo 4. Configuration CakePHP Cookbook Documentation, Release 3.x Caching Configuration See the Caching Configuration for information on configuring caching in CakePHP. Error and Exception Handling Configuration See the Error and Exception Configuration for information on configuring error and exception handlers. Logging Configuration See the log-configuration for information on configuring logging in CakePHP. Email Configuration See the Email Configuration for information on configuring email presets in CakePHP. Session Configuration See the Session Configuration for information on configuring session handling in CakePHP. Routing configuration See the Routes Configuration for more information on configuring routing and creating routes for your application. Additional Class Paths Additional class paths are setup through the autoloaders your application uses. When using Composer to generate your autoloader, you could do the following, to provide fallback paths for controllers in your application: "autoload": { "psr-4": { "App\\Controller\\": "/path/to/directory/with/controller/folders", "App\": "src" } } The above would setup paths for both the App and App\Controller namespace. The first key will be searched, and if that path does not contain the class/file the second key will be searched. You can also map a single namespace to multiple directories with the following: Additional Class Paths 39 CakePHP Cookbook Documentation, Release 3.x "autoload": { "psr-4": { "App\": ["src", "/path/to/directory"] } } View and Plugin Paths Since views and plugins are not classes, they cannot have an autoloader configured. CakePHP provides two Configure variables to setup additional paths for these resources. In your config/app.php you can set these variables: $config = [ // More configuration ’App’ => [ ’paths’ => [ ’views’ => [APP . ’View/’, APP . ’View2/’], ’plugins’ => [ROOT . ’/Plugin/’, ’/path/to/other/plugins/’] ] ] ]; Paths should end in /, or they will not work properly. Inflection Configuration See the inflection-configuration docs for more information. Configure Class class Cake\Core\Configure CakePHP’s Configure class can be used to store and retrieve application or runtime specific values. Be careful, this class allows you to store anything in it, then use it in any other part of your code: a sure temptation to break the MVC pattern CakePHP was designed for. The main goal of Configure class is to keep centralized variables that can be shared between many objects. Remember to try to live by “convention over configuration” and you won’t end up breaking the MVC structure we’ve set in place. You can access Configure from anywhere in your application: Configure::read(’debug’); Writing Configuration data static Cake\Core\Configure::write($key, $value) 40 Capítulo 4. Configuration CakePHP Cookbook Documentation, Release 3.x Use write() to store data in the application’s configuration: Configure::write(’Company.name’,’Pizza, Inc.’); Configure::write(’Company.slogan’,’Pizza for your body and soul’); Note: The dot notation used in the $key parameter can be used to organize your configuration settings into logical groups. The above example could also be written in a single call: Configure::write(’Company’, [ ’name’ => ’Pizza, Inc.’, ’slogan’ => ’Pizza for your body and soul’ ]); You can use Configure::write(’debug’, $bool) to switch between debug and production modes on the fly. This is especially handy for JSON interactions where debugging information can cause parsing problems. Reading Configuration Data static Cake\Core\Configure::read($key = null) Used to read configuration data from the application. Defaults to CakePHP’s important debug value. If a key is supplied, the data is returned. Using our examples from write() above, we can read that data back: Configure::read(’Company.name’); Configure::read(’Company.slogan’); // Yields: ’Pizza, Inc.’ // Yields: ’Pizza for your body // and soul’ Configure::read(’Company’); // Yields: array(’name’ => ’Pizza, Inc.’, ’slogan’ => ’Pizza for your body and soul’); If $key is left null, all values in Configure will be returned. Checking to see if Configuration Data is Defined static Cake\Core\Configure::check($key) Used to check if a key/path exists and has not-null value: $exists = Configure::check(’Company.name’); Deleting Configuration Data static Cake\Core\Configure::delete($key) Used to delete information from the application’s configuration: Configure Class 41 CakePHP Cookbook Documentation, Release 3.x Configure::delete(’Company.name’); Reading & Deleting Configuration Data static Cake\Core\Configure::consume($key) Read and delete a key from Configure. This is useful when you want to combine reading and deleting values in a single operation. Reading and writing configuration files static Cake\Core\Configure::config($name, $engine) CakePHP comes with two built-in configuration file engines. Cake\Core\Configure\Engine\PhpConfig is able to read PHP config files, in the same format that Configure has historically read. Cake\Core\Configure\Engine\IniConfig is able to read ini config files. See the PHP documentation1 for more information on the specifics of ini files. To use a core config engine, you’ll need to attach it to Configure using Configure::config(): use Cake\Core\Configure\Engine\PhpConfig; // Read config files from config Configure::config(’default’, new PhpConfig()); // Read config files from another path. Configure::config(’default’, new PhpConfig(’/path/to/your/config/files/’)); You can have multiple engines attached to Configure, each reading different kinds or sources of configuration files. You can interact with attached engines using a few other methods on Configure. To see check which engine aliases are attached you can use Configure::configured(): // Get the array of aliases for attached engines. Configure::configured(); // Check if a specific engine is attached Configure::configured(’default’); static Cake\Core\Configure::drop($name) You can also remove attached engines. Configure::drop(’default’) would remove the default engine alias. Any future attempts to load configuration files with that engine would fail: Configure::drop(’default’); Loading Configuration Files static Cake\Core\Configure::load($key, $config = ‘default’, $merge = true) 1 42 http://php.net/parse_ini_file Capítulo 4. Configuration CakePHP Cookbook Documentation, Release 3.x Once you’ve attached a config engine to Configure you can load configuration files: // Load my_file.php using the ’default’ engine object. Configure::load(’my_file’, ’default’); Loaded configuration files merge their data with the existing runtime configuration in Configure. This allows you to overwrite and add new values into the existing runtime configuration. By setting $merge to true, values will not ever overwrite the existing configuration. Creating or Modifying Configuration Files static Cake\Core\Configure::dump($key, $config = ‘default’, $keys = array()) Dumps all or some of the data in Configure into a file or storage system supported by a config engine. The serialization format is decided by the config engine attached as $config. For example, if the ‘default’ engine is a Cake\Configure\Engine\PhpConfig, the generated file will be a PHP configuration file loadable by the Cake\Configure\Engine\PhpConfig Given that the ‘default’ engine is an instance of PhpConfig. my_config.php: Save all data in Configure to the file Configure::dump(’my_config.php’, ’default’); Save only the error handling configuration: Configure::dump(’error.php’, ’default’, [’Error’, ’Exception’]); Configure::dump() can be used to either modify or overwrite configuration files that are readable with Configure::load() Storing Runtime Configuration static Cake\Core\Configure::store($name, $cacheConfig = ‘default’, $data = null) You can also store runtime configuration values for use in a future request. Since configure only remembers values for the current request, you will need to store any modified configuration information if you want to use it in subsequent requests: // Store the current configuration in the ’user_1234’ key in the ’default’ cache. Configure::store(’user_1234’, ’default’); Stored configuration data is persisted in the named cache configuration. /core-libraries/caching documentation for more information on caching. See the Restoring Runtime Configuration static Cake\Core\Configure::restore($name, $cacheConfig = ‘default’) Once you’ve stored runtime configuration, you’ll probably need to restore it so you can access it again. Configure::restore() does exactly that: Reading and writing configuration files 43 CakePHP Cookbook Documentation, Release 3.x // Restore runtime configuration from the cache. Configure::restore(’user_1234’, ’default’); When restoring configuration information it’s important to restore it with the same key, and cache configuration as was used to store it. Restored information is merged on top of the existing runtime configuration. Creating your Own Configuration Engines Since configuration engines are an extensible part of CakePHP, you can create configuration engines in your application and plugins. Configuration engines need to implement the Cake\Core\Configure\ConfigEngineInterface. This interface defines a read method, as the only required method. If you like XML files, you could create a simple Xml config engine for you application: // In src/Configure/Engine/XmlConfig.php namespace App\Configure\Engine; use Cake\Core\Configure\ConfigEngineInterface; use Cake\Utility\Xml; class XmlConfig implements ConfigEngineInterface { public function __construct($path = null) { if (!$path) { $path = CONFIG; } $this->_path = $path; } public function read($key) { $xml = Xml::build($this->_path . $key . ’.xml’); return Xml::toArray($xml); } public function dump($key, $data) { // Code to dump data to file } } In your config/bootstrap.php you could attach this engine and use it: use App\Configure\Engine\XmlConfig; Configure::config(’xml’, new XmlConfig()); ... Configure::load(’my_xml’, ’xml’); The read() method of a config engine, must return an array of the configuration information that the resource named $key contains. 44 Capítulo 4. Configuration CakePHP Cookbook Documentation, Release 3.x interface Cake\Core\Configure\ConfigEngineInterface Defines the interface used by classes that read configuration data and store it in Configure Cake\Core\Configure\ConfigEngineInterface::read($key) Parameters • $key (string) – The key name or identifier to load. This method should load/parse the configuration data identified by $key and return an array of data in the file. Cake\Core\Configure\ConfigEngineInterface::dump($key) Parameters • $key (string) – The identifier to write to. • $data (array) – The data to dump. This method should dump/store the provided configuration data to a key identified by $key. Built-in Configuration Engines PHP Configuration Files class Cake\Core\Configure\PhpConfig Allows you to read configuration files that are stored as plain PHP files. You can read either files from your config or from plugin configs directories by using plugin syntax. Files must contain a $config variable. An example configuration file would look like: $config = [ ’debug’ => 0, ’Security’ => [ ’salt’ => ’its-secret’ ], ’App’ => [ ’namespace’ => ’App’ ] ]; Files without $config will cause an ConfigureException Load your custom configuration file by inserting the following in config/bootstrap.php: Configure::load(’customConfig’); Ini Configuration Files class Cake\Core\Configure\IniConfig Built-in Configuration Engines 45 CakePHP Cookbook Documentation, Release 3.x Allows you to read configuration files that are stored as plain .ini files. The ini files must be compatible with php’s parse_ini_file function, and benefit from the following improvements • dot separated values are expanded into arrays. • boolean-ish values like ‘on’ and ‘off’ are converted to booleans. An example ini file would look like: debug = 0 [Security] salt = its-secret [App] namespace = App The above ini file, would result in the same end configuration data as the PHP example above. Array structures can be created either through dot separated values, or sections. Sections can contain dot separated keys for deeper nesting. Bootstrapping CakePHP If you have any additional configuration needs, you should add them to your application’s config/bootstrap.php file. This file is included before each request, and CLI command. This file is ideal for a number of common bootstrapping tasks: • Defining convenience functions. • Declaring constants. • Creating cache configurations. • Configuring inflections. • Loading configuration files. Be careful to maintain the MVC software design pattern when you add things to the bootstrap file: it might be tempting to place formatting functions there in order to use them in your controllers. As you’ll see in the Controllers and Views sections there are better ways you add custom logic to your application. 46 Capítulo 4. Configuration CAPÍTULO 5 Routing class Cake\Routing\Router Routing provides you tools that map URLs to controller actions. By defining routes, you can separate how your application is implemented from how its URL’s are structured. Routing in CakePHP also encompasses the idea of reverse routing, where an array of parameters can be transformed into a URL string. By using reverse routing, you can more easily re-factor your application’s URL structure without having to update all your code. Quick Tour This section will teach you by example the most common uses of the CakePHP Router. Typically you want to display something as a landing page, so you add this to your routes.php file: use Cake\Routing\Router; Router::connect(’/’, [’controller’ => ’Articles’, ’action’ => ’index’]); This will execute the index method in the ArticlesController when the homepage of your site is visited. Sometimes you need dynamic routes that will accept multiple parameters, this would be the case, for example of a route for viewing an article’s content: Router::connect(’/articles/*’, [’controller’ => ’Articles’, ’action’ => ’view’]); The above route will accept any url looking like /articles/15 and invoke the method view(15) in the ArticlesController. This will not, though, prevent people from trying to access URLs looking like /articles/foobar. If you wish, you can restring some parameters to conform to a regular expression: Router::connect( ’/articles/:id’, [’controller’ => ’Articles’, ’action’ => ’view’], [’id’ => ’\d+’, ’pass’ => [’id’]] ); 47 CakePHP Cookbook Documentation, Release 3.x The previous example changed the star matcher by a new placeholder :id. Using placeholders allows us to validate parts of the url, in this case we used the \d+ regular expression so that only digits are matched. Finally, we told the Router to treat the id placeholder as a function argument to the view() function by specifying the pass option. More on using this options later. The CakePHP Router can also match routes in reverse. That means that from an array containing similar parameters, it is capable of generation a URL string: use Cake\Routing\Router; echo Router::url([’controller’ => ’Articles’, ’action’ => ’view’, ’id’ => 15]); // Will output /articles/15 Routes can also be labelled with a unique name, this allows you to quickly reference them when building links instead of specifying each of the routing parameters: use Cake\Routing\Router; Router::connect( ’/login’, [’controller’ => ’Users’, ’action’ => ’login’], [’_name’ => ’login’] ); echo Router::url([’_name’ => ’login’]); // Will output /login To help keep your routing code DRY, the Router has the concept of ‘scopes’. A scope defines a common path segment, and optionally route defaults. Any routes connected inside a scope will inherit the path/defaults from their wrapping scopes: Router::scope(’/blog’, [’plugin’ => ’Blog’], function($routes) { $routes->connect(’/’, [’controller’ => ’Articles’]); }); The above route would match /blog/ and send it to Blog\Controller\ArticlesController::index(). The application skeleton comes with a few routes to get you started. Once you’ve added your own routes, you can remove the default routes if you don’t need them. Connecting Routes static Cake\Routing\Router::connect($route, $defaults =[], $options =[]) To keep your code DRY you can should use ‘routing scopes’. Routing scopes not only let you keep your code DRY, they also help Router optimize its operation. As seen above you can also use Router::connect() to connect routes. This method defaults to the / scope. To create a scope and connect some routes we’ll use the scope() method: 48 Capítulo 5. Routing CakePHP Cookbook Documentation, Release 3.x // In config/routes.php Router::scope(’/’, function($routes) { $routes->connect(’/:controller’, [’action’ => ’index’]); $routes->connect(’/:controller/:action/*’); }); The connect() method takes up to three parameters: the URL template you wish to match, the default values for your route elements, and the options for the route. Options frequently include regular expression rules to help the router match elements in the URL. The basic format for a route definition is: $routes->connect( ’URL template’, [’default’ => ’defaultValue’], [’option’ => ’matchingRegex’] ); The first parameter is used to tell the router what sort of URL you’re trying to control. The URL is a normal slash delimited string, but can also contain a wildcard (*) or Route Elements. Using a wildcard tells the router that you are willing to accept any additional arguments supplied. Routes without a * only match the exact template pattern supplied. Once you’ve specified a URL, you use the last two parameters of connect() to tell CakePHP what to do with a request once it has been matched. The second parameter is an associative array. The keys of the array should be named after the route elements the URL template represents. The values in the array are the default values for those keys. Let’s look at some basic examples before we start using the third parameter of connect(): $routes->connect( ’/pages/*’, [’controller’ => ’Pages’, ’action’ => ’display’] ); This route is found in the routes.php file distributed with CakePHP. It matches any URL starting with /pages/ and hands it to the display() action of the PagesController();. A request to /pages/products would be mapped to PagesController->display(’products’). In addition to the greedy star /* there is also the /** trailing star syntax. Using a trailing double star, will capture the remainder of a URL as a single passed argument. This is useful when you want to use an argument that included a / in it: $routes->connect( ’/pages/**’, [’controller’ => ’Pages’, ’action’ => ’show’] ); The incoming URL of /pages/the-example-/-and-proof would result in a single passed argument of the-example-/-and-proof. You can use the second parameter of connect() to provide any routing parameters that are composed of the default values of the route: Connecting Routes 49 CakePHP Cookbook Documentation, Release 3.x $routes->connect( ’/government’, [’controller’ => ’Pages’, ’action’ => ’display’, 5] ); This example shows how you can use the second parameter of connect() to define default parameters. If you built a site that features products for different categories of customers, you might consider creating a route. This allows you link to /government rather than /pages/display/5. Another common use for the Router is to define an “alias” for a controller. Let’s say that instead of accessing our regular URL at /users/some_action/5, we’d like to be able to access it by /cooks/some_action/5. The following route easily takes care of that: $routes->connect( ’/cooks/:action/*’, [’controller’ => ’Users’] ); This is telling the Router that any URL beginning with /cooks/ should be sent to the users controller. The action called will depend on the value of the :action parameter. By using Route Elements, you can create variable routes, that accept user input or variables. The above route also uses the greedy star. The greedy star indicates to Router that this route should accept any additional positional arguments given. These arguments will be made available in the Passed Arguments array. When generating URLs, routes are used too. Using [’controller’ => ’Users’, ’action’ => ’some_action’, 5] as a url will output /cooks/some_action/5 if the above route is the first match found. Route Elements You can specify your own route elements and doing so gives you the power to define places in the URL where parameters for controller actions should lie. When a request is made, the values for these route elements are found in $this->request->params in the controller. When you define a custom route element, you can optionally specify a regular expression - this tells CakePHP how to know if the URL is correctly formed or not. If you choose to not provide a regular expression, any non / character will be treated as part of the parameter: $routes->connect( ’/:controller/:id’, [’action’ => ’view’], [’id’ => ’[0-9]+’] ); The above example illustrates how to create a quick way to view models from any controller by crafting a URL that looks like /controllername/:id. The URL provided to connect() specifies two route elements: :controller and :id. The :controller element is a CakePHP default route element, so the router knows how to match and identify controller names in URLs. The :id element is a custom route element, and must be further clarified by specifying a matching regular expression in the third parameter of connect(). CakePHP does not automatically produce lowercased urls when using the :controller parameter. If you need this, the above example could be rewritten like so: 50 Capítulo 5. Routing CakePHP Cookbook Documentation, Release 3.x $routes->connect( ’/:controller/:id’, [’action’ => ’view’], [’id’ => ’[0-9]+’, ’routeClass’ => ’InflectedRoute’] ); The special InflectedRoute class will make sure that the :controller and :plugin parameters are correctly lowercased. Note: Patterns used for route elements must not contain any capturing groups. If they do, Router will not function correctly. Once this route has been defined, requesting /apples/5 would call the view() method of the ApplesController. Inside the view() method, you would need to access the passed ID at $this->request->params[’id’]. If you have a single controller in your application and you do not want the controller name to appear in the URL, you can map all URLs to actions in your controller. For example, to map all URLs to actions of the home controller, e.g have URLs like /demo instead of /home/demo, you can do the following: $routes->connect(’/:action’, [’controller’ => ’Home’]); If you would like to provide a case insensitive URL, you can use regular expression inline modifiers: $routes->connect( ’/:userShortcut’, [’controller’ => ’Teachers’, ’action’ => ’profile’, 1], [’userShortcut’ => ’(?i:principal)’] ); One more example, and you’ll be a routing pro: $routes->connect( ’/:controller/:year/:month/:day’, [’action’ => ’index’], [ ’year’ => ’[12][0-9]{3}’, ’month’ => ’0[1-9]|1[012]’, ’day’ => ’0[1-9]|[12][0-9]|3[01]’ ] ); This is rather involved, but shows how powerful routes can be The URL supplied has four route elements. The first is familiar to us: it’s a default route element that tells CakePHP to expect a controller name. Next, we specify some default values. Regardless of the controller, we want the index() action to be called. Finally, we specify some regular expressions that will match years, months and days in numerical form. Note that parenthesis (grouping) are not supported in the regular expressions. You can still specify alternates, as above, but not grouped with parenthesis. Once defined, this route will match /articles/2007/02/01, /articles/2004/11/16, handing the requests to the index() actions of their respective controllers, with the date parameters in Connecting Routes 51 CakePHP Cookbook Documentation, Release 3.x $this->request->params. There are several route elements that have special meaning in CakePHP, and should not be used unless you want the special meaning • controller Used to name the controller for a route. • action Used to name the controller action for a route. • plugin Used to name the plugin a controller is located in. • prefix Used for Prefix Routing • _ext Used for Routing File Extensions routing. • _base Set to false to remove the base path from the generated URL. If your application is not in the root directory, this can be used to generate URLs that are ‘cake relative’. cake relative URLs are required when using requestAction. • _scheme Set to create links on different schemes like webcal or ftp. Defaults to the current scheme. • _host Set the host to use for the link. Defaults to the current host. • _port Set the port if you need to create links on non-standard ports. • _full If true the FULL_BASE_URL constant will be prepended to generated URLs. • # Allows you to set URL hash fragments. • _ssl Set to true to convert the generated URL to https or false to force http. • _method Define the HTTP verb/method to use. Useful when working with Creating RESTful Routes. • _name Name of route. If you have setup named routes, you can use this key to specify it. Passing Parameters to Action When connecting routes using Route Elements you may want to have routed elements be passed arguments instead. By using the 3rd argument of Cake\Routing\Router::connect() you can define which route elements should also be made available as passed arguments: // SomeController.php public function view($articleId = null, $slug = null) { // Some code here... } // routes.php Router::connect( ’/blog/:id-:slug’, // E.g. /blog/3-CakePHP_Rocks [’controller’ => ’Blog’, ’action’ => ’view’], [ // order matters since this will simply map ":id" to $articleId in your action ’pass’ => [’id’, ’slug’], ’id’ => ’[0-9]+’ ] ); 52 Capítulo 5. Routing CakePHP Cookbook Documentation, Release 3.x And now, thanks to the reverse routing capabilities, you can pass in the URL array like below and CakePHP will know how to form the URL as defined in the routes: // view.ctp // This will return a link to /blog/3-CakePHP_Rocks echo $this->Html->link(’CakePHP Rocks’, [ ’controller’ => ’Blog’, ’action’ => ’view’, ’id’ => 3, ’slug’ => ’CakePHP_Rocks’ ]); // You can also used numerically indexed parameters. echo $this->Html->link(’CakePHP Rocks’, [ ’controller’ => ’Blog’, ’action’ => ’view’, 3, ’CakePHP_Rocks’ ]); Using Named Routes Sometimes you’ll find typing out all the URL parameters for a route too verbose, or you’d like to take advantage of the performance improvements that named routes have. When connecting routes you can specifiy a _name option, this option can be used in reverse routing to identify the route you want to use: // Connect a route with a name. $routes->connect( ’/login’, [’controller’ => ’Users’, ’action’ => ’login’], [’_name’ => ’login’] ); // Generate a URL using a named route. $url = Router::url([’_name’ => ’login’]); // Generate a URL using a named route, // with some query string args. $url = Router::url([’_name’ => ’login’, ’username’ => ’jimmy’]); If your route template contains any route elements like :controller you’ll need to supply those as part of the options to Router::url(). Prefix Routing static Cake\Routing\Router::prefix($name, $callback) Many applications require an administration section where privileged users can make changes. This is often done through a special URL such as /admin/users/edit/5. In CakePHP, prefix routing can be enabled by using the prefix scope method: Connecting Routes 53 CakePHP Cookbook Documentation, Release 3.x Router::prefix(’admin’, function($routes) { // All routes here will be prefixed with ‘/admin‘ // And have the prefix => admin route element added. $routes->connect(’/:controller’, [’action’ => ’index’]); $routes->connect(’/:controller/:action/*’); }); Prefixes are mapped to sub-namespaces in your application’s Controller namespace. By having prefixes as separate controllers you can create smaller and simpler controllers. Behavior that is common to the prefixed and non-prefixed controllers can be encapsulated using inheritance, Components, or traits. Using our users example, accessing the URL /admin/users/edit/5 would call the edit method of our src/Controller/Admin/UsersController.php passing 5 as the first parameter. The view file used would be src/Template/Admin/Users/edit.ctp You can map the URL /admin to your index action of pages controller using following route: Router::prefix(’admin’, function($routes) { // Because you are in the admin scope, // you do not need to include the /admin prefix // or the admin route element. $routes->connect(’/’, [’controller’ => ’Pages’, ’action’ => ’index’]); }); You can define prefixes inside plugin scopes as well: Router::plugin(’DebugKit’, function($routes) { $routes->prefix(’admin’, function($routes) { $routes->connect(’/:controller’); }); }); The above would create a route template like /debug_kit/admin/:controller. The connected route would have the plugin and prefix route elements set. When defining prefixes, you can nest multiple prefixes if necessary: Router::prefix(’manager’, function($routes) { $routes->prefix(’admin’, function($routes) { $routes->connect(’/:controller’); }); }); The above would create a route template like /manager/admin/:controller. The connected route would have the prefix route element set to manager/admin. The current prefix will be available $this->request->params[’prefix’] from the controller methods through When using prefix routes it’s important to set the prefix option. Here’s how to build this link using the HTML helper: // Go into a prefixed route. echo $this->Html->link( ’Manage articles’, 54 Capítulo 5. Routing CakePHP Cookbook Documentation, Release 3.x [’prefix’ => ’manager’, ’controller’ => ’Articles’, ’action’ => ’add’] ); // Leave a prefix echo $this->Html->link( ’View Post’, [’prefix’ => false, ’controller’ => ’Articles’, ’action’ => ’view’, 5] ); Note: You should connect prefix routes before you connect fallback routes. Plugin Routing static Cake\Routing\Router::plugin($name, $options =[], $callback) Plugin routes are most easily created using the plugin() method. This method creates a new routing scope for the plugin’s routes: Router::plugin(’DebugKit’, function($routes) { // Routes connected here are prefixed with ’/debug_kit’ and // have the plugin route element set to ’DebugKit’. $routes->connect(’/:controller’); }); When creating plugin scopes, you can customize the path element used with the path option: Router::plugin(’DebugKit’, [’path’ => ’/debugger’], function($routes) { // Routes connected here are prefixed with ’/debugger’ and // have the plugin route element set to ’DebugKit’. $routes->connect(’/:controller’); }); When using scopes you can nest plugin scopes within prefix scopes: Router::prefix(’admin’, function($routes) { $routes->plugin(’DebugKit’, function($routes) { $routes->connect(’/:controller’); }); }); The above would create a route that looks like /admin/debug_kit/:controller. It would have the prefix, and plugin route elements set. You can create links that point to a plugin, by adding the plugin key to your URL array: echo $this->Html->link( ’New todo’, [’plugin’ => ’Todo’, ’controller’ => ’TodoItems’, ’action’ => ’create’] ); Conversely if the active request is a plugin request and you want to create a link that has no plugin you can do the following: Connecting Routes 55 CakePHP Cookbook Documentation, Release 3.x echo $this->Html->link( ’New todo’, [’plugin’ => null, ’controller’ => ’Users’, ’action’ => ’profile’] ); By setting plugin => null you tell the Router that you want to create a link that is not part of a plugin. SEO-Friendly Routing As a SEO-minded developer, it’ll be desirable to outfit your URLs with dashes in order to avoid your application being cast into the search engine shadows, and hoist it to the divine rankings of the search engine gods. The DashedRoute class furnishes your application with the ability to route plugin, controller, and camelized action names to a dashed URL. For example, if we had a ToDo plugin, with a TodoItems controller, and a showItems action, it could be accessed at /to-do/todo-items/show-items with the following router connection: Router::scope(’/’, function($routes) { $routes->connect(’/:plugin/:controller/:action’, [’plugin’ => ’*’, ’controller’ => ’*’, ’action’ => ’*’], [’routeClass’ => ’DashedRoute’] ); $routes->fallbacks(); }); Under the / routing scope, the previous example will attempt to catch all plugin/controller/action dashed routes and map them to their respective actions. Routing File Extensions static Cake\Routing\Router::extensions(string|array|null $extensions, $merge = true) To handle different file extensions with your routes, you need one extra line in your routes config file: Router::extensions([’html’, ’rss’]); This will enable the named extensions for all routes connected after this method call. Any routes connected prior to it will not inherit the extensions. By default the extensions you passed will be merged with existing list of extensions. You can pass false for the second argument to override existing list. Calling the method with arguments will return existing list of extensions. You can set extensions per scope as well: Router::scope(’/api’, function($routes) { $routes->extensions([’json’, ’xml’]); }); Setting the extensions should be the first thing you do in a scope, as the extensions will only be applied to routes connected after the extensions are set. By using extensions, you tell the router to remove any matching file extensions, and then parse what remains. If you want to create a URL such as /page/title-of-page.html you would create your route using: 56 Capítulo 5. Routing CakePHP Cookbook Documentation, Release 3.x Router::scope(’/api’, function($routes) { $routes->extensions([’json’, ’xml’]); $routes->connect( ’/page/:title’, [’controller’ => ’Pages’, ’action’ => ’view’], [ ’pass’ => [’title’] ] ); }); Then to create links which map back to the routes simply use: $this->Html->link( ’Link title’, [’controller’ => ’Pages’, ’action’ => ’view’, ’title’ => ’super-article’, ’_ext’ => ’ht ); File extensions are used by Request Handling to do automatic view switching based on content types. Creating RESTful Routes static Cake\Routing\Router::mapResources($controller, $options) Router makes it easy to generate RESTful routes for your controllers. If we wanted to allow REST access to a recipe database, we’d do something like this: // In config/routes.php... Router::scope(’/’, function($routes) { $routes->extensions([’json’]); $routes->resources(’Recipes’); }); The first line sets up a number of default routes for easy REST access where method specifies the desired result format (e.g. xml, json, rss). These routes are HTTP Request Method sensitive. HTTP format GET GET POST PUT PATCH DELETE URL.format /recipes.format /recipes/123.format /recipes.format /recipes/123.format /recipes/123.format /recipes/123.format Controller action invoked RecipesController::index() RecipesController::view(123) RecipesController::add() RecipesController::edit(123) RecipesController::edit(123) RecipesController::delete(123) CakePHP’s Router class uses a number of different indicators to detect the HTTP method being used. Here they are in order of preference: 1. The _method POST variable 2. The X_HTTP_METHOD_OVERRIDE 3. The REQUEST_METHOD header Creating RESTful Routes 57 CakePHP Cookbook Documentation, Release 3.x The _method POST variable is helpful in using a browser as a REST client (or anything else that can do POST easily). Just set the value of _method to the name of the HTTP request method you wish to emulate. Creating Nested Resources Once you have connected resources in a scope, you can connect routes for sub-resources as well. Subresource routes will be prepended by the original resource name and a id parameter. For example: Router::scope(’/api’, function($routes) { $routes->resources(’Articles’, function($routes) { $routes->resources(’Comments’); }); }); Will generate resource routes for both articles and comments. The comments routes will look like: /api/articles/:article_id/comments /api/articles/:article_id/comments/:id You can get the article_id in CommentsController by: $this->request->params[’article_id’] Note: While you can nest resources as deeply as you require, it is not recommended to nest more than 2 resources together. Limiting the Routes Created By default CakePHP will connect 6 routes for each resource. If you’d like to only connect specific resource routes you can use the only option: $routes->resources(’Articles’, [ ’only’ => [’index’, ’view’] ]); Would create read only resource routes. The route names are create, update, view, index, and delete. Changing the Controller Actions Used You may need to change the controller action names that are used when connecting routes. For example, if your edit action is called update you can use the actions key to rename the actions used: $routes->resources(’Articles’, [ ’actions’ => [’update’ => ’update’, ’add’ => ’create’] ]); The above would use update for the update action, and create instead of add. 58 Capítulo 5. Routing CakePHP Cookbook Documentation, Release 3.x Custom Route Classes for Resource Routes You can provide connectOptions key in the $options array for resources() to provide custom setting used by connect(): Router::scope(’/’, function($routes) { $routes->resources(’books’, array( ’connectOptions’ => array( ’routeClass’ => ’ApiRoute’, ) ); }); Passed Arguments Passed arguments are additional arguments or path segments that are used when making a request. They are often used to pass parameters to your controller methods.: http://localhost/calendars/view/recent/mark In the above example, both recent and mark are passed arguments to CalendarsController::view(). Passed arguments are given to your controllers in three ways. First as arguments to the action method called, and secondly they are available in $this->request->params[’pass’] as a numerically indexed array. When using custom routes you can force particular parameters to go into the passed arguments as well. If you were to visit the previously mentioned URL, and you had a controller action that looked like: CalendarsController extends AppController { public function view($arg1, $arg2) { debug(func_get_args()); } } You would get the following output: Array ( [0] => recent [1] => mark ) This same data is also available at $this->request->params[’pass’] and $this->passedArgs in your controllers, views, and helpers. The values in the pass array are numerically indexed based on the order they appear in the called URL: debug($this->request->params[’pass’]); Either of the above would output: Passed Arguments 59 CakePHP Cookbook Documentation, Release 3.x Array ( [0] => recent [1] => mark ) When generating URLs, using a routing array you add passed arguments as values without string keys in the array: [’controller’ => ’Articles’, ’action’ => ’view’, 5] Since 5 has a numeric key, it is treated as a passed argument. Generating URLs static Cake\Routing\Router::url($url = null, $full = false) Generating URLs or Reverse routing is a feature in CakePHP that is used to allow you to easily change your URL structure without having to modify all your code. By using routing arrays to define your URLs, you can later configure routes and the generated URLs will automatically update. If you create URLs using strings like: $this->Html->link(’View’, ’/articles/view/’ . $id); And then later decide that /articles should really be called ‘articles’ instead, you would have to go through your entire application renaming URLs. However, if you defined your link like: $this->Html->link( ’View’, [’controller’ => ’Articles’, ’action’ => ’view’, $id] ); Then when you decided to change your URLs, you could do so by defining a route. This would change both the incoming URL mapping, as well as the generated URLs. When using array URLs, you can define both query string parameters and document fragments using special keys: Router::url([ ’controller’ => ’Articles’, ’action’ => ’index’, ’?’ => [’page’ => 1], ’#’ => ’top’ ]); // Will generate a URL like. /articles/index?page=1#top Router will also convert any unknown parameters in a routing array to querystring parameters. The ? is offered for backwards compatibility with older versions of CakePHP. You can also use any of the special route elements when generating URLs: 60 Capítulo 5. Routing CakePHP Cookbook Documentation, Release 3.x • _ext Used for Routing File Extensions routing. • _base Set to false to remove the base path from the generated URL. If your application is not in the root directory, this can be used to generate URLs that are ‘cake relative’. cake relative URLs are required when using requestAction. • _scheme Set to create links on different schemes like webcal or ftp. Defaults to the current scheme. • _host Set the host to use for the link. Defaults to the current host. • _port Set the port if you need to create links on non-standard ports. • _full If true the FULL_BASE_URL constant will be prepended to generated URLs. • _ssl Set to true to convert the generated URL to https or false to force http. • _name Name of route. If you have setup named routes, you can use this key to specify it. Redirect Routing static Cake\Routing\Router::redirect($route, $url, $options =[]) Redirect routing allows you to issue HTTP status 30x redirects for incoming routes, and point them at different URLs. This is useful when you want to inform client applications that a resource has moved and you don’t want to expose two URLs for the same content Redirection routes are different from normal routes as they perform an actual header redirection if a match is found. The redirection can occur to a destination within your application or an outside location: $routes->redirect( ’/home/*’, [’controller’ => ’Articles’, ’action’ => ’view’], [’persist’ => true] // Or [’persist’=>[’id’]] for default routing where the // view action expects $id as an argument. ); Redirects /home/* to /articles/view and passes the parameters to /articles/view. Using an array as the redirect destination allows you to use other routes to define where a URL string should be redirected to. You can redirect to external locations using string URLs as the destination: $routes->redirect(’/articles/*’, ’http://google.com’, [’status’ => 302]); This would redirect /articles/* to http://google.com with a HTTP status of 302. Custom Route Classes Custom route classes allow you to extend and change how individual routes parse requests and handle reverse routing. Route classes have a few conventions: • Route classes are expected to be found in the Routing\\Route namespace of your application or plugin. Redirect Routing 61 CakePHP Cookbook Documentation, Release 3.x • Route classes should extend Cake\Routing\Route. • Route classes should implement one or both of match() and/or parse(). The parse() method is used to parse an incoming URL. It should generate an array of request parameters that can be resolved into a controller & action. Return false from this method to indicate a match failure. The match() method is used to match an array of URL parameters and create a string URL. If the URL parameters do not match the route false should be returned. You can use a custom route class when making a route by using the routeClass option: Router::connect( ’/:slug’, [’controller’ => ’Articles’, ’action’ => ’view’], [’routeClass’ => ’SlugRoute’] ); This route would create an instance of SlugRoute and allow you to implement custom parameter handling. You can use plugin route classes using standard plugin syntax. Handling Named Parameters in URLs Although named parameters were removed in CakePHP 3.0, applications may have published URLs containing them. You can continue to accept URLs containing named parameters. In your controller’s beforeFilter() method you can call parseNamedParams() to extract any named parameters from the passed arguments: public function beforeFilter() { parent::beforeFilter(); Router::parseNamedParams($this->request); } This will populate $this->request->params[’named’] with any named parameters found in the passed arguments. Any passed argument that was interpreted as a named parameter, will be removed from the list of passed arguments. RequestActionTrait trait Cake\Routing\RequestActionTrait This trait allows classes which include it to create sub-requests or request actions. Cake\Routing\RequestActionTrait::requestAction(string $url, array $options) This function calls a controller’s action from any location and returns data from the action. The $url passed is a CakePHP-relative URL (/controllername/actionname/params). To pass extra data to the receiving controller action add to the $options array. Note: You can use requestAction() to retrieve a fully rendered view by passing ‘return’ in the options: requestAction($url, [’return’]);. It is important to note that making a 62 Capítulo 5. Routing CakePHP Cookbook Documentation, Release 3.x requestAction using ‘return’ from a controller method can cause script and css tags to not work correctly. Warning: If used without caching requestAction can lead to poor performance. It is seldom appropriate to use in a controller. requestAction is best used in conjunction with (cached) elements – as a way to fetch data for an element before rendering. Let’s use the example of putting a “latest comments” element in the layout. First we need to create a controller function that will return the data: // Controller/CommentsController.php class CommentsController extends AppController { public function latest() { if (!$this->request->is(’requested’)) { throw new ForbiddenException(); } return $this->Comments->find(’all’, [ ’order’ => ’Comment.created DESC’, ’limit’ => 10 ]); } } You should always include checks to make sure your requestAction methods are actually originating from requestAction. Failing to do so will allow requestAction methods to be directly accessible from a URL, which is generally undesirable. If we now create a simple element to call that function: // View/Element/latest_comments.ctp $comments = $this->requestAction(’/comments/latest’); foreach ($comments as $comment) { echo $comment->title; } We can then place that element anywhere to get the output using: echo $this->element(’latest_comments’); Written in this way, whenever the element is rendered, a request will be made to the controller to get the data, the data will be processed, and returned. However in accordance with the warning above it’s best to make use of element caching to prevent needless processing. By modifying the call to element to look like this: echo $this->element(’latest_comments’, [], [’cache’ => ’+1 hour’]); The requestAction call will not be made while the cached element view file exists and is valid. In addition, requestAction now takes array based cake style URLs: RequestActionTrait 63 CakePHP Cookbook Documentation, Release 3.x echo $this->requestAction( [’controller’ => ’Articles’, ’action’ => ’featured’], [’return’] ); The URL based array are the same as the ones that HtmlHelper::link() uses with one difference - if you are using passed parameters, you must put them in a second array and wrap them with the correct key. This is because requestAction merges the extra parameters (requestAction’s 2nd parameter) with the request->params member array and does not explicitly place them under the pass key. Any additional keys in the $option array will be made available in the requested action’s request->params property: echo $this->requestAction(’/articles/view/5’); As an array in the requestAction would then be: echo $this->requestAction( [’controller’ => ’Articles’, ’action’ => ’view’, 5], ); You can also pass querystring arguments, post data or cookies using the appropriate keys. Cookies can be passed using the cookies key. Get parameters can be set with query and post data can be sent using the post key: $vars = $this->requestAction(’/articles/popular’, [ ’query’ => [’page’ = > 1], ’cookies’ => [’remember_me’ => 1], ]); Note: Unlike other places where array URLs are analogous to string URLs, requestAction treats them differently. When using an array URL in conjunction with requestAction() you must specify all parameters that you will need in the requested action. This includes parameters like $this->request->data. In addition to passing all required parameters, passed arguments must be done in the second array as seen above. 64 Capítulo 5. Routing CAPÍTULO 6 Request and Response Objects The request and response objects provide an abstraction around HTTP requests and responses. The request object in CakePHP allows you to easily introspect an incoming request, while the response object allows you to effortlessly create HTTP responses from your controllers. Request class Cake\Network\Request Request is the default request object used in CakePHP. It centralizes a number of features for interrogating and interacting with request data. On each request one Request is created and then passed by reference to the various layers of an application that use request data. By default the request is assigned to $this->request, and is available in Controllers, Cells, Views and Helpers. You can also access it in Components using the controller reference. Some of the duties Request performs include: • Processing the GET, POST, and FILES arrays into the data structures you are familiar with. • Providing environment introspection pertaining to the request. Information like the headers sent, the client’s IP address, and the subdomain/domain names the server your application is running on. • Providing access to request parameters both as array indexes and object properties. Request Parameters Request exposes several interfaces for accessing request parameters: $this->request->params[’controller’]; $this->request->param(’controller’); All of the above will access the same value. All Route Elements are accessed through this interface. In addition to Route Elements, you also often need access to Passed Arguments. These are both available on the request object as well: 65 CakePHP Cookbook Documentation, Release 3.x // Passed arguments $this->request->pass; $this->request[’pass’]; $this->request->params[’pass’]; Will all provide you access to the passed arguments. There are several important/useful parameters that CakePHP uses internally, these are also all found in the request parameters: • plugin The plugin handling the request. Will be null when there is no plugin. • controller The controller handling the current request. • action The action handling the current request. • prefix The prefix for the current action. See Prefix Routing for more information. • bare Present when the request came from Controller\Controller::requestAction() and included the bare option. Bare requests do not have layouts rendered. • requested Present and set to true when Controller\Controller::requestAction(). the action came from Query String Parameters Cake\Network\Request::query($name) Query string parameters can be read using Network\Request::$query: // URL is /posts/index?page=1&sort=title $this->request->query[’page’]; You can either directly access the query property, or you can use query() method to read the URL query array in an error-free manner. Any keys that do not exist will return null: $foo = $this->request->query(’value_that_does_not_exist’); // $foo === null Request Body Data Cake\Network\Request::data($name) All POST data can be accessed using Cake\Network\Request::data(). Any form data that contains a data prefix will have that data prefix removed. For example: // An input with a name attribute equal to ’MyModel[title]’ is accessible at $this->request->data(’MyModel.title’); Any keys that do not exist will return null: $foo = $this->request->data(’Value.that.does.not.exist’); // $foo == null You can also access the array of data, as an array: 66 Capítulo 6. Request and Response Objects CakePHP Cookbook Documentation, Release 3.x $this->request->data[’title’]; $this->request->data[’comments’][1][’author’]; PUT, PATCH or DELETE Data Cake\Network\Request::input($callback[, $options ]) When building REST services, you often accept request data on PUT and DELETE requests. Any application/x-www-form-urlencoded request body data will automatically be parsed and set to $this->data for PUT and DELETE requests. If you are accepting JSON or XML data, see below for how you can access those request bodies. When accessing the input data, you can decode it with an optional function. This is useful when interacting with XML or JSON request body content. Additional parameters for the decoding function can be passed as arguments to input(): $this->request->input(’json_decode’); Environment Variables (from $_SERVER and $_ENV) Cake\Network\Request::env($key, $value = null) Request::env() is a wrapper for env() global function and acts as a getter/setter for enviromnent variables without having to modify globals $_SERVER and $_ENV: // Get a value $value = $this->request->env(’HTTP_HOST’); // Set a value. Generally helpful in testing. $this->request->env(’REQUEST_METHOD’, ’POST’); XML or JSON Data Applications employing REST often exchange data in non-URL-encoded post bodies. You can read input data in any format using Network\Request::input(). By providing a decoding function, you can receive the content in a deserialized format: // Get JSON encoded data submitted to a PUT/POST action $data = $this->request->input(’json_decode’); Some deserializing methods require additional parameters when called, such as the ‘as array’ parameter on json_decode. If you want XML converted into a DOMDocument object, Network\Request::input() supports passing in additional parameters as well: // Get Xml encoded data submitted to a PUT/POST action $data = $this->request->input(’Xml::build’, [’return’ => ’domdocument’]); Request 67 CakePHP Cookbook Documentation, Release 3.x Path Information The request object also provides useful information about the paths in your application. $request->base and $request->webroot are useful for generating URLs, and determining whether or not your application is in a subdirectory. The various properties you can use are: // Assume the current request URL is /subdir/articles/edit/1?page=1 // Holds /subdir/articles/edit/1?page=1 $request->here; // Holds /subdir $request->base; // Holds /subdir/ $request->webroot; Checking Request Conditions Cake\Network\Request::is($type) The request object provides an easy way to inspect certain conditions in a given request. By using the is() method you can check a number of common conditions, as well as inspect other application specific request criteria: $this->request->is(’post’); You can also easily extend the request detectors that are available, by using Cake\Network\Request::addDetector() to create new kinds of detectors. There are four different types of detectors that you can create: • Environment value comparison - Compares a value fetched from env() for equality with the provided value. • Pattern value comparison - Pattern value comparison allows you to compare a value fetched from env() to a regular expression. • Option based comparison - Option based comparisons use a list of options to create a regular expression. Subsequent calls to add an already defined options detector will merge the options. • Callback detectors - Callback detectors allow you to provide a ‘callback’ type to handle the check. The callback will receive the request object as its only parameter. Cake\Network\Request::addDetector($name, $options) Some examples would be: // Add an environment detector. $this->request->addDetector( ’post’, [’env’ => ’REQUEST_METHOD’, ’value’ => ’POST’] ); 68 Capítulo 6. Request and Response Objects CakePHP Cookbook Documentation, Release 3.x // Add a pattern value detector. $this->request->addDetector( ’iphone’, [’env’ => ’HTTP_USER_AGENT’, ’pattern’ => ’/iPhone/i’] ); // Add an option detector $this->request->addDetector(’internalIp’, [ ’env’ => ’CLIENT_IP’, ’options’ => [’192.168.0.101’, ’192.168.0.100’] ]); // Add a callback detector. Can either be an anonymous function // or a regular callable. $this->request->addDetector( ’awesome’, [’callback’ => function ($request) { return isset($request->awesome); }] ); Request also includes methods like Cake\Network\Request::domain(), Cake\Network\Request::subdomains() and Cake\Network\Request::host() to help applications with subdomains, have a slightly easier life. There are several built-in detectors that you can use: • is(’get’) Check to see whether the current request is a GET. • is(’put’) Check to see whether the current request is a PUT. • is(’post’) Check to see whether the current request is a POST. • is(’delete’) Check to see whether the current request is a DELETE. • is(’head’) Check to see whether the current request is HEAD. • is(’options’) Check to see whether the current request is OPTIONS. • is(’ajax’) Check to see whether the current request came with X-Requested-With = XMLHttpRequest. • is(’ssl’) Check to see whether the request is via SSL • is(’flash’) Check to see whether the request has a User-Agent of Flash • is(’mobile’) Check to see whether the request came from a common list of mobile agents. Session Data To access the session for a given request use the session() method: $this->request->session()->read(’User.name’); For more information, see the Sessions documentation for how to use the session object. Request 69 CakePHP Cookbook Documentation, Release 3.x Host and Domain Name Cake\Network\Request::domain($tldLength = 1) Returns the domain name your application is running on: // Prints ’example.org’ echo $request->domain(); Cake\Network\Request::subdomains($tldLength = 1) Returns the subdomains your application is running on as an array: // Returns [’my’, ’dev’] for ’my.dev.example.org’ $request->subdomains(); Cake\Network\Request::host() Returns the host your application is on: // Prints ’my.dev.example.org’ echo $request->host(); Working With HTTP Methods & Headers Cake\Network\Request::method() Returns the HTTP method the request was made with: // Output POST echo $request->method(); Cake\Network\Request::allowMethod($methods) Set allowed HTTP methods. If not matched, will throw MethodNotAllowedException. The 405 response will include the required Allow header with the passed methods Cake\Network\Request::header($name) Allows you to access any of the HTTP_* headers that were used for the request. For example: $this->request->header(’User-Agent’); would return the user agent used for the request. Cake\Network\Request::referer($local = false) Returns the referring address for the request. Cake\Network\Request::clientIp() Returns the current visitor’s IP address. 70 Capítulo 6. Request and Response Objects CakePHP Cookbook Documentation, Release 3.x Trusting Proxy Headers If your application is behind a load balancer or running on a cloud service, you will often get the load balancer host, port and scheme in your requests. Often load balancers will also send HTTP-X-Forwarded-* headers with the original values. The forwarded headers will not be used by CakePHP out of the box. To have the request object use these headers set the trustProxy property to true: $this->request->trustProxy = true; // These methods will not use the proxied headers. $this->request->port(); $this->request->host(); $this->request->scheme(); $this->request->clientIp(); Checking Accept Headers Cake\Network\Request::accepts($type = null) Find out which content types the client accepts, or check whether it accepts a particular type of content. Get all types: $this->request->accepts(); Check for a single type: $this->request->accepts(’application/json’); Cake\Network\Request::acceptLanguage($language = null) Get all the languages accepted by the client, or check whether a specific language is accepted. Get the list of accepted languages: $this->request->acceptLanguage(); Check whether a specific language is accepted: $this->request->acceptLanguage(’es-es’); Response class Cake\Network\Response Cake\Network\Response is the default response class in CakePHP. It encapsulates a number of features and functionality for generating HTTP responses in your application. It also assists in testing, as it can be mocked/stubbed allowing you to inspect headers that will be sent. Like Cake\Network\Request, Cake\Network\Response consolidates a number of methods previously found on Controller, RequestHandlerComponent and Dispatcher. The old methods are deprecated in favour of using Cake\Network\Response. Response 71 CakePHP Cookbook Documentation, Release 3.x Response provides an interface to wrap the common response-related tasks such as: • Sending headers for redirects. • Sending content type headers. • Sending any header. • Sending the response body. Changing the Response Class CakePHP uses Response by default. Response is a flexible and transparent class. If you need to override it with your own application-specific class, you can replace Response in webroot/index.php. This will make all the controllers in your application use CustomResponse instead of Cake\Network\Response. You can also replace the response instance by setting $this->response in your controllers. Overriding the response object is handy during testing, as it allows you to stub out the methods that interact with header(). See the section on Response and Testing for more information. Dealing with Content Types Cake\Network\Response::type($contentType = null) You can control the Content-Type of your application’s responses with Cake\Network\Response::type(). If your application needs to deal with content types that are not built into Response, you can map them with type() as well: // Add a vCard type $this->response->type([’vcf’ => ’text/v-card’]); // Set the response Content-Type to vcard. $this->response->type(’vcf’); Usually, you’ll want to map additional content types in your controller’s beforeFilter() callback, so you can leverage the automatic view switching features of RequestHandlerComponent if you are using it. Setting the Character Set Cake\Network\Response::charset($charset = null) Sets the charset that will be used in the response: $this->response->charset(’UTF-8’); Sending Files Cake\Network\Response::file($path, $options =[]) 72 Capítulo 6. Request and Response Objects CakePHP Cookbook Documentation, Release 3.x There are times when you want to send files as responses for your requests. You can accomplish that by using Cake\Network\Response::file(): public function sendFile($id) { $file = $this->Attachment->getFile($id); $this->response->file($file[’path’]); // Return response object to prevent controller from trying to render // a view. return $this->response; } As shown in the above example, you must pass the file path to the method. CakePHP will send a proper content type header if it’s a known file type listed in Cake\Network\Reponse::$_mimeTypes. You can add new types prior to calling Cake\Network\Response::file() by using the Cake\Network\Response::type() method. If you want, you can also force a file to be downloaded instead of displayed in the browser by specifying the options: $this->response->file( $file[’path’], [’download’ => true, ’name’ => ’foo’] ); The supported options are: name The name allows you to specify an alternate file name to be sent to the user. download A boolean value indicating whether headers should be set to force download. Sending a String as File You can respond with a file that does not exist on the disk, such as a pdf or an ics generated on the fly from a string: public function sendIcs() { $icsString = $this->Calendar->generateIcs(); $this->response->body($icsString); $this->response->type(’ics’); // Optionally force file download $this->response->download(’filename_for_download.ics’); // Return response object to prevent controller from trying to render // a view. return $this->response; } Setting Headers Cake\Network\Response::header($header = null, $value = null) Response 73 CakePHP Cookbook Documentation, Release 3.x Setting headers is done with the Cake\Network\Response::header() method. It can be called with a few different parameter configurations: // Set a single header $this->response->header(’Location’, ’http://example.com’); // Set multiple headers $this->response->header([ ’Location’ => ’http://example.com’, ’X-Extra’ => ’My header’ ]); $this->response->header([ ’WWW-Authenticate: Negotiate’, ’Content-type: application/pdf’ ]); Setting the same header() multiple times will result in overwriting the previous values, just as regular header calls. Headers are not sent when Cake\Network\Response::header() is called; instead they are buffered until the response is actually sent. You can now use the convenience method Cake\Network\Response::location() to directly set or get the redirect location header. Interacting with Browser Caching Cake\Network\Response::disableCache() You sometimes need to force browsers not to cache the results of a controller action. Cake\Network\Response::disableCache() is intended for just that: public function index() { // Do something. $this->response->disableCache(); } Warning: Using disableCache() with downloads from SSL domains while trying to send files to Internet Explorer can result in errors. Cake\Network\Response::cache($since, $time = ‘+1 day’) You can also tell clients that you Cake\Network\Response::cache(): want them to cache responses. By using public function index() { // Do something. $this->response->cache(’-1 minute’, ’+5 days’); } The above would tell clients to cache the resulting response for 5 days, hopefully speeding up your visitors’ experience. CakeResponse::cache() sets the Last-Modified value to the first argument. 74 Capítulo 6. Request and Response Objects CakePHP Cookbook Documentation, Release 3.x Expires header and the max-age directive are set based on the second parameter. Cache-Control’s public directive is set as well. Fine Tuning HTTP Cache One of the best and easiest ways of speeding up your application is to use HTTP cache. Under this caching model, you are only required to help clients decide if they should use a cached copy of the response by setting a few headers such as modified time and response entity tag. Rather than forcing you to code the logic for caching and for invalidating (refreshing) it once the data has changed, HTTP uses two models, expiration and validation, which usually are much simpler to use. Apart from using Cake\Network\Response::cache(), you can also use many other methods to fine-tune HTTP cache headers to take advantage of browser or reverse proxy caching. The Cache Control Header Cake\Network\Response::sharable($public = null, $time = null) Used under the expiration model, this header contains multiple indicators that can change the way browsers or proxies use the cached content. A Cache-Control header can look like this: Cache-Control: private, max-age=3600, must-revalidate Response class helps you set this header with some utility methods that will produce a final valid Cache-Control header. The first is the Cake\Network\Response::sharable() method, which indicates whether a response is to be considered sharable across different users or clients. This method actually controls the public or private part of this header. Setting a response as private indicates that all or part of it is intended for a single user. To take advantage of shared caches, the control directive must be set as public. The second parameter of this method is used to specify a max-age for the cache, which is the number of seconds after which the response is no longer considered fresh: public function view() { // ... // Set the Cache-Control as public for 3600 seconds $this->response->sharable(true, 3600); } public function my_data() { // ... // Set the Cache-Control as private for 3600 seconds $this->response->sharable(false, 3600); } Response exposes separate methods for setting each of the directives in the Cache-Control header. Response 75 CakePHP Cookbook Documentation, Release 3.x The Expiration Header Cake\Network\Response::expires($time = null) You can set the Expires header to a date and time after which the response is no longer considered fresh. This header can be set using the Cake\Network\Response::expires() method: public function view() { $this->response->expires(’+5 days’); } This method also accepts a DateTime instance or any string that can be parsed by the DateTime class. The Etag Header Cake\Network\Response::etag($tag = null, $weak = false) Cache validation in HTTP is often used when content is constantly changing, and asks the application to only generate the response contents if the cache is no longer fresh. Under this model, the client continues to store pages in the cache, but it asks the application every time whether the resource has changed, instead of using it directly. This is commonly used with static resources such as images and other assets. The etag() method (called entity tag) is a string that uniquely identifies the requested resource, as a checksum does for a file, in order to determine whether it matches a cached resource. To take advantage of this header, you must either call the Cake\Network\Response::checkNotModified() method manually or include the RequestHandlerComponent in your controller: public function index() { $articles = $this->Article->find(’all’); $this->response->etag($this->Article->generateHash($articles)); if ($this->response->checkNotModified($this->request)) { return $this->response; } // ... } The Last Modified Header Cake\Network\Response::modified($time = null) Also, under the HTTP cache validation model, you can set the Last-Modified header to indicate the date and time at which the resource was modified for the last time. Setting this header helps CakePHP tell caching clients whether the response was modified or not based on their cache. To take advantage of this header, you must either call the Cake\Network\Response::checkNotModified() method or include the RequestHandlerComponent in your controller: public function view() { $article = $this->Article->find(’first’); $this->response->modified($article[’Article’][’modified’]); if ($this->response->checkNotModified($this->request)) { 76 Capítulo 6. Request and Response Objects CakePHP Cookbook Documentation, Release 3.x return $this->response; } // ... } The Vary Header Cake\Network\Response::vary($header) In some cases, you might want to serve different content using the same URL. This is often the case if you have a multilingual page or respond with different HTML depending on the browser. Under such circumstances you can use the Vary header: $this->response->vary(’User-Agent’); $this->response->vary(’Accept-Encoding’, ’User-Agent’); $this->response->vary(’Accept-Language’); Sending Not-Modified Responses Cake\Network\Response::checkNotModified(Request $request) Compares the cache headers for the request object with the cache header from the response and determines whether it can still be considered fresh. If so, deletes the response content, and sends the 304 Not Modified header: // In a controller action. if ($this->response->checkNotModfied($this->request)) { return $this->response; } Sending the Response Cake\Network\Response::send() Once you are done creating a response, calling send() will send all the set headers as well as the body. This is done automatically at the end of each request by Dispatcher. Response and Testing The Response class helps make testing controllers and components easier. By having a single place to mock/stub headers you can more easily test controllers and components: public function testSomething() { $this->controller->response = $this->getMock(’Cake\Network\Response’); $this->controller->response->expects($this->once())->method(’header’); // ... } Response 77 CakePHP Cookbook Documentation, Release 3.x Additionally, you can run tests from the command line more easily, as you can use mocks to avoid the ‘headers sent’ errors that can occur when trying to set headers in CLI. 78 Capítulo 6. Request and Response Objects CAPÍTULO 7 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 { } 79 CakePHP Cookbook Documentation, Release 3.x 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. Note: 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 Connecting Routes 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 Request and Response Objects 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 } 80 Capítulo 7. Controllers CakePHP Cookbook Documentation, Release 3.x 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. Note: 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 Components 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 http://api20.cakephp.org/class/controller. 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 Ciclo de Vida dos Callbacks em uma Requisição 81 CakePHP Cookbook Documentation, Release 3.x 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. Changed in version 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(); } ... } 82 Capítulo 7. Controllers CakePHP Cookbook Documentation, Release 3.x 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 app/View/Posts/my_action.ctp ao invés de 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’)); } } Note: 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)1 . 1 http://en.wikipedia.org/wiki/Post/Redirect/Get Métodos dos Controllers 83 CakePHP Cookbook Documentation, Release 3.x 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. 84 Capítulo 7. Controllers CakePHP Cookbook Documentation, Release 3.x 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 Métodos dos Controllers 85 CakePHP Cookbook Documentation, Release 3.x 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 86 Capítulo 7. Controllers CakePHP Cookbook Documentation, Release 3.x passar dados extras para serem recebidos pela ação do controller, adicione-os no parâmetro options em um formato de array. Note: 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. Warning: 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’, ’l } } 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’), Métodos dos Controllers 87 CakePHP Cookbook Documentation, Release 3.x 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)) ); Note: 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. $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 http://api20.cakephp.org/class/controller. 88 Capítulo 7. Controllers CakePHP Cookbook Documentation, Release 3.x 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. Note: 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 Atributos do Controller 89 CakePHP Cookbook Documentation, Release 3.x AppController. property Controller::$components O array de componentes permite que você diga ao CakePHP quais Components 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 Configuring Components 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) ) ) ); } Mais sobre Controllers Request and Response Objects The request and response objects provide an abstraction around HTTP requests and responses. The request object in CakePHP allows you to easily introspect an incoming request, while the response object allows you to effortlessly create HTTP responses from your controllers. Request class Cake\Network\Request Request is the default request object used in CakePHP. It centralizes a number of features for interrogating and interacting with request data. On each request one Request is created and then passed by reference to the various layers of an application that use request data. By default the request is assigned to 90 Capítulo 7. Controllers CakePHP Cookbook Documentation, Release 3.x $this->request, and is available in Controllers, Cells, Views and Helpers. You can also access it in Components using the controller reference. Some of the duties Request performs include: • Processing the GET, POST, and FILES arrays into the data structures you are familiar with. • Providing environment introspection pertaining to the request. Information like the headers sent, the client’s IP address, and the subdomain/domain names the server your application is running on. • Providing access to request parameters both as array indexes and object properties. Request Parameters Request exposes several interfaces for accessing request parameters: $this->request->params[’controller’]; $this->request->param(’controller’); All of the above will access the same value. All Route Elements are accessed through this interface. In addition to Route Elements, you also often need access to Passed Arguments. These are both available on the request object as well: // Passed arguments $this->request->pass; $this->request[’pass’]; $this->request->params[’pass’]; Will all provide you access to the passed arguments. There are several important/useful parameters that CakePHP uses internally, these are also all found in the request parameters: • plugin The plugin handling the request. Will be null when there is no plugin. • controller The controller handling the current request. • action The action handling the current request. • prefix The prefix for the current action. See Prefix Routing for more information. • bare Present when the request came from Controller\Controller::requestAction() and included the bare option. Bare requests do not have layouts rendered. • requested Present and set to true when Controller\Controller::requestAction(). the action came from Query String Parameters Cake\Network\Request::query($name) Query string parameters can be read using Network\Request::$query: // URL is /posts/index?page=1&sort=title $this->request->query[’page’]; Mais sobre Controllers 91 CakePHP Cookbook Documentation, Release 3.x You can either directly access the query property, or you can use query() method to read the URL query array in an error-free manner. Any keys that do not exist will return null: $foo = $this->request->query(’value_that_does_not_exist’); // $foo === null Request Body Data Cake\Network\Request::data($name) All POST data can be accessed using Cake\Network\Request::data(). Any form data that contains a data prefix will have that data prefix removed. For example: // An input with a name attribute equal to ’MyModel[title]’ is accessible at $this->request->data(’MyModel.title’); Any keys that do not exist will return null: $foo = $this->request->data(’Value.that.does.not.exist’); // $foo == null You can also access the array of data, as an array: $this->request->data[’title’]; $this->request->data[’comments’][1][’author’]; PUT, PATCH or DELETE Data Cake\Network\Request::input($callback[, $options ]) When building REST services, you often accept request data on PUT and DELETE requests. Any application/x-www-form-urlencoded request body data will automatically be parsed and set to $this->data for PUT and DELETE requests. If you are accepting JSON or XML data, see below for how you can access those request bodies. When accessing the input data, you can decode it with an optional function. This is useful when interacting with XML or JSON request body content. Additional parameters for the decoding function can be passed as arguments to input(): $this->request->input(’json_decode’); Environment Variables (from $_SERVER and $_ENV) Cake\Network\Request::env($key, $value = null) Request::env() is a wrapper for env() global function and acts as a getter/setter for enviromnent variables without having to modify globals $_SERVER and $_ENV: 92 Capítulo 7. Controllers CakePHP Cookbook Documentation, Release 3.x // Get a value $value = $this->request->env(’HTTP_HOST’); // Set a value. Generally helpful in testing. $this->request->env(’REQUEST_METHOD’, ’POST’); XML or JSON Data Applications employing REST often exchange data in non-URL-encoded post bodies. You can read input data in any format using Network\Request::input(). By providing a decoding function, you can receive the content in a deserialized format: // Get JSON encoded data submitted to a PUT/POST action $data = $this->request->input(’json_decode’); Some deserializing methods require additional parameters when called, such as the ‘as array’ parameter on json_decode. If you want XML converted into a DOMDocument object, Network\Request::input() supports passing in additional parameters as well: // Get Xml encoded data submitted to a PUT/POST action $data = $this->request->input(’Xml::build’, [’return’ => ’domdocument’]); Path Information The request object also provides useful information about the paths in your application. $request->base and $request->webroot are useful for generating URLs, and determining whether or not your application is in a subdirectory. The various properties you can use are: // Assume the current request URL is /subdir/articles/edit/1?page=1 // Holds /subdir/articles/edit/1?page=1 $request->here; // Holds /subdir $request->base; // Holds /subdir/ $request->webroot; Checking Request Conditions Cake\Network\Request::is($type) The request object provides an easy way to inspect certain conditions in a given request. By using the is() method you can check a number of common conditions, as well as inspect other application specific request criteria: Mais sobre Controllers 93 CakePHP Cookbook Documentation, Release 3.x $this->request->is(’post’); You can also easily extend the request detectors that are available, by using Cake\Network\Request::addDetector() to create new kinds of detectors. There are four different types of detectors that you can create: • Environment value comparison - Compares a value fetched from env() for equality with the provided value. • Pattern value comparison - Pattern value comparison allows you to compare a value fetched from env() to a regular expression. • Option based comparison - Option based comparisons use a list of options to create a regular expression. Subsequent calls to add an already defined options detector will merge the options. • Callback detectors - Callback detectors allow you to provide a ‘callback’ type to handle the check. The callback will receive the request object as its only parameter. Cake\Network\Request::addDetector($name, $options) Some examples would be: // Add an environment detector. $this->request->addDetector( ’post’, [’env’ => ’REQUEST_METHOD’, ’value’ => ’POST’] ); // Add a pattern value detector. $this->request->addDetector( ’iphone’, [’env’ => ’HTTP_USER_AGENT’, ’pattern’ => ’/iPhone/i’] ); // Add an option detector $this->request->addDetector(’internalIp’, [ ’env’ => ’CLIENT_IP’, ’options’ => [’192.168.0.101’, ’192.168.0.100’] ]); // Add a callback detector. Can either be an anonymous function // or a regular callable. $this->request->addDetector( ’awesome’, [’callback’ => function ($request) { return isset($request->awesome); }] ); Request also includes methods like Cake\Network\Request::domain(), Cake\Network\Request::subdomains() and Cake\Network\Request::host() to help applications with subdomains, have a slightly easier life. There are several built-in detectors that you can use: 94 Capítulo 7. Controllers CakePHP Cookbook Documentation, Release 3.x • is(’get’) Check to see whether the current request is a GET. • is(’put’) Check to see whether the current request is a PUT. • is(’post’) Check to see whether the current request is a POST. • is(’delete’) Check to see whether the current request is a DELETE. • is(’head’) Check to see whether the current request is HEAD. • is(’options’) Check to see whether the current request is OPTIONS. • is(’ajax’) Check to see whether the current request came with X-Requested-With = XMLHttpRequest. • is(’ssl’) Check to see whether the request is via SSL • is(’flash’) Check to see whether the request has a User-Agent of Flash • is(’mobile’) Check to see whether the request came from a common list of mobile agents. Session Data To access the session for a given request use the session() method: $this->request->session()->read(’User.name’); For more information, see the Sessions documentation for how to use the session object. Host and Domain Name Cake\Network\Request::domain($tldLength = 1) Returns the domain name your application is running on: // Prints ’example.org’ echo $request->domain(); Cake\Network\Request::subdomains($tldLength = 1) Returns the subdomains your application is running on as an array: // Returns [’my’, ’dev’] for ’my.dev.example.org’ $request->subdomains(); Cake\Network\Request::host() Returns the host your application is on: // Prints ’my.dev.example.org’ echo $request->host(); Mais sobre Controllers 95 CakePHP Cookbook Documentation, Release 3.x Working With HTTP Methods & Headers Cake\Network\Request::method() Returns the HTTP method the request was made with: // Output POST echo $request->method(); Cake\Network\Request::allowMethod($methods) Set allowed HTTP methods. If not matched, will throw MethodNotAllowedException. The 405 response will include the required Allow header with the passed methods Cake\Network\Request::header($name) Allows you to access any of the HTTP_* headers that were used for the request. For example: $this->request->header(’User-Agent’); would return the user agent used for the request. Cake\Network\Request::referer($local = false) Returns the referring address for the request. Cake\Network\Request::clientIp() Returns the current visitor’s IP address. Trusting Proxy Headers If your application is behind a load balancer or running on a cloud service, you will often get the load balancer host, port and scheme in your requests. Often load balancers will also send HTTP-X-Forwarded-* headers with the original values. The forwarded headers will not be used by CakePHP out of the box. To have the request object use these headers set the trustProxy property to true: $this->request->trustProxy = true; // These methods will not use the proxied headers. $this->request->port(); $this->request->host(); $this->request->scheme(); $this->request->clientIp(); Checking Accept Headers Cake\Network\Request::accepts($type = null) Find out which content types the client accepts, or check whether it accepts a particular type of content. Get all types: 96 Capítulo 7. Controllers CakePHP Cookbook Documentation, Release 3.x $this->request->accepts(); Check for a single type: $this->request->accepts(’application/json’); Cake\Network\Request::acceptLanguage($language = null) Get all the languages accepted by the client, or check whether a specific language is accepted. Get the list of accepted languages: $this->request->acceptLanguage(); Check whether a specific language is accepted: $this->request->acceptLanguage(’es-es’); Response class Cake\Network\Response Cake\Network\Response is the default response class in CakePHP. It encapsulates a number of features and functionality for generating HTTP responses in your application. It also assists in testing, as it can be mocked/stubbed allowing you to inspect headers that will be sent. Like Cake\Network\Request, Cake\Network\Response consolidates a number of methods previously found on Controller, RequestHandlerComponent and Dispatcher. The old methods are deprecated in favour of using Cake\Network\Response. Response provides an interface to wrap the common response-related tasks such as: • Sending headers for redirects. • Sending content type headers. • Sending any header. • Sending the response body. Changing the Response Class CakePHP uses Response by default. Response is a flexible and transparent class. If you need to override it with your own application-specific class, you can replace Response in webroot/index.php. This will make all the controllers in your application use CustomResponse instead of Cake\Network\Response. You can also replace the response instance by setting $this->response in your controllers. Overriding the response object is handy during testing, as it allows you to stub out the methods that interact with header(). See the section on Response and Testing for more information. Mais sobre Controllers 97 CakePHP Cookbook Documentation, Release 3.x Dealing with Content Types Cake\Network\Response::type($contentType = null) You can control the Content-Type of your application’s responses with Cake\Network\Response::type(). If your application needs to deal with content types that are not built into Response, you can map them with type() as well: // Add a vCard type $this->response->type([’vcf’ => ’text/v-card’]); // Set the response Content-Type to vcard. $this->response->type(’vcf’); Usually, you’ll want to map additional content types in your controller’s beforeFilter() callback, so you can leverage the automatic view switching features of RequestHandlerComponent if you are using it. Setting the Character Set Cake\Network\Response::charset($charset = null) Sets the charset that will be used in the response: $this->response->charset(’UTF-8’); Sending Files Cake\Network\Response::file($path, $options =[]) There are times when you want to send files as responses for your requests. You can accomplish that by using Cake\Network\Response::file(): public function sendFile($id) { $file = $this->Attachment->getFile($id); $this->response->file($file[’path’]); // Return response object to prevent controller from trying to render // a view. return $this->response; } As shown in the above example, you must pass the file path to the method. CakePHP will send a proper content type header if it’s a known file type listed in Cake\Network\Reponse::$_mimeTypes. You can add new types prior to calling Cake\Network\Response::file() by using the Cake\Network\Response::type() method. If you want, you can also force a file to be downloaded instead of displayed in the browser by specifying the options: 98 Capítulo 7. Controllers CakePHP Cookbook Documentation, Release 3.x $this->response->file( $file[’path’], [’download’ => true, ’name’ => ’foo’] ); The supported options are: name The name allows you to specify an alternate file name to be sent to the user. download A boolean value indicating whether headers should be set to force download. Sending a String as File You can respond with a file that does not exist on the disk, such as a pdf or an ics generated on the fly from a string: public function sendIcs() { $icsString = $this->Calendar->generateIcs(); $this->response->body($icsString); $this->response->type(’ics’); // Optionally force file download $this->response->download(’filename_for_download.ics’); // Return response object to prevent controller from trying to render // a view. return $this->response; } Setting Headers Cake\Network\Response::header($header = null, $value = null) Setting headers is done with the Cake\Network\Response::header() method. It can be called with a few different parameter configurations: // Set a single header $this->response->header(’Location’, ’http://example.com’); // Set multiple headers $this->response->header([ ’Location’ => ’http://example.com’, ’X-Extra’ => ’My header’ ]); $this->response->header([ ’WWW-Authenticate: Negotiate’, ’Content-type: application/pdf’ ]); Mais sobre Controllers 99 CakePHP Cookbook Documentation, Release 3.x Setting the same header() multiple times will result in overwriting the previous values, just as regular header calls. Headers are not sent when Cake\Network\Response::header() is called; instead they are buffered until the response is actually sent. You can now use the convenience method Cake\Network\Response::location() to directly set or get the redirect location header. Interacting with Browser Caching Cake\Network\Response::disableCache() You sometimes need to force browsers not to cache the results of a controller action. Cake\Network\Response::disableCache() is intended for just that: public function index() { // Do something. $this->response->disableCache(); } Warning: Using disableCache() with downloads from SSL domains while trying to send files to Internet Explorer can result in errors. Cake\Network\Response::cache($since, $time = ‘+1 day’) You can also tell clients that you Cake\Network\Response::cache(): want them to cache responses. By using public function index() { // Do something. $this->response->cache(’-1 minute’, ’+5 days’); } The above would tell clients to cache the resulting response for 5 days, hopefully speeding up your visitors’ experience. CakeResponse::cache() sets the Last-Modified value to the first argument. Expires header and the max-age directive are set based on the second parameter. Cache-Control’s public directive is set as well. Fine Tuning HTTP Cache One of the best and easiest ways of speeding up your application is to use HTTP cache. Under this caching model, you are only required to help clients decide if they should use a cached copy of the response by setting a few headers such as modified time and response entity tag. Rather than forcing you to code the logic for caching and for invalidating (refreshing) it once the data has changed, HTTP uses two models, expiration and validation, which usually are much simpler to use. Apart from using Cake\Network\Response::cache(), you can also use many other methods to fine-tune HTTP cache headers to take advantage of browser or reverse proxy caching. 100 Capítulo 7. Controllers CakePHP Cookbook Documentation, Release 3.x The Cache Control Header Cake\Network\Response::sharable($public = null, $time = null) Used under the expiration model, this header contains multiple indicators that can change the way browsers or proxies use the cached content. A Cache-Control header can look like this: Cache-Control: private, max-age=3600, must-revalidate Response class helps you set this header with some utility methods that will produce a final valid Cache-Control header. The first is the Cake\Network\Response::sharable() method, which indicates whether a response is to be considered sharable across different users or clients. This method actually controls the public or private part of this header. Setting a response as private indicates that all or part of it is intended for a single user. To take advantage of shared caches, the control directive must be set as public. The second parameter of this method is used to specify a max-age for the cache, which is the number of seconds after which the response is no longer considered fresh: public function view() { // ... // Set the Cache-Control as public for 3600 seconds $this->response->sharable(true, 3600); } public function my_data() { // ... // Set the Cache-Control as private for 3600 seconds $this->response->sharable(false, 3600); } Response exposes separate methods for setting each of the directives in the Cache-Control header. The Expiration Header Cake\Network\Response::expires($time = null) You can set the Expires header to a date and time after which the response is no longer considered fresh. This header can be set using the Cake\Network\Response::expires() method: public function view() { $this->response->expires(’+5 days’); } This method also accepts a DateTime instance or any string that can be parsed by the DateTime class. The Etag Header Cake\Network\Response::etag($tag = null, $weak = false) Cache validation in HTTP is often used when content is constantly changing, and asks the application to only generate the response contents if the cache is no longer fresh. Under this model, the client continues to store pages in the cache, but it asks the application every time whether the resource has changed, instead of using it directly. This is commonly used with static resources such as images and other assets. The etag() method (called entity tag) is a string that uniquely identifies the requested resource, as a checksum does for a file, in order to determine whether it matches a cached resource. Mais sobre Controllers 101 CakePHP Cookbook Documentation, Release 3.x To take advantage of this header, you must either call the Cake\Network\Response::checkNotModified() method manually or include the RequestHandlerComponent in your controller: public function index() { $articles = $this->Article->find(’all’); $this->response->etag($this->Article->generateHash($articles)); if ($this->response->checkNotModified($this->request)) { return $this->response; } // ... } The Last Modified Header Cake\Network\Response::modified($time = null) Also, under the HTTP cache validation model, you can set the Last-Modified header to indicate the date and time at which the resource was modified for the last time. Setting this header helps CakePHP tell caching clients whether the response was modified or not based on their cache. To take advantage of this header, you must either call the Cake\Network\Response::checkNotModified() method or include the RequestHandlerComponent in your controller: public function view() { $article = $this->Article->find(’first’); $this->response->modified($article[’Article’][’modified’]); if ($this->response->checkNotModified($this->request)) { return $this->response; } // ... } The Vary Header Cake\Network\Response::vary($header) In some cases, you might want to serve different content using the same URL. This is often the case if you have a multilingual page or respond with different HTML depending on the browser. Under such circumstances you can use the Vary header: $this->response->vary(’User-Agent’); $this->response->vary(’Accept-Encoding’, ’User-Agent’); $this->response->vary(’Accept-Language’); Sending Not-Modified Responses Cake\Network\Response::checkNotModified(Request $request) Compares the cache headers for the request object with the cache header from the response and determines whether it can still be considered fresh. If so, deletes the response content, and sends the 304 Not Modified header: // In a controller action. if ($this->response->checkNotModfied($this->request)) { 102 Capítulo 7. Controllers CakePHP Cookbook Documentation, Release 3.x return $this->response; } Sending the Response Cake\Network\Response::send() Once you are done creating a response, calling send() will send all the set headers as well as the body. This is done automatically at the end of each request by Dispatcher. Response and Testing The Response class helps make testing controllers and components easier. By having a single place to mock/stub headers you can more easily test controllers and components: public function testSomething() { $this->controller->response = $this->getMock(’Cake\Network\Response’); $this->controller->response->expects($this->once())->method(’header’); // ... } Additionally, you can run tests from the command line more easily, as you can use mocks to avoid the ‘headers sent’ errors that can occur when trying to set headers in CLI. The Pages Controller CakePHP ships with a default controller PagesController.php. This is a simple and optional controller for serving up static content. The home page you see after installation is generated using this controller. If you make the view file src/Template/Pages/about_us.ctp you can access it using the URL http://example.com/pages/about_us. You are free to modify the Pages Controller to meet your needs. When you “bake” an app using Composer the Pages Controller is created in your src/Controller/ folder. Components Components are packages of logic that are shared between controllers. CakePHP comes with a fantastic set of core components you can use to aid in various common tasks. You can also create your own components. If you find yourself wanting to copy and paste things between controllers, you should consider creating your own component to contain the functionality. Creating components keeps controller code clean and allows you to reuse code between projects. For more information on the components included in CakePHP, check out the chapter for each component: Mais sobre Controllers 103 CakePHP Cookbook Documentation, Release 3.x Authentication class AuthComponent(ComponentCollection $collection, array $config =[]) Identifying, authenticating and authorizing users is a common part of almost every web application. In CakePHP AuthComponent provides a pluggable way to do these tasks. AuthComponent allows you to combine authentication objects, and authorization objects to create flexible ways of identifying and checking user authorization. Suggested Reading Before Continuing Configuring authentication requires several steps including defining a users table, creating a model, controller & views, etc. This is all covered step by step in the Blog Tutorial. Authentication Authentication is the process of identifying users by provided credentials and ensuring that users are who they say they are. Generally this is done through a username and password, that are checked against a known list of users. In CakePHP, there are several built-in ways of authenticating users stored in your application. • FormAuthenticate allows you to authenticate users based on form POST data. Usually this is a login form that users enter information into. • BasicAuthenticate allows you to authenticate users using Basic HTTP authentication. • DigestAuthenticate allows you to authenticate users using Digest HTTP authentication. By default AuthComponent uses FormAuthenticate. Choosing an Authentication Type Generally you’ll want to offer form based authentication. It is the easiest for users using a web-browser to use. If you are building an API or webservice, you may want to consider basic authentication or digest authentication. The key differences between digest and basic authentication are mostly related to how passwords are handled. In basic authentication, the username and password are transmitted as plain-text to the server. This makes basic authentication un-suitable for applications without SSL, as you would end up exposing sensitive passwords. Digest authentication uses a digest hash of the username, password, and a few other details. This makes digest authentication more appropriate for applications without SSL encryption. You can also use authentication systems like openid as well, however openid is not part of CakePHP core. Configuring Authentication Handlers You configure authentication handlers using the authenticate config. You can configure one or many handlers for authentication. Using multiple handlers allows you to support different ways of logging users in. When logging users in, authentication handlers are checked in the order they are declared. Once one handler is able to identify the user, no other handlers will be checked. Conversely you can halt all authentication by throwing an exception. You will need to catch any thrown exceptions, and handle them as needed. 104 Capítulo 7. Controllers CakePHP Cookbook Documentation, Release 3.x You can configure authentication handlers in your controller’s beforeFilter or, in the $components array. You can pass configuration information into each authentication object, using an array: // Basic setup $this->Auth->config(’authenticate’, [’Form’]); // Pass settings in $this->Auth->config(’authenticate’, [ ’Basic’ => [’userModel’ => ’Members’], ’Form’ => [’userModel’ => ’Members’] ]); In the second example you’ll notice that we had to declare the userModel key twice. To help you keep your code DRY, you can use the all key. This special key allows you to set settings that are passed to every attached object. The all key is also exposed as AuthComponent::ALL: // Pass settings in using ’all’ $this->Auth->config(’authenticate’, [ AuthComponent::ALL => [’userModel’ => ’Members’], ’Basic’, ’Form’ ]); In the above example, both Form and Basic will get the settings defined for the ‘all’ key. Any settings passed to a specific authentication object will override the matching key in the ‘all’ key. The core authentication objects support the following configuration keys. • fields The fields to use to identify a user by. You can use keys username and password to specify your username and password fields respectively. • userModel The model name of the users table, defaults to Users. • scope Additional conditions to use when looking up and authenticating users, [’Users.is_active’ => true]. i.e. • contain Extra models to contain and return with identified user’s info. • passwordHasher Password hasher class. Defaults to Default. To configure different fields for user in $components array: // Pass settings in $components array public $components = [ ’Auth’ => [ ’authenticate’ => [ ’Form’ => [ ’fields’ => [’username’ => ’email’, ’password’ => ’passwd’] ] ] ] ]; Do not put other Auth configuration keys (like authError, loginAction etc) within the authenticate or Form element. They should be at the same level as the authenticate key. The setup above with other Auth configuration should look like: Mais sobre Controllers 105 CakePHP Cookbook Documentation, Release 3.x // Pass settings in $components array public $components = [ ’Auth’ => [ ’loginAction’ => [ ’controller’ => ’Users’, ’action’ => ’login’, ’plugin’ => ’Users’ ], ’authError’ => ’Did you really think you are allowed to see that?’, ’authenticate’ => [ ’Form’ => [ ’fields’ => [’username’ => ’email’] ] ] ] ]; In addition to the common configuration, Basic authentication supports the following keys: • realm The realm being authenticated. Defaults to env(’SERVER_NAME’). In addition to the common configuration Digest authentication supports the following keys: • realm The realm authentication is for, Defaults to the servername. • nonce A nonce used for authentication. Defaults to uniqid(). • qop Defaults to auth, no other values are supported at this time. • opaque A string that must md5($config[’realm’]) be returned unchanged by clients. Defaults to Identifying Users and Logging Them In AuthComponent::identify() You need to manually call $this->Auth->identify() to identify the user using credentials provided in request. Then use $this->Auth->setUser() to log the user in i.e. save user info to session. When authenticating users, attached authentication objects are checked in the order they are attached. Once one of the objects can identify the user, no other objects are checked. A sample login function for working with a login form could look like: public function login() { if ($this->request->is(’post’)) { $user = $this->Auth->identify(); if ($user) { $this->Auth->setUser($user); return $this->redirect($this->Auth->redirectUrl()); } else { $this->Flash->error( __(’Username or password is incorrect’), ’default’, [], ’auth’ ); 106 Capítulo 7. Controllers CakePHP Cookbook Documentation, Release 3.x } } } The above code will attempt to first identify a user in using the POST data. If successful we set the user info to session so that it persists across requests and redirect to either the last page they were visiting or a URL specified in the loginRedirect config. If the login is unsuccessful, a flash message is set. Warning: $this->Auth->setUser($data) will log the user in with whatever data is passed to the method. It won’t actually check the credentials against an authentication class. Redirecting Users After Login AuthComponent::redirectUrl() After logging a user in, you’ll generally want to redirect them back to where they came from. Pass a URL in to set the destination a user should be redirected to upon logging in. If no parameter is passed, gets the authentication redirect URL. The URL returned is as per following rules: • Returns the normalized URL from session Auth.redirect value if it is present and for the same domain the current app is running on. • If there is no session value and there is a config loginRedirect, the loginRedirect value is returned. • If there is no session and no loginRedirect, / is returned. Using Digest and Basic Authentication for Logging In Basic and digest are stateless authentication schemes and don’t require an initial POST or a form. If using only basic / digest authenticators you don’t require a login action in your controller. Also you can set $this->Auth->sessionKey to false to ensure AuthComponent doesn’t try to read user info from session. You may also want to set config unauthorizedRedirect to false which will cause AuthComponent to throw a ForbiddenException instead of default behavior of redirecting to referer. Stateless authentication will re-verify the user’s credentials on each request, this creates a small amount of additional overhead, but allows clients to login in without using cookies and makes is suitable for APIs. Creating Custom Authentication Objects Because authentication objects are pluggable, you can create custom authentication objects in your application or plugins. If for example you wanted to create an OpenID authentication object. In app/Auth/OpenidAuthenticate.php you could put the following: use Cake\Auth\BaseAuthenticate; class OpenidAuthenticate extends BaseAuthenticate { public function authenticate(Request $request, Response $response) { // Do things for OpenID here. // Return an array of user if they could authenticate the user, // return false if not. } } Mais sobre Controllers 107 CakePHP Cookbook Documentation, Release 3.x Authentication objects should return false if they cannot identify the user and an array of user information if they can. It’s not required that you extend BaseAuthenticate, only that your authentication object implements an authenticate() method. The BaseAuthenticate class provides a number of helpful methods that are commonly used. You can also implement a getUser() method if your authentication object needs to support stateless or cookie-less authentication. See the sections on basic and digest authentication below for more information. Using Custom Authentication Objects Once you’ve created your custom authentication object, you can use them by including them in AuthComponents authenticate array: $this->Auth->config(’authenticate’, [ ’Openid’, // app authentication object. ’AuthBag.Combo’, // plugin authentication object. ]); Creating Stateless Authentication Systems Authentication objects can implement a getUser() method that can be used to support user login systems that don’t rely on cookies. A typical getUser method looks at the request/environment and uses the information there to confirm the identity of the user. HTTP Basic authentication for example uses $_SERVER[’PHP_AUTH_USER’] and $_SERVER[’PHP_AUTH_PW’] for the username and password fields. On each request, these values are used to re-identify the user and ensure they are valid user. As with authentication object’s authenticate() method the getUser() method should return an array of user information on success or false on failure.: public function getUser($request) { $username = env(’PHP_AUTH_USER’); $pass = env(’PHP_AUTH_PW’); if (empty($username) || empty($pass)) { return false; } return $this->_findUser($username, $pass); } The above is how you could implement getUser method for HTTP basic authentication. The _findUser() method is part of BaseAuthenticate and identifies a user based on a username and password. Handling Unauthenticated Requests When an unauthenticated user tries to access a protected page first the unauthenticated() method of the last authenticator in the chain is called. The authenticate object can handle sending response or redirection by returning a response object, to indicate no further action is necessary. Due to this, the order in which you specify the authentication provider in authenticate config matters. If authenticator returns null, AuthComponent redirects user to login action. If it’s an AJAX request and config ajaxLogin is specified that element is rendered else a 403 HTTP status code is returned. 108 Capítulo 7. Controllers CakePHP Cookbook Documentation, Release 3.x Displaying Auth Related Flash Messages In order to display the session error messages that Auth generates, you need to add the following code to your layout. Add the following two lines to the src/Template/Layout/default.ctp file in the body section: echo $this->Flash->render(); echo $this->Flash->render(’auth’); You can customize the error messages, and flash settings AuthComponent uses. Using flash config you can configure the parameters AuthComponent uses for setting flash messages. The available keys are • key - The key to use, defaults to ‘auth’. • params - The array of additional params to use, defaults to []. In addition to the flash message settings you can customize other error messages AuthComponent uses. In your controller’s beforeFilter, or component settings you can use authError to customize the error used for when authorization fails: $this->Auth->config(’authError’, "Woopsie, you are not authorized to access this area."); Sometimes, you want to display the authorization error only after the user has already logged-in. You can suppress this message by setting its value to boolean false. In your controller’s beforeFilter(), or component settings: if (!$this->Auth->user()) { $this->Auth->config(’authError’, false); } Hashing Passwords You are responsible for hashing the passwords before they are persisted to the database, the easiest way is to use a setter function in your User entity: namespace App\Model\Entity; use Cake\Auth\DefaultPasswordHasher; use Cake\ORM\Entity; class User extends Entity { // ... protected function _setPassword($password) { return (new DefaultPasswordHasher)->hash($password); } // ... } AuthComponent is configured by default to use the DefaultPasswordHasher when validating user credentials so no additional configuration is required in order to authenticate users. DefaultPasswordHasher uses the bcrypt hashing algorithm internally, which is one of the stronger password hashing solution used in the industry. While it is recommended that you use this password hasher class, the case may be that you are managing a database of users whose password was hashed differently. Mais sobre Controllers 109 CakePHP Cookbook Documentation, Release 3.x Creating Custom Password Hasher Classes In order to use a different password hasher, you need to create the class in src/Auth/LegacyPasswordHasher.php and implement the hash and check methods. This class needs to extend the AbstractPasswordHasher class: namespace App\Auth; use \Cake\Auth\AbstractPasswordHasher; class LegacyPasswordHasher extends AbstractPasswordHasher { public function hash($password) { return sha1($password); } public function check($password, $hashedPassword) { return sha1($password) === $hashedPassword; } } Then you are required to configure the AuthComponent to use your own password hasher: public $components = [ ’Auth’ => [ ’authenticate’ => [ ’Form’ => [ ’passwordHasher’ => [ ’className’ => ’Legacy’, ] ] ] ] ]; Supporting legacy systems is a good idea, but it is even better to keep your database with the latest security advancements. The following section will explain how to migrate from one hashing algorithm to CakePHP’s default Changing Hashing Algorithms CakePHP provides a clean way to migrate your users’ passwords from one algorithm to another, this is achieved through the FallbackPasswordHasher class. Assuming you are using LegacyPasswordHasher from the previous example, you can configure the AuthComponent as follows: public $components = [ ’Auth’ => [ ’authenticate’ => [ ’Form’ => [ ’passwordHasher’ => [ ’className’ => ’Fallback’, ’hashers’ => [’Default’, ’Legacy’] ] ] ] 110 Capítulo 7. Controllers CakePHP Cookbook Documentation, Release 3.x ] ]; The first name appearing in the hashers key indicates which of the classes is the preferred one, but it will fallback to the others in the list if the check was unsuccessful. In order to update old users’ passwords on the fly, you can change the login function accordingly: public function login() { if ($this->request->is(’post’)) { $user = $this->Auth->identify(); if ($user) { $this->Auth->setUser($user); if ($this->Auth->authenticationProvider()->needsPasswordRehash()) { $user = $this->Users->get($this->Auth->user(’id’)); $user->password = $this->request->data(’password’); $this->Users->save($user); } return $this->redirect($this->Auth->redirectUrl()); } ... } } As you cans see we are just setting the plain password again to to property so the setter function in the entity hashes the password as shown in previous examples and then saved again to the database. Hashing Passwords For Digest Authentication Because Digest authentication requires a password hashed in the format defined by the RFC, in order to correctly hash a password for use with Digest authentication you should use the special password hashing function on DigestAuthenticate. If you are going to be combining digest authentication with any other authentication strategies, it’s also recommended that you store the digest password in a separate column, from the normal password hash: namespace App\Model\Table; use Cake\Auth\DigestAuthenticate; use Cake\Event\Event; use Cake\ORM\Table; class UsersTable extends Table { public function beforeSave(Event $event) { $entity = $event->data[’entity’]; // Make a password for digest auth. $entity->digest_hash = DigestAuthenticate::password( $entity->username, $entity->plain_password, env(’SERVER_NAME’) ); return true; } Mais sobre Controllers 111 CakePHP Cookbook Documentation, Release 3.x } Passwords for digest authentication need a bit more information than other password hashes, based on the RFC for digest authentication. Note: The third parameter of DigestAuthenticate::password() must match the ‘realm’ config value defined when DigestAuthentication was configured in AuthComponent::$authenticate. This defaults to env(’SCRIPT_NAME’). You may wish to use a static string if you want consistent hashes in multiple environments. Manually Logging Users In AuthComponent::setUser(array $user) Sometimes the need arises where you need to manually log a user in, such as just after they registered for your application. You can do this by calling $this->Auth->setUser() with the user data you want to ‘login’: public function register() { $user = $this->Users->newEntity($this->request->data); if ($this->Users->save($user)) { $this->Auth->setUser($user->toArray()); return $this->redirect([ ’controller’ => ’Users’, ’action’ => ’home’ ]); } } Warning: Be sure to manually add the new User id to the array passed to the setUser() method. Otherwise you won’t have the user id available. Accessing the Logged In User AuthComponent::user($key = null) Once a user is logged in, you will often need some particular information about the current user. You can access the currently logged in user using AuthComponent::user(): // From inside a controller or other component. $this->Auth->user(’id’); If the current user is not logged in or the key doesn’t exist, null will be returned. Logging Users Out AuthComponent::logout() Eventually you’ll want a quick way to de-authenticate someone, and redirect them to where they need to go. This method is also useful if you want to provide a ‘Log me out’ link inside a members’ area of your application: 112 Capítulo 7. Controllers CakePHP Cookbook Documentation, Release 3.x public function logout() { return $this->redirect($this->Auth->logout()); } Logging out users that logged in with Digest or Basic auth is difficult to accomplish for all clients. Most browsers will retain credentials for the duration they are still open. Some clients can be forced to logout by sending a 401 status code. Changing the authentication realm is another solution that works for some clients. Authorization Authorization is the process of ensuring that an identified/authenticated user is allowed to access the resources they are requesting. If enabled AuthComponent can automatically check authorization handlers and ensure that logged in users are allowed to access the resources they are requesting. There are several built-in authorization handlers, and you can create custom ones for your application, or as part of a plugin. • ControllerAuthorize Calls isAuthorized() on the active controller, and uses the return of that to authorize a user. This is often the most simple way to authorize users. Note: The ActionsAuthorize & CrudAuthorize adapter available in CakePHP 2.x have now been moved to a separate plugin cakephp/acl2 . Configuring Authorization Handlers You configure authorization handlers using the authorize config key. You can configure one or many handlers for authorization. Using multiple handlers allows you to support different ways of checking authorization. When authorization handlers are checked, they will be called in the order they are declared. Handlers should return false, if they are unable to check authorization, or the check has failed. Handlers should return true if they were able to check authorization successfully. Handlers will be called in sequence until one passes. If all checks fail, the user will be redirected to the page they came from. Additionally you can halt all authorization by throwing an exception. You will need to catch any thrown exceptions, and handle them. You can configure authorization handlers in your controller’s beforeFilter or, in the $components array. You can pass configuration information into each authorization object, using an array: // Basic setup $this->Auth->config(’authorize’, [’Controller’]); // Pass settings in $this->Auth->config(’authorize’, [ ’Actions’ => [’actionPath’ => ’controllers/’], ’Controller’ ]); Much like authenticate, authorize, helps you keep your code DRY, by using the all key. This special key allows you to set settings that are passed to every attached object. The all key is also exposed as AuthComponent::ALL: 2 https://github.com/cakephp/acl Mais sobre Controllers 113 CakePHP Cookbook Documentation, Release 3.x // Pass settings in using ’all’ $this->Auth->config(’authorize’, [ AuthComponent::ALL => [’actionPath’ => ’controllers/’], ’Actions’, ’Controller’ ]); In the above example, both the Actions and Controller will get the settings defined for the ‘all’ key. Any settings passed to a specific authorization object will override the matching key in the ‘all’ key. If an authenticated user tries to go to a URL he’s not authorized to access, he’s redirected back to the referrer. If you do not want such redirection (mostly needed when using stateless authentication adapter) you can set config option unauthorizedRedirect to false. This causes AuthComponent to throw a ForbiddenException instead of redirecting. Creating Custom Authorize Objects Because authorize objects are pluggable, you can create custom authorize objects in your application or plugins. If for example you wanted to create an LDAP authorize object. In src/Auth/LdapAuthorize.php you could put the following: namespace App\Auth; use Cake\Auth\BaseAuthorize; use Cake\Network\Request; class LdapAuthorize extends BaseAuthorize { public function authorize($user, Request $request) { // Do things for ldap here. } } Authorize objects should return false if the user is denied access, or if the object is unable to perform a check. If the object is able to verify the user’s access, true should be returned. It’s not required that you extend BaseAuthorize, only that your authorize object implements an authorize() method. The BaseAuthorize class provides a number of helpful methods that are commonly used. Using Custom Authorize Objects Once you’ve created your custom authorize object, you can use them by including them in your AuthComponent’s authorize array: $this->Auth->config(’authorize’, [ ’Ldap’, // app authorize object. ’AuthBag.Combo’, // plugin authorize object. ]); Using No Authorization If you’d like to not use any of the built-in authorization objects, and want to handle things entirely outside of AuthComponent you can set $this->Auth->config(’authorize’, false);. By default AuthComponent starts off with authorize set to false. If you don’t use an authorization scheme, make sure to check authorization yourself in your controller’s beforeFilter, or with another component. 114 Capítulo 7. Controllers CakePHP Cookbook Documentation, Release 3.x Making Actions Public AuthComponent::allow($actions = null) There are often times controller actions that you wish to remain entirely public, or that don’t require users to be logged in. AuthComponent is pessimistic, and defaults to denying access. You can mark actions as public actions by using AuthComponent::allow(). By marking actions as public, AuthComponent, will not check for a logged in user, nor will authorize objects be checked: // Allow all actions $this->Auth->allow(); // Allow only the index action. $this->Auth->allow(’index’); // Allow only the view and index actions. $this->Auth->allow([’view’, ’index’]); By calling it empty you allow all actions to be public. For a single action you can provide the action name as string. Otherwise use an array. Note: You should not add the “login” action of your UsersController to allow list. Doing so would cause problems with normal functioning of AuthComponent. Making Actions Require Authorization AuthComponent::deny($actions = null) By default all actions require authorization. However, after making actions public, you want to revoke the public access. You can do so using AuthComponent::deny(): // Deny all actions. $this->Auth->deny(); // Deny one action $this->Auth->deny(’add’); // Deny a group of actions. $this->Auth->deny([’add’, ’edit’]); By calling it empty you deny all actions. For a single action you can provide the action name as string. Otherwise use an array. Using ControllerAuthorize ControllerAuthorize allows you to handle authorization checks in a controller callback. This is ideal when you have very simple authorization, or you need to use a combination of models + components to do your authorization, and don’t want to create a custom authorize object. The callback is always called isAuthorized() and it should return a boolean as to whether or not the user is allowed to access resources in the request. The callback is passed the active user, so it can be checked: class AppController extends Controller { public $components = [ ’Auth’ => [’authorize’ => ’Controller’], ]; Mais sobre Controllers 115 CakePHP Cookbook Documentation, Release 3.x public function isAuthorized($user = null) { // Any registered user can access public functions if (empty($this->request->params[’prefix’])) { return true; } // Only admins can access admin functions if ($this->request->params[’prefix’] === ’admin’) { return (bool)($user[’role’] === ’admin’); } // Default deny return false; } } The above callback would provide a very simple authorization system where, only users with role = admin could access actions that were in the admin prefix. Configuration options The following settings can all be defined either in your controller’s $components array or using $this->Auth->config(): ajaxLogin The name of an optional view element to render when an AJAX request is made with an invalid or expired session. allowedActions Controller actions for which user validation is not required. authenticate Set to an array of Authentication objects you want to use when logging users in. There are several core authentication objects, see the section on Suggested Reading Before Continuing. authError Error to display when user attempts to access an object or action to which they do not have access. You can suppress authError message from being displayed by setting this value to boolean false. authorize Set to an array of Authorization objects you want to use when authorizing users on each request, see the section on Authorization. flash Settings to use when Auth needs to do a flash message with FlashComponent::set(). Available keys are: • element - The element to use, defaults to ‘default’. • key - The key to use, defaults to ‘auth’ • params - The array of additional params to use, defaults to [] loginAction A URL (defined as a string or array) to the controller action that handles logins. Defaults to /users/login. loginRedirect The URL (defined as a string or array) to the controller action users should be redirected to after logging in. This value will be ignored if the user has an Auth.redirect value in their 116 Capítulo 7. Controllers CakePHP Cookbook Documentation, Release 3.x session. logoutRedirect The default action to redirect to after the user is logged out. While AuthComponent does not handle post-logout redirection, a redirect URL will be returned from AuthComponent::logout(). Defaults to loginAction. unauthorizedRedirect Controls handling of unauthorized access. By default unauthorized user is redirected to the referrer URL or loginAction or ‘/’. If set to false a ForbiddenException exception is thrown instead of redirecting. CookieComponent class Cake\Controller\Component\CookieComponent(ComponentRegistry $collection, array $config =[]) The CookieComponent is a wrapper around the native PHP setcookie method. It makes it easier to manipulate cookies, and automatically encrypt cookie data. Configuring Cookies Cookies can be configured either globally or per top-level name. The global configuration data will be merged with the top-level configuration. So only need to override the parts that are different. To configure the global settings use the config() method: $this->Cookie->config(’path’, ’/’); $this->Cookie->config([ ’expires’ => ’+10 days’, ’httpOnly’ => true ]); To configure a specific key use the configKey() method: $this->Cookie->configKey(’User’, ’path’, ’/’); $this->Cookie->configKey(’User’, [ ’expires’ => ’+10 days’, ’httpOnly’ => true ]); There are a number of configurable values for cookies: expires How long the cookies should last for. Defaults to 1 month. path The path on the server in which the cookie will be available on. If path is set to ‘/foo/’, the cookie will only be available within the /foo/ directory and all sub-directories such as /foo/bar/ of domain. The default value is app’s base path. domain The domain that the cookie is available. To make the cookie available on all subdomains of example.com set domain to ‘.example.com’. secure Indicates that the cookie should only be transmitted over a secure HTTPS connection. When set to true, the cookie will only be set if a secure connection exists. key Encryption key used when encrypted cookies are enabled. Defaults to Security.salt. Mais sobre Controllers 117 CakePHP Cookbook Documentation, Release 3.x httpOnly Set to true to make HTTP only cookies. Cookies that are HTTP only are not accessible in JavaScript. Defaults to false. encryption Type of encryption to use. Defaults to ‘aes’. Can also be ‘rijndael’ for backwards compatibility. Using the Component The CookieComponent offers a number of methods for working with Cookies. Cake\Controller\Component\CookieComponent::write(mixed $key, mixed $value = null) The write() method is the heart of the cookie component. $key is the cookie variable name you want, and the $value is the information to be stored: $this->Cookie->write(’name’, ’Larry’); You can also group your variables by using dot notation in the key parameter: $this->Cookie->write(’User.name’, ’Larry’); $this->Cookie->write(’User.role’, ’Lead’); If you want to write more than one value to the cookie at a time, you can pass an array: $this->Cookie->write(’User’, [’name’ => ’Larry’, ’role’ => ’Lead’] ); All values in the cookie are encrypted with AES by default. If you want to store the values as plain text, be sure to configure the key space: $this->Cookie->configKey(’User’, ’encryption’, false); Cake\Controller\Component\CookieComponent::read(mixed $key = null) This method is used to read the value of a cookie variable with the name specified by $key.: // Outputs "Larry" echo $this->Cookie->read(’name’); // You can also use the dot notation for read echo $this->Cookie->read(’User.name’); // To get the variables which you had grouped // using the dot notation as an array use the following $this->Cookie->read(’User’); // This outputs something like [’name’ => ’Larry’, ’role’ => ’Lead’] Cake\Controller\Component\CookieComponent::check($key) Parameters • $key (string) – The key to check. Used to check whether a key/path exists and has a non-null value. 118 Capítulo 7. Controllers CakePHP Cookbook Documentation, Release 3.x Cake\Controller\Component\CookieComponent::delete(mixed $key) Deletes a cookie variable of the name in $key. Works with dot notation: // Delete a variable $this->Cookie->delete(’bar’); // Delete the cookie variable bar, but not everything under foo $this->Cookie->delete(’foo.bar’); Cross Site Request Forgery By enabling the CSRF Component you get protection against attacks. CSRF3 or Cross Site Request Forgery is a common vulnerability in web applications. It allows an attacker to capture and replay a previous request, and sometimes submit data requests using image tags or resources on other domains. The CsrfComponent works by setting a cookie to the user’s browser. When forms are created with the Cake\View\Helper\FormHelper, a hidden field is added containing the CSRF token. During the Controller.startup event, if the request is a POST, PUT, DELETE, PATCH request the component will compare the request data & cookie value. If either is missing or the two values mismatch the component will throw a CakeNetworkExceptionForbiddenException. Using the CsrfComponent Simply by adding the CsrfComponent‘ to your components array, you can benefit from the CSRF protection it provides: public $components = [ ’Csrf’ => [ ’secure’ => true ] ]; Settings can be passed into the component through your component’s settings. The available configuration options are: • cookieName The name of the cookie to send. Defaults to csrfToken. • expiry How long the CSRF token should last. Defaults to browser session. • secure Whether or not the cookie will be set with the Secure flag. Defaults to false. • field The form field to check. Defaults to _csrfToken. Changing this will also require configuring FormHelper. When enabled, you can access the current CSRF token on the request object: $token = $this->request->param(’_csrfToken’); 3 http://en.wikipedia.org/wiki/Cross-site_request_forgery Mais sobre Controllers 119 CakePHP Cookbook Documentation, Release 3.x Integration with FormHelper The CsrfComponent integrates seamlessly with FormHelper. Each time you create a form with FormHelper, it will insert a hidden field containing the CSRF token. Note: When using the CsrfComponent you should always start your forms with the FormHelper. If you do not, you will need to manually create hidden inputs in each of your forms. CSRF Protection and AJAX Requests In addition to request data parameters, CSRF tokens can be submitted through a special X-CSRF-Token header. Using a header often makes it easier to integrate a CSRF token with JavaScript heavy applications, or XML/JSON based API endpoints. Disabling the CSRF Component for Specific Actions While not recommended, you may want to disable the CsrfComponent on certain requests. You can do this using the controller’s event dispatcher, during the beforeFilter method: public function beforeFilter(Event $event) { $this->eventManager()->detach($this->Csrf); } FlashComponent class Cake\Controller\Component\FlashComponent(ComponentCollection $collection, array $config =[]) FlashComponent provides a way to set one-time notification messages to be displayed after processing a form or acknowledging data. CakePHP refers to these messages as “flash messages”. FlashComponent writes flash messages to $_SESSION, to be rendered in a View using FlashHelper. Setting Flash Messages FlashComponent provides two ways to set flash messages: its __call magic method and its set() method. To furnish your application with verbosity, FlashComponent’s __call magic method allows you use a method name that maps to an element located under the src/Template/Element/Flash directory. By convention, camelcased methods will map to the lowercased and underscored element name: // Uses src/Template/Element/Flash/success.ctp $this->Flash->success(’This was successful’); // Uses src/Template/Element/Flash/great_success.ctp $this->Flash->greatSuccess(’This was greatly successful’); Alternatively, to set a plain-text message without rendering an element, you can use the set() method: 120 Capítulo 7. Controllers CakePHP Cookbook Documentation, Release 3.x $this->Flash->set(’This is a message’); FlashComponent’s __call and set() methods optionally take a second parameter, an array of options: • key Defaults to ‘flash’. The array key found under the ‘Flash’ key in the session. • element Defaults to null, but will automatically be set when using the __call() magic method. The element name to use for rendering. • params An optional array of keys/values to make available as variables within an element. An example of using these options: // In your Controller $this->Flash->success(’The user has been saved’, [ ’key’ => ’positive’, ’params’ => [ ’name’ => $user->name, ’email’ => $user->email ] ]); // In your View <?= $this->Flash->render(’positive’) ?> <!-- In src/Template/Element/Flash/success.ctp --> <div id="flash-<?= h($key) ?>" class="message-info success"> <?= h($message) ?>: <?= h($params[’name’]) ?>, <?= h($params[’email’]) ?>. </div> Note: By default, CakePHP does not escape the HTML in flash messages. If you are using any request or user data in your flash messages, you should escape it with h when formatting your messages. For more information about rendering your flash messages, please refer to the FlashHelper section. SecurityComponent class SecurityComponent(ComponentCollection $collection, array $config =[]) The Security Component creates an easy way to integrate tighter security in your application. It provides methods for various tasks like: • Restricting which HTTP methods your application accepts. • Form tampering protection • Requiring that SSL be used. • Limiting cross controller communication. Like all components it is configured through several configurable parameters. All of these properties can be set directly or through setter methods of the same name in your controller’s beforeFilter. Mais sobre Controllers 121 CakePHP Cookbook Documentation, Release 3.x By using the Security Component you automatically get form tampering protection. Hidden token fields will automatically be inserted into forms and checked by the Security component. If you are using Security component’s form protection features and other components that process form data in their startup() callbacks, be sure to place Security Component before those components in your $components array. Note: When using the Security Component you must use the FormHelper to create your forms. In addition, you must not override any of the fields’ “name” attributes. The Security Component looks for certain indicators that are created and managed by the FormHelper (especially those created in View\Helper\FormHelper::create() and View\Helper\FormHelper::end()). Dynamically altering the fields that are submitted in a POST request (e.g. disabling, deleting or creating new fields via JavaScript) is likely to cause the request to be send to the blackhole callback. See the $validatePost or $disabledFields configuration parameters. Handling Blackhole Callbacks SecurityComponent::blackHole(object $controller, string $error) If an action is restricted by the Security Component it is ‘black-holed’ as an invalid request which will result in a 400 error by default. You can configure this behavior by setting the blackHoleCallback configuration option to a callback function in the controller. By configuring a callback method you can customize how the blackhole process works: public function beforeFilter(Event $event) { $this->Security->config(’blackHoleCallback’, ’blackhole’); } public function blackhole($type) { // Handle errors. } The $type parameter can have the following values: • ‘auth’ Indicates a form validation error, or a controller/action mismatch error. • ‘secure’ Indicates an SSL method restriction failure. Restrict Actions to SSL SecurityComponent::requireSecure() Sets the actions that require a SSL-secured request. Takes any number of arguments. Can be called with no arguments to force all actions to require a SSL-secured. SecurityComponent::requireAuth() Sets the actions that require a valid Security Component generated token. Takes any number of arguments. Can be called with no arguments to force all actions to require a valid authentication. 122 Capítulo 7. Controllers CakePHP Cookbook Documentation, Release 3.x Restricting Cross Controller Communication property SecurityComponent::$allowedControllers A list of controllers which can send requests to this controller. This can be used to control cross controller requests. property SecurityComponent::$allowedActions A list of actions which are allowed to send requests to this controller’s actions. This can be used to control cross controller requests. These configuration options allow you to restrict cross controller communication. Set them with the config() method. Form Tampering Prevention By default the SecurityComponent prevents users from tampering with forms in specific ways. The SecurityComponent will prevent the following things: • Unknown fields cannot be added to the form. • Fields cannot be removed from the form. • Values in hidden inputs cannot be modified. Preventing these types of tampering is accomplished by working with the FormHelper and tracking which fields are in a form. The values for hidden fields are tracked as well. All of this data is combined and turned into a hash. When a form is submitted, the SecurityComponent will use the POST data to build the same structure and compare the hash. Note: The SecurityComponent will not prevent select options from being added/changed. Nor will it prevent radio options from being added/changed. property SecurityComponent::$unlockedFields Set to a list of form fields to exclude from POST validation. Fields can be unlocked either in the Component, or with FormHelper::unlockField(). Fields that have been unlocked are not required to be part of the POST and hidden unlocked fields do not have their values checked. property SecurityComponent::$validatePost Set to false to completely skip the validation of POST requests, essentially turning off form validation. Usage Using the security component is generally done in the controllers beforeFilter(). You would specify the security restrictions you want and the Security Component will enforce them on its startup: namespace App\Controller; use App\Controller\AppController; use Cake\Event\Event; Mais sobre Controllers 123 CakePHP Cookbook Documentation, Release 3.x class WidgetsController extends AppController { public $components = [’Security’]; public function beforeFilter(Event $event) { if (isset($this->request->params[’admin’])) { $this->Security->requireSecure(); } } } The above example would force all actions that had admin routing to require secure SSL requests: namespace App\Controller; use App\Controller\AppController; use Cake\Event\Event; class WidgetsController extends AppController { public $components = [’Security’]; public function beforeFilter(Event $event) { if (isset($this->params[’admin’])) { $this->Security->blackHoleCallback = ’forceSSL’; $this->Security->requireSecure(); } } public function forceSSL() { return $this->redirect(’https://’ . env(’SERVER_NAME’) . $this->here); } } This example would force all actions that had admin routing to require secure SSL requests. When the request is black holed, it will call the nominated forceSSL() callback which will redirect non-secure requests to secure requests automatically. CSRF Protection CSRF or Cross Site Request Forgery is a common vulnerability in web applications. It allows an attacker to capture and replay a previous request, and sometimes submit data requests using image tags or resources on other domains. To enable CSRF protection features use the Cross Site Request Forgery. Disabling Security Component for Specific Actions There may be cases where you want to disable all security checks for an action (ex. AJAX requests). You may “unlock” these actions by listing them in $this->Security->unlockedActions in your beforeFilter. The unlockedActions property will not affect other features of SecurityComponent: 124 Capítulo 7. Controllers CakePHP Cookbook Documentation, Release 3.x namespace App\Controller; use App\Controller\AppController; use Cake\Event\Event; class WidgetController extends AppController { public $components = [’Security’]; public function beforeFilter(Event $event) { $this->Security->config(’unlockedActions’, [’edit’]); } } This example would disable all security checks for the edit action. Pagination class Cake\Controller\Component\PaginatorComponent One of the main obstacles of creating flexible and user-friendly web applications is designing an intuitive user interface. Many applications tend to grow in size and complexity quickly, and designers and programmers alike find they are unable to cope with displaying hundreds or thousands of records. Refactoring takes time, and performance and user satisfaction can suffer. Displaying a reasonable number of records per page has always been a critical part of every application and used to cause many headaches for developers. CakePHP eases the burden on the developer by providing a quick, easy way to paginate data. Pagination in CakePHP is offered by a Component in the controller, to make building paginated queries easier. In the View View\Helper\PaginatorHelper is used to make the generation of pagination links & buttons simple. Using Controller::paginate() In the controller, we start by defining the default query conditions pagination will use in the $paginate controller variable. These conditions, serve as the basis for your pagination queries. They are augmented by the sort, direction limit, and page parameters passed in from the URL. It is important to note that the order key must be defined in an array structure like below: class ArticlesController extends AppController { public $components = [’Paginator’]; public $paginate = [ ’limit’ => 25, ’order’ => [ ’Articles.title’ => ’asc’ ] Mais sobre Controllers 125 CakePHP Cookbook Documentation, Release 3.x ]; } You can also include any of the options supported by ORM\Table::find(), such as fields: class ArticlesController extends AppController { public $components = [’Paginator’]; public $paginate = [ ’fields’ => [’Articles.id’, ’Articles.created’], ’limit’ => 25, ’order’ => [ ’Articles.title’ => ’asc’ ] ]; } While you can pass most of the query options from the paginate property it is often cleaner and simpler to bundle up your pagination options into a custom-find-methods. You can define the finder pagination uses by setting the finder option: class ArticlesController extends AppController { public $paginate = [ ’finder’ => ’published’, ]; } In addition to defining general pagination values, you can define more than one set of pagination defaults in the controller, you just name the keys of the array after the model you wish to configure: class ArticlesController extends AppController { public $paginate = [ ’Articles’ => [], ’Authors’ => [], ]; } The values of the Articles and Authors keys could contain all the properties that a model/key less $paginate array could. Once the $paginate property has been defined, we can use the Controller\Controller::paginate() method to create the pagination data, and add the PaginatorHelper if it hasn’t already been added. The controller’s paginate method will return the result set of the paginated query, and set pagination metadata to the request. You can access the pagination metadata at $this->request->params[’paging’]. A more complete example of using paginate() would be: class ArticlesController extends AppController { public function index() { 126 Capítulo 7. Controllers CakePHP Cookbook Documentation, Release 3.x $this->set(’articles’, $this->paginate()); } } By default the paginate() method will use the default model for a controller. You can also pass the resulting query of a find method: public function index() { $query = $this->Articles->find(’popular’)->where([’author_id’ => 1]); $this->set(’articles’, $this->paginate($query)); } If you want to paginate a different model you can provide a query for it, the table object itself, or its name: // Using a query $comments = $this->paginate($commentsTable->find()); // Using the model name. $comments = $this->paginate(’Comments’); // Using a table object. $comments = $this->paginate($commentTable); Using the Paginator Directly If you need to paginate data from another component you may want to use the PaginatorComponent directly. It features a similar API to the controller method: $articles = $this->Paginator->paginate($articleTable->find(), $config); // Or $articles = $this->Paginator->paginate($articleTable, $config); The first parameter should be the query object from a find on table object you wish to paginate results from. Optionally, you can pass the table object and let the query be constructed for you. The second parameter should be the array of settings to use for pagination. This array should have the same structure as the $paginate property on a controller. Control which Fields Used for Ordering By default sorting can be done on any non-virtual column a table has. This is sometimes undesirable as it allows users to sort on un-indexed columnsthat can be expensive to order by. You can set the whitelist of fields that can be sorted using the sortWhitelist option. This option is required when you want to sort on any associated data, or computed fields that may be part of your pagination query: public $paginate = [ ’sortWhitelist’ => [ ’id’, ’title’, ’Users.username’, ’created’ ] ]; Mais sobre Controllers 127 CakePHP Cookbook Documentation, Release 3.x Any requests that attempt to sort on fields not in the whitelist will be ignored. Limit the Maximum Number of Rows that can be Fetched The number of results that are fetched is exposed to the user as the limit parameter. It is generally undesirable to allow users to fetch all rows in a paginated set. By default CakePHP limits the maximum number of rows that can be fetched to 100. If this default is not appropriate for your application, you can adjust it as part of the pagination options: public $paginate = [ // Other keys here. ’maxLimit’ => 10 ]; If the request’s limit param is greater than this value, it will be reduced to the maxLimit value. Out of Range Page Requests The PaginatorComponent will throw a NotFoundException when trying to access a non-existent page, i.e. page number requested is greater than total page count. So you could either let the normal error page be rendered or use a try catch block and take appropriate action when a NotFoundException is caught: public function index() { try { $this->paginate(); } catch (NotFoundException $e) { // Do something here like redirecting to first or last page. // $this->request->params[’paging’] will give you required info. } } Pagination in the View Check the View\Helper\PaginatorHelper documentation for how to create links for pagination navigation. Request Handling class RequestHandlerComponent(ComponentCollection $collection, array $config =[]) The Request Handler component is used in CakePHP to obtain additional information about the HTTP requests that are made to your applications. You can use it to inform your controllers about AJAX as well as gain additional insight into content types that the client accepts and automatically changes to the appropriate layout when file extensions are enabled. 128 Capítulo 7. Controllers CakePHP Cookbook Documentation, Release 3.x By default RequestHandler will automatically detect AJAX requests based on the HTTP-XRequested-With header that many JavaScript libraries use. When used in conjunction with Cake\Routing\Router::extensions(), RequestHandler will automatically switch the layout and view files to those that match the requested type. Furthermore, if a helper with the same name as the requested extension exists, it will be added to the Controllers Helper array. Lastly, if XML/JSON data is POST’ed to your Controllers, it will be parsed into an array which is assigned to $this->request->data, and can then be saved as model data. In order to make use of RequestHandler it must be included in your $components array: class WidgetsController extends AppController { public $components = [’RequestHandler’]; // Rest of controller } Obtaining Request Information Request Handler has several methods that provide information about the client and its request. RequestHandlerComponent::accepts($type = null) $type can be a string, or an array, or null. If a string, accepts will return true if the client accepts the content type. If an array is specified, accepts return true if any one of the content types is accepted by the client. If null returns an array of the content-types that the client accepts. For example: class ArticlesController extends AppController { public $components = [’RequestHandler’]; public function beforeFilter() { if ($this->RequestHandler->accepts(’html’)) { // Execute code only if client accepts an HTML (text/html) // response. } elseif ($this->RequestHandler->accepts(’xml’)) { // Execute XML-only code } if ($this->RequestHandler->accepts([’xml’, ’rss’, ’atom’])) { // Executes if the client accepts any of the above: XML, RSS // or Atom. } } } Other request ‘type’ detection methods include: RequestHandlerComponent::isXml() Returns true if the current request accepts XML as a response. RequestHandlerComponent::isRss() Returns true if the current request accepts RSS as a response. Mais sobre Controllers 129 CakePHP Cookbook Documentation, Release 3.x RequestHandlerComponent::isAtom() Returns true if the current call accepts an Atom response, false otherwise. RequestHandlerComponent::isMobile() Returns true if user agent string matches a mobile web browser, or if the client accepts WAP content. The supported Mobile User Agent strings are: •Android •AvantGo •BlackBerry •DoCoMo •Fennec •iPad •iPhone •iPod •J2ME •MIDP •NetFront •Nokia •Opera Mini •Opera Mobi •PalmOS •PalmSource •portalmmm •Plucker •ReqwirelessWeb •SonyEricsson •Symbian •UP.Browser •webOS •Windows CE •Windows Phone OS •Xiino RequestHandlerComponent::isWap() Returns true if the client accepts WAP content. 130 Capítulo 7. Controllers CakePHP Cookbook Documentation, Release 3.x All of the above request detection methods can be used in a similar fashion to filter functionality intended for specific content types. For example when responding to AJAX requests, you often will want to disable browser caching, and change the debug level. However, you want to allow caching for non-AJAX requests. The following would accomplish that: if ($this->request->is(’ajax’)) { $this->disableCache(); } // Continue Controller action Automatically Decoding Request Data RequestHandlerComponent::addInputType($type, $handler) Add a request data decoder. The handler should contain a callback, and any additional arguments for the callback. The callback should return an array of data contained in the request input. For example adding a CSV handler in your controllers’ beforeFilter could look like: $parser = function ($data) { $rows = str_getcsv($data, "\n"); foreach ($rows as &$row) { $row = str_getcsv($row, ’,’); } return $rows; }; $this->RequestHandler->addInputType(’csv’, [$parser]); You can use any callable4 for the handling function. You can also pass additional arguments to the callback, this is useful for callbacks like json_decode: $this->RequestHandler->addInputType(’json’, [’json_decode’, true]); The above will make $this->request->data an array of the JSON input data, without the additional true you’d get a set of StdClass objects. Checking Content-Type Preferences RequestHandlerComponent::prefers($type = null) Determines which content-types the client prefers. If no parameter is given the most likely content type is returned. If $type is an array the first type the client accepts will be returned. Preference is determined primarily by the file extension parsed by Router if one has been provided, and secondly by the list of contenttypes in HTTP\_ACCEPT: $this->RequestHandler->prefers(’json’); 4 http://php.net/callback Mais sobre Controllers 131 CakePHP Cookbook Documentation, Release 3.x Responding To Requests RequestHandlerComponent::renderAs($controller, $type) Change the render mode of a controller to the specified type. Will also append the appropriate helper to the controller’s helper array if available and not already in the array: // Force the controller to render an xml response. $this->RequestHandler->renderAs($this, ’xml’); This method will also attempt to add a helper that matches your current content type. For example if you render as rss, the RssHelper will be added. RequestHandlerComponent::respondAs($type, $options) Sets the response header based on content-type map names. This method lets you set a number of response properties at once: $this->RequestHandler->respondAs(’xml’, [ // Force download ’attachment’ => true, ’charset’ => ’UTF-8’ ]); RequestHandlerComponent::responseType() Returns the current response type Content-type header or null if one has yet to be set. Taking Advantage of HTTP Cache Validation The HTTP cache validation model is one of the processes used for cache gateways, also known as reverse proxies, to determine if they can serve a stored copy of a response to the client. Under this model, you mostly save bandwidth, but when used correctly you can also save some CPU processing, reducing this way response times. Enabling the RequestHandlerComponent in your controller automatically activates a check done before rendering the view. This check compares the response object against the original request to determine whether the response was not modified since the last time the client asked for it. If response is evaluated as not modified, then the view rendering process is stopped, saving processing time, saving bandwidth and no content is returned to the client. The response status code is then set to 304 Not Modified. You can opt-out this automatic checking by setting the checkHttpCache setting to false: public $components = [ ’RequestHandler’ => [ ’checkHttpCache’ => false ]]; 132 Capítulo 7. Controllers CakePHP Cookbook Documentation, Release 3.x Using custom ViewClasses RequestHandlerComponent::viewClassMap($type, $viewClass) When using JsonView/XmlView you might want to override the default serialization with a custom View class, or add View classes for other types. You can map existing and new types to your custom classes. You can also set this automatically by using the viewClassMap setting: public $components = [ ’RequestHandler’ => [ ’viewClassMap’ => [ ’json’ => ’ApiKit.MyJson’, ’xml’ => ’ApiKit.MyXml’, ’csv’ => ’ApiKit.Csv’ ] ]]; Sessions class SessionComponent(ComponentCollection $collection, array $config =[]) The CakePHP SessionComponent provides a way to persist client data between page requests. It acts as a wrapper for $_SESSION as well as providing convenience methods for several $_SESSION related functions. Sessions can be configured in a number of ways in CakePHP. For more information, you should see the Session configuration documentation. Interacting with Session Data The Session component is used to interact with session information. It includes basic CRUD functions as well as features for creating feedback messages to users. It should be noted that Array structures can be created in the Session by using dot notation. User.username would reference the following: So [’User’ => [ ’username’ => ’[email protected]’ ]]; Dots are used to indicate nested arrays. This notation is used for all Session component methods wherever a name/key is used. SessionComponent::write($name, $value) Write to the Session puts $value into $name. $name can be a dot separated array. For example: $this->Session->write(’Person.eyeColor’, ’Green’); This writes the value ‘Green’ to the session under Person => eyeColor. Mais sobre Controllers 133 CakePHP Cookbook Documentation, Release 3.x SessionComponent::read($name) Returns the value at $name in the Session. If $name is null the entire session will be returned. E.g: $green = $this->Session->read(’Person.eyeColor’); Retrieve the value Green from the session. Reading data that does not exist will return null. SessionComponent::check($name) Used to check if a Session variable has been set. Returns true on existence and false on nonexistence. SessionComponent::delete($name) Clear the session data at $name. E.g: $this->Session->delete(’Person.eyeColor’); Our session data no longer has the value ‘Green’, or the index eyeColor set. However, Person is still in the Session. To delete the entire Person information from the session use: $this->Session->delete(’Person’); SessionComponent::destroy() The destroy method will delete the session cookie and all session data stored in the temporary file system. It will then destroy the PHP session and then create a fresh session: $this->Session->destroy(); Configuring Components Many of the core components require configuration. Some examples of components requiring configuration are Authentication and CookieComponent. Configuration for these components, and for components in general, is usually done via loadComponent() in your Controller’s initialize method or via the $components array: class PostsController extends AppController { public function initialize() { parent::initialize(); $this->loadComponent(’Auth’, [ ’authorize’ => [’controller’], ’loginAction’ => [’controller’ => ’Users’, ’action’ => ’login’] ]); $this->loadComponent(’Cookie’, [’expiry’ => ’1 day’]); } } You can configure components at runtime using the config() method. Often, this is done in your controller’s beforeFilter() method. The above could also be expressed as: public function beforeFilter() { $this->Auth->config(’authorize’, [’controller’]); $this->Auth->config(’loginAction’, [’controller’ => ’Users’, ’action’ => ’login’]); 134 Capítulo 7. Controllers CakePHP Cookbook Documentation, Release 3.x $this->Cookie->config(’name’, ’CookieMonster’); } Like helpers, components implement a config() method that is used to get and set any configuration data for a component: // Read config data. $this->Auth->config(’loginAction’); // Set config $this->Csrf->config(’cookieName’, ’token’); As with helpers, components will automatically merge their $_defaultConfig property with constructor configuration to create the $_config property which is accessible with config(). Aliasing Components One common setting to use is the className option, which allows you to alias components. This feature is useful when you want to replace $this->Auth or another common Component reference with a custom implementation: // src/Controller/PostsController.php class PostsController extends AppController { public function initialize() { parent::initialize(’Auth’, [ ’className’ => ’MyAuth’ ]); } } // src/Controller/Component/MyAuthComponent.php use Cake\Controller\Component\AuthComponent; class MyAuthComponent extends AuthComponent { // Add your code to override the core AuthComponent } The above would alias MyAuthComponent to $this->Auth in your controllers. Note: Aliasing a component replaces that instance anywhere that component is used, including inside other Components. Using Components Once you’ve included some components in your controller, using them is pretty simple. Each component you use is exposed as a property on your controller. If you had loaded up the Cake\Controller\Component\FlashComponent and the Cake\Controller\Component\CookieComponent in your controller, you could access them like so: Mais sobre Controllers 135 CakePHP Cookbook Documentation, Release 3.x class PostsController extends AppController { public $components = [’Flash’, ’Cookie’]; public function delete() { if ($this->Post->delete($this->request->data(’Post.id’)) { $this->Flash->success(’Post deleted.’); return $this->redirect([’action’ => ’index’]); } } Note: Since both Models and Components are added to Controllers as properties they share the same ‘namespace’. Be sure to not give a component and a model the same name. Loading Components on the Fly You might not need all of your components available on every controller action. In situations like this you can load a component at runtime using the Component Registry. From inside a controller’s method you can do the following: $this->OneTimer = $this->Components->load(’OneTimer’); $this->OneTimer->getTime(); Note: Keep in mind that components loaded on the fly will not have missed callbacks called. If you rely on the initialize or startup callbacks being called, you may need to call them manually depending on when you load your component. Component Callbacks Components also offer a few request life-cycle callbacks that allow them to augment the request cycle. See the base Component API and /core-libraries/events for more information on the callbacks components offer. Creating a Component Suppose our online application needs to perform a complex mathematical operation in many different parts of the application. We could create a component to house this shared logic for use in many different controllers. The first step is to create a new component file and class. Create the file in src/Controller/Component/MathComponent.php. The basic structure for the component would look something like this: namespace App\Controller\Component; use Cake\Controller\Component; 136 Capítulo 7. Controllers CakePHP Cookbook Documentation, Release 3.x class MathComponent extends Component { public function doComplexOperation($amount1, $amount2) { return $amount1 + $amount2; } } Note: All components must extend Component. Failing to do this will trigger an exception. Including your Component in your Controllers Once our component is finished, we can use it in the application’s controllers by loading it during the controller’s initialize() method. Once loaded, the controller will be given a new attribute named after the component, through which we can access an instance of it: /* Make the new component available at $this->Math, as well as the standard $this->Csrf */ public function initialize() { parent::initialize(); $this->loadComponent(’Math’); $this->loadComponent(’Csrf’); } When including Components in a Controller you can also declare a set of parameters that will be passed on to the Component’s constructor. These parameters can then be handled by the Component: public function initialize() { parent::initialize(); $this->loadComponent(’Math’, [ ’precision’ => 2, ’randomGenerator’ => ’srand’ ]); $this->loadComponent(’Csrf’); } The above would pass the array containing precision MathComponent::__construct() as the second parameter. and randomGenerator to Using Other Components in your Component Sometimes one of your components may need to use another component. In this case you can include other components in your component the exact same way you include them in controllers - using the $components var: // src/Controller/Component/CustomComponent.php use Cake\Controller\Component; class CustomComponent extends Component { // The other component your component uses public $components = [’Existing’]; Mais sobre Controllers 137 CakePHP Cookbook Documentation, Release 3.x public function initialize(Controller $controller) { $this->Existing->foo(); } public function bar() { // ... } } // src/Controller/Component/ExistingComponent.php use Cake\Controller\Component; class ExistingComponent extends Component { public function foo() { // ... } } Note: In contrast to a component included in a controller no callbacks will be triggered on a component’s component. Accessing a Component’s Controller From within a Component you can access the current controller through the registry: $controller = $this->_registry->getController(); You can also easily access the controller in any callback method from the event object: $controller = $event->subject(); Component API class Component The base Component class offers a few methods for lazily loading other Components through Cake\Controller\ComponentRegistry as well as dealing with common handling of settings. It also provides prototypes for all the component callbacks. Component::__construct(ComponentRegistry $registry, $config =[]) Constructor for the base component class. All $config that are also public properties will have their values changed to the matching value in $config. Callbacks Component::initialize(Event $event) Is called before the controller’s beforeFilter method, but after the controller’s initialize() method. 138 Capítulo 7. Controllers CakePHP Cookbook Documentation, Release 3.x Component::startup(Event $event) Is called after the controller’s beforeFilter method but before the controller executes the current action handler. Component::beforeRender(Event $event) Is called after the controller executes the requested action’s logic, but before the controller’s renders views and layout. Component::shutdown(Event $event) Is called before output is sent to the browser. Component::beforeRedirect(Event $event, Controller $controller, $url, $response) Is invoked when the controller’s redirect method is called but before any further action. If this method returns false the controller will not continue on to redirect the request. The $url, and $response paramaters allow you to inspect and modify the location or any other headers in the response. Mais sobre Controllers 139 CakePHP Cookbook Documentation, Release 3.x 140 Capítulo 7. Controllers CAPÍTULO 8 Views Views (ou Visões) são o V do MVC. Views são responsáveis por gerar a saída de dados específica para uma determinada requisição. Geralmente esta saída é apresentada na forma de HTML, XML ou JSON. No entanto, disponibilizar arquivos através de streaming (fluxo de informação, geralmente multimídia, através de pacotes) ou criar PDFs, que podem ser baixados, também são de responsabilidade da Camada View. O CakePHP traz incluso várias classes do tipo View para lidar com os cenários mais comuns de renderização: • Para criar webservices em XML ou JSON, você pode usar o views/json-and-xml-views • Para prover arquivos protegidos ou arquivos criados dinamicamente, views/media-view você pode usar • Para criar múltiplos temas para as visões, você pode usar views/themes View Templates Em CakePHP, você fala com seus usuários através da camada view (visão). Na maior parte do tempo, suas views exibirão documentos (X)HTML nos navegadores, mas você pode também precisar prover dados AMF para um objeto em Flash, responder a uma aplicação remota via SOAP ou gerar um arquivo CSV para um usuário. Por padrão, no CakePHP os arquivos do tipo view são escritos em PHP comum e possuem a extensão .ctp (CakePHP Template). Estes arquivos contém toda a lógica de apresentação necessária para transformar os dados recebidos do controller em um formato pronto para o público. Caso você prefira usar uma linguagem de template como Twig ou Smarty, uma subclasse da View irá fazer uma ponte entre sua linguagem de template e o CakePHP. Arquivos do tipo view são guardados em /app/View/, dentro do diretório com o nome do controller que usa os arquivos e nomeado de acordo com a ação correspondente. Por exemplo, a ação “view()” do controller Products será normalmente encontrada em /app/View/Products/view.ctp. A camada view no CakePHP pode ser composta de diferentes partes. Cada parte tem diferentes usos e serão cobertas em seções específicas: 141 CakePHP Cookbook Documentation, Release 3.x • views: views é a única parte da página que está em execução. Compõem a parte crucial da resposta da aplicação. • elements: pedaços de código pequenos e reutilizáveis. Elements geralmente são renderizados dentro de views. • layouts: arquivos da view contendo código de apresentação que envolve várias interfaces da aplicação. A maior parte dos arquivos views é renderizada dentro de um layout. • helpers: essas classes encapsulam lógica da view que seja necessária em vários lugares na camada view. Helpers no CakePHP podem ajudá-lo a construir formulários, construir funcionalidade AJAX, paginar dados do model, prover feeds RSS, dentre outras coisas. Estendendo Views New in version 2.1. A extensão de uma View permite que você inclua uma view dentro de outra. Combinando isto com view blocks você tem uma maneira poderosa para deixar suas views DRY (enxutas). Por exemplo, sua aplicação tem uma barra lateral (sidebar) que precisa mudar a depender de quando uma view específica é renderizada. Estendendo um mesmo arquivo de view, você pode evitar repetições de marcações em comum e apenas definir as que mudam: // app/View/Common/view.ctp <h1><?php echo $this->fetch(’title’); ?></h1> <?php echo $this->fetch(’content’); ?> <div class="actions"> <h3>Related actions</h3> <ul> <?php echo $this->fetch(’sidebar’); ?> </ul> </div> O arquivo de view acima pode ser usado como uma view pai. Esta espera que a view que a estende defina os blocos sidebar e title. O bloco content é um bloco especial que o CakePHP cria. Ele conterá todo o conteúdo não-capturado da view que a estende. Considerando que nosso arquivo view tem uma variável $post com informação sobre nosso post, nossa view poderá parecer como: <?php // app/View/Posts/view.ctp $this->extend(’/Common/view’); $this->assign(’title’, $post) $this->start(’sidebar’); ?> <li> echo $this->Html->link(’edit’, array( ’action’ => ’edit’, $post[’Post’][’id’] )); ?> </li> 142 Capítulo 8. Views CakePHP Cookbook Documentation, Release 3.x <?php $this->end(); ?> // O conteúdo restante estará disponível como o bloco ‘content‘ // na view pai. echo h($post[’Post’][’body’]); A view de post acima mostra como você pode estender uma view e preenche-la com um conjunto de blocos. Qualquer conteúdo que não estiver definido em um bloco será capturado e colocado em um bloco especial chamado content. Quando uma view contém uma chamada para extend(), a execução continua até o fim do arquivo view atual. Uma vez finalizada, a view estendida será renderizada. Chamar extend() mais de uma vez em um arquivo view irá sobrescrever a view pai que será processada em seguida: $this->extend(’/Common/view’); $this->extend(’/Common/index’); O trecho acima resultará em /Common/index.ctp sendo renderizada como a view pai para a view atual. Você pode aninhar views estendidas quantas vezes forem necessárias. Cada view pode estender outra view se quiser. Cada view pai pegará o conteúdo da view anterior como o bloco content. Note: Você deve evitar o uso de content como o nome de um bloco em sua aplicação. CakePHP usa este nome em views estendidas para conteúdos não-capturados. Usando Blocos de Views (Visões) New in version 2.1. Blocos de views substituem $scripts_for_layout e provêm uma API flexível que permite criar slots ou blocos em suas views/layouts que podem ser definidas em qualquer lugar. Por exemplo, blocos são ideais para implementar recursos como barras laterais ou regiões para carregar seções na parte de baixo ou no topo do layout. Blocos podem ser definidos de duas formas. Seja capturando um bloco ou por atribuição direta. Os métodos start(), append() e end() permitem trabalhar com captura de blocos: // cria um bloco lateral. $this->start(’sidebar’); echo $this->element(’sidebar/recent_topics’); echo $this->element(’sidebar/recent_comments’); $this->end(); // Concatena na barra lateral em seguida. $this->append(’sidebar’); echo $this->element(’sidebar/popular_topics’); $this->end(); Também é possível concatenar blocos utilizando o método start() múltiplas vezes. assign() pode ser usado para limpar ou sobrescrever o bloco: Usando Blocos de Views (Visões) O método 143 CakePHP Cookbook Documentation, Release 3.x // Limpa o conteúdo anterior da barra lateral. $this->assign(’sidebar’, ’’); Note: Você deve evitar o uso de content como o nome de um bloco em sua aplicação. CakePHP usa este nome em views estendidas para conteúdos não-capturados . Exibindo blocos New in version 2.1. Você pode exibir blocos usando o método fetch(). fetch() irá retornar um bloco de maneira segura, retornando ‘’ se o bloco não existir”: echo $this->fetch(’sidebar’); Você também pode usar o fetch para exibir condicionalmente um conteúdo que deve envolver um bloco que deveria existir. Isto é útil em layouts ou views estendidas, nas quais você queira mostrar cabeçalhos e outras marcações condicionalmente: // em app/View/Layouts/default.ctp <?php if ($this->fetch(’menu’)): ?> <div class="menu"> <h3>Menu options</h3> <?php echo $this->fetch(’menu’); ?> </div> <?php endif; ?> Utilizando blocos para arquivos de script e CSS New in version 2.1. Blocos substituem a variável obsoleta $scripts_for_layout do layout. Em vez de usá-la, você deve usar blocos. A HtmlHelper vincula-se aos blocos da view e a cada um dos seus métodos php:meth:~HtmlHelper::script(), css() e meta() quando o bloco com o mesmo nome utiliza a opção inline = false: <?php // no seu arquivo de view $this->Html->script(’carousel’, array(’inline’ => false)); $this->Html->css(’carousel’, array(’inline’ => false)); ?> // no seu arquivo de layout <!DOCTYPE html> <html lang="en"> <head> <title><?php echo $this->fetch(’title’); ?></title> <?php echo $this->fetch(’script’); ?> <?php echo $this->fetch(’css’); ?> 144 Capítulo 8. Views CakePHP Cookbook Documentation, Release 3.x </head> // o resto do layout continua A HtmlHelper também permite você controlar para que bloco os scripts e CSS vão: // na sua view $this->Html->script(’carousel’, array(’block’ => ’scriptBottom’)); // no seu layout echo $this->fetch(’scriptBottom’); Layouts Um layout contem o código de apresentação que envolve uma view. Qualquer coisa que você queira ver em todas as suas views deve ser colocada em um layout. Arquivos de layouts devem ser colocados em /app/View/Layouts. O layout padrão do CakePHP pode ser sobrescrito criando um novo layout padrão em /app/View/Layouts/default.ctp. Uma vez que um novo layout padrão tenha sido criado, o código da view renderizado pelo controller é colocado dentro do layout padrão quando a página é renderizada. Quando você cria um layout, você precisa dizer ao CakePHP onde colocar o código de suas views. Para isso, garanta que o seu layout inclui um lugar para $this->fetch(’content’). A seguir, um exemplo de como um layout padrão deve parecer: <!DOCTYPE html> <html lang="en"> <head> <title><?php echo $title_for_layout?></title> <link rel="shortcut icon" href="favicon.ico" type="image/x-icon"> <!-- Incluir arquivos extenos e scripts aqui (Ver o helper HTML para mais detalhes) --> echo $this->fetch(’meta’); echo $this->fetch(’css’); echo $this->fetch(’script’); ?> </head> <body> <!-- Se você quiser exibir algum menu em todas as suas views, inclua-o aqui --> <div id="header"> <div id="menu">...</div> </div> <!-- Aqui é onde eu quero que minhas views sejam exibidas --> <?php echo $this->fetch(’content’); ?> <!-- Adicionar um rodapé para cada página exibida --> <div id="footer">...</div> Layouts 145 CakePHP Cookbook Documentation, Release 3.x </body> </html> Note: Na versão anterior a 2.1, o método fetch() não estava disponível, fetch(’content’) é uma substituição para $content_for_layout e as linhas fetch(’meta’), fetch(’css’) and fetch(’script’) estavam contidas na variável $scripts_for_layout na versão 2.0. Os blocos script, css e meta contém qualquer conteúdo definido nas views usando o helper HTML embutido. Útil na inclusão de arquivos javascript e CSS de views. Note: Quando usar HtmlHelper::css() ou HtmlHelper::script() em views, especifique ‘false’ para a opção ‘inline’ para colocar o código html em um bloco de mesmo nome. (Veja a API para mais detalhes de uso) O bloco content contem o conteúdo da view renderizada. $title_for_layout contém o título da página, Esta variável é gerada automaticamente, mas você poderá sobrescrevê-la definindo-a em seu controller/view. Para definir o título para o layout, o modo mais fácil é no controller, setando a variável $title_for_layout: class UsersController extends AppController { public function view_active() { $this->set(’title_for_layout’, ’View Active Users’); } } Você também pode setar a variável title_for_layout no arquivo de view: $this->set(’title_for_layout’, $titleContent); Você pode criar quantos layouts você desejar: apenas coloque-os no diretório app/View/Layouts, e defina qual deles usar dentro das ações do seu controller usando a propriedade $layout do controller ou view: // de um controller public function admin_view() { // códigos $this->layout = ’admin’; } // de um arquivo view $this->layout = ’loggedin’; Por exemplo, se a seção do meu site incluir um pequeno espaço para banner, eu posso criar um novo layout com um pequeno espaço para propaganda e especificá-lo como layout para as ações de todos os controllers usando algo como: class UsersController extends AppController { public function view_active() { $this->set(’title_for_layout’, ’View Active Users’); 146 Capítulo 8. Views CakePHP Cookbook Documentation, Release 3.x $this->layout = ’default_small_ad’; } public function view_image() { $this->layout = ’image’; //output user image } } O CakePHP tem em seu núcleo, dois layouts (além do layout padrão) que você pode usar em suas próprias aplicações: ‘ajax’ e ‘flash’. O layout Ajax é útil para elaborar respostas Ajax - é um layout vazio (a maior parte das chamadas ajax requer pouca marcação de retorno, preferencialmente a uma interface totalmente renderizada). O layout flash é usado para mensagens mostradas pelo método Controller::flash(). Outros três layouts, XML, JS, e RSS, existem no núcleo como um modo rápido e fácil de servir conteúdo que não seja text/html. Usando layouts a partir de plugins New in version 2.1. Se você quiser usar um layout que existe em um plugin, você pode usar a sintaxe de plugin. Por exemplo, para usar o layout de contato do plugin de contatos: class UsersController extends AppController { public function view_active() { $this->layout = ’Contacts.contact’; } } Elements Muitas aplicações possuem pequenos blocos de código de apresentação que precisam ser repetidos a cada página, às vezes em diferentes lugares no layout. O CakePHP ajuda você a repetir partes do seu website que precisam ser reutilizados. Estas partes reutilizáveis são chamadas de Elements (ou Elementos). Propagandas, caixas de ajuda, controles de navegação, menus extras, formulários de login e chamadas geralmente são implementadas como elements. Um element é básicamente uma mini-view que pode ser incluída em outras views, layouts e até mesmo em outros elements. Elements podem ser usados para criar uma view mais legível, colocando o processamento de elementos repetidos em seu próprio arquivo. Eles também podem ajudá-lo a re-usar conteúdos fragmentados pela sua aplicação. Elements são colocados na pasta /app/View/Elements/ e possuem a extensão .ctp no nome do arquivo. Eles são exibidos através do uso do método element da view: echo $this->element(’helpbox’); Elements 147 CakePHP Cookbook Documentation, Release 3.x Passando variáveis em um Element Você pode passar dados para um element através do segundo argumento do element: echo $this->element(’helpbox’, array( "helptext" => "Oh, este texto é muito útil." )); Dentro do arquivo do element, todas as variáveis passadas estão disponíveis como membros do array de parâmetros (da mesma forma que Controller::set() no controller trabalha com arquivos de views). No exemplo acima, o arquivo /app/View/Elements/helpbox.ctp pode usar a variável $helptext: // Dentro de app/View/Elements/helpbox.ctp echo $helptext; //outputs "Oh, este texto é muito útil." O método View::element() também suporta opções para o element. As opções suportadas são ‘cache’ e ‘callbacks’. Um exemplo: echo $this->element(’helpbox’, array( "helptext" => "Isto é passado para o *element * como $helptext", "foobar" => "TIsto é passado para o *element * como $foobar", ), array( "cache" => "long_view", // usa a configuração de cache "long_view" "callbacks" => true // atribue verdadeiro para ter before/afterRender chamado pelo ) ); O cache de element é facilitado através da classe Cache. Você pode configurar elements para serem guardados em qualquer configuração de cache que você tenha definido. Isto permite uma maior flexibilidade para decidir onde e por quantos elements são guardados. Para fazer o cache de diferentes versões de um mesmo element em uma aplicação, defina uma única chave de cache usando o seguinte formato: $this->element(’helpbox’, array(), array( "cache" => array(’config’ => ’short’, ’key’ => ’unique value’) ) ); Você pode tirar vantagem de elements usando requestAction(). A função requestAction() carrega variáveis da views a partir de ações do controller e as retorna como um array. Isto habilita seus elements para atuar verdadeiramente no estilo MVC. Crie uma ação de controller que prepara as variáveis da view para seu element, depois chame requestAction() no segundo parâmetro do element() para carregar as variáveis da view a partir do seu controller. Para isto, em seu controller, adicione algo como segue, como exemplo de Post: class PostsController extends AppController { // ... public function index() { $posts = $this->paginate(); if ($this->request->is(’requested’)) { return $posts; 148 Capítulo 8. Views CakePHP Cookbook Documentation, Release 3.x } else { $this->set(’posts’, $posts); } } } Em seguida, no element, você poderá acessar os modelos de posts paginados. Para obter os últimos cinco posts em uma lista ordenada, você pode fazer algo como: <h2>Latest Posts</h2> <?php $posts = $this->requestAction(’posts/index/sort:created/direction:asc/limit:5’); ?> <?php foreach ($posts as $post): ?> <ol> <li><?php echo $post[’Post’][’title’]; ?></li> </ol> <?php endforeach; ?> Caching Elements Você pode tomar proveito do CakePHP view caching, se você fornecer um parâmetro de cache. Se definido como true, o element será guardado na configuração de cache ‘default’. Caso contrário, você poderá definir qual configuração de cache deve ser usada. Veja /core-libraries/caching para mais informações de configuração Cache. Um exemplo simples de caching um element seria: echo $this->element(’helpbox’, array(), array(’cache’ => true)); Se você renderiza o mesmo element mais que uma vez em uma view e tem caching ativado, esteja certo de definir o parâmetro chave (key) para um nome diferente cada vez. Isto irá prevenir que cada chamada sucessiva substitua o resultado armazenado da chamada element() anterior. E.g.: echo $this->element( ’helpbox’, array(’var’ => $var), array(’cache’ => array(’key’ => ’first_use’, ’config’ => ’view_long’) ); echo $this->element( ’helpbox’, array(’var’ => $differenVar), array(’cache’ => array(’key’ => ’second_use’, ’config’ => ’view_long’) ); O código acima garante que ambos os resultados do element serão armazenados separadamente. Se você quiser que todos os elementos armazenados usem a mesma configuração de cache, você pode salvar alguma repetição, setando View::$elementCache para a configuração de cache que você quer usar. O CakePHP usará esta configuração, quando nenhuma outra for dada. Elements 149 CakePHP Cookbook Documentation, Release 3.x Requisitando Elements de um Plugin 2.0 Para carregar um element de um plugin, use a opção plugin (retirada da opção data na versão 1.x): echo $this->element(’helpbox’, array(), array(’plugin’ => ’Contacts’)); 2.1 Se você está usando um plugin e deseja usar elements de dentro deste plugin apenas use plugin syntax. Se a view está renderizando para um controller/action de plugin, o nome do plugin será automaticamente prefixado antes de todos os elements usados, ao menos que outro nome de plugin esteja presente. Se o element não existir no plugin, será procurado na pasta principal da APP.: echo $this->element(’Contacts.helpbox’); Se sua view é parte de um plugin você pode omitir o nome do plugin. Por exemplo, se você está no ContactsController do plugin Contatos: echo $this->element(’helpbox’); // and echo $this->element(’Contacts.helpbox’); São equivalentes e resultarão no mesmo elemento sendo renderizado. Changed in version 2.1: A opção $options[plugin] foi descontinuada e o suporte para Plugin.element foi adicionado. View API class View Métodos de Views são acessíveis por todas as views, elements e arquivos de layout. Para chamar qualquer método de uma view use $this->method(). View::set(string $var, mixed $value) Views têm métodos set() que são análogos aos set() encontrados nos objetos controllers. Usando set() em seu arquivo view serão adicionados variáveis para layouts e elements que serão renderizados posteriormente. Veja Métodos dos Controllers para maiores informações de como usar o set(). No seu arquivo de view, você pode: $this->set(’activeMenuButton’, ’posts’); Assim em seu layout a variável $activeMenuButton estará disponível e conterá o valor ‘posts’. View::getVar(string $var) Obtem o valor de viewVar com o nome $var 150 Capítulo 8. Views CakePHP Cookbook Documentation, Release 3.x View::getVars() Obtem uma lista de todas as variáveis disponíveis da view, no escopo renderizado corrente. Retorna um array com os nomes das variáveis. View::element(string $elementPath, array $data, array $options = array()) Renderiza um elemento ou parte de uma view. Veja a seção Elements para maiores informações e exemplos. View::uuid(string $object, mixed $url) Gera um DOM ID não randômico único para um objeto, baseado no tipo do objeto e url. Este método é frequentemente usado por helpers que precisam gerar DOM ID únicos para elementos como JsHelper: $uuid = $this->uuid(’form’, array(’controller’ => ’posts’, ’action’ => ’index’)); //$uuid contains ’form0425fe3bad’ View::addScript(string $name, string $content) Adiciona conteúdo para buffer de scripts internos. Este buffer é disponibilizado no layout como $scripts_for_layout. Este método auxilia na criação de helpers que necessitam adicionar javascript or css diretamente para o layout. Ciente que scripts adicionados de layouts, or elements do layout não serão adicionados para $scripts_for_layout. Este método é frequentemente usado dentro dos helpers, como nos Helpers /core-libraries/helpers/js e /core-libraries/helpers/html. Deprecated since version 2.1: Use a feature Usando Blocos de Views (Visões), ao invés. View::blocks() Obtem o nome de todos os blocos definidos como um array. View::start($name) Inicia a caputura de bloco para um bloco de view. Veja a seção em Usando Blocos de Views (Visões) para exemplos. New in version 2.1. View::end() Finaliza o mais recente bloco sendo capturado. Veja a seção em Usando Blocos de Views (Visões) para exemplos. New in version 2.1. View::append($name, $content) Anexa no bloco com $name. Veja a seção em Usando Blocos de Views (Visões) para examplos. New in version 2.1. View::assign($name, $content) Atribui o valor de um bloco. Isso irá sobrescrever qualquer conteúdo existente. Veja a seção em Usando Blocos de Views (Visões) para exemplos. New in version 2.1. View::fetch($name) Fetch o valor do bloco. ‘’ Serão retornados de blocos que não estão definidos Veja a seção em Usando Blocos de Views (Visões) para exemplos. View API 151 CakePHP Cookbook Documentation, Release 3.x New in version 2.1. View::extend($name) Estende o view/element/layout corrente com o nome fornecido. Veja a seção em Estendendo Views para examplos. New in version 2.1. property View::$layout Seta o layout onde a view corrente será envolvida. property View::$elementCache A configuração de cache usada para armazenar elements. Setando esta propriedade a configuração padrâo usada para armazenar elements será alterada Este padrão pode ser sobrescrito usando a opção ‘cache’ no método do element. property View::$request Uma instância de CakeRequest. Use esta instância para acessar informaçãoes sobre a requisição atual. property View::$output Contem o último conteúdo renderizado de uma view, seja um arquivo de view ou conteúdo do layout. Deprecated since version 2.1: Use $view->Blocks->get(’content’); ao invés. property View::$Blocks Uma instância de ViewBlock. Usada para prover um bloco de funcionalidades de view na view renderizada. New in version 2.1. 152 Capítulo 8. Views CAPÍTULO 9 Bibliotecas Centrais O CakePHP vem com uma infinidade de funções e classes internas. Essas classes e funções tentam cobrir algumas das mais comuns funcionalidades requisitadas em aplicações web. General Purpose (Uso Geral) Bibliotecas de uso geral estão disponíveis e são reutilizáveis em muitos lugares através do CakePHP. Behaviors (Comportamentos) Components (Componentes) Helpers (Auxiliares) Utilities (Utilitários) Além dos componentes centrais do MVC, o CakePHP inclui uma grande seleção de classes utilitárias que ajudam você a fazer qualquer coisa como requisições de webservice, gerenciamento de cache, log, internacionalização e mais. 153 CakePHP Cookbook Documentation, Release 3.x 154 Capítulo 9. Bibliotecas Centrais CAPÍTULO 10 Plugins CakePHP permite que você defina uma combinação de controllers, models, e views e lance-os como um pacote de aplicação que outras pessoas podem usar em suas aplicações CakePHP. Você quer ter um módulo de gestão de usuários, um blog simples, ou um módulo de serviços web em uma das suas aplicações? Empacote-os como um plugin do CakePHP para que você possa colocá-lo em outras aplicações. A principal diferença entre um plugin e a aplicação em que ele é instalado, é a configuração da aplicação (base de dados, conexão, etc.). Caso contrário, ele opera em seu próprio espaço, comportando-se como se fosse uma aplicação por conta própria. Instalando um Plugin Para instalar um plugin, basta simplesmente colocar a pasta plugin dentro do diretório app/Plugin. Se você está instalando um plugin chamado ‘ContactManager’, então você deve ter uma pasta dentro de app/Plugin chamado ‘ContactManager’ em que podem estar os diretórios de plugin: View, Model, Controller, webroot, e qualquer outro diretório. Novo para CakePHP 2.0, plugins precisam ser carregados manualmente em config/bootstrap.php. Você pode carregá-los um por um ou todos eles em uma única chamada: CakePlugin::loadAll(); // Carrega todos os plugins de uma vez CakePlugin::load(’ContactManager’); // Carrega um único plugin loadAll carrega todos os plugins disponíveis, enquanto permite que você defina certas configurações para plugins específicos. load() funciona de maneira semelhante, mas carrega somente os plugins que você especificar explicitamente. Há uma porção de coisas que você pode fazer com os métodos load e loadAll para ajudar com a configuração do plugin e roteamento. Talvez você tenha que carregar todos os plugins automaticamente, enquanto especifica rotas personalizadas e arquivos de bootstrap para certos plugins. Sem problema: 155 CakePHP Cookbook Documentation, Release 3.x CakePlugin::loadAll(array( ’Blog’ => array(’routes’ => true), ’ContactManager’ => array(’bootstrap’ => true), ’WebmasterTools’ => array(’bootstrap’ => true, ’routes’ => true), )); Com este estilo de configuração, você não precisa mais fazer manualmente um include() ou require() de arquivo de configuração do plugin ou rotas – acontece automaticamente no lugar e na hora certa. Os exatos mesmos parâmetros também poderiam ter sido fornecidos ao método load(), o que teria carregado apenas aqueles três plugins, e não o resto. Finalmente, você pode especificar também um conjunto de padrões para loadAll que se aplicará a cada plugin que não tem uma configuração mais específica. Carrega o arquivo bootstrap de todos os plugins, e as rotas do plugin Blog: CakePlugin::loadAll(array( array(’bootstrap’ => true), ’Blog’ => array(’routes’ => true) )); Note que todos os arquivos especificados devem realmente existir no plugin(s) configurado ou o PHP irá dar avisos (warnings) para cada arquivo que não pôde carregar. Isto é especialmente importante para lembrar ao especificar padrões para todos os plugins. Alguns plugins precisam que seja criado uma ou mais tabelas em sua base de dados. Nesses casos, muitas vezes eles vão incluir um arquivo de esquema que você pode chamar a partir do cake shell dessa forma: user@host $ cake schema create --plugin ContactManager A maioria dos plugins indicará o procedimento adequado para a configurá-los e configurar a base de dados, em sua documentação. Alguns plugins irão exigir mais configurações do que outros. Usando um Plugin Você pode referenciar controllers, models, components, behaviors, e helpers de um plugin, prefixando o nome do plugin antes do nome da classe. Por exemplo, digamos que você queira usar o ContactInfoHelper do plugin CantactManager para a saída de algumas informações de contato em uma de suas views. Em seu controller, seu array $helpers poderia ser assim: public $helpers = array(’ContactManager.ContactInfo’); Você então será capaz de acessar o ContactInfoHelper como qualquer outro helper em sua view, tal como: echo $this->ContactInfo->address($contact); 156 Capítulo 10. Plugins CakePHP Cookbook Documentation, Release 3.x Criando Seus Próprios Plugins Como um exemplo de trabalho, vamos começar a criar o plugin ContactManager referenciado acima. Para começar, vamos montar a nossa estrutura básica de plugins. Ela deve se parecer com isso: /app /Plugin /ContactManager /Controller /Component /Model /Behavior /View /Helper /Layouts Note o nome da pasta do plugin, ‘ContactManager‘. É importante que essa pasta tenha o mesmo nome do plugin. Dentro da pasta do plugin, você verá que se parece muito com uma aplicação CakePHP, e que basicamente é isso mesmo. Você realmente não tem que incluir qualquer uma dessas pastas se você não for usá-las. Alguns plugins podem definir somente um Component e um Behavior, e nesse caso eles podem omitir completamente o diretório ‘View’. Um plugin pode também ter basicamente qualquer um dos outros diretórios que sua aplicação pode, como Config, Console, Lib, webroot, etc. Note: Se você quer ser capaz de acessar seu plugin com uma URL, é necessário definir um AppController e AppModel para o plugin. Estas duas classes especiais são nomeadas após o plugin, e estendem AppController e AppModel da aplicação pai. Aqui está o que deve ser semelhante para nosso exemplo ContactManager: // /app/Plugin/ContactManager/Controller/ContactManagerAppController.php: class ContactManagerAppController extends AppController { } // /app/Plugin/ContactManager/Model/ContactManagerAppModel.php: class ContactManagerAppModel extends AppModel { } Se você se esqueceu de definir estas classes especiais, o CakePHP irá entregar a você erros “Missing Controller” até que você tenha feito isso. Por favor, note que o processo de criação de plugins pode ser muito simplificado usando o Cake shell. Para assar um plugin por favor use o seguinte comando: user@host $ cake bake plugin ContactManager Agora você pode assar usando as mesmas convenções que se aplicam ao resto de sua aplicação. Por exemplo - assando controllers: Criando Seus Próprios Plugins 157 CakePHP Cookbook Documentation, Release 3.x user@host $ cake bake controller Contacts --plugin ContactManager Por favor consulte o capítulo /console-and-shells/code-generation-with-bake se você tiver quaisquer problemas com o uso da linha de comando. Plugin Controllers Controllers de nosso plugin ContactManager serão armazenados em /app/Plugin/ContactManager/Controller/. Como a principal coisa que vamos fazer é a gestão de contatos, vamos precisar de um ContactsController para este plugin. Então, nós colocamos nosso novo ContactsController em /app/Plugin/ContactManager/Controller e deve se parecer com isso: // app/Plugin/ContactManager/Controller/ContactsController.php class ContactsController extends ContactManagerAppController { public $uses = array(’ContactManager.Contact’); public function index() { //... } } Note: Este controller estende o AppController do plugin (chamado ContactManagerAppController) ao invés do AppController da aplicação pai. Observe também como o nome do model é prefixado com o nome do plugin. Isto é necessário para diferenciar entre models do plugin e models da aplicação principal. Neste caso, o array $uses não seria necessário com ContactManager. Contact seria o model padrão para este controller, no entanto está incluído para demostrar adequadamente como preceder o nome do plugin. Se você quiser acessar o que nós fizemos até agora, visite /contact_manager/contacts. Você deve obter um erro “Missing Model” porque não temos um model Contact definido ainda. Plugin Models Models para plugins são armazenados em /app/Plugin/ContactManager/Model. Nós já definimos um ContactsController para este plugin, então vamos criar o model para o controller, chamado Contact: // /app/Plugin/ContactManager/Model/Contact.php: class Contact extends ContactManagerAppModel { } Visitando /contact_manager/contacts agora (dado que você tem uma tabela em seu banco de dados chamada ‘contacts’) deveria nos dar um erro “Missing View”. Vamos criar na próxima. Note: Se você precisar fazer referência a um model dentro de seu plugin, você precisa incluir o nome do 158 Capítulo 10. Plugins CakePHP Cookbook Documentation, Release 3.x plugin com o nome do model, separados por um ponto. Por exemplo: // /app/Plugin/ContactManager/Model/Contact.php: class Contact extends ContactManagerAppModel { public $hasMany = array(’ContactManager.AltName’); } Se você preferir que as chaves do array para associação não tenha o prefixo do plugin nelas, use uma sintaxe alternativa: // /app/Plugin/ContactManager/Model/Contact.php: class Contact extends ContactManagerAppModel { public $hasMany = array( ’AltName’ => array( ’className’ => ’ContactManager.AltName’ ) ); } Plugin Views Views se comportam exatamente como fazem em aplicações normais. Basta colocá-las na pasta certa dentro de /app/Plugin/[PluginName]/View/. Para nosso plugin ContactManager, vamos precisar de uma view para nosso action ContactsController::index(), por isso vamos incluir isso como: // /app/Plugin/ContactManager/View/Contacts/index.ctp: <h1>Contacts</h1> <p>Following is a sortable list of your contacts</p> <!-- A sortable list of contacts would go here....--> Note: Para obter informações sobre como usar elements de um plugin, veja Elements Substituindo views de plugins de dentro da sua aplicação Você pode substituir algumas views de plugins de dentro da sua app usando caminhos especiais. Se você tem um plugin chamado ‘ContactManager’ você pode substituir os arquivos de view do plugin com lógicas de view da aplicação específica criando arquivos usando o modelo a seguir “app/View/Plugin/[Plugin]/[Controller]/[view].ctp”. Para o controller Contacts você pode fazer o seguinte arquivo: /app/View/Plugin/ContactManager/Contacts/index.ctp A criação desse, permite a você substituir “/app/Plugin/ContactManager/View/Contacts/index.ctp”. Plugin Views 159 CakePHP Cookbook Documentation, Release 3.x Imagens de Plugin, CSS e Javascript Imagens, css e javascript de um plugin (mas não arquivos PHP), podem ser servidos por meio do diretório de plugin ‘webroot’, assim como imagens, css e javascript da aplicação principal: app/Plugin/ContactManager/webroot/ css/ js/ img/ flash/ pdf/ Você pode colocar qualquer tipo de arquivo em qualquer diretório, assim como um webroot normal. A única restrição é que MediaView precisa saber o mime-type do arquivo. Linkando para imagens, css e javascript em plugins Basta preceder /plugin_name/ no início de um pedido para um arquivo dentro do plugin, e ele vai funcionar como se fosse um arquivo do webroot de sua aplicação. Por exemplo, linkando para ‘/contact_manager/js/some_file.js’ ‘app/Plugin/ContactManager/webroot/js/some_file.js’. deveria servir o arquivo Note: É importante notar o /your_plugin/ prefixado antes do caminho do arquivo. Isso faz a magica acontecer! Components, Helpers e Behaviors Um plugin pode ter Conponents, Helpers e Behaviors como uma aplicação CakePHP normal. Você pode até criar plugins que consistem apenas de Components, Helpers ou Behaviors que podem ser uma ótima maneira de contruir componentes reutilizáveis que podem ser facilmente acoplados em qualquer projeto. A construção destes componentes é exatamente o mesmo que contruir dentro de uma aplicação normal, sem convenção especial de nomenclatura. Referindo-se ao seu componente de dentro ou fora do seu plugin, exige somente que o nome do plugin esteja prefixado antes do nome do componente. Por exemplo: // Componente definido no plugin ’ContactManager’ class ExampleComponent extends Component { } // dentro de seu controller: public $components = array(’ContactManager.Example’); A mesma técnica se aplica aos Helpers e Behaviors. Note: Ao criar Helpers você pode notar que AppHelper não está disponível automaticamente. Você deve declarar os recursos que precisar com Uses: 160 Capítulo 10. Plugins CakePHP Cookbook Documentation, Release 3.x // Declare o uso do AppHelper para seu Helper Plugin App::uses(’AppHelper’, ’View/Helper’); Expanda seu Plugin Este exemplo criou um bom começo para um plugin, mas há muito mais coisas que você pode fazer. Como uma regra geral, qualquer coisa que você pode fazer com sua aplicação, você pode fazer dentro de um plugin em seu lugar. Vá em frente, inclua algumas bibliotecas de terceiros em ‘Vendor’, adicione algumas novas shells para o cake console, e não se esqueça de criar casos de testes para que usuários de seus plugins possam testar automaticamente as funcionalidades de seus plugins! Em nosso exemplo ContactManager, poderíamos criar os actions add/remove/edit/delete em ContactsController, implementar a validação no model Contact, e implementar uma funcionalidade que poderia se esperar ao gerenciar seus contatos. Cabe a você decidir o que implementar em seus plugins. Só não se esqueça de compartilhar seu código com a comunidade para que todos possam se beneficiar de seus impressionantes componentes reutilizáveis! Plugin Dicas Uma vez que o plugin foi instalado em /app/Plugin, você pode acessá-lo através da URL /plugin_name/controller_name/action. Em nosso plugin ContactManager de exemplo, acessamos nosso ContactsController com /contact_manager/contacts. Algumas dicas finais sobre como trabalhar com plugins em suas aplicações CakePHP: • Quando você não tiver um [Plugin]AppController e [Plugin]AppModel, você terá um erro Missing Controller quando estiver tentando acessar um controller de plugin. • Você pode definir seus layouts para plugins, dentro de app/Plugin/[Plugin]/View/Layouts. Caso contrário, o plugin irá utilizar por padrão os layouts da pasta /app/View/Layouts. • Você pode fazer um inter-plugin de comunicação usando $this->requestAction(’/plugin_name/contro em seus controllers. • Se você usar requestAction, esteja certo que os nomes dos controllers e das models sejam tão únicos quanto possível. Caso contrário você poderá obter do PHP o erro “redefined class ...” Expanda seu Plugin 161 CakePHP Cookbook Documentation, Release 3.x 162 Capítulo 10. Plugins CAPÍTULO 11 Desenvolvimento Nesta seção vamos cobrir os vários aspectos do desenvolvimento de uma aplicação CakePHP. Temas como manipulação de erros e exceções, depuração e testes serão abordados. Sessions CakePHP provides a wrapper and suite of utility features on top of PHP’s native session extension. Sessions allow you to identify unique users across the requests and store persistent data for specific users. Unlike Cookies, session data is not available on the client side. Usage of $_SESSION is generally avoided in CakePHP, and instead usage of the Session classes is preferred. Session Configuration Session configuration is stored in Configure under the top level Session key, and a number of options are available: • Session.cookie - Change the name of the session cookie. • Session.cookiePath - The url path for which session cookie is set. session.cookie_path php.ini config. Defaults to base path of app. Maps to the • Session.timeout - The number of minutes before CakePHP’s session handler expires the session. • Session.cookieTimeout - The number of minutes before the session cookie expires. If this is undefined, it will use the same value as Session.timeout. This affects the session cookie, and is handled by PHP itself. • Session.defaults - Allows you to use one the built-in default session configurations as a base for your session configuration. See below for the built-in defaults. • Session.handler - Allows you to define a custom session handler. The core database and cache session handlers use this. See below for additional information on Session handlers. 163 CakePHP Cookbook Documentation, Release 3.x • Session.ini - Allows you to set additional session ini settings for your config. This combined with Session.handler replace the custom session handling features of previous versions CakePHP’s defaults session.cookie_secure to true, when your application is on an SSL protocol. If your application serves from both SSL and non-SSL protocols, then you might have problems with sessions being lost. If you need access to the session on both SSL and non-SSL domains you will want to disable this: Configure::write(’Session’, [ ’defaults’ => ’php’, ’ini’ => [ ’session.cookie_secure’ => false ] ]); The session cookie path defaults to app’s base path. To change this you can use the cookiePath config. For e.g. if you want your session to persist across all subdomains you can do: Configure::write(’Session’, [ ’defaults’ => ’php’, ’cookiePath’ => ’/’, ’ini’ => [ ’session.cookie_domain’ => ’.yourdomain.com’ ] ]); Built-in Session Handlers & Configuration CakePHP comes with several built-in session configurations. You can either use these as the basis for your session configuration, or you can create a fully custom solution. To use defaults, simply set the ‘defaults’ key to the name of the default you want to use. You can then override any sub setting by declaring it in your Session config: Configure::write(’Session’, [ ’defaults’ => ’php’ ]); The above will use the built-in ‘php’ session configuration. You could augment part or all of it by doing the following: Configure::write(’Session’, [ ’defaults’ => ’php’, ’cookie’ => ’my_app’, ’timeout’ => 4320 // 3 days ]); The above overrides the timeout and cookie name for the ‘php’ session configuration. The built-in configurations are: • php - Saves sessions with the standard settings in your php.ini file. • cake - Saves sessions as files inside app/tmp/sessions. This is a good option when on hosts that don’t allow you to write outside your own home dir. 164 Capítulo 11. Desenvolvimento CakePHP Cookbook Documentation, Release 3.x • database - Use the built-in database sessions. See below for more information. • cache - Use the built-in cache sessions. See below for more information. Session Handlers Session handlers can also be defined in the session config array. By defining the ‘handler.engine’ config key, you can name the class name, or provide a handler instance. The class/object must implement the native PHP SessionHandlerInterface. Implementing this interface will allow Session to automatically map the methods for the handler. Both the core Cache and Database session handlers use this method for saving sessions. Additional settings for the handler should be placed inside the handler array. You can then read those values out from inside your handler: ’Session’ => [ ’handler’ => [ ’engine’ => ’Database’, ’model’ => ’CustomSessions’ ] ] The above shows how you could setup the Database session handler with an application model. When using class names as your handler.engine, CakePHP will expect to find your class in the Network\Session namespace. For example, if you had a AppSessionHandler class, the file should be src/Network/Session/AppSessionHandler.php, and the class name should be App\\Network\\Session\\AppSessionHandler. You can also use session handlers from inside plugins. By setting the engine to MyPlugin.PluginSessionHandler. Database Sessions The changes in session configuration change how you define database sessions. Most of the time you will only need to set Session.handler.model in your configuration as well as choose the database defaults: Configure::write(’Session’, [ ’defaults’ => ’database’, ’handler’ => [ ’model’ => ’CustomSessions’ ] ]); The above will tell Session to use the built-in ‘database’ defaults, and specify that a model called CustomSessions will be the delegate for saving session information to the database. If you do not need a fully custom session handler, but still require database-backed session storage, you can simplify the above code to: Configure::write(’Session’, [ ’defaults’ => ’database’ ]); This configuration will require a database table to be added with at least these fields: Sessions 165 CakePHP Cookbook Documentation, Release 3.x CREATE TABLE ‘sessions‘ ( ‘id‘ varchar(255) NOT NULL DEFAULT ’’, ‘data‘ text, ‘expires‘ int(11) DEFAULT NULL, PRIMARY KEY (‘id‘) ); You can find a copy of the schema for the sessions table in the application skeleton. Cache Sessions The Cache class can be used to store sessions as well. This allows you to store sessions in a cache like APC, memcache, or Xcache. There are some caveats to using cache sessions, in that if you exhaust the cache space, sessions will start to expire as records are evicted. To use Cache based sessions you can configure you Session config like: Configure::write(’Session’, [ ’defaults’ => ’cache’, ’handler’ => [ ’config’ => ’session’ ] ]); This will configure Session to use the CacheSession class as the delegate for saving the sessions. You can use the ‘config’ key which cache configuration to use. The default cache configuration is ’default’. Setting ini directives The built-in defaults attempt to provide a common base for session configuration. You may need to tweak specific ini flags as well. CakePHP exposes the ability to customize the ini settings for both default configurations, as well as custom ones. The ini key in the session settings, allows you to specify individual configuration values. For example you can use it to control settings like session.gc_divisor: Configure::write(’Session’, [ ’defaults’ => ’php’, ’ini’ => [ ’session.gc_divisor’ => 1000, ’session.cookie_httponly’ => true ] ]); Creating a Custom Session Handler Creating a custom session handler is straightforward in CakePHP. In this example we’ll create a session handler that stores sessions both in the Cache (apc) and the database. This gives us the best of fast IO of apc, without having to worry about sessions evaporating when the cache fills up. 166 Capítulo 11. Desenvolvimento CakePHP Cookbook Documentation, Release 3.x First we’ll need to create our custom class and put it in src/Network/Session/ComboSession.php. The class should look something like: namespace App\Network\Session; use Cake\Cache\Cache; use Cake\Core\Configure; use Cake\Network\Session\DatabaseSession; class ComboSession extends DatabaseSession { public $cacheKey; public function __construct() { $this->cacheKey = Configure::read(’Session.handler.cache’); parent::__construct(); } // Read data from the session. public function read($id) { $result = Cache::read($id, $this->cacheKey); if ($result) { return $result; } return parent::read($id); } // Write data into the session. public function write($id, $data) { $result = Cache::write($id, $data, $this->cacheKey); if ($result) { return parent::write($id, $data); } return false; } // Destroy a session. public function destroy($id) { Cache::delete($id, $this->cacheKey); return parent::destroy($id); } // Removes expired sessions. public function gc($expires = null) { return Cache::gc($this->cacheKey) && parent::gc($expires); } } Our class extends the built-in DatabaseSession so we don’t have to duplicate all of its logic and behavior. We wrap each operation with a Cake\Cache\Cache operation. This lets us fetch sessions from the fast cache, and not have to worry about what happens when we fill the cache. Using this session handler is also easy. In your app.php make the session block look like the following: Sessions 167 CakePHP Cookbook Documentation, Release 3.x ’Session’ => [ ’defaults’ => ’database’, ’handler’ => [ ’engine’ => ’ComboSession’, ’model’ => ’Session’, ’cache’ => ’apc’ ] ], // Make sure to add a apc cache config ’Cache’ => [ ’apc’ => [’engine’ => ’Apc’] ] Now our application will start using our custom session handler for reading & writing session data. class Session Accessing the Session Object You can access the session data any place you have access to a request object. This means the session is easily accessible from: • Controllers • Views • Helpers • Cells • Components In addition to the basic session object, you can also use the Cake\Controller\Component\SessionComponent and Cake\View\Helper\SessionHelper to interact with the session in controllers and views. A basic example of session usage would be: $name = $this->request->session()->read(’User.name’); // If you are accessing the session multiple times, // you will probably want a local variable. $session = $this->request->session(); $name = $session->read(’User.name’); Reading & Writing Session Data Session::read($key) You can read values from the session using Hash::extract() compatible syntax: $session->read(’Config.language’); Session::write($key, $value) 168 Capítulo 11. Desenvolvimento CakePHP Cookbook Documentation, Release 3.x $key should be the dot separated path you wish to write $value to: $session->write(’Config.language’, ’eng’); Session::delete($key) When you need to delete data from the session, you can use delete: $session->delete(’Config.language’); Session::check($key) If you want to see if data exists in the session, you can use check(): if ($session->check(’Config.language’)) { // Config.language exists and is not null. } Destroying the Session Session::destroy() Destroying the session is useful when users log out. To destroy a session, use the destroy() method: $session->destroy(); Destroying a session will remove all serverside data in the session, but will not remove the session cookie. Rotating Session Identifiers Session::renew() While AuthComponent automatically renews the session id when users login and out, you may need to rotate the session id’s manually. To do this use the renew() method: $session->renew(); Flash Messages Flash messages are small messages displayed to end users once. They are often used to present error messages, or confirm that actions took place successfully. To set and display flash messages you should use FlashComponent and /views/helpers/flash Error & Exception Handling Many of PHP’s internal methods use errors to communicate failures. These errors will need to be trapped and dealt with. CakePHP comes with default error trapping that prints and or logs errors as they occur. This same error handler is used to catch uncaught exceptions from controllers and other parts of your application. Error & Exception Handling 169 CakePHP Cookbook Documentation, Release 3.x Error & Exception Configuration Error configuration is done inside your application’s config/app.php file. By default CakePHP uses the ErrorHandler or ConsoleErrorHandler class to trap errors and print/log the errors. You can replace this behavior by changing out the default error handler. The default error handler also handles uncaught exceptions. Error handling accepts a few options that allow you to tailor error handling for your application: • errorLevel - int - The level of errors you are interested in capturing. Use the built-in php error constants, and bitmasks to select the level of error you are interested in. • trace - boolean - Include stack traces for errors in log files. Stack traces will be included in the log after each error. This is helpful for finding where/when errors are being raised. • exceptionRenderer - string - The class responsible for rendering uncaught exceptions. If you choose a custom class you should place the file for that class in src/Error. This class needs to implement a render() method. • log - boolean - When true, exceptions + their stack traces will be logged to Cake\Log\Log. • skipLog - array - An array of exception classnames that should not be logged. This is useful to remove NotFoundExceptions or other common, but uninteresting logs messages. ErrorHandler by default, displays errors when debug is true, and logs errors when debug is false. The type of errors captured in both cases is controlled by errorLevel. The fatal error handler will be called independent of debug level or errorLevel configuration, but the result will be different based on debug level. The default behavior for fatal errors is show a page to internal server error (debug disabled) or a page with the message, file and line (debug enabled). Note: If you use a custom error handler, the supported options will depend on your handler. Creating your Own Error Handler You can create an error handler out of any callback type. For example you could use a class called AppError to handle your errors. By extending the BaseErrorHandler you can supply custom logic for handling errors. An example would be: // In config/bootstrap.php use App\Error\AppError; $errorHandler = new AppError(); $errorHandler->register(); // In src/Error/AppError.php namespace App\Error; use Cake\Error\BaseErrorHandler; class AppError extends BaseErrorHandler { public function _displayError($error, $debug) { return ’There has been an error!’; 170 Capítulo 11. Desenvolvimento CakePHP Cookbook Documentation, Release 3.x } public function _displayException($exception) { return ’There has been an exception!’; } } The BaseErrorHandler defines two abstract methods. _displayError is used when errors are triggered. The _displayException method is called when there is an uncaught exception. Changing Fatal Error Behavior The default error handlers convert fatal errors into exceptions and re-use the exception handling logic to render an error page. If you do not want to show the standard error page, you can override it like: // In config/bootstrap.php use App\Error\AppError; $errorHandler = new AppError(); $errorHandler->register(); // In src/Error/AppError.php namespace App\Error; use Cake\Error\BaseErrorHandler; class AppError { // Other methods. public function handleFatalError($code, $description, $file, $line) { return ’A fatal error has happened’; } } Exception Classes There are a number of exception classes in CakePHP. The built in exception handling will capture any uncaught exceptions and render a useful page. Exceptions that do not specifically use a 400 range code, will be treated as an Internal Server Error. Built in Exceptions for CakePHP There are several built-in exceptions inside CakePHP, outside of the internal framework exceptions, there are several exceptions for HTTP methods exception Cake\Network\Exception\BadRequestException Used for doing 400 Bad Request error. exception Cake\Network\Exception\UnauthorizedException Used for doing a 401 Not found error. Error & Exception Handling 171 CakePHP Cookbook Documentation, Release 3.x exception Cake\Network\Exception\ForbiddenException Used for doing a 403 Forbidden error. exception Cake\Network\Exception\NotFoundException Used for doing a 404 Not found error. exception Cake\Network\Exception\MethodNotAllowedException Used for doing a 405 Method Not Allowed error. exception Cake\Network\Exception\InternalErrorException Used for doing a 500 Internal Server Error. exception Cake\Network\Exception\NotImplementedException Used for doing a 501 Not Implemented Errors. You can throw these exceptions from you controllers to indicate failure states, or HTTP errors. An example use of the HTTP exceptions could be rendering 404 pages for items that have not been found: public function view($id) { $post = $this->Post->findById($id); if (!$post) { throw new NotFoundException(’Could not find that post’); } $this->set(’post’, $post); } By using exceptions for HTTP errors, you can keep your code both clean, and give RESTful responses to client applications and users. In addition, the following framework layer exceptions are available, and will be thrown from a number of CakePHP core components: exception Cake\View\Exception\MissingViewException The chosen view file could not be found. exception Cake\View\Exception\MissingLayoutException The chosen layout could not be found. exception Cake\View\Exception\MissingHelperException The chosen helper could not be found. exception Cake\View\Exception\MissingElementException The chosen element file could not be found. exception Cake\View\Exception\MissingCellException The chosen cell class could not be found. exception Cake\View\Exception\MissingCellViewException The chosen cell view file could not be found. exception Cake\Controller\Exception\MissingComponentException A configured component could not be found. exception Cake\Controller\Exception\MissingActionException The requested controller action could not be found. 172 Capítulo 11. Desenvolvimento CakePHP Cookbook Documentation, Release 3.x exception Cake\Controller\Exception\PrivateActionException Accessing private/protected/_ prefixed actions. exception Cake\Console\Exception\ConsoleException A console library class encounter an error. exception Cake\Console\Exception\MissingTaskException A configured task could not found. exception Cake\Console\Exception\MissingShellException The shell class could not be found. exception Cake\Console\Exception\MissingShellMethodException The chosen shell class has no method of that name. exception Cake\Database\Exception\MissingConnectionException A model’s connection is missing. exception Cake\Database\Exception\MissingDriverException A database driver could not be found. exception Cake\Database\Exception\MissingExtensionException A PHP extension is missing for the database driver. exception Cake\ORM\Exception\MissingTableException A model’s table could not be found. exception Cake\ORM\Exception\MissingEntityException A model’s entity could not be found. exception Cake\ORM\Exception\MissingBehaviorException A model’s behavior could not be found. exception Cake\Routing\Exception\MissingControllerException The requested controller could not be found. exception Cake\Routing\Exception\MissingRouteException The requested URL cannot be reverse routed or cannot be parsed. exception Cake\Routing\Exception\MissingDispatcherFilterException The dispatcher filter could not be found. exception Cake\Core\Exception\Exception Base exception class in CakePHP. All framework layer exceptions thrown by CakePHP will extend this class. These exception classes all extend Exception. By extending Exception, you can create your own ‘framework’ errors. All of the standard Exceptions that CakePHP will throw also extend Exception. Cake\Core\Exception\Exception::responseHeader($header = null, $value = null) See Cake\Network\Request::header() All Http and Cake exceptions extend the Exception class, which has a method to add headers to the response. For instance when throwing a 405 MethodNotAllowedException the rfc2616 says: “The response MUST include an Allow header containing a list of valid methods for the requested resource.” Error & Exception Handling 173 CakePHP Cookbook Documentation, Release 3.x Using HTTP Exceptions in your Controllers You can throw any of the HTTP related exceptions from your controller actions to indicate failure states. For example: public function view($id) { $post = $this->Post->read(null, $id); if (!$post) { throw new NotFoundException(); } $this->set(compact(’post’)); } The above would cause the configured exception handler to catch and process the NotFoundException. By default this will create an error page, and log the exception. Exception Renderer class Cake\Core\Exception\ExceptionRenderer(Exception $exception) The ExceptionRenderer class with the help of ErrorController takes care of rendering the error pages for all the exceptions thrown by you application. The error page views are located at src/Template/Error/. For all 4xx and 5xx errors the view files error400.ctp and error500.ctp are used respectively. You can customize them as per your needs. By default your src/Template/Layout/default.ctp is used for error pages too. If for example, you want to use another layout src/Template/Layout/my_error.ctp for your error pages. Simply edit the error views and add the statement $this->layout = ’my_error’; to the error400.ctp and error500.ctp. Each framework layer exception has its own view file located in the core templates but you really don’t need to bother customizing them as they are used only during development. With debug turned off all framework layer exceptions are converted to InternalErrorException. Creating your own Application Exceptions You can create your own application exceptions using any of the built in SPL exceptions1 , Exception itself, or Cake\Core\Exception\Exception. If your application contained the following exception: use Cake\Core\Exception\Exception; class MissingWidgetException extends Exception {}; You could provide nice development errors, by creating src/Template/Error/missing_widget.ctp. When in production mode, the above error would be treated as a 500 error. The constructor for Cake\Core\Exception\Exception has been extended, allowing you to pass in hashes of data. These hashes are interpolated into the the messageTemplate, as well as into the view that is used to represent the error in development mode. This allows you to create data rich exceptions, by providing more 1 http://php.net/manual/en/spl.exceptions.php 174 Capítulo 11. Desenvolvimento CakePHP Cookbook Documentation, Release 3.x context for your errors. You can also provide a message template which allows the native __toString() methods to work as normal: use Cake\Core\Exception\Exception; class MissingWidgetException extends Exception { protected $_messageTemplate = ’Seems that %s is missing.’; } throw new MissingWidgetException(array(’widget’ => ’Pointy’)); When caught by the built in exception handler, you would get a $widget variable in your error view template. In addition if you cast the exception as a string or use its getMessage() method you will get Seems that Pointy is missing.. This allows you easily and quickly create your own rich development errors, just like CakePHP uses internally. Creating Custom Status Codes You can create custom HTTP status codes by changing the code used when creating an exception: throw new MissingWidgetHelperException(’Its not here’, 501); Will create a 501 response code, you can use any HTTP status code you want. In development, if your exception doesn’t have a specific template, and you use a code equal to or greater than 500 you will see the error500 template. For any other error code you’ll get the error400 template. If you have defined an error template for your custom exception, that template will be used in development mode. If you’d like your own exception handling logic even in production, see the next section. Extending and Implementing your own Exception Handlers You can implement application specific exception handling in one of a few ways. Each approach gives you different amounts of control over the exception handling process. • Create and register your own custom error handlers. • Extend the BaseErrorHandler provided by CakePHP. • Set the exceptionRenderer option on the default error handler. In the next few sections, we will detail the various approaches and the benefits each has. Create and Register your own Exception Handler Creating your own exception handler gives you full control over the exception handling process. You will have to call set_exception_handler yourself in this situation. Extend the BaseErrorHandler The Error & Exception Configuration section has an example of this. Error & Exception Handling 175 CakePHP Cookbook Documentation, Release 3.x Using the exceptionRenderer Option of the Default Handler If you don’t want to take control of the exception handling, but want to change how exceptions are rendered you can use the exceptionRenderer option in config/app.php to choose a class that will render exception pages. By default Cake\Core\Exception\ExceptionRenderer is used. Your custom exception renderer class should be placed in src/Error. In a custom exception rendering class you can provide specialized handling for application specific errors: // In src/Error/AppExceptionRenderer.php namespace App\Error; use Cake\Error\ExceptionRenderer; class AppExceptionRenderer extends ExceptionRenderer { public function missingWidget($error) { return ’Oops that widget is missing!’; } } The above would handle any exceptions of the type MissingWidgetException, and allow you to provide custom display/handling logic for those application exceptions. Exception handling methods get the exception being handled as their argument. Your custom exception rendering can return either a string or a Response object. By returning a Response you can take full control over the entire response. Note: Your custom renderer should expect an exception in its constructor, and implement a render method. Failing to do so will cause additional errors. If you are using a custom exception handling this setting will have no effect. Unless you reference it inside your implementation. Creating a Custom Controller to Handle Exceptions In your ExceptionRenderer sub-class, you can use the _getController method to allow you to return a custom controller to handle your errors. By default CakePHP uses ErrorController which omits a few of the normal callbacks to help ensure errors always display. However, you may need a more custom error handling controller in your application. By implementing _getController in your AppExceptionRenderer class, you can use any controller you want: namespace App\Error; use App\Controller\SuperCustomErrorController; use Cake\Error\ExceptionRenderer; class AppExceptionRenderer extends ExceptionRenderer { protected function _getController($exception) { return new SuperCustomErrorController(); } } Alternatively, 176 you could just override the core ErrorController, by including one in Capítulo 11. Desenvolvimento CakePHP Cookbook Documentation, Release 3.x src/Controller. If you are using a custom controller for error handling, make sure you do all the setup you need in your constructor, or the render method. As those are the only methods that the built-in ErrorHandler class directly call. Logging Exceptions Using the built-in exception handling, you can log all the exceptions that are dealt with by ErrorHandler by setting the log option to true in your config/app.php. Enabling this will log every exception to Cake\Log\Log and the configured loggers. Note: If you are using a custom exception handler this setting will have no effect. Unless you reference it inside your implementation. Debugging Debugging is an inevitable and necessary part of any development cycle. While CakePHP doesn’t offer any tools that directly connect with any IDE or editor, CakePHP does provide several tools to assist in debugging and exposing what is running under the hood of your application. Basic Debugging debug(mixed $var, boolean $showHtml = null, $showFrom = true) The debug() function is a globally available function that works similarly to the PHP function print\_r(). The debug() function allows you to show the contents of a variable in a number of different ways. First, if you’d like data to be shown in an HTML-friendly way, set the second parameter to true. The function also prints out the line and file it is originating from by default. Output from this function is only shown if the core $debug variable has been set to true. The stackTrace() function is available globally, and allows you to output a stack trace whereever the function is called. Using the Debugger Class class Cake\Error\Debugger To use the debugger, first ensure that Configure::read(’debug’) is set to true. Outputting Values static Cake\Error\Debugger::dump($var, $depth = 3) Dump prints out the contents of a variable. It will print out all properties and methods (if any) of the supplied variable: Debugging 177 CakePHP Cookbook Documentation, Release 3.x $foo = array(1,2,3); Debugger::dump($foo); // Outputs array( 1, 2, 3 ) // Simple object $car = new Car(); Debugger::dump($car); // Outputs object(Car) { color => ’red’ make => ’Toyota’ model => ’Camry’ mileage => (int)15000 } Logging With Stack Traces static Cake\Error\Debugger::log($var, $level = 7, $depth = 3) Creates a detailed stack trace log at the time of invocation. The log() method prints out data similar to that done by Debugger::dump(), but to the debug.log instead of the output buffer. Note your tmp directory (and its contents) must be writable by the web server for log() to work correctly. Generating Stack Traces static Cake\Error\Debugger::trace($options) Returns the current stack trace. Each line of the trace includes the calling method, including which file and line the call originated from: // In PostsController::index() pr(Debugger::trace()); // Outputs PostsController::index() - APP/Controller/DownloadsController.php, line 48 Dispatcher::_invoke() - CORE/src/Routing/Dispatcher.php, line 265 Dispatcher::dispatch() - CORE/src/Routing/Dispatcher.php, line 237 [main] - APP/webroot/index.php, line 84 Above is the stack trace generated by calling Debugger::trace() in a controller action. Reading the stack trace bottom to top shows the order of currently running functions (stack frames). 178 Capítulo 11. Desenvolvimento CakePHP Cookbook Documentation, Release 3.x Getting an Excerpt From a File static Cake\Error\Debugger::excerpt($file, $line, $context) Grab an excerpt from the file at $path (which is an absolute filepath), highlights line number $line with $context number of lines around it.: pr(Debugger::excerpt(ROOT . DS . LIBS . ’debugger.php’, 321, 2)); // Will Array ( [0] [1] [2] output the following. => <code><span style="color: #000000"> * @access public</span></code> => <code><span style="color: #000000"> */</span></code> => <code><span style="color: #000000"> function excerpt($file, $line, $context = [3] => <span class="code-highlight"><code><span style="color: #000000"> $data = [4] => <code><span style="color: #000000"> $data = @explode("\n", file_get_conte ) Although this method is used internally, it can be handy if you’re creating your own error messages or log entries for custom situations. static Cake\Error\Debugger::getType($var) Get the type of a variable. Objects will return their class name Using Logging to Debug Logging messages is another good way to debug applications, and you can use Cake\Log\Log to do logging in your application. All objects that use LogTrait have an instance method log() which can be used to log messages: $this->log(’Got here’, ’debug’); The above would write Got here into the debug log. You can use log entries to help debug methods that involve redirects or complicated loops. You can also use Cake\Log\Log::write() to write log messages. This method can be called statically anywhere in your application one CakeLog has been loaded: // At the top of the file you want to log in. use Cake\Log\Log; // Anywhere that Log has been imported. Log::debug(’Got here’); Debug Kit DebugKit is a plugin that provides a number of good debugging tools. It primarily provides a toolbar in the rendered HTML, that provides a plethora of information about your application and the current request. You can download DebugKit2 from GitHub. 2 https://github.com/cakephp/debug_kit Debugging 179 CakePHP Cookbook Documentation, Release 3.x Testing CakePHP comes with comprehensive testing support built-in. CakePHP comes with integration for PHPUnit3 . In addition to the features offered by PHPUnit, CakePHP offers some additional features to make testing easier. This section will cover installing PHPUnit, and getting started with Unit Testing, and how you can use the extensions that CakePHP offers. Installing PHPUnit CakePHP uses PHPUnit as its underlying test framework. PHPUnit is the de-facto standard for unit testing in PHP. It offers a deep and powerful set of features for making sure your code does what you think it does. PHPUnit can be installed through using either a PHAR package4 or Composer5 . Install PHPUnit with Composer To install PHPUnit with Composer, add the following to you application’s require section in its composer.json: "phpunit/phpunit": "*", After updating your package.json, run Composer again inside your application directory: $ php composer.phar install You can now run PHPUnit using: $ bin/phpunit Using the PHAR File After you have downloaded the phpunit.phar file, you can use it to run your tests: php phpunit.phar Test Database Setup Remember to have a debug level of at least 1 in your config/app.php file before running any tests. Tests are not accessible via the web runner when debug is equal to 0. Before running any tests you should be sure to add a test datasource configuration to config/app.php. This configuration is used by CakePHP for fixture tables and data: 3 http://phpunit.de http://phpunit.de/#download 5 http://getcomposer.org 4 180 Capítulo 11. Desenvolvimento CakePHP Cookbook Documentation, Release 3.x ’Datasources’ => [ ’test’ => [ ’datasource’ => ’Cake\Database\Driver\Mysql’, ’persistent’ => false, ’host’ => ’dbhost’, ’login’ => ’dblogin’, ’password’ => ’dbpassword’, ’database’ => ’test_database’ ], ], Note: It’s a good idea to make the test database and your actual database different databases. This will prevent embarrassing mistakes later. Checking the Test Setup After installing PHPUnit and setting up your test datasource configuration you can make sure you’re ready to write and run your own tests by running your application’s tests: // For system wide PHPUnit $ phpunit // For phpunit.phar $ php phpunit.phar // For Composer installed phpunit $ bin/phpunit The above should run any tests you have, or let you know that no tests were run. To run a specific test you can supply the path to the test as a parameter to PHPUnit. For example, if you had a test case for ArticlesTable class you could run it with: $ phpunit tests/TestCase/Model/Table/ArticlesTableTest You should see a green bar with some additional information about the tests run, and number passed. Note: If you are on a windows system you probably won’t see any colours. Test Case Conventions Like most things in CakePHP, test cases have some conventions. Concerning tests: 1. PHP files containing tests should be in your tests/TestCase/[Type] directories. 2. The filenames of these files should end in Test.php instead of just .php. 3. The classes containing tests should extend Cake\TestSuite\TestCase, Cake\TestSuite\IntegrationTestCase or \PHPUnit_Framework_TestCase. Testing 181 CakePHP Cookbook Documentation, Release 3.x 4. Like other classnames, the test case classnames should match the filename. RouterTest.php should contain class RouterTest extends TestCase. 5. The name of any method containing a test (i.e. containing an assertion) should begin with test, as in testPublished(). You can also use the @test annotation to mark methods as test methods. Creating Your First Test Case In the following example, we’ll create a test case for a very simple helper method. The helper we’re going to test will be formatting progress bar HTML. Our helper looks like: namespace App\View\Helper; class ProgressHelper extends AppHelper { public function bar($value) { $width = round($value / 100, 2) * 100; return sprintf( ’<div class="progress-container"> <div class="progress-bar" style="width: %s%%"></div> </div>’, $width); } } This is a very simple example, but it will be useful to show how you can create a simple test case. After creating and saving our helper, we’ll create the test case file in tests/TestCase/View/Helper/ProgressHelperTest.php. In that file we’ll start with the following: namespace App\Test\TestCase\View\Helper; use App\View\Helper\ProgressHelper; use Cake\TestSuite\TestCase; use Cake\View\View; class ProgressHelperTest extends TestCase { public function setUp() { } public function testBar() { } } We’ll flesh out this skeleton in a minute. We’ve added two methods to start with. First is setUp(). This method is called before every test method in a test case class. Setup methods should initialize the objects needed for the test, and do any configuration needed. In our setup method we’ll add the following: public function setUp() { parent::setUp(); $View = new View(); 182 Capítulo 11. Desenvolvimento CakePHP Cookbook Documentation, Release 3.x $this->Progress = new ProgressHelper($View); } Calling the parent method is important in test cases, as TestCase::setUp() does a number things like backing up the values in Core\Configure and, storing the paths in Core\App. Next, we’ll fill out the test method. We’ll use some assertions to ensure that our code creates the output we expect: public function testBar() { $result = $this->Progress->bar(90); $this->assertContains(’width: 90%’, $result); $this->assertContains(’progress-bar’, $result); $result = $this->Progress->bar(33.3333333); $this->assertContains(’width: 33%’, $result); } The above test is a simple one but shows the potential benefit of using test cases. We use assertContains() to ensure that our helper is returning a string that contains the content we expect. If the result did not contain the expected content the test would fail, and we would know that our code is incorrect. By using test cases you can easily describe the relationship between a set of known inputs and their expected output. This helps you be more confident of the code you’re writing as you can easily check that the code you wrote fulfills the expectations and assertions your tests make. Additionally because tests are code, they are easy to re-run whenever you make a change. This helps prevent the creation of new bugs. Running Tests Once you have PHPUnit installed and some test cases written, you’ll want to run the test cases very frequently. It’s a good idea to run tests before committing any changes to help ensure you haven’t broken anything. By using phpunit you can run your application and plugin tests. To run your application’s tests you can simply run: $ phpunit From your application’s root directory. To run a plugin’s tests, first cd into the plugin directory, then use phpunit to run the tests. Filtering Test Cases When you have larger test cases, you will often want to run a subset of the test methods when you are trying to work on a single failing case. With the CLI runner you can use an option to filter test methods: $ phpunit --filter testSave tests/TestCase/Model/Table/ArticlesTableTest The filter parameter is used as a case-sensitive regular expression for filtering which test methods to run. Testing 183 CakePHP Cookbook Documentation, Release 3.x Generating Code Coverage You can generate code coverage reports from the command line using PHPUnit’s built-in code coverage tools. PHPUnit will generate a set of static HTML files containing the coverage results. You can generate coverage for a test case by doing the following: $ phpunit --coverage-html webroot/coverage tests/TestCase/Model/Table/ArticlesTableTest This will put the coverage results in your application’s webroot directory. You should be able to view the results by going to http://localhost/your_app/coverage. Combining Test Suites for Plugins Often times your application will be composed of several plugins. In these situations it can be pretty tedious to run tests for each plugin. You can make running tests for each of the plugins that compose your application by adding additional <testsuite> sections to your application’s phpunit.xml file: <testsuites> <testsuite name="App Test Suite"> <directory>./tests/TestCase</directory> </testsuite> <!-- Add your plugin suites --> <testsuite name="Forum plugin"> <directory>./plugins/Forum/tests/TestCase</directory> </testsuite> </testsuites> Any additional test suites added to the <testsuites> element will automatically be run when you use phpunit. Test Case Lifecycle Callbacks Test cases have a number of lifecycle callbacks you can use when doing testing: • setUp is called before every test method. Should be used to create the objects that are going to be tested, and initialize any data for the test. Always remember to call parent::setUp() • tearDown is called after every test method. Should be used to cleanup after the test is complete. Always remember to call parent::tearDown(). • setupBeforeClass is called once before test methods in a case are started. This method must be static. • tearDownAfterClass is called once after test methods in a case are started. This method must be static. 184 Capítulo 11. Desenvolvimento CakePHP Cookbook Documentation, Release 3.x Fixtures When testing code that depends on models and the database, one can use fixtures as a way to generate temporary data tables loaded with sample data that can be used by the test. The benefit of using fixtures is that your test has no chance of disrupting live application data. In addition, you can begin testing your code prior to actually developing live content for an application. CakePHP uses the connection named test in your config/datasources.php configuration file. If this connection is not usable, an exception will be raised and you will not be able to use database fixtures. CakePHP performs the following during the course of a fixture based test case: 1. Creates tables for each of the fixtures needed. 2. Populates tables with data, if data is provided in fixture. 3. Runs test methods. 4. Empties the fixture tables. 5. Removes fixture tables from database. Test Connections By default CakePHP will alias each connection in your application. Each connection defined in your application’s bootstrap that does not start with test_ will have a test_ prefixed alias created. Aliasing connections ensures, you don’t accidentally use the wrong connection in test cases. Connection aliasing is transparent to the rest of your application. For example if you use the ‘default’ connection, instead you will get the test connection in test cases. If you use the ‘replica’ connection, the test suite will attempt to use ‘test_replica’. Creating Fixtures When creating a fixture you will mainly define two things: how the table is created (which fields are part of the table), and which records will be initially populated to the table. Let’s create our first fixture, that will be used to test our own Article model. Create a file named ArticlesFixture.php in your tests/Fixture directory, with the following content: namespace App\Test\Fixture; use Cake\Test\TestFixture; class ArticlesFixture extends TestFixture { // Optional. Set this property to load fixtures to a different test datasource public $connection = ’test’; public $fields = [ ’id’ => [’type’ => ’integer’], ’title’ => [’type’ => ’string’, ’length’ => 255, ’null’ => false], ’body’ => ’text’, ’published’ => [’type’ => ’integer’, ’default’ => ’0’, ’null’ => false], Testing 185 CakePHP Cookbook Documentation, Release 3.x ’created’ => ’datetime’, ’updated’ => ’datetime’, ’_constraints’ => [ ’primary’ => [’type’ => ’primary’, ’columns’ => [’id’]] ] ]; public $records = [ [ ’id’ => 1, ’title’ => ’First Article’, ’body’ => ’First Article Body’, ’published’ => ’1’, ’created’ => ’2007-03-18 10:39:23’, ’updated’ => ’2007-03-18 10:41:31’ ], [ ’id’ => 2, ’title’ => ’Second Article’, ’body’ => ’Second Article Body’, ’published’ => ’1’, ’created’ => ’2007-03-18 10:41:23’, ’updated’ => ’2007-03-18 10:43:31’ ], [ ’id’ => 3, ’title’ => ’Third Article’, ’body’ => ’Third Article Body’, ’published’ => ’1’, ’created’ => ’2007-03-18 10:43:23’, ’updated’ => ’2007-03-18 10:45:31’ ] ]; } The $connection property defines the datasource of which the fixture will use. If your application uses multiple datasources, you should make the fixtures match the model’s datasources but prefixed with test_. For example if your model uses the mydb datasource, your fixture should use the test_mydb datasource. If the test_mydb connection doesn’t exist, your models will use the default test datasource. Fixture datasources must be prefixed with test to reduce the possibility of accidentally truncating all your application’s data when running tests. We use $fields to specify which fields will be part of this table, and how they are defined. The format used to define these fields is the same used with Cake\Database\Schema\Table. The keys available for table definition are: type CakePHP internal data type. Currently supported: • string: maps to VARCHAR or CHAR • uuid: maps to UUID • text: maps to TEXT • integer: maps to INT 186 Capítulo 11. Desenvolvimento CakePHP Cookbook Documentation, Release 3.x • biginteger: maps to BIGINTEGER • decimal: maps to DECIMAL • float: maps to FLOAT • datetime: maps to DATETIME • timestamp: maps to TIMESTAMP • time: maps to TIME • date: maps to DATE • binary: maps to BLOB fixed Used with string types to create CHAR columns in platforms that support them. length Set to the specific length the field should take. precision Set the number of decimal places used on float & decimal fields. null Set to either true (to allow NULLs) or false (to disallow NULLs). default Default value the field takes. We can define a set of records that will be populated after the fixture table is created. The format is fairly straight forward, $records is an array of records. Each item in $records should be a single row. Inside each row, should be an associative array of the columns and values for the row. Just keep in mind that each record in the $records array must have a key for every field specified in the $fields array. If a field for a particular record needs to have a null value, just specify the value of that key as null. Dynamic Data and Fixtures Since records for a fixture are declared as a class property, you cannot easily use functions or other dynamic data to define fixtures. To solve this problem, you can define $records in the init() function of your fixture. For example if you wanted all the created and updated timestamps to reflect today’s date you could do the following: namespace App\Test\Fixture; use Cake\TestSuite\Fixture\TestFixture; class ArticlesFixture extends TestFixture { public $fields = [ ’id’ => [’type’ => ’integer’], ’title’ => [’type’ => ’string’, ’length’ => 255, ’null’ => false], ’body’ => ’text’, ’published’ => [’type’ => ’integer’, ’default’ => ’0’, ’null’ => false], ’created’ => ’datetime’, ’updated’ => ’datetime’, ’_constraints’ => [ ’primary’ => [’type’ => ’primary’, ’columns’ => [’id’]], ] ]; Testing 187 CakePHP Cookbook Documentation, Release 3.x public function init() { $this->records = [ [ ’id’ => 1, ’title’ => ’First Article’, ’body’ => ’First Article Body’, ’published’ => ’1’, ’created’ => date(’Y-m-d H:i:s’), ’updated’ => date(’Y-m-d H:i:s’), ], ]; parent::init(); } } When overriding init() remember to always call parent::init(). Importing Table Information Defining the schema in fixture files can be really handy when creating plugins or libraries or if you are creating an application that needs to easily be portable. Redefining the schema in fixtures can become difficult to maintain in larger applications. Because of this CakePHP provides the ability to import the schema from an existing connection and use the reflected table definition to create the table definition used in the test suite. Let’s start with an example. Assuming you have a table named articles available in your application, change the example fixture given in the previous section (tests/Fixture/ArticlesFixture.php) to: class ArticlesFixture extends TestFixture { public $import = [’table’ => ’articles’] } If you want to use a different connection use: class ArticlesFixture extends TestFixture { public $import = [’table’ => ’articles’, ’connection’ => ’other’]; } You can naturally import your table definition from an existing model/table, but have your records defined directly on the fixture as it was shown on previous section. For example: class ArticlesFixture extends TestFixture { public $import = [’table’ => ’articles’]; public $records = [ [ ’id’ => 1, ’title’ => ’First Article’, ’body’ => ’First Article Body’, ’published’ => ’1’, ’created’ => ’2007-03-18 10:39:23’, ’updated’ => ’2007-03-18 10:41:31’ ], 188 Capítulo 11. Desenvolvimento CakePHP Cookbook Documentation, Release 3.x [ ’id’ => 2, ’title’ => ’Second Article’, ’body’ => ’Second Article Body’, ’published’ => ’1’, ’created’ => ’2007-03-18 10:41:23’, ’updated’ => ’2007-03-18 10:43:31’ ], [ ’id’ => 3, ’title’ => ’Third Article’, ’body’ => ’Third Article Body’, ’published’ => ’1’, ’created’ => ’2007-03-18 10:43:23’, ’updated’ => ’2007-03-18 10:45:31’ ] ]; } Finally, you can not load/create any schema in a fixture. This is useful if you already have a test database setup with all the empty tables created. By defining neither $fields or $import a fixture will only insert its records and truncate the records on each test method. Loading Fixtures in your Test Cases After you’ve created your fixtures, you’ll want to use them in your test cases. In each test case you should load the fixtures you will need. You should load a fixture for every model that will have a query run against it. To load fixtures you define the $fixtures property in your model: class ArticlesTest extends TestCase { public $fixtures = [’app.articles’, ’app.comments’]; } The above will load the Article and Comment fixtures from the application’s Fixture directory. You can also load fixtures from CakePHP core, or plugins: class ArticlesTest extends TestCase { public $fixtures = [’plugin.debug_kit.articles’, ’core.comments’]; } Using the core prefix will load fixtures from CakePHP, and using a plugin name as the prefix, will load the fixture from the named plugin. You can control when your fixtures are loaded by setting Cake\TestSuite\TestCase::$autoFixtures to false and later load them using Cake\TestSuite\TestCase::loadFixtures(): class ArticlesTest extends TestCase { public $fixtures = [’app.articles’, ’app.comments’]; public $autoFixtures = false; public function testMyFunction() { $this->loadFixtures(’Article’, ’Comment’); Testing 189 CakePHP Cookbook Documentation, Release 3.x } } You can load fixtures in subdirectories. Using multiple directories can make it easier to organize your fixtures if you have a larger application. To load fixtures in subdirectories, simply include the subdirectory name in the fixture name: class ArticlesTest extends CakeTestCase { public $fixtures = [’app.blog/articles’, ’app.blog/comments’]; } In the above example, both fixtures would be loaded from tests/Fixture/blog/. Testing Tables Let’s say we already have our Articles src/Model/Table/ArticlesTable.php, and it looks like: Table class defined in namespace App\Model\Table; use Cake\ORM\Table; use Cake\ORM\Query; class ArticlesTable extends Table { public function findPublished(Query $query, array $options) { $query->where([ $this->alias() . ’.published’ => 1 ]); return $query; } } We now want to set up a test that will test this table class. Let’s now create a file named ArticlesTableTest.php in your tests/TestCase/Model/Table directory, with the following contents: namespace App\Test\TestCase\Model\Table; use Cake\ORM\TableRegistry; use Cake\TestSuite\TestCase; class ArticleTest extends TestCase { public $fixtures = [’app.articles’]; } In our test cases’ variable $fixtures we define the set of fixtures that we’ll use. You should remember to include all the fixtures that will have queries run against them. 190 Capítulo 11. Desenvolvimento CakePHP Cookbook Documentation, Release 3.x Creating a Test Method Let’s now add a method to test the function published() in the Article model. Edit the file tests/TestCase/Model/Table/ArticlesTableTest.php so it now looks like this: namespace App\Test\TestCase\Model\Table; use Cake\ORM\TableRegistry; use Cake\TestSuite\TestCase; class ArticleTest extends TestCase { public $fixtures = [’app.articles’]; public function setUp() { parent::setUp(); $this->Articles = TableRegistry::get(’Articles’); } public function testFindPublished() { $query = $this->Articles->find(’published’); $this->assertInstanceOf(’Cake\ORM\Query’, $query); $result = $query->hydrate(false)->toArray(); $expected = [ [’id’ => 1, ’title’ => ’First Article’], [’id’ => 2, ’title’ => ’Second Article’], [’id’ => 3, ’title’ => ’Third Article’] ]; $this->assertEquals($expected, $result); } } You can see we have added a method called testPublished(). We start by creating an instance of our ArticlesTable class, and then run our find(’published’) method. In $expected we set what we expect should be the proper result (that we know since we have defined which records are initially populated to the article table.) We test that the result equals our expectation by using the assertEquals method. See the Running Tests section for more information on how to run your test case. Mocking Model Methods There will be times you’ll want to mock methods on models when testing them. You should use getMockForModel to create testing mocks of table classes. It avoids issues with reflected properties that normal mocks have: public function testSendingEmails() { $model = $this->getMockForModel(’EmailVerification’, [’send’]); $model->expects($this->once()) ->method(’send’) ->will($this->returnValue(true)); $model->verifyEmail(’[email protected]’); } Testing 191 CakePHP Cookbook Documentation, Release 3.x Controller Integration Testing While you can test controller classes in a similar fashion to Helpers, Models, and Components, CakePHP offers a specialized IntegrationTestCase class. Using this class as the base class for your controller test cases allows you to more easily do integration testing with your controllers. If you are unfamiliar with integration testing, it is a testing approach that makes it easy to test multiple units in concert. The integration testing features in CakePHP simulate an HTTP request being handled by your application. For example, testing your controller will also exercise any components, models and helpers that would be involved in handling a given request. This gives you a more high level test of your application and all its working parts. Say you have a typical Articles controller, and its corresponding model. The controller code looks like: namespace App\Controller; use App\Controller\AppController; class ArticlesController extends AppController { public $helpers = [’Form’, ’Html’]; public function index($short = null) { if (!empty($this->request->data)) { $article = $this->Articles->newEntity($this->request->data); $this->Articles->save($article); } if (!empty($short)) { $result = $this->Article->find(’all’, [ ’fields’ => [’id’, ’title’] ]); } else { $result = $this->Article->find(); } $this->set([ ’title’ => ’Articles’, ’articles’ => $result ]); } } Create a file named ArticlesControllerTest.php in your tests/TestCase/Controller directory and put the following inside: namespace App\Test\TestCase\Controller; use Cake\ORM\TableRegistry; use Cake\TestSuite\IntegrationTestCase; class ArticlesControllerTest extends IntegrationTestCase { public $fixtures = [’app.articles’]; public function testIndex() { $this->get(’/articles?page=1’); 192 Capítulo 11. Desenvolvimento CakePHP Cookbook Documentation, Release 3.x $this->assertResponseOk(); // More asserts. } public function testIndexQueryData() { $this->get(’/articles?page=1’); $this->assertResponseOk(); // More asserts. } public function testIndexShort() { $this->get(’/articles/index/short’); $this->assertResponseOk(); $this->assertResponseContains(’Articles’); // More asserts. } public function testIndexPostData() { $data = [ ’user_id’ => 1, ’published’ => 1, ’slug’ => ’new-article’, ’title’ => ’New Article’, ’body’ => ’New Body’ ]; $this->post(’/articles/add’, $data); $this->assertResponseOk(); $articles = TableRegistry::get(’Articles’); $query = $articles->find()->where([’title’ => $data[’title’]]); $this->assertEquals(1, $query->count()); } } This example shows a few of the request sending methods and a few of the assertions that IntegrationTestCase provides. Before you can do any assertions you’ll need to dispatch a request. You can use one of the following methods to send a request: • get() Sends a GET request. • post() Sends a POST request. • put() Sends a PUT request. • delete() Sends a DELETE request. • patch() Sends a PATCH request. All of the methods except get() and delete() accept a second parameter that allows you to send a request body. After dispatching a request you can use the various assertions provided by IntegrationTestCase or by PHPUnit to ensure your request had the correct side-effects. Testing 193 CakePHP Cookbook Documentation, Release 3.x Assertion methods The IntegrationTestCase class provides a number of assertion methods that make testing responses much simpler. Some examples are: // Check for a 2xx response code $this->assertResponseOk(); // Check for a 4xx response code $this->assertResponseError(); // Check for a 5xx response code $this->assertResponseFailure(); // Check the Location header $this->assertRedirect([’controller’ => ’articles’, ’action’ => ’index’]); // Assert content in the response $this->assertResponseContains(’You won!’); // Assert layout $this->assertLayout(’default’); // Assert which template was rendered (if any) $this->assertTemplate(’index’); // Assert data in the session $this->assertSession(1, ’Auth.User.id’); // Assert response header. $this->assertHeader(’Content-Type’, ’application/json’); // Assert view variables $this->assertEquals(’jose’, $this->viewVariable(’user.username’)); // Assert cookies in the response $this->assertEquals(’1’, $this->cookies()); Testing a JSON Responding Controller JSON is a friendly and common format to use when building a web service. Testing the endpoints of your web service is very simple with CakePHP. Let us begin with a simple example controller that responds in JSON: class MarkersController extends AppController { public $components = [’RequestHandler’]; public function view($id) { $marker = $this->Markers->get($id); $this->set([ ’_serialize’ => [’marker’], ’marker’ => $marker, 194 Capítulo 11. Desenvolvimento CakePHP Cookbook Documentation, Release 3.x ]); } } Now we create the file tests/TestCase/Controller/MarkersControllerTest.php and make sure our web service is returning the proper response: class MarkersControllerTest extends IntegrationTestCase { public function testGet() { $this->configRequest([ ’headers’ => [’Accept’ => ’application/json’] ]); $result = $this->get(’/markers/view/1.json’); // Check that the response was a 200 $this->assertResponseOk(); $expected = [ [’id’ => 1, ’lng’ => 66, ’lat’ => 45], ]; $expected = json_encode($expected, JSON_PRETTY_PRINT); $this->assertEquals($expected, $this->_response->body()); } } We use the JSON_PRETTY_PRINT option as CakePHP’s built in JsonView will use that option when debug is enabled. Testing Views Generally most applications will not directly test their HTML code. Doing so is often results in fragile, difficult to maintain test suites that are prone to breaking. When writing functional tests using IntegrationTestCase you can inspect the rendered view content by setting the return option to ‘view’. While it is possible to test view content using IntegrationTestCase, a more robust and maintable integration/view testing can be accomplished using tools like Selenium webdriver6 . Testing Components Let’s pretend we have a component called PagematronComponent in our application. This component helps us set the pagination limit value across all the controllers that use it. Here is our example component located in app/Controller/Component/PagematronComponent.php: class PagematronComponent extends Component { public $controller = null; public function setController($controller) { $this->controller = $controller; 6 http://seleniumhq.org Testing 195 CakePHP Cookbook Documentation, Release 3.x // Make sure the controller is using pagination if (!isset($this->controller->paginate)) { $this->controller->paginate = []; } } public function startup(Event $event) { $this->setController($event->subject()); } public function adjust($length = ’short’) { switch ($length) { case ’long’: $this->controller->paginate[’limit’] = 100; break; case ’medium’: $this->controller->paginate[’limit’] = 50; break; default: $this->controller->paginate[’limit’] = 20; break; } } } Now we can write tests to ensure our paginate limit parameter is being set correctly by the adjust method in our component. We create the file tests/TestCase/Controller/Component/PagematronComponentTest.php: namespace App\Test\TestCase\Controller\Component; use use use use use App\Controller\Component\PagematronComponent; Cake\Controller\Controller; Cake\Controller\ComponentCollection; Cake\Network\Request; Cake\Network\Response; class PagematronComponentTest extends TestCase { public $component = null; public $controller = null; public function setUp() { parent::setUp(); // Setup our component and fake test controller $collection = new ComponentCollection(); $this->component = new PagematronComponent($collection); $request = new Request(); $response = new Response(); $this->controller = $this->getMock( ’Cake\Controller\Controller’, [], 196 Capítulo 11. Desenvolvimento CakePHP Cookbook Documentation, Release 3.x [$request, $response] ); $this->component->setController($this->controller); } public function testAdjust() { // Test our adjust method with different parameter settings $this->component->adjust(); $this->assertEquals(20, $this->controller->paginate[’limit’]); $this->component->adjust(’medium’); $this->assertEquals(50, $this->controller->paginate[’limit’]); $this->component->adjust(’long’); $this->assertEquals(100, $this->controller->paginate[’limit’]); } public function tearDown() { parent::tearDown(); // Clean up after we’re done unset($this->component, $this->controller); } } Testing Helpers Since a decent amount of logic resides in Helper classes, it’s important to make sure those classes are covered by test cases. First we create an example helper to test. The CurrencyRendererHelper will help us display currencies in our views and for simplicity only has one method usd(): // src/View/Helper/CurrencyRendererHelper.php namespace App\View\Helper; use Cake\View\Helper; class CurrencyRendererHelper extends Helper { public function usd($amount) { return ’USD ’ . number_format($amount, 2, ’.’, ’,’); } } Here we set the decimal places to 2, decimal separator to dot, thousands separator to comma, and prefix the formatted number with ‘USD’ string. Now we create our tests: // tests/TestCase/View/Helper/CurrencyRendererHelperTest.php namespace App\Test\TestCase\View\Helper; Testing 197 CakePHP Cookbook Documentation, Release 3.x use App\View\Helper\CurrencyRendererHelper; use Cake\TestSuite\TestCase; use Cake\View\View; class CurrencyRendererHelperTest extends TestCase { public $helper = null; // Here we instantiate our helper public function setUp() { parent::setUp(); $view = new View(); $this->helper = new CurrencyRendererHelper($view); } // Testing the usd() function public function testUsd() { $this->assertEquals(’USD 5.30’, $this->helper->usd(5.30)); // We should always have 2 decimal digits $this->assertEquals(’USD 1.00’, $this->helper->usd(1)); $this->assertEquals(’USD 2.05’, $this->helper->usd(2.05)); // Testing the thousands separator $this->assertEquals( ’USD 12,000.70’, $this->helper->usd(12000.70) ); } } Here, we call usd() with different parameters and tell the test suite to check if the returned values are equal to what is expected. Save this and execute the test. You should see a green bar and messaging indicating 1 pass and 4 assertions. Creating Test Suites If you want several of your tests to run at the same time, you can create a test suite. A test suite is composed of several test cases. You can either create test suites in your application’s phpunit.xml file, or by creating suite classes using CakeTestSuite. Using phpunit.xml is good when you only need simple include/exclude rules to define your test suite. A simple example would be <testsuites> <testsuite name="Models"> <directory>src/Model</directory> <file>src/Service/UserServiceTest.php</file> <exclude>src/Model/Cloud/ImagesTest.php</exclude> </testsuite> </testsuites> CakeTestSuite offers several methods for easily creating test suites based on the file system. It allows 198 Capítulo 11. Desenvolvimento CakePHP Cookbook Documentation, Release 3.x you to run any code you want to prepare your test suite. If we wanted to create a test suite for all our model tests we could would create tests/TestCase/AllModelTest.php. Put the following in it: class AllModelTest extends TestSuite { public static function suite() { $suite = new CakeTestSuite(’All model tests’); $suite->addTestDirectory(TESTS . ’Case/Model’); return $suite; } } The code above will group all test cases found in the tests/TestCase/Model/ folder. To add an individual file, use $suite->addTestFile($filename);. You can recursively add a directory for all tests using: $suite->addTestDirectoryRecursive(TESTS . ’TestCase’); Would recursively add all test cases in the tests/TestCase/ directory. Creating Tests for Plugins Tests for plugins are created in their own directory inside the plugins folder.: /app /Plugin /Blog /Test /TestCase /Fixture They work just like normal tests but you have to remember to use the naming conventions for plugins when importing classes. This is an example of a testcase for the BlogPost model from the plugins chapter of this manual. A difference from other tests is in the first line where ‘Blog.BlogPost’ is imported. You also need to prefix your plugin fixtures with plugin.blog.blog_posts: namespace Blog\Test\TestCase\Model; use Blog\Model\BlogPost; use Cake\TestSuite\TestCase; class BlogPostTest extends TestCase { // Plugin fixtures located in /plugins/Blog/tests/Fixture/ public $fixtures = [’plugin.blog.blog_posts’]; public $BlogPost; public function testSomething() { // Test something. } } If you want to use plugin fixtures in the app tests you can plugin.pluginName.fixtureName syntax in the $fixtures array. Testing reference them using 199 CakePHP Cookbook Documentation, Release 3.x Generating Tests with Bake If you use bake to generate scaffolding, it will also generate test stubs. If you need to re-generate test case skeletons, or if you want to generate test skeletons for code you wrote, you can use bake: bin/cake bake test <type> <name> <type> should be one of: 1. Entity 2. Table 3. Controller 4. Component 5. Behavior 6. Helper 7. Shell 8. Cell While <name> should be the name of the object you want to bake a test skeleton for. Integration with Jenkins Jenkins7 is a continuous integration server, that can help you automate the running of your test cases. This helps ensure that all your tests stay passing and your application is always ready. Integrating a CakePHP application with Jenkins is fairly straightforward. The following assumes you’ve already installed Jenkins on *nix system, and are able to administer it. You also know how to create jobs, and run builds. If you are unsure of any of these, refer to the Jenkins documentation8 . Create a Job Start off by creating a job for your application, and connect your repository so that jenkins can access your code. Add Test Database Config Using a separate database just for Jenkins is generally a good idea, as it stops bleed through and avoids a number of basic problems. Once you’ve created a new database in a database server that jenkins can access (usually localhost). Add a shell script step to the build that contains the following: 7 8 http://jenkins-ci.org http://jenkins-ci.org/ 200 Capítulo 11. Desenvolvimento CakePHP Cookbook Documentation, Release 3.x cat > config/app_local.php <<’CONFIG’ <?php $config = [ ’Datasources’ => [ ’test’ => [ ’datasource’ => ’Database/Mysql’, ’host’ => ’localhost’, ’database’ => ’jenkins_test’, ’login’ => ’jenkins’, ’password’ => ’cakephp_jenkins’, ’encoding’ => ’utf8’ ] ] ]; CONFIG Then uncomment the following line in your config/bootstrap.php file: //Configure::load(’app_local.php’, ’default’); By creating an app_local.php file, you have an easy way to define configuration specific to Jenkins. You can use this same configuration file to override any other configuration files you need on Jenkins. It’s often a good idea to drop and re-create the database before each build as well. This insulates you from chained failures, where one broken build causes others to fail. Add another shell script step to the build that contains the following: mysql -u jenkins -pcakephp_jenkins -e ’DROP DATABASE IF EXISTS jenkins_test; CREATE DATABAS Add your Tests Add another shell script step to your build. In this step install your dependencies and run the tests for your application. Creating a junit log file, or clover coverage is often a nice bonus, as it gives you a nice graphical view of your testing results: # Download Composer if it is missing. test -f ’composer.phar’ || curl -sS https://getcomposer.org/installer| php # Install dependencies php composer.phar install bin/phpunit --log-junit junit.xml --coverage-clover clover.xml If you use clover coverage, or the junit results, make sure to configure those in Jenkins as well. Failing to configure those steps will mean you won’t see the results. Run a Build You should be able to run a build now. Check the console output and make any necessary changes to get a passing build. Testing 201 CakePHP Cookbook Documentation, Release 3.x REST Many newer application programmers are realizing the need to open their core functionality to a greater audience. Providing easy, unfettered access to your core API can help get your platform accepted, and allows for mashups and easy integration with other systems. While other solutions exist, REST is a great way to provide easy access to the logic you’ve created in your application. It’s simple, usually XML-based (we’re talking simple XML, nothing like a SOAP envelope), and depends on HTTP headers for direction. Exposing an API via REST in CakePHP is simple. The Simple Setup The fastest way to get up and running with REST is to add a few lines to setup resource routes in your config/routes.php file. Once the router has been set up to map REST requests to certain controller actions, we can move on to creating the logic in our controller actions. A basic controller might look something like this: // src/Controller/RecipesController.php class RecipesController extends AppController { public $components = array(’RequestHandler’); public function index() { $recipes = $this->Recipes->find(’all’); $this->set(array( ’recipes’ => $recipes, ’_serialize’ => array(’recipes’) )); } public function view($id) { $recipe = $this->Recipes->findById($id); $this->set(array( ’recipe’ => $recipe, ’_serialize’ => array(’recipe’) )); } public function edit($id) { $recipe = $this->Recipes->get($id); if ($this->request->is([’post’, ’put’])) { $this->Recipes->patchEntity($recipe, $this->request->data); if ($this->Recipes->save($this->request->data)) { $message = ’Saved’; } else { $message = ’Error’; } } $this->set(array( ’message’ => $message, ’_serialize’ => array(’message’) 202 Capítulo 11. Desenvolvimento CakePHP Cookbook Documentation, Release 3.x )); } public function delete($id) { $recipe = $this->Recipes->get($id); $message = ’Deleted’; if (!$this->Recipes->delete($recipe)) { $message = ’Error’; } $this->set(array( ’message’ => $message, ’_serialize’ => array(’message’) )); } } RESTful controllers often use parsed extensions to serve up different views based on different kinds of requests. Since we’re dealing with REST requests, we’ll be making XML views. You can also easily make JSON views using CakePHP’s built-in /views/json-and-xml-views. By using the built in XmlView we can define a _serialize view variable. This special view variable is used to define which view variables XmlView should serialize into XML. If we wanted to modify the data before it is converted into XML we should not define the _serialize view variable, and instead use view files. We place the REST views for our RecipesController inside src/Template/recipes/xml. We can also use the Xml for quick-and-easy XML output in those views. Here’s what our index view might look like: // app/View/Recipes/xml/index.ctp // Do some formatting and manipulation on // the $recipes array. $xml = Xml::fromArray(array(’response’ => $recipes)); echo $xml->asXML(); When serving up a specific content type using Cake\Routing\Router::extensions(), CakePHP automatically looks for a view helper that matches the type. Since we’re using XML as the content type, there is no built-in helper, however if you were to create one it would automatically be loaded for our use in those views. The rendered XML will end up looking something like this: <recipes> <recipe> <id>234</id> <created>2008-06-13</created> <modified>2008-06-14</modified> <author> <id>23423</id> <first_name>Billy</first_name> <last_name>Bob</last_name> </author> <comment> <id>245</id> <body>Yummy yummmy</body> REST 203 CakePHP Cookbook Documentation, Release 3.x </comment> </recipe> ... </recipes> Creating the logic for the edit action is a bit trickier, but not by much. Since you’re providing an API that outputs XML, it’s a natural choice to receive XML as input. Not to worry, the Cake\Controller\Component\RequestHandler and Cake\Routing\Router classes make things much easier. If a POST or PUT request has an XML content-type, then the input is run through CakePHP’s Xml class, and the array representation of the data is assigned to $this->request->data. Because of this feature, handling XML and POST data in parallel is seamless: no changes are required to the controller or model code. Everything you need should end up in $this->request->data. Accepting Input in Other Formats Typically REST applications not only output content in alternate data formats, but also accept data in different formats. In CakePHP, the RequestHandlerComponent helps facilitate this. By default, it will decode any incoming JSON/XML input data for POST/PUT requests and supply the array version of that data in $this->request->data. You can also wire in additional deserializers for alternate formats if you need them, using RequestHandler::addInputType(). Dispatcher Filters There are several reasons to want a piece of code to be run before any controller code is executed or right before the response is sent to the client, such as response caching, header tuning, special authentication or just to provide access to a mission-critical API response in lesser time than a complete request dispatching cycle would take. CakePHP provides a clean interface for attaching filters to the dispatch cycle. It is similar to a middleware layer, but re-uses the existing event subsystem used in other parts of CakePHP. Since they do not work exactly like traditional middleware, we refer to them as Dispatcher Filters. Built-in Filters CakePHP comes with several dispatcher filters built-in. They handle common features that all applications are likely to need. The built-in filters are: • AssetFilter checks whether the request is referring to a theme or plugin asset file, such as a CSS, JavaScript or image file stored in either a plugin’s webroot folder or the corresponding one for a Theme. It will serve the file accordingly if found, stopping the rest of the dispatching cycle. • CacheFilter when Cache.check config variable is enabled, will check if the response was already cached in the file system for a similar request and serve the cached code immediately. • RoutingFilter applies application routing $request->params with the results of routing. 204 rules to the request URL. Populates Capítulo 11. Desenvolvimento CakePHP Cookbook Documentation, Release 3.x • ControllerFactory uses $request->params to locate the controller that will handle the current request. Using Filters Filters are usually enabled in your application’s bootstrap.php file, but you could easily load them any time before the request is dispatched. Adding and removing filters is done through Cake\Routing\DispatcherFactory. By default, the CakePHP application template comes with a couple filter classes already enabled for all requests; let’s take a look at how they are added: DispatcherFactory::add(’Cache’); DispatcherFactory::add(’Routing’); DispatcherFactory::add(’ControllerFactory’); // Use options to set priority DispatcherFactory::add(’Asset’, [’priority’ => 1]); While using the string name is convenient, you can also pass instances into add(): use Cake\Routing\Filter\RoutingFilter; DispatcherFactory::add(new RoutingFilter()); Configuring Filter Order When adding filters, you can control the order they are invoked in using event handler priorities. While filters can define a default priority using the _priority property, you can set a specific priority when attaching the filter: DispatcherFactory::add(’Asset’, [’priority’ => 1]); DispatcherFactory::add(new AssetFilter([’priority’ => 1])); Conditionally Applying Filters If you don’t want to run a filter on every request, you can use conditions to only apply it some of the time. You can apply conditions using the for and when options. The for option lets you match on URL substrings, while the when option allows you to run a callable: // Only runs on requests starting with ‘/blog‘ DispatcherFactory::add(’BlogHeader’, [’for’ => ’/blog’]); // Only run on GET requests. DispatcherFactory::add(’Cache’, [ ’when’ => function ($request, $response) { return $request->is(’get’); } ]); The callable provided to when should return true when the filter should run. The callable can expect to get the current request and response as arguments. Dispatcher Filters 205 CakePHP Cookbook Documentation, Release 3.x Building a Filter To create a filter, define a class in src/Routing/Filter. In this example, we’ll be making a filter that adds a tracking cookie for the first landing page. First, create the file. Its contents should look like: namespace App\Routing\Filter; use Cake\Event\Event; use Cake\Routing\DispatcherFilter; class TrackingCookieFilter extends DispatcherFilter { public function beforeDispatch(Event $event) { $request = $event->data[’request’]; $response = $event->data[’response’]; if (!$request->cookie(’landing_page’)) { $response->cookie([ ’name’ => ’landing_page’, ’value’ => $request->here(), ’expire’ => ’+ 1 year’, ]); } } } Save this file into src/Routing/Filter/TrackingCookieFilter.php. As you can see, like other classes in CakePHP, dispatcher filters have a few conventions: • Class names end in Filter. • Classes are in the Routing\Filter namespace. For example, App\Routing\Filter. • Generally filters extend Cake\Routing\DispatcherFilter. DispatcherFilter exposes two methods that can be overridden in subclasses, they are beforeDispatch and afterDispatch. These methods are executed before or after any controller is executed respectively. Both methods receive a Cake\Event\Event object containing the request and response objects (Cake\Network\Request and Cake\Network\Response instances) inside the data property. While our filter was pretty simple, there are a few other interesting things we can do in filter methods. By returning an Response object, you can short-circuit the dispatch process and prevent the controller from being called. When returning a response, you should also remember to call $event->stopPropagation() so other filters are not called. Note: When a beforeDispatch method returns a response, the controller, and afterDispatch event will not be invoked. Let’s now create another filter for altering response headers in any public page, in our case it would be anything served from the PagesController: namespace App\Routing\Filter; 206 Capítulo 11. Desenvolvimento CakePHP Cookbook Documentation, Release 3.x use Cake\Event\Event; use Cake\Routing\DispatcherFilter; class HttpCacheFilter extends DispatcherFilter { public function afterDispatch(Event $event) { $request = $event->data[’request’]; $response = $event->data[’response’]; if ($response->statusCode() === 200) { $response->sharable(true); $response->expires(strtotime(’+1 day’)); } } } // In our bootstrap.php DispatcherFactory::add(’HttpCache’, [’for’ => ’/pages’]) This filter will send a expiration header to 1 day in the future for all responses produced by the pages controller. You could of course do the same in the controller, this is just an example of what could be done with filters. For instance, instead of altering the response, you could cache it using Cake\Cache\Cache and serve the response from the beforeDispatch callback. While powerful, dispatcher filters have the potential to make your application more difficult to maintain. Filters are an extremely powerful tool when used wisely and adding response handlers for each URL in your app is not a good use for them. Keep in mind that not everything needs to be a filter; Controllers and Components are usually a more accurate choice for adding any request handling code to your app. Dispatcher Filters 207 CakePHP Cookbook Documentation, Release 3.x 208 Capítulo 11. Desenvolvimento CAPÍTULO 12 Implementação Uma vez que sua aplicação está completa, ou mesmo antes quando você quiser colocá-la no ar. Existem algumas poucas coisas que você deve fazer quando colocar em produção uma aplicação CakePHP. Definindo a Raiz Definir a raiz do documento da sua aplicação corretamente é um passo importante para manter seu código protegido e sua aplicação mais segura. As aplicações desenvolvidas com o CakePHP devem ter a raiz apontando para o diretório app/webroot. Isto torna a aplicação e os arquivos de configurações inacessíveis via URL. Configurar a raiz do documento depende de cada webserver. Veja a /installation/advanced-installation para informações sobre webservers específicos. Atualizar o core.php Atualizar o arquivo core.php, especificamente o valor do debug é de extrema importância. Tornar o debug igual a 0 desabilita muitos recursos do processo de desenvolvimento que nunca devem ser expostos ao mundo. Desabilitando o debug, as coisas a seguir mudarão: • Mensagens de depuração criadas com pr() e debug() serão desabilitadas. • O cache interno do CakePHP será descartado após 99 anos em vez de 10 segundos. • Views de erros serão menos informativas, retornando mensagens de erros genéricas. • Erros não serão mostrados. • O rastro da pilha de exceções será desabilitado. Além dos itens citados acima, muitos plugins e extensões usam o valor do debug para modificarem seus comportamentos. 209 CakePHP Cookbook Documentation, Release 3.x Multiplas aplicações usando o mesmo core do CakePHP Existem algumas maneiras de você configurar múltiplas aplicações para usarem o mesmo core (núcleo) do CakePHP. Você pode usar o include_path do PHP ou definir a constante CAKE_CORE_INCLUDE_PATH no webroot/index.php da sua aplicação. Geralmente, usar o include_path do PHP é a meneira mais fácil e robusta pois o CakePHP já vem pré-configurado para olhar o include_path. No seu arquivo php.ini localize a diretiva include_path existente e anexe no final o caminho para o diretório lib do CakePHP: include_path = ’.:/usr/share/php:/usr/share/cakephp-2.0/lib’ Assumimos que você está rodando /usr/share/cakephp-2.0. 210 um servidor *nix e instalou o CakePHP em Capítulo 12. Implementação CAPÍTULO 13 Apêndices Os apêndices contêm informações sobre os novos recursos introduzidos em cada versão e a forma de executar a migração entre versões. Guia de Migração para a versão 3.0 A versão 3.0 ainda está em desenvolvimento, e qualquer mudança estará disponível apenas no branch 3.0 do git. 3.0 Migration Guide This page summarizes the changes from CakePHP 2.x that will assist in migrating a project to 3.0, as well as a reference to get up to date with the changes made to the core since the CakePHP 2.x branch. Be sure to read the other pages in this guide for all the new features and API changes. Requirements • CakePHP 3.x supports PHP Version 5.4.19 and above. • CakePHP 3.x requires the mbstring extension. • CakePHP 3.x requires the mcrypt extension. • CakePHP 3.x requires the intl extension. Warning: CakePHP 3.0 will not work if you do not meet the above requirements. 211 CakePHP Cookbook Documentation, Release 3.x Application Directory Layout The application directory layout has changed and now follows PSR-41 . You should use the app skeleton2 project as a reference point when updating your application. CakePHP should be installed with Composer Since CakePHP can no longer easily be installed via PEAR, or in a shared directory, those options are no longer supported. Instead you should use Composer3 to install CakePHP into your application. Namespaces All of CakePHP’s core classes are now namespaced and follow PSR-4 autoloading specifications. For example src/Cache/Cache.php is namespaced as Cake\Cache\Cache. Global constants and helper methods like __() and debug() are not namespaced for convenience sake. Removed Constants The following deprecated constants have been removed: • IMAGES • CSS • JS • IMAGES_URL • JS_URL • CSS_URL • DEFAULT_LANGUAGE Configuration Configuration in CakePHP 3.0 is significantly different than in previous versions. You should read the Configuration documentation for how configuration is done in 3.0. You can no longer use App::build() to configure additional class paths. Instead you should map additional paths using your application’s autoloader. See the section on Additional Class Paths for more information. Two new configure variables provide the path configuration for plugins, and views. You can add multiple paths to App.paths.templates and App.paths.plugins to configure multiple paths for templates & plugins. 1 http://www.php-fig.org/psr/psr-4/ https://github.com/cakephp/app 3 http://getcomposer.org 2 212 Capítulo 13. Apêndices CakePHP Cookbook Documentation, Release 3.x New ORM CakePHP 3.0 features a new ORM that has been re-built from the ground up. The new ORM is significantly different and incompatible with the previous one. Upgrading to the new ORM will require extensive changes in any application that is being upgraded. See the new <no title> documentation for information on how to use the new ORM. Basics • LogError() was removed, it provided no benefit and is rarely/never used. • The following global functions have been removed: config(), cache(), clearCache(), convertSlashes(), am(), fileExistsInPath(), sortByKey(). Debugging • Configure::write(’debug’, $bool) does not support 0/1/2 anymore. A simple boolean is used instead to switch debug mode on or off. Object settings/configuration • Objects used in CakePHP now have a consistent instance-configuration storage/retrieval system. Code which previously accessed for example: $object->settings should instead be updated to use $object->config(). Cache • Memcache engine has been removed, use Cake\Cache\Cache\Engine\Memcached instead. • Cache engines are now lazy loaded upon first use. • Cake\Cache\Cache::engine() has been added. • Cake\Cache\Cache::enabled() has been added. This replaced the Cache.disable configure option. • Cake\Cache\Cache::enable() has been added. • Cake\Cache\Cache::disable() has been added. • Cache configurations are now immutable. If you need to change configuration you must first drop the configuration and then re-create it. This prevents synchronization issues with configuration options. • Cache::set() has been removed. It is recommended that you create multiple cache configurations to replace runtime configuration tweaks previously possible with Cache::set(). • All CacheEngine subclasses now implement a config() method. • Cake\Cache\Cache::readMany(), Cake\Cache\Cache::deleteMany(), Cake\Cache\Cache::writeMany() were added. Guia de Migração para a versão 3.0 and 213 CakePHP Cookbook Documentation, Release 3.x All Cake\Cache\Cache\CacheEngine methods now honor/are responsible for handling the configured key prefix. The Cake\Cache\CacheEngine::write() no longer permits setting the duration on write - the duration is taken from the cache engine’s runtime config. Calling a cache method with an empty key will now throw an InvalidArgumentException, instead of returning false. Core App • App::pluginPath() has been removed. Use CakePlugin::path() instead. • App::build() has been removed. • App::location() has been removed. • App::paths() has been removed. • App::load() has been removed. • App::objects() has been removed. • App::RESET has been removed. • App::APPEND has been removed. • App::PREPEND has been removed. • App::REGISTER has been removed. Plugin • Cake\Core\Plugin::load() does not setup an autoloader unless you set the autoload option to true. • When loading plugins you can no longer provide a callable. • When loading plugins you can no longer provide an array of config files to load. Configure The config reader classes have been renamed: • Cake\Configure\PhpReader renamed to Cake\Core\Configure\EnginePhpConfig • Cake\Configure\IniReader renamed to Cake\Core\Configure\EngineIniConfig • Cake\Configure\ConfigReaderInterface Cake\Core\Configure\ConfigEngineInterface renamed to • Cake\Core\Configure::consume() was added. 214 Capítulo 13. Apêndices CakePHP Cookbook Documentation, Release 3.x Object The Object class has been removed. It formerly contained a grab bag of methods that were used in various places across the framework. The most useful of these methods have been extracted into traits. You can use the Cake\Log\LogTrait to access the log() method. The Cake\Routing\RequestActionTrait provides requestAction(). Console The cake executable has been moved from the app/Console directory to the bin directory within the application skeleton. You can now invoke CakePHP’s console with bin/cake. TaskCollection Replaced This class has been renamed to Cake\Console\TaskRegistry. See the section on /core-libraries/registry-objects for more information on the features provided by the new class. You can use the cake upgrade rename_collections to assist in upgrading your code. Tasks no longer have access to callbacks, as there were never any callbacks to use. Shell • Shell::__construct() has Cake\Console\ConsoleIo. changed. It now takes an instance of • Shell::param() has been added as convenience access to the params. Additionally all shell methods will be transformed to camel case when invoked. For example, if you had a hello_world() method inside a shell and invoked it with bin/cake my_shell hello_world, you will need to rename the method to helloWorld. There are no changes required in the way you invoke commands. Shell / Task Shells and Tasks have been moved from Console/Command and Console/Command/Task to Shell and Shell/Task. ApiShell Removed The ApiShell was removed as it didn’t provide any benefit over the file source itself and the online documentation/API4 . 4 http://api.cakephp.org/ Guia de Migração para a versão 3.0 215 CakePHP Cookbook Documentation, Release 3.x ExtractTask • bin/cake i18n extract no longer includes untranslated validation messages. If you want translated validation messages you should wrap those messages in __() calls like any other content. BakeShell / TemplateTask • Bake templates have been moved under src/Template/Bake. Also, the theme option, used for selecting a bake template, has been renamed to template. Event The getEventManager() method, was removed on all objects that had it. An eventManager() method is now provided by the EventManagerTrait. The EventManagerTrait contains the logic of instantiating and keeping a reference to a local event manager. The Event subsystem has had a number of optional features removed. When dispatching events you can no longer use the following options: • passParams This option is now enabled always implicitly. You cannot turn it off. • break This option has been removed. You must now stop events. • breakOn This option has been removed. You must now stop events. Log • Log configurations are now immutable. If you need to change configuration you must first drop the configuration and then re-create it. This prevents synchronization issues with configuration options. • Log engines are now lazily loaded upon the first write to the logs. • Cake\Log\Log::engine() has been added. • The following methods have been removed from Cake\Log\Log :: defaultLevels(), enabled(), enable(), disable(). • You can no longer create custom levels using Log::levels(). • When configuring loggers you should use ’levels’ instead of ’types’. • You can no longer specify custom log levels. You must use the default set of log levels. You should use logging scopes to create custom log files or specific handling for different sections of your application. Using a non-standard log level will now throw an exception. • Cake\Log\LogTrait was added. You can use this trait in your classes to add the log() method. • The logging scope passed to Cake\Log\Log::write() is now forwarded to the log engines’ write() method in order to provide better context to the engines. 216 Capítulo 13. Apêndices CakePHP Cookbook Documentation, Release 3.x • Log engines are now required to implement Psr\Log\LogInterface instead of Cake’s own LogInterface. In general, if you extended Cake\Log\Engine\BaseEngine you just need to rename the write() method to log(). Routing Named Parameters Named parameters were removed in 3.0. Named parameters were added in 1.2.0 as a ‘pretty’ version of query string parameters. While the visual benefit is arguable, the problems named parameters created are not. Named parameters required special handling in CakePHP as well as any PHP or JavaScript library that needed to interact with them, as named parameters are not implemented or understood by any library except CakePHP. The additional complexity and code required to support named parameters did not justify their existence, and they have been removed. In their place you should use standard query string parameters or passed arguments. By default Router will treat any additional parameters to Router::url() as query string arguments. Since many applications will still need to parse incoming URLs containing named parameters. Cake\Routing\Router::parseNamedParams() has been added to allow backwards compatibility with existing URLs. RequestActionTrait • Cake\Routing\RequestActionTrait::requestAction() has had some of the extra options changed: – options[url] is now options[query]. – options[data] is now options[post]. – Named parameters are no longer supported. Router • Named parameters have been removed, see above for more information. • The full_base option has been replaced with the _full option. • The ext option has been replaced with the _ext option. • _scheme, _port, _host, _base, _full, _ext options added. • String URLs are no longer modified by adding the plugin/controller/prefix names. • The default fallback route handling was removed. If no routes match a parameter set / will be returned. • Route classes are responsible for all URL generation including query string parameters. This makes routes far more powerful and flexible. Guia de Migração para a versão 3.0 217 CakePHP Cookbook Documentation, Release 3.x • Persistent parameters were removed. They were replaced with Cake\Routing\Router::urlFilter() which allows a more flexible way to mutate URLs being reverse routed. • Router::parseExtensions() has been removed. Use Cake\Routing\Router::extensions() instead. This method must be called before routes are connected. It won’t modify existing routes. • Router::setExtensions() has been removed. Use Cake\Routing\Router::extensions() instead. • Router::resourceMap() has been removed. • The [method] option has been renamed to _method. • The ability to match arbitrary headers with [] style parameters has been removed. If you need to parse/match on arbitrary conditions consider using custom route classes. • Router::promote() has been removed. • Router::parse() will now raise an exception when a URL cannot be handled by any route. • Router::url() will now raise an exception when no route matches a set of parameters. • Routing scopes have been introduced. Routing scopes allow you to keep your routes file DRY and give Router hints on how to optimize parsing & reverse routing URLs. Route • CakeRoute was re-named to Route. • The signature of match() has changed to match($url, $context = array()) See Cake\Routing\Route::match() for information on the new signature. Dispatcher Filters Configuration Changed Dispatcher filters are no longer added to your application using Configure. You now append them with Cake\Routing\DispatcherFactory. This means if your application used Dispatcher.filters, you should now use php:meth:Cake\Routing\DispatcherFactory::add(). In addition to configuration changes, dispatcher filters have had some conventions updated, and features added. See the Dispatcher Filters documentation for more information. FilterAssetFilter • Plugin & theme assets handled by the AssetFilter are no longer read via include instead they are treated as plain text files. This fixes a number of issues with JavaScript libraries like TinyMCE and environments with short_tags enabled. • Support for the Asset.filter configuration and hooks were removed. This feature can easily be replaced with a plugin or dispatcher filter. 218 Capítulo 13. Apêndices CakePHP Cookbook Documentation, Release 3.x Network Request • CakeRequest has been renamed to Cake\Network\Request. • Cake\Network\Request::port() was added. • Cake\Network\Request::scheme() was added. • Cake\Network\Request::cookie() was added. • Cake\Network\Request::$trustProxy was added. This makes it easier to put CakePHP applications behind load balancers. • Cake\Network\Request::$data is no longer merged with the prefixed data key, as that prefix has been removed. • Cake\Network\Request::env() was added. • Cake\Network\Request::acceptLanguage() was changed from static method to nonstatic. • Request detector for “mobile” has been removed from the core. Instead the app template adds detectors for “mobile” and “tablet” using MobileDetect lib. • The method onlyAllow() has been renamed to allowMethod() and no longer accepts “var args”. All method names need to be passed as first argument, either as string or array of strings. Response • The mapping of mimetype text/plain to extension csv has been removed. As a consequence Cake\Controller\Component\RequestHandlerComponent doesn’t set extension to csv if Accept header contains mimetype text/plain which was a common annoyance when receiving a jQuery XHR request. Sessions The session class is no longer static, instead the session can be accessed through the request object. See the Sessions documentation for using the session object. • Cake\Network\Session and related session classes have been moved under the Cake\Network namespace. • SessionHandlerInterface has been removed in favor of the one provided by PHP itself. • The property Session::$requestCountdown has been removed. • The session checkAgent feature has been removed. It caused a number of bugs when chrome frame, and flash player are involved. • The conventional sessions database table name is now sessions instead of cake_sessions. Guia de Migração para a versão 3.0 219 CakePHP Cookbook Documentation, Release 3.x • The session cookie timeout is automatically updated in tandem with the timeout in the session data. • The path for session cookie now defaults to app’s base path instead of “/”. Also new config variable Session.cookiePath has been added to easily customize the cookie path. Network\Http • HttpSocket is now Cake\Network\Http\Client. • HttpClient has been re-written from the ground up. It has a simpler/easier to use API, support for new authentication systems like OAuth, and file uploads. It uses PHP’s stream APIs so there is no requirement for cURL. See the /core-libraries/httpclient documentation for more information. Network\Email • Cake\Network\Email\Email::config() is now used to define configuration profiles. This replaces the EmailConfig classes in previous versions. • Cake\Network\Email\Email::profile() replaces config() as the way to modify per instance configuration options. • Cake\Network\Email\Email::drop() has been added to allow the removal of email configuration. • Cake\Network\Email\Email::configTransport() has been added to allow the definition of transport configurations. This change removes transport options from delivery profiles and allows you to easily re-use transports across email profiles. • Cake\Network\Email\Email::dropTransport() has been added to allow the removal of transport configuration. Controller Controller • The $helpers, $components properties are now merged with all parent classes not just AppController and the plugin AppController. The properties are merged differently now as well. Instead of all settings in all classes being merged together, the configuration defined in the child class will be used. This means that if you have some configuration defined in your AppController, and some configuration defined in a subclass, only the configuration in the subclass will be used. • Controller::httpCodes() has been removed, use Cake\Network\Response::httpCodes() instead. • Controller::disableCache() has been Cake\Network\Response::disableCache() instead. removed, use • Controller::flash() has been removed. This method was rarely used in real applications and served no purpose anymore. 220 Capítulo 13. Apêndices CakePHP Cookbook Documentation, Release 3.x • Controller::validate() and Controller::validationErrors() have been removed. They were left over methods from the 1.x days where the concerns of models + controllers were far more intertwined. • Controller::loadModel() now loads table objects. • The Controller::$scaffold property has been removed. Dynamic scaffolding has been removed from CakePHP core, and will be provided as a standalone plugin. • The Controller::$ext property has been removed. You now have to extend and override the View::$_ext property if you want to use a non-default view file extension. • The Controller::$Components property has been removed and replaced with _components. If you need to load components at runtime you should use $this->loadComponent() on your controller. • The signature of Cake\Controller\Controller::redirect() has been changed to Controller::redirect(string|array $url, int $status = null). The 3rd argument $exit has been dropped. The method can no longer send response and exit script, instead it returns a Response instance with appropriate headers set. • The base, webroot, here, data, action, and params magic properties have been removed. You should access all of these properties on $this->request instead. • Underscore prefixed controller methods like _someMethod() are no longer treated as private methods. Use proper visibility keywords instead. Only public methods can be used as controller actions. Scaffold Removed The dynamic scaffolding in CakePHP has been removed from CakePHP core. It was infrequently used, and never intended for production use. It will be replaced by a standalone plugin that people requiring that feature can use. ComponentCollection Replaced This class has been renamed to Cake\Controller\ComponentRegistry. See the section on /core-libraries/registry-objects for more information on the features provided by the new class. You can use the cake upgrade rename_collections to assist in upgrading your code. Component • The _Collection property is now _registry. Cake\Controller\ComponentRegistry now. It contains an instance of • All components should now use the config() method to get/set configuration. • Default configuration for components should be defined in the $_defaultConfig property. This property is automatically merged with any configuration provided to the constructor. • Configuration options are no longer set as public properties. Guia de Migração para a versão 3.0 221 CakePHP Cookbook Documentation, Release 3.x Controller\Components CookieComponent • Uses Cake\Network\Request::cookie() to read cookie data, this eases testing, and allows for ControllerTestCase to set cookies. • Cookies encrypted in previous versions of CakePHP using the cipher method are now un-readable because Security::cipher() has been removed. You will need to re-encrypt cookies with the rijndael or aes method before upgrading. • CookieComponent::type() has been removed and replaced with configuration data accessed through config(). • write() no longer takes encryption or expires parameters. Both of these are now managed through config data. See CookieComponent for more information. • The path for cookies now defaults to app’s base path instead of “/”. AuthComponent • Default is now the default password hasher used by authentication classes. It uses exclusively the bcrypt hashing algorithm. If you want to continue using SHA1 hashing used in 2.x use ’passwordHasher’ => ’Weak’ in your authenticator configuration. • BaseAuthenticate::_password() has been removed. Use a PasswordHasher class instead. • A new FallbackPasswordHasher was added to help users migrate old passwords from one algorithm to another. Check AuthComponent’s documentation for more info. • BlowfishAuthenticate class has been removed. Just use FormAuthenticate • BlowfishPasswordHasher class has been removed. Use DefaultPasswordHasher instead. • The loggedIn() method has been removed. Use user() instead. • Configuration options are no longer set as public properties. • The methods allow() and deny() no longer accept “var args”. All method names need to be passed as first argument, either as string or array of strings. • The method login() has been removed and replaced by setUser() instead. To login a user you now have to call identify() which returns user info upon successful identification and then use setUser() to save the info to session for persistence across requests. ACL related classes were moved to a separate plugin. Password hashers, Authentication and Authorization providers where moved to the \Cake\Auth namespace. You are required to move your providers and hashers to the App\Auth namespace as well. 222 Capítulo 13. Apêndices CakePHP Cookbook Documentation, Release 3.x RequestHandlerComponent • The following methods have been removed from RequestHandler component:: isAjax(), isFlash(), isSSL(), isPut(), isPost(), isGet(), isDelete(). Use the Cake\Network\Request::is() method instead with relevant argument. • RequestHandler::setContent() was removed, use Cake\Network\Response::type() instead. • RequestHandler::getReferer() was removed, use Cake\Network\Request::referer() instead. • RequestHandler::getClientIP() was removed, use Cake\Network\Request::clientIp() instead. • RequestHandler::getAjaxVersion() was removed. • RequestHandler::mapType() was removed, use Cake\Network\Response::mapType() instead. • Configuration options are no longer set as public properties. SecurityComponent • The following methods and their related properties have been removed from Security component: requirePost(), requireGet(), requirePut(), requireDelete(). Use the Cake\Network\Request::allowMethod() instead. • SecurityComponent::$disabledFields() SecurityComponent::$unlockedFields(). has been removed, use • The CSRF related features in SecurityComponent have been extracted and moved into a separate CsrfComponent. This allows you more easily use CSRF protection without having to use form tampering prevention. • Configuration options are no longer set as public properties. • The methods requireAuth() and requireSecure() no longer accept “var args”. All method names need to be passed as first argument, either as string or array of strings. SessionComponent • SessionComponent::setFlash() is deprecated. You should use FlashComponent instead. Error Custom ExceptionRenderers are now expected to either return a Cake\Network\Response object or string when rendering errors. This means that any methods handling specific exceptions must return a response or string value. Guia de Migração para a versão 3.0 223 CakePHP Cookbook Documentation, Release 3.x Model The Model layer in 2.x has been entirely re-written and replaced. You should review the New ORM Upgrade Guide for information on how to use the new ORM. • The Model class has been removed. • The BehaviorCollection class has been removed. • The DboSource class has been removed. • The Datasource class has been removed. • The various datasource classes have been removed. ConnectionManager • ConnectionManager has been moved to the Cake\Database namespace. • ConnectionManager has had the following methods removed: – sourceList – getSourceName – loadDataSource – enumConnectionObjects • Database\ConnectionManager::config() has been added and is now the only way to configure connections. • Database\ConnectionManager::get() has been added. It replaces getDataSource(). • Database\ConnectionManager::configured() has been added. It and config() replace sourceList() & enumConnectionObjects() with a more standard and consistent API. • ConnectionManager::create() has been removed. It can be replaced by config($name, $config) and get($name). Behaviors • Underscore prefixed behavior methods like _someMethod() are no longer treated as private methods. Use proper visibility keywords instead. TreeBehavior The TreeBheavior was completely re-written to use the new ORM. Although it works the same as in 2.x, a few methods were renamed or removed: 224 Capítulo 13. Apêndices CakePHP Cookbook Documentation, Release 3.x - ‘‘TreeBehavior::children()‘‘ is now a custom finder ‘‘find(’children’)‘‘. ‘‘TreeBehavior::generateTreeList()‘‘ is now a custom finder ‘‘find(’treeList’)‘‘. ‘‘TreeBehavior::getParentNode()‘‘ was removed. ‘‘TreeBehavior::getPath()‘‘ is now a custom finder ‘‘find(’path’)‘‘. ‘‘TreeBehavior::reorder()‘‘ was removed. ‘‘TreeBehavior::verify()‘‘ was removed. TestSuite TestCase • _normalizePath() has been added to allow path comparison tests to run across all operation systems regarding their DS settings (\ in Windows vs / in UNIX, for example). The following assertion methods have been removed as they have long been deprecated and replaced by their new PHPUnit counterpart: • assertEqual() in favor of assertEquals() • assertNotEqual() in favor of assertNotEquals() • assertIdentical() in favor of assertSame() • assertNotIdentical() in favor of assertNotSame() • assertPattern() in favor of assertRegExp() • assertNoPattern() in favor of assertNotRegExp() • assertReference() if favor of assertSame() • assertIsA() in favor of assertInstanceOf() Note that some methods have switched the argument order, e.g. assertEqual($is, $expected) should now be assertEquals($expected, $is). The following assertion methods have been deprecated and will be removed in the future: • assertWithinMargin() in favor of assertWithinRange() • assertTags() in favor of assertHtml() Both method replacements also switched the argument order for a consistent assert method API with $expected as first argument. ControllerTestCase • You can now simulate query strings, POST data and cookie values when using testAction(). The default method for testAction() is now GET. Guia de Migração para a versão 3.0 225 CakePHP Cookbook Documentation, Release 3.x View Themes are now Basic Plugins Having themes and plugins as ways to create modular application components has proven to be limited, and confusing. In CakePHP 3.0, themes no longer reside inside the application. Instead they are standalone plugins. This solves a few problems with themes: • You could not put themes in plugins. • Themes could not provide helpers, or custom view classes. Both these issues are solved by converting themes into plugins. View Folders Renamed The folders containing view files now go under src/Template instead of src/View. This was done to separate the view files from files containing php classes (eg. Helpers, View classes). The following View folders have been renamed to avoid naming collisions with controller names: • Layouts is now Layout • Elements is now Element • Scaffolds is now Scaffold • Errors is now Error • Emails is now Email (same for Email inside Layout) HelperCollection Replaced This class has been renamed to Cake\View\HelperRegistry. See the section on /core-libraries/registry-objects for more information on the features provided by the new class. You can use the cake upgrade rename_collections to assist in upgrading your code. View Class • The plugin key has been Cake\View\View::element(). SomePlugin.element_name instead. removed from $options argument Specify the element name of as • View::getVar() has been removed, use Cake\View\View::get() instead. • View::$ext has been removed and instead a protected property View::$_ext has been added. • View::addScript() has been removed. Use Usando Blocos de Views (Visões) instead. • The base, webroot, here, data, action, and params magic properties have been removed. You should access all of these properties on $this->request instead. 226 Capítulo 13. Apêndices CakePHP Cookbook Documentation, Release 3.x • View::start() no longer appends to an existing block. Instead it will overwrite the block content when end is called. If you need to combine block contents you should fetch the block content when calling start a second time, or use the capturing mode of append(). • View::prepend() no longer has a capturing mode. • View::startIfEmpty() has been removed. Now that start() always overwrites startIfEmpty serves no purpose. • The View::$Helpers property has been removed and replaced with _helpers. If you need to load helpers at runtime you should use $this->addHelper() in your view files. ViewBlock • ViewBlock::append() has been removed, use Cake\ViewViewBlock::concat() instead. However, View::append() still exists. JsonView • By default JSON data will have HTML entities encoded now. This prevents possible XSS issues when JSON view content is embedded in HTML files. • Cake\View\JsonView now supports the _jsonOptions view variable. This allows you to configure the bit-mask options used when generating JSON. View\Helper • The $settings property is now called $_config and should be accessed through the config() method. • Configuration options are no longer set as public properties. • Helper::clean() was removed. It was never robust enough to fully prevent XSS. instead you should escape content with h or use a dedicated library like htmlPurifier. • Helper::output() was removed. This method was deprecated in 2.x. • Methods Helper::webroot(), Helper::url(), Helper::assetUrl(), Helper::assetTimestamp() have been moved to new Cake\View\Helper\UrlHelper helper. Helper::url() is now available as Cake\View\Helper\UrlHelper::build(). • Magic accessors to deprecated properties have been removed. The following properties now need to be accessed from the request object: – base – here – webroot – data Guia de Migração para a versão 3.0 227 CakePHP Cookbook Documentation, Release 3.x – action – params Helper Helper has had the following methods removed: • Helper::setEntity() • Helper::entity() • Helper::model() • Helper::field() • Helper::value() • Helper::_name() • Helper::_initInputField() • Helper::_selectedArray() These methods were part used only by FormHelper, and part of the persistent field features that have proven to be problematic over time. FormHelper no longer relies on these methods and the complexity they provide is not necessary anymore. The following methods have been removed: • Helper::_parseAttributes() • Helper::_formatAttribute() These methods can now be found on the StringTemplate class that helpers frequently use. See the StringTemplateTrait for an easy way to integrate string templates into your own helpers. FormHelper FormHelper has been entirely rewritten for 3.0. It features a few large changes: • FormHelper works with the new ORM. But has an extensible system for integrating with other ORMs or datasources. • FormHelper features an extensible widget system that allows you to create new custom input widgets and easily augment the built-in ones. • String templates are the foundation of the helper. Instead of munging arrays together everywhere, most of the HTML FormHelper generates can be customized in one central place using template sets. In addition to these larger changes, some smaller breaking changes have been made as well. These changes should help streamline the HTML FormHelper generates and reduce the problems people had in the past: • The data[ prefix was removed from all generated inputs. The prefix serves no real purpose anymore. 228 Capítulo 13. Apêndices CakePHP Cookbook Documentation, Release 3.x • The various standalone input methods like text(), select() and others no longer generate id attributes. • The inputDefaults option has been removed from create(). • Options default and onsubmit of create() have been removed. Instead one should use javascript event binding or set all required js code for onsubmit. • end() can no longer make buttons. You should create buttons with button() or submit(). • FormHelper::tagIsInvalid() has been removed. Use isFieldError() instead. • FormHelper::inputDefaults() has been removed. You can use templates() to define/augment the templates FormHelper uses. • The wrap and class options have been removed from the error() method. • The showParents option has been removed from select(). • The div, before, after, between and errorMessage options have been removed from input(). You can use templates to update the wrapping HTML. The templates option allows you to override the loaded templates for one input. • The separator, between, and legend options have been removed from radio(). You can use templates to change the wrapping HTML now. • The format24Hours parameter has been removed from hour(). It has been replaced with the format option. • The minYear, and maxYear parameters have been removed from year(). Both of these parameters can now be provided as options. • The dateFormat and timeFormat parameters have been removed from datetime(). You can use the template to define the order the inputs should be displayed in. • The submit() has had the div, before and after options removed. You can customize the submitContainer template to modify this content. • The inputs method no longer accepts legend and fieldset in the $fields parameter, you must use the $options parameter. It now also requires $fields parameter to be an array. The $blacklist parameter has been removed, the functionality has been replaced by specifying ’field’ => false in the $fields parameter. • The inline parameter has been removed from postLink() method. You should use the block option instead. Setting block => true will emulate the previous behavior. • The timeFormat parameter for hour(), time() and dateTime() now defaults to 24, complying with ISO 8601. • The $confirmMessage argument of Cake\View\Helper\FormHelper::postLink() has been removed. You should now use key confirm in $options to specify the message. It is recommended that you review the /views/helpers/form documentation for more details on how to use the FormHelper in 3.0. Guia de Migração para a versão 3.0 229 CakePHP Cookbook Documentation, Release 3.x HtmlHelper • HtmlHelper::useTag() has been removed, use tag() instead. • HtmlHelper::loadConfig() has been removed. Customizing the tags can now be done using templates() or the templates setting. • The second parameter $options for HtmlHelper::css() now always requires an array as documented. • The first parameter $data for HtmlHelper::style() now always requires an array as documented. • The inline parameter has been removed from meta(), css(), script(), scriptBlock() methods. You should use the block option instead. Setting block => true will emulate the previous behavior. • HtmlHelper::meta() now requires $type to be a string. Additional options can further on be passed as $options. • HtmlHelper::nestedList() now requires $options to be an array. The forth argument for the tag type has been removed and included in the $options array. • The $confirmMessage argument of Cake\View\Helper\HtmlHelper::link() has been removed. You should now use key confirm in $options to specify the message. PaginatorHelper • link() has been removed. It was no longer used by the helper internally. It had low usage in user land code, and no longer fit the goals of the helper. • next() no longer has ‘class’, or ‘tag’ options. It no longer has disabled arguments. Instead templates are used. • prev() no longer has ‘class’, or ‘tag’ options. It no longer has disabled arguments. Instead templates are used. • first() no longer has ‘after’, ‘ellipsis’, ‘separator’, ‘class’, or ‘tag’ options. • last() no longer has ‘after’, ‘ellipsis’, ‘separator’, ‘class’, or ‘tag’ options. • numbers() no longer has ‘separator’, ‘tag’, ‘currentTag’, ‘currentClass’, ‘class’, ‘tag’, ‘ellipsis’ options. These options are now facilitated through templates. It also requires the $options parameter to be an array now. • The %page% style placeholders have Cake\View\Helper\PaginatorHelper::counter(). holders instead. been removed from Use {{page}} style place- • url() has been renamed to generateUrl() to avoid method declaration clashes with Helper::url(). By default all links and inactive texts are wrapped in <li> elements. This helps make CSS easier to write, and improves compatibility with popular CSS frameworks. 230 Capítulo 13. Apêndices CakePHP Cookbook Documentation, Release 3.x Instead of the various options in each method, you should use the templates feature. See the paginatortemplates documentation for information on how to use templates. TimeHelper • TimeHelper::__set(), TimeHelper::__get(), and TimeHelper::__isset() were removed. These were magic methods for deprecated attributes. • TimeHelper::serverOffset() has been removed. It promoted incorrect time math practices. • TimeHelper::niceShort() has been removed. NumberHelper • NumberHelper::format() now requires $options to be an array. SessionHelper • SessionHelper::flash() is deprecated. You should use /views/helpers/flash instead. JsHelper • JsHelper and all associated engines have been removed. It could only generate a very small subset of javascript code for selected library and hence trying to generate all javascript code using just the helper often became an impediment. It’s now recommended to directly use javascript library of your choice. CacheHelper Removed CacheHelper has been removed. The caching functionality it provided was non-standard, limited and incompatible with non-html layouts and data views. These limitations meant a full rebuild would be necessary. Edge Side Includes have become a standardized way to implement the functionality CacheHelper used to provide. However, implementing Edge Side Includes5 in PHP has a number of limitations and edge cases. Instead of building a sub-par solution, we recommend that developers needing full response caching use Varnish6 or Squid7 instead. I18n The I18n subsystem was completely rewritten. In general, you can expect the same behavior as in previous versions, specifically if you are using the __() family of functions. 5 http://en.wikipedia.org/wiki/Edge_Side_Includes http://varnish-cache.org 7 http://squid-cache.org 6 Guia de Migração para a versão 3.0 231 CakePHP Cookbook Documentation, Release 3.x Internally, the I18n class uses Aura\Intl, and appropriate methods are exposed to access the specific features of this library. For this reason most methods inside I18n were removed or renamed. Due to the use of ext/intl, the L10n class was completely removed. It provided outdated and incomplete data in comparison to the data available from the Locale class in PHP. The default application language will no longer be changed automatically by the browser accepted language nor by having the Config.language value set in the browser session. You can, however, use a dispatcher filter to get automatic language switching from the Accept-Language header sent by the browser: // In config/bootstrap.php DispatcherFactory::addFilter(’LocaleSelector’); There is no built-in replacement for automatically selecting the language by setting a value in the user session. The default formatting function for translated messages is no longer sprintf, but the more advanced and feature rich MessageFormatter class. In general you can rewrite placeholders in messages as follows: // Before: __(’Today is a %s day in %s’, ’Sunny’, ’Spain’); // After: __(’Today is a {0} day in {1}’, ’Sunny’, ’Spain’); You can avoid rewriting your messages by using the old sprintf formatter: I18n::defaultFormatter(’sprintf’); Additionally, the Config.language value was removed and it can no longer be used to control the current language of the application. Instead, you can use the I18n class: // Before Configure::write(’Config.language’, ’fr_FR’); // Now I18n::locale(’en_US’); • The methods below have been moved: – From Cake\I18n\Multibyte::utf8() to Cake\Utility\String::utf8() – From Cake\I18n\Multibyte::ascii() to Cake\Utility\String::ascii() – From Cake\I18n\Multibyte::checkMultibyte() Cake\Utility\String::isMultibyte() to • Since CakePHP now requires the mbstring extension, the Multibyte class has been removed. • Error messages throughout CakePHP are no longer passed through I18n functions. This was done to simplify the internals of CakePHP and reduce overhead. The developer facing messages are rarely, if ever, actually translated - so the additional overhead reaps very little benefit. 232 Capítulo 13. Apêndices CakePHP Cookbook Documentation, Release 3.x L10n • Cake\I18n\L10n ‘s constructor now takes a Cake\Network\Request instance as argument. Testing • The TestShell has been removed. CakePHP, the application skeleton and newly baked plugins all use phpunit to run tests. • The webrunner (webroot/test.php) has been removed. CLI adoption has greatly increased since the initial release of 2.x. Additionaly, CLI runners offer superior integration with IDE’s and other automated tooling. If you find yourself in need of a way to run tests from a browser you should checkout VisualPHPUnit8 . It offers many additional features over the old webrunner. • ControllerTestCase is deprecated and will be removed for CakePHP 3.0.0. You should use the new Controller Integration Testing features instead. • The fixtures should now be referenced by plural form: // Instead of $fixtures = [’app.article’]; // You should use $fixtures = [’app.articles’]; Utility Set Class Removed The Set class has been removed, you should use the Hash class instead now. Folder & File The folder and file classes have been renamed: • Cake\Utility\File renamed to Cake\Filesystem\File • Cake\Utility\Folder renamed to Cake\Filesystem\Folder Inflector • Transliterations for Cake\Utility\Inflector::slug() have changed. If you use custom transliterations you will need to update your code. Instead of regular expressions, transliterations use simple string replacement. This yielded significant performance improvements: 8 https://github.com/NSinopoli/VisualPHPUnit Guia de Migração para a versão 3.0 233 CakePHP Cookbook Documentation, Release 3.x // Instead of Inflector::rules(’transliteration’, array( ’/ä|æ/’ => ’ae’, ’/å/’ => ’aa’ )); // You should use Inflector::rules(’transliteration’, [ ’ä’ => ’ae’, ’æ’ => ’ae’, ’å’ => ’aa’ ]); • Separate set of uninflected and irregular rules for pluralization and singularization have been removed. Instead we now have a common list for each. When using Cake\Utility\Inflector::rules() with type ‘singular’ and ‘plural’ you can no longer use keys like ‘uninflected’, ‘irregular’ in $rules argument array. You can add / overwrite the list of uninflected and irregular rules using Cake\Utility\Inflector::rules() by using values ‘uninflected’ and ‘irregular’ for $type argument. Sanitize • Sanitize class has been removed. Security • Security::cipher() has been removed. It is insecure and promoted bad cryptographic practices. You should use Security::rijndael() instead. • The Configure value Security.cipherSeed is no longer required. Security::cipher() it serves no use. With the removal of • Backwards compatibility in Cake\Utility\Security::rijndael() for values encrypted prior to CakePHP 2.3.1 has been removed. You should re-encrypt values using a recent version of CakePHP 2.x before migrating. • The ability to generate blowfish hash been removed. You can no longer use type “blowfish” for Security::hash(). One should just use PHP’s password_hash() and password_verify() to generate and verify blowfish hashes. The compability library ircmaxell/password-compat9 . which is installed along with CakePHP provides these functions for PHP < 5.5. Time • CakeTime has been renamed to Cake\I18n\Time. 9 https://packagist.org/packages/ircmaxell/password-compat 234 Capítulo 13. Apêndices CakePHP Cookbook Documentation, Release 3.x • CakeTime::serverOffset() has been removed. It promoted incorrect time math practises. • CakeTime::niceShort() has been removed. • CakeTime::convert() has been removed. • CakeTime::convertSpecifiers() has been removed. • CakeTime::dayAsSql() has been removed. • CakeTime::daysAsSql() has been removed. • CakeTime::fromString() has been removed. • CakeTime::gmt() has been removed. • CakeTime::toATOM() has been renamed to toAtomString. • CakeTime::toRSS() has been renamed to toRssString. • CakeTime::toUnix() has been renamed to toUnixString. • CakeTime::wasYesterday() has been renamed to isYesterday to match the rest of the method naming. • CakeTime::format() Does not use sprintf format strings anymore, you can use i18nFormat instead. • Time::timeAgoInWords() now requires $options to be an array. Time is not a collection of static methods anymore, it extends DateTime to inherit all its methods and adds location aware formatting functions with the help of the intl extension. In general, expressions looking like this: CakeTime::aMethod($date); Can be migrated by rewriting it to: (new Time($date))->aMethod(); Number The Number library was rewritten to internally use the NumberFormatter class. • CakeNumber has been renamed to Cake\I18n\Number. • Number::format() now requires $options to be an array. • Number::addFormat() was removed. • Number::fromReadableSize() has been moved to Cake\Utility\String::parseFileSize(). Validation • The range for Validation::range() now is inclusive if $lower and $upper are provided. Guia de Migração para a versão 3.0 235 CakePHP Cookbook Documentation, Release 3.x • Validation::ssn() has been removed. Xml • Xml::build() now requires $options to be an array. • Xml::build() no longer accepts a URL. If you need to create an XML document from a URL, use Http\Client. New ORM Upgrade Guide CakePHP 3.0 features a new ORM that has been re-written from the ground up. While the ORM used in 1.x and 2.x has served us well for a long time it had a few issues that we wanted to fix. • Frankenstein - is it a record, or a table? Currently it’s both. • Inconsistent API - Model::read() for example. • No query object - Queries are always defined as arrays, this has some limitations and restrictions. For example it makes doing unions and sub-queries much harder. • Returns arrays. This is a common complaint about CakePHP, and has probably reduced adoption at some levels. • No record object - This makes attaching formatting methods difficult/impossible. • Containable - Should be part of the ORM, not a crazy hacky behaviour. • Recursive - This should be better controlled as defining which associations are included, not a level of recursiveness. • DboSource - It is a beast, and Model relies on it more than datasource. That separation could be cleaner and simpler. • Validation - Should be separate, it’s a giant crazy function right now. Making it a reusable bit would make the framework more extensible. The ORM in CakePHP 3.0 solves these and many more problems. The new ORM focuses on relational data stores right now. In the future and through plugins we will add non relational stores like ElasticSearch and others. Design of the New ORM The new ORM solves several problems by having more specialized and focused classes. In the past you would use Model and a Datasource for all operations. Now the ORM is split into more layers: • Cake\Database\Connection - Provides a platform independent way to create and use connections. This class provides a way to use transactions, execute queries and access schema data. • Cake\Database\Dialect - The classes in this namespace provide platform specific SQL and transform queries to work around platform specific limitations. 236 Capítulo 13. Apêndices CakePHP Cookbook Documentation, Release 3.x • Cake\Database\Type - Is the gateway class to CakePHP database type conversion system. It is a pluggable framework for adding abstract column types and providing mappings between database, PHP representations and PDO bindings for each data type. For example datetime columns are represented as DateTime instances in your code now. • Cake\ORM\Table - The main entry point into the new ORM. Provides access to a single table. Handles the definition of assocation, use of behaviors and creation of entities and query objects. • Cake\ORM\Behavior - The base class for behaviors, which act very similar to behaviors in previous versions of CakePHP. • Cake\ORM\Query - A fluent object based query builder that replaces the deeply nested arrays used in previous versions of CakePHP. • Cake\ORM\ResultSet - A collection of results that gives powerful tools for manipulating data in aggregate. • Cake\ORM\Entity - Represents a single row result. Makes accessing data and serializing to various formats a snap. Now that you are more familiar with some of the classes you’ll interact with most frequently in the new ORM it is good to look at the three most important classes. The Table, Query and Entity classes do much of the heavy lifting in the new ORM, and each serves a different purpose. Table Objects Table objects are the gateway into your data. They handle many of the tasks that Model did in previous releases. Table classes handle tasks like: • Creating queries. • Providing finders. • Validating and saving entities. • Deleting entities. • Defining & accessing associations. • Triggering callback events. • Interacting with behaviors. The documentation chapter on /orm/table-objects provides far more detail on how to use table objects than this guide can. Generally when moving existing model code over it will end up in a table object. Table objects don’t contain any platform dependent SQL. Instead they collaborate with entities and the query builder to do their work. Table objects also interact with behaviors and other interested parties through published events. Query Objects While these are not classes you will build yourself, your application code will make extensive use of the /orm/query-builder which is central to the new ORM. The query builder makes it easy to build Guia de Migração para a versão 3.0 237 CakePHP Cookbook Documentation, Release 3.x simple or complex queries including those that were previously very difficult in CakePHP like HAVING, UNION and sub-queries. The various find() calls your application has currently will need to be updated to use the new query builder. The Query object is responsible for containing the data to make a query without executing the query itself. It collaborates with the connection/dialect to generate platform specific SQL which is executed creating a ResultSet as the output. Entity Objects In previous versions of CakePHP the Model class returned dumb arrays that could not contain any logic or behavior. While the community made this short-coming less painful with projects like CakeEntity, the array results were often a short coming that caused many developers trouble. For CakePHP 3.0, the ORM always returns object result sets unless you explicitly disable that feature. The chapter on /orm/entities covers the various tasks you can accomplish with entities. Entities are created in one of two ways. Either by loading data from the database, or converting request data into entities. Once created, entities allow you to manipulate the data they contain and persist their data by collaborating with table objects. Key Differences The new ORM is a large departure from the existing Model layer, there are many important differences that are important in understanding how the new ORM operates and how to update your code. Inflection Rules Updated You may have noticed that table classes have a pluralized name. In addition to tables having pluralized names, associations are also referred in the plural form. This is in contrast to Model where class names and association aliases were singular. There are a few reasons for this change: • Table classes represent collections of data, not single rows. • Associations link tables together, describing the relations between many things. While the conventions for table objects are to always use plural forms, your entity association properties will be populated based on the association type. Note: BelongsTo and HasOne associations will use the singular form in entity properties, while HasMany and BelongsToMany (HABTM) will use plural forms. The convention change for table objects is most apparent when building queries. Instead of expressing queries like: // Wrong $query->where([’User.active’ => 1]); You need to use the plural form: 238 Capítulo 13. Apêndices CakePHP Cookbook Documentation, Release 3.x // Correct $query->where([’Users.active’ => 1]); Find returns a Query Object One important difference in the new ORM is that calling find on a table will not return the results immediately, but will return a Query object; this serves several purposes. It is possible to alter queries further, after calling find: $articles = TableRegistry::get(’Articles’); $query = $articles->find(); $query->where([’author_id’ => 1])->order([’title’ => ’DESC’]); It is possible to stack custom finders to append conditions, sorting, limit and any other clause to the same query before it is executed: $query = $articles->find(’approved’)->find(’popular’); $query->find(’latest’); You can compose queries one into the other to create subqueries easier than ever: $query = $articles->find(’approved’); $favoritesQuery = $article->find(’favorites’, [’for’ => $user]); $query->where([’id’ => $favoritesQuery->select([’id’])]); You can decorate queries with iterators and call methods without even touching the database, this is great when you have parts of your view cached and having the results taken from the database is not actually required: // No queries made in this example! $results = $articles->find() ->order([’title’ => ’DESC’]) ->extract(’title’); Queries can be seen as the result object, trying to iterate the query, calling toArray or any method inherited from collection, will result in the query being executed and results returned to you. The biggest difference you will find when coming from CakePHP 2.x is that find(’first’) does not exist anymore. There is a trivial replacement for it, and it is the first method: // Before $article = $this->Article->find(’first’); // Now $article = $this->Articles->find()->first(); // Before $article = $this->Article->find(’first’, [ ’conditions’ => [’author_id’ => 1] ]); Guia de Migração para a versão 3.0 239 CakePHP Cookbook Documentation, Release 3.x // Now $article = $this->Articles->find(’all’, [ ’conditions’ => [’author_id’ => 1] ])->first(); If you are a loading a single record by its primary key, it will be better to just call get: $article = $this->Articles->get(10); Finder Method Changes Returning a query object from a find method has several advantages, but comes at a cost for people migrating from 2.x. If you had some custom find methods in your models, they will need some modifications. This is how you create custom finder methods in 3.0: class ArticlesTable { public function findPopular(Query $query, array $options) { return $query->where([’times_viewed’ > 1000]); } public function findFavorites(Query $query, array $options) { $for = $options[’for’]; return $query->matching(’Users.Favorites’, function ($q) use ($for) { return $q->where([’Favorites.user_id’ => $for]); }); } } As you can see, they are pretty straightforward, they get a Query object instead of an array and must return a Query object back. For 2.x users that implemented afterFind logic in custom finders, you should check out the map-reduce section, or use the features found on the collection-objects. If in your models you used to rely on having an afterFind for all find operations you can migrate this code in one of a few ways: 1. Override your entity constructor method and do additional formatting there. 2. Create accessor methods in your entity to create the virtual properties. 3. Redefine findAll() and attach a map/reduce function. In the 3rd case above your code would look like: public function findAll(Query $query, array $options) { $mapper = function ($row, $key, $mr) { // Your afterFind logic }; return $query->mapReduce($mapper); } You may have noticed that custom finders receive an options array, you can pass any extra information to your finder using this parameter. This is great news for people migrating from 2.x. Any of the query keys that were used in previous versions will be converted automatically for you in 3.x to the correct functions: 240 Capítulo 13. Apêndices CakePHP Cookbook Documentation, Release 3.x // This works in both CakePHP 2.x and 3.0 $articles = $this->Articles->find(’all’, [ ’fields’ => [’id’, ’title’], ’conditions’ => [ ’OR’ => [’title’ => ’Cake’, ’author_id’ => 1], ’published’ => true ], ’contain’ => [’Authors’], // The only change! (notice plural) ’order’ => [’title’ => ’DESC’], ’limit’ => 10, ]); Hopefully, migrating from older versions is not as daunting as it first seems, much of the features we have added helps you remove code as you can better express your requirements using the new ORM and at the same time the compatibility wrappers will help you rewrite those tiny differences in a fast and painless way. One of the other nice improvements in 3.x around finder methods is that behaviors can implement finder methods with no fuss. By simply defining a method with a matching name and signature on a Behavior the finder will automatically be available on any tables the behavior is attached to. Recursive and ContainableBehavior Removed In previous versions of CakePHP you needed to use recursive, bindModel(), unbindModel() and ContainableBehavior to reduce the loaded data to the set of associations you were interested in. A common tactic to manage associations was to set recursive to -1 and use Containable to manage all associations. In CakePHP 3.0 ContainableBehavior, recursive, bindModel, and unbindModel have all been removed. Instead the contain() method has been promoted to be a core feature of the query builder. Associations are only loaded if they are explicitly turned on. For example: $query = $this->Articles->find(’all’); Will only load data from the articles table as no associations have been included. To load articles and their related authors you would do: $query = $this->Articles->find(’all’)->contain([’Authors’]); By only loading associated data that has been specifically requested you spend less time fighting the ORM trying to get only the data you want. No afterFind Event or Virtual Fields In previous versions of CakePHP you needed to make extensive use of the afterFind callback and virtual fields in order to create generated data properties. These features have been removed in 3.0. Because of how ResultSets iteratively generate entities, the afterFind callback was not possible. Both afterFind and virtual fields can largely be replaced with virtual properies on entities. For example if your User entity has both first and last name columns you can add an accessor for full_name and generate the property on the fly: namespace App\Model\Entity; Guia de Migração para a versão 3.0 241 CakePHP Cookbook Documentation, Release 3.x use Cake\ORM\Entity; class User extends Entity { public function getFullName() { return $this->first_name . ’ } } ’ . $this->last_name; Once defined you can access your new property using $user->full_name. Using the map-reduce features of the ORM allow you to build aggregated data from your results, which is another use case that the afterFind callback was often used for. While virtual fields are no longer an explicit feature of the ORM, adding calculated fields is easy to do in your finder methods. By using the query builder and expression objects you can achieve the same results that virtual fields gave: namespace App\Model\Table; use Cake\ORM\Table; use Cake\ORM\Query; class ReviewsTable extends Table { function findAverage(Query $query, array $options = []) { $avg = $query->func()->avg(’rating’); $query->select([’average’ => $avg]); return $query; } } Associations No Longer Defined as Properties In previous versions of CakePHP the various associations your models had were defined in properties like $belongsTo and $hasMany. In CakePHP 3.0, associations are created with methods. Using methods allows us to sidestep the many limitations class definitions have, and provide only one way to define associations. Your initialize method and all other parts of your application code, interact with the same API when manipulating associations: namespace App\Model\Table; use Cake\ORM\Table; use Cake\ORM\Query; class ReviewsTable extends Table { public function initialize(array $config) { $this->belongsTo(’Movies’); $this->hasOne(’Ratings’); $this->hasMany(’Comments’) $this->belongsToMany(’Tags’) } 242 Capítulo 13. Apêndices CakePHP Cookbook Documentation, Release 3.x } As you can see from the example above each of the association types uses a method to create the association. One other difference is that hasAndBelongsToMany has been renamed to belongsToMany. To find out more about creating associations in 3.0 see the section on table-associations. Another welcome improvement to CakePHP is the ability to create your own association classes. If you have association types that are not covered by the built-in relation types you can create a custom Association sub-class and define the association logic you need. Validation No Longer Defined as a Property Like associations, validation rules were defined as a class property in previous versions of CakePHP. This array would then be lazily transformed into a ModelValidator object. This transformation step added a layer of indirection, complicating rule changes at runtime. Futhermore, validation rules being defined as a property made it difficult for a model to have multiple sets of validation rules. In CakePHP 3.0, both these problems have been remedied. Validation rules are always built with a Validator object, and it is trivial to have multiple sets of rules: namespace App\Model\Table; use Cake\ORM\Table; use Cake\ORM\Query; class ReviewsTable extends Table { public function validationDefault($validator) { $validator->validatePresence(’body’) ->add(’body’, ’length’, [ ’rule’ => [’minLength’, 20], ’message’ => ’Reviews must be 20 characters or more’, ]) ->add(’user_id’, ’exists’, [ ’rule’ => function ($value, $context) { $q = $this->association(’Users’) ->find() ->where([’id’ => $value]); return $q->count() === 1; }, ’message’ => ’A valid user is required.’ ]); return $validator; } } You can define as many validation methods as you need. Each method should be prefixed with validation and accept a $validator argument. You can then use your validators when saving using the validate option. See the documentation on saving-entities for more information. Guia de Migração para a versão 3.0 243 CakePHP Cookbook Documentation, Release 3.x Identifier Quoting Disabled by Default In the past CakePHP has always quoted identifiers. Parsing SQL snippets and attempting to quote identifiers was both error prone and expensive. If you are following the conventions CakePHP sets out, the cost of identifier quoting far outweighs any benefit it provides. Because of this identifier quoting has been disabled by default in 3.0. You should only need to enable identifier quoting if you are using column names or table names that contain special characters or are reserved words. If required, you can enable identifier quoting when configuring a connection: // In config/app.php ’Datasources’ => [ ’default’ => [ ’className’ => ’Cake\Database\Driver\Mysql’, ’login’ => ’root’, ’password’ => ’super_secret’, ’host’ => ’localhost’, ’database’ => ’cakephp’, ’quoteIdentifiers’ => true ] ], Note: Identifiers in QueryExpression objects will not be quoted, and you will need to quote them manually or use IdentifierExpression objects. Updating Behaviors Like most ORM related features, behaviors have changed in 3.0 as well. They now attach to Table instances which are the conceptual descendent of the Model class in previous versions of CakePHP. There are a few key differences from behaviors in CakePHP 2.x: • Behaviors are no longer shared across multiple tables. This means you no longer have to ‘namespace’ settings stored in a behavior. Each table using a behavior will get its own instance. • The method signatures for mixin methods have changed. • The method signatures for callback methods have changed. • The base class for behaviors have changed. • Behaviors can easily add finder methods. New Base Class The base class for behaviors has changed. Behaviors should now extend Cake\ORM\Behavior; if a behavior does not extend this class an exception will be raised. In addition to the base class changing, the constructor for behaviors has been modified, and the startup method has been removed. Behaviors that need access to the table they are attached to should define a constructor: 244 Capítulo 13. Apêndices CakePHP Cookbook Documentation, Release 3.x namespace App\Model\Behavior; use Cake\ORM\Behavior; class SluggableBehavior extends Behavior { protected $_table; public function __construct(Table $table, array $config) { parent::__construct($table, $config); $this->_table = $table; } } Mixin Methods Signature Changes Behaviors continue to offer the ability to add ‘mixin’ methods to Table objects, however the method signature for these methods has changed. In CakePHP 3.0, behavior mixin methods can expect the same arguments provided to the table ‘method’. For example: // Assume table has a slug() method provided by a behavior. $table->slug($someValue); The behavior providing the slug method will receive only 1 argument, and its method signature should look like: public function slug($value) { // Code here. } Callback Method Signature Changes Behavior callbacks have been unified with all other listener methods. Instead of their previous arguments, they need to expect an event object as their first argument: public function beforeFind(Event $event, Query $query, array $options) { // Code. } See table-callbacks for the signatures of all the callbacks a behavior can subscribe to. Informações Gerais CakePHP Development Process Here we attempt to explain the process we use when developing the CakePHP framework. We rely heavily on community interaction through tickets and IRC chat. IRC is the best place to find members of the Informações Gerais 245 CakePHP Cookbook Documentation, Release 3.x development team10 and discuss ideas, the latest code, and make general comments. If something more formal needs to be proposed or there is a problem with a release, the ticket system is the best place to share your thoughts. We currently maintain 4 versions of CakePHP. • stable : Tagged releases intended for production where stability is more important than features. Issues filed against these releases will be fixed in the related branch, and be part of the next release. • maintenance branch : Development branches become maintenance branches once a stable release point has been reached. Maintenance branches are where all bugfixes are committed before making their way into a stable release. Maintenance branches have the same name as the major version they are for example 1.2. If you are using a stable release and need fixes that haven’t made their way into a stable release check here. • development branches : Development branches contain leading edge fixes and features. They are named after the version number they are for example 1.3. Once development branches have reached a stable release point they become maintenance branches, and no further new features are introduced unless absolutely necessary. • feature branches : Feature branches contain unfinished or possibly unstable features and are recommended only for power users interested in the most advanced feature set and willing to contribute back to the community. Feature branches are named with the following convention version-feature. An example would be 1.3-router Which would contain new features for the Router for 1.3. Hopefully this will help you understand what version is right for you. Once you pick your version you may feel compelled to contribute a bug report or make general comments on the code. • If you are using a stable version or maintenance branch, please submit tickets or discuss with us on IRC. • If you are using the development branch or feature branch, the first place to go is IRC. If you have a comment and cannot reach us in IRC after a day or two, please submit a ticket. If you find an issue, the best answer is to write a test. The best advice we can offer in writing tests is to look at the ones included in the core. As always, if you have any questions or comments, visit us at #cakephp on irc.freenode.net. Glossary routing array An array of attributes that are passed to Router::url(). They typically look like: [’controller’ => ’Posts’, ’action’ => ’view’, 5] HTML attributes An array of key => values that are composed into HTML attributes. For example: // Given [’class’ => ’my-class’, ’target’ => ’_blank’] // Would generate class="my-class" target="_blank" 10 https://github.com/cakephp?tab=members 246 Capítulo 13. Apêndices CakePHP Cookbook Documentation, Release 3.x If an option can be minimized or accepts it’s name as the value, then true can be used: // Given [’checked’ => true] // Would generate checked="checked" plugin syntax Plugin syntax refers to the dot separated class name indicating classes are part of a plugin: // The plugin is "DebugKit", and the class name is "Toolbar". ’DebugKit.Toolbar’ // The plugin is "AcmeCorp/Tools", and the class name is "Toolbar". ’AcmeCorp/Tools.Toolbar’ dot notation Dot notation defines an array path, by separating nested levels with . For example: Cache.default.engine Would point to the following value: [ ’Cache’ => [ ’default’ => [ ’engine’ => ’File’ ] ] ] CSRF Cross Site Request Forgery. Prevents replay attacks, double submissions and forged requests from other domains. CDN Content Delivery Network. A 3rd party vendor you can pay to help distribute your content to data centers around the world. This helps put your static assets closer to geographically distributed users. routes.php A file in config directory that contains routing configuration. This file is included before each request is processed. It should connect all the routes your application needs so requests can be routed to the correct controller + action. DRY Don’t repeat yourself. Is a principle of software development aimed at reducing repetition of information of all kinds. In CakePHP DRY is used to allow you to code things once and re-use them across your application. Informações Gerais 247 CakePHP Cookbook Documentation, Release 3.x 248 Capítulo 13. Apêndices CAPÍTULO 14 Índices e Tabelas • genindex • modindex 249 CakePHP Cookbook Documentation, Release 3.x 250 Capítulo 14. Índices e Tabelas PHP Namespace Index c Cake\Console\Exception, 173 Cake\Controller\Component, 125 Cake\Controller\Exception, 172 Cake\Core, 40 Cake\Core\Configure, 44 Cake\Core\Exception, 173 Cake\Database\Exception, 173 Cake\Error, 177 Cake\Network, 90 Cake\Network\Exception, 171 Cake\ORM\Exception, 173 Cake\Routing, 47 Cake\Routing\Exception, 173 Cake\View\Exception, 172 251 CakePHP Cookbook Documentation, Release 3.x 252 PHP Namespace Index Index Symbols AuthComponent (class), 104 :action, 48 :controller, 48 :plugin, 48 $this->request, 65, 90 $this->response, 71, 97 __construct() (Component method), 138 B BadRequestException, 171 beforeFilter() (Controller method), 81 beforeRedirect() (Component method), 139 beforeRender() (Component method), 139 beforeRender() (Controller method), 81 beforeScaffold() (Controller method), 84 A acceptLanguage() (Cake\Network\Request method), blackHole() (SecurityComponent method), 122 Blocks (View property), 152 71, 97 blocks() (View method), 151 accepts() (Cake\Network\Request method), 71, 96 accepts() (RequestHandlerComponent method), 129 C addDetector() (Cake\Network\Request method), 68, cache() (Cake\Network\Response method), 74, 100 94 addInputType() (RequestHandlerComponent cacheAction (Controller property), 90 Cake\Console\Exception (namespace), 173 method), 131 Cake\Controller\Component (namespace), 117, 120, addScript() (View method), 151 125 admin routing, 53 Cake\Controller\Exception (namespace), 172 afterFilter() (Controller method), 81 Cake\Core (namespace), 40 afterScaffoldSave() (Controller method), 84 Cake\Core\Configure (namespace), 44 afterScaffoldSaveError() (Controller method), 84 Cake\Core\Exception (namespace), 173 allow() (AuthComponent method), 115 allowedActions (SecurityComponent property), 123 Cake\Database\Exception (namespace), 173 allowedControllers (SecurityComponent property), Cake\Error (namespace), 177 Cake\Network (namespace), 65, 90 123 allowMethod() (Cake\Network\Request method), Cake\Network\Exception (namespace), 171 Cake\ORM\Exception (namespace), 173 70, 96 Cake\Routing (namespace), 47 app.php, 37 Cake\Routing\Exception (namespace), 173 app.php.default, 37 Cake\View\Exception (namespace), 172 append() (View method), 151 CDN, 247 application exceptions, 174 charset() (Cake\Network\Response method), 72, 98 assign() (View method), 151 253 CakePHP Cookbook Documentation, Release 3.x E check() (Cake\Controller\Component\CookieComponent method), 118 element() (View method), 151 check() (Cake\Core\Configure method), 41 elementCache (View property), 152 check() (Session method), 169 end() (View method), 151 check() (SessionComponent method), 134 env() (Cake\Network\Request method), 67, 92 checkNotModified() (Cake\Network\Response etag() (Cake\Network\Response method), 76, 101 method), 77, 102 Exception, 173 clientIp() (Cake\Network\Request method), 70, 96 ExceptionRenderer (class in Cake\Core\Exception), Component (class), 138 174 components (Controller property), 90 excerpt() (Cake\Error\Debugger method), 179 config() (Cake\Core\Configure method), 42 expires() (Cake\Network\Response method), 76, 101 ConfigEngineInterface (interface in extend() (View method), 152 Cake\Core\Configure), 44 extensions() (Cake\Routing\Router method), 56 configuration, 37 F Configure (class in Cake\Core), 40 connect() (Cake\Routing\Router method), 48 fetch() (View method), 151 ConsoleException, 173 file extensions, 56 constructClasses() (Controller method), 85 file() (Cake\Network\Response method), 72, 98 consume() (Cake\Core\Configure method), 42 flash() (Controller method), 84 Controller (class), 81 FlashComponent (class in CookieComponent (class in Cake\Controller\Component), 120 Cake\Controller\Component), 117 ForbiddenException, 171 CSRF, 247 D G getType() (Cake\Error\Debugger method), 179 data() (Cake\Network\Request method), 66, 92 getVar() (View method), 150 debug() (global function), 177 getVars() (View method), 150 Debugger (class in Cake\Error), 177 greedy star, 48 delete() (Cake\Controller\Component\CookieComponent H method), 118 delete() (Cake\Core\Configure method), 41 header() (Cake\Network\Request method), 70, 96 delete() (Session method), 169 header() (Cake\Network\Response method), 73, 99 delete() (SessionComponent method), 134 helpers (Controller property), 89 deny() (AuthComponent method), 115 host() (Cake\Network\Request method), 70, 95 destroy() (Session method), 169 HTML attributes, 246 destroy() (SessionComponent method), 134 disableCache() (Cake\Network\Response method), I 74, 100 identify() (AuthComponent method), 106 disableCache() (Controller method), 85 IniConfig (class in Cake\Core\Configure), 45 domain() (Cake\Network\Request method), 70, 95 initialize() (Component method), 138 dot notation, 247 input() (Cake\Network\Request method), 67, 92 drop() (Cake\Core\Configure method), 42 InternalErrorException, 172 DRY, 247 is() (Cake\Network\Request method), 68, 93 dump() (Cake\Core\Configure method), 43 isAtom() (RequestHandlerComponent method), 129 dump() (Cake\Core\Configure\ConfigEngineInterface isMobile() (RequestHandlerComponent method), method), 45 130 dump() (Cake\Error\Debugger method), 177 isRss() (RequestHandlerComponent method), 129 isWap() (RequestHandlerComponent method), 130 254 Index CakePHP Cookbook Documentation, Release 3.x isXml() (RequestHandlerComponent method), 129 L layout (View property), 152 load() (Cake\Core\Configure method), 42 loadModel() (Controller method), 88 log() (Cake\Error\Debugger method), 178 logout() (AuthComponent method), 112 M mapResources() (Cake\Routing\Router method), 57 method() (Cake\Network\Request method), 70, 96 MethodNotAllowedException, 172 MissingActionException, 172 MissingBehaviorException, 173 MissingCellException, 172 MissingCellViewException, 172 MissingComponentException, 172 MissingConnectionException, 173 MissingControllerException, 173 MissingDispatcherFilterException, 173 MissingDriverException, 173 MissingElementException, 172 MissingEntityException, 173 MissingExtensionException, 173 MissingHelperException, 172 MissingLayoutException, 172 MissingRouteException, 173 MissingShellException, 173 MissingShellMethodException, 173 MissingTableException, 173 MissingTaskException, 173 MissingViewException, 172 modified() (Cake\Network\Response method), 76, 102 N name (Controller property), 88 NotFoundException, 172 NotImplementedException, 172 O output (View property), 152 P paginate (Controller property), 90 paginate() (Controller method), 86 Index PaginatorComponent (class in Cake\Controller\Component), 125 passed arguments, 59 PhpConfig (class in Cake\Core\Configure), 45 plugin routing, 55 plugin syntax, 247 plugin() (Cake\Routing\Router method), 55 postConditions() (Controller method), 85 prefers() (RequestHandlerComponent method), 131 prefix routing, 53 prefix() (Cake\Routing\Router method), 53 PrivateActionException, 172 Q query() (Cake\Network\Request method), 66, 91 R read() (Cake\Controller\Component\CookieComponent method), 118 read() (Cake\Core\Configure method), 41 read() (Cake\Core\Configure\ConfigEngineInterface method), 45 read() (Session method), 168 read() (SessionComponent method), 133 redirect() (Cake\Routing\Router method), 61 redirect() (Controller method), 83 redirectUrl() (AuthComponent method), 107 referer() (Cake\Network\Request method), 70, 96 referer() (Controller method), 85 render() (Controller method), 82 renderAs() (RequestHandlerComponent method), 132 renew() (Session method), 169 Request (class in Cake\Network), 65, 90 request (View property), 152 requestAction() (Cake\Routing\RequestActionTrait method), 62 requestAction() (Controller method), 86 RequestActionTrait (trait in Cake\Routing), 62 RequestHandlerComponent (class), 128 requireAuth() (SecurityComponent method), 122 requireSecure() (SecurityComponent method), 122 respondAs() (RequestHandlerComponent method), 132 Response (class in Cake\Network), 71, 97 responseHeader() (Cake\Core\Exception\Exception method), 173 255 CakePHP Cookbook Documentation, Release 3.x responseType() (RequestHandlerComponent write() (Cake\Core\Configure method), 40 write() (Session method), 168 method), 132 write() (SessionComponent method), 133 restore() (Cake\Core\Configure method), 43 Router (class in Cake\Routing), 47 routes.php, 47, 247 routing array, 246 S scaffoldError() (Controller method), 84 SecurityComponent (class), 121 send() (Cake\Network\Response method), 77, 103 Session (class), 168 SessionComponent (class), 133 set() (Controller method), 82 set() (View method), 150 setUser() (AuthComponent method), 112 sharable() (Cake\Network\Response method), 75, 101 shutdown() (Component method), 139 start() (View method), 151 startup() (Component method), 138 store() (Cake\Core\Configure method), 43 subdomains() (Cake\Network\Request method), 70, 95 T trace() (Cake\Error\Debugger method), 178 trailing star, 48 type() (Cake\Network\Response method), 72, 98 U UnauthorizedException, 171 unlockedFields (SecurityComponent property), 123 url() (Cake\Routing\Router method), 60 user() (AuthComponent method), 112 uses (Controller property), 89 uuid() (View method), 151 V validatePost (SecurityComponent property), 123 vary() (Cake\Network\Response method), 77, 102 View (class), 150 viewClassMap() (RequestHandlerComponent method), 133 W write() (Cake\Controller\Component\CookieComponent method), 118 256 Index
© Copyright 2024