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.
Esse tutorial vai guiar você através da criação de uma simples aplicação de marcação (bookmarker). Para começar, nós vamos instalar o CakePHP, criar nosso banco de dados, e usar as ferramentas que o CakePHP fornece para subir nossa aplicação de forma rápida.
Aqui está o que você vai precisar:
Um servidor de banco de dados. Nós vamos usar o servidor MySQL neste
tutorial. Você precisa saber o suficiente sobre SQL para criar um banco de
dados: O CakePHP vai tomar as rédeas a partir daí. Por nós estarmos
usando o MySQL, também certifique-se que você tem a extensão pdo_mysql
habilitada no PHP.
Conhecimento básico sobre PHP.
Vamos começar!
A maneira mais fácil de instalar o CakePHP é usando Composer, um gerenciador de dependências para o PHP. É uma forma simples de instalar o CakePHP a partir de seu terminal ou prompt de comando. Primeiro, você precisa baixar e instalar o Composer. Se você tiver instalada a extensão cURL do PHP, execute o seguinte comando:
curl -s https://getcomposer.org/installer | php
Ao invés disso, você também pode baixar o arquivo composer.phar
do
site oficial.
Em seguida, basta digitar a seguinte linha no seu terminal a partir do diretório
onde se localiza o arquivo composer.phar
para instalar o esqueleto de
aplicações do CakePHP no diretório bookmarker
.
php composer.phar create-project --prefer-dist cakephp/app:^3.8 bookmarker
A vantagem de usar Composer é que ele irá completar automaticamente um conjunto importante de tarefas, como configurar as permissões de arquivo e criar a sua config/app.php.
Há outras maneiras de instalar o CakePHP. Se você não puder ou não quiser usar Composer, veja a seção Instalação.
Independentemente de como você baixou o CakePHP, uma vez que sua instalação for concluída, a estrutura dos diretórios deve ficar parecida com o seguinte:
/bookmarker
/bin
/config
/logs
/plugins
/src
/tests
/tmp
/vendor
/webroot
.editorconfig
.gitignore
.htaccess
.travis.yml
composer.json
index.php
phpunit.xml.dist
README.md
Agora pode ser um bom momento para aprender sobre como a estrutura de diretórios do CakePHP funciona: Confira a seção Estrutura de pastas do CakePHP.
Podemos checar rapidamente que a nossa instalação está correta, verificando a página inicial padrão. Antes que você possa fazer isso, você vai precisar iniciar o servidor de desenvolvimento:
bin/cake server
Isto irá iniciar o servidor embutido do PHP na porta 8765. Abra
http://localhost:8765
em seu navegador para ver a página de boas-vindas.
Todas as verificações devem estar checadas corretamente, a não ser a conexão com
banco de dados do CakePHP. Se não, você pode precisar instalar extensões do PHP
adicionais, ou definir permissões de diretório.
Em seguida, vamos criar o banco de dados para a nossa aplicação. Se você
ainda não tiver feito isso, crie um banco de dados vazio para uso
nesse tutorial, com um nome de sua escolha, por exemplo, cake_bookmarks
.
Você pode executar o seguinte SQL para criar as tabelas necessárias:
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) NOT NULL,
password VARCHAR(255) NOT NULL,
created DATETIME,
modified DATETIME
);
CREATE TABLE bookmarks (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
title VARCHAR(50),
description TEXT,
url TEXT,
created DATETIME,
modified DATETIME,
FOREIGN KEY user_key (user_id) REFERENCES users(id)
);
CREATE TABLE tags (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255),
created DATETIME,
modified DATETIME,
UNIQUE KEY (title)
);
CREATE TABLE bookmarks_tags (
bookmark_id INT NOT NULL,
tag_id INT NOT NULL,
PRIMARY KEY (bookmark_id, tag_id),
INDEX tag_idx (tag_id, bookmark_id),
FOREIGN KEY tag_key(tag_id) REFERENCES tags(id),
FOREIGN KEY bookmark_key(bookmark_id) REFERENCES bookmarks(id)
);
Você deve ter notado que a tabela bookmarks_tags
utilizada uma chave
primária composta. O CakePHP suporta chaves primárias compostas em quase todos os
lugares, tornando mais fácil construir aplicações multi-arrendados.
Os nomes de tabelas e colunas que usamos não foram arbitrárias. Usando convenções de nomenclatura do CakePHP, podemos alavancar o desenvolvimento e evitar ter de configurar o framework. O CakePHP é flexível o suficiente para acomodar até mesmo esquemas de banco de dados legados inconsistentes, mas aderir às convenções vai lhe poupar tempo.
Em seguida, vamos dizer ao CakePHP onde o nosso banco de dados está e como se conectar a ele. Para muitos, esta será a primeira e última vez que você vai precisar configurar qualquer coisa.
A configuração é bem simples: basta alterar os valores do array
Datasources.default
no arquivo config/app.php pelos que se
aplicam à sua configuração. A amostra completa da gama de configurações pode
ser algo como o seguinte:
return [
// Mais configuração acima.
'Datasources' => [
'default' => [
'className' => 'Cake\Database\Connection',
'driver' => 'Cake\Database\Driver\Mysql',
'persistent' => false,
'host' => 'localhost',
'username' => 'cakephp',
'password' => 'AngelF00dC4k3~',
'database' => 'cake_bookmarks',
'encoding' => 'utf8',
'timezone' => 'UTC',
'cacheMetadata' => true,
],
],
// Mais configuração abaixo.
];
Depois de salvar o seu arquivo config/app.php, você deve notar que a mensagem ‘CakePHP is able to connect to the database’ tem uma marca de verificação.
Nota
Uma cópia do arquivo de configuração padrão do CakePHP é encontrado em config/app.default.php.
Devido a nosso banco de dados seguir as convenções do CakePHP, podemos usar o bake console para gerar rapidamente uma aplicação básica . Em sua linha de comando execute:
bin/cake bake all users
bin/cake bake all bookmarks
bin/cake bake all tags
Isso irá gerar os controllers, models, views, seus casos de teste
correspondentes, e fixtures para os nossos users, bookmarks e tags. Se você
parou seu servidor, reinicie-o e vá para http://localhost:8765/bookmarks
.
Você deverá ver uma aplicação que dá acesso básico, mas funcional a tabelas de banco de dados. Adicione alguns users, bookmarks e tags.
Quando você criou seus users, você deve ter notado que as senhas foram armazenadas como texto simples. Isso é muito ruim do ponto de vista da segurança, por isso vamos consertar isso.
Este também é um bom momento para falar sobre a camada de modelo. No CakePHP, separamos os métodos que operam em uma coleção de objetos, e um único objeto em diferentes classes. Métodos que operam na recolha de entidades são colocadas na classe Table, enquanto as características pertencentes a um único registro são colocados na classe Entity.
Por exemplo, a criptografia de senha é feita no registro individual, por isso vamos implementar esse comportamento no objeto entidade. Dada a circunstância de nós querermos criptografar a senha cada vez que é definida, vamos usar um método modificador/definidor. O CakePHP vai chamar métodos de definição baseados em convenções a qualquer momento que uma propriedade é definida em uma de suas entidades. Vamos adicionar um definidor para a senha. Em src/Model/Entity/User.php adicione o seguinte:
namespace App\Model\Entity;
use Cake\ORM\Entity;
use Cake\Auth\DefaultPasswordHasher;
class User extends Entity
{
// Code from bake.
protected function _setPassword($value)
{
$hasher = new DefaultPasswordHasher();
return $hasher->hash($value);
}
}
Agora atualize um dos usuários que você criou anteriormente, se você alterar sua senha, você deve ver um senha criptografada ao invés do valor original nas páginas de lista ou visualização. O CakePHP criptografa senhas com bcrypt por padrão. Você também pode usar sha1 ou md5 caso venha a trabalhar com um banco de dados existente.
Agora que estamos armazenando senhas com segurança, podemos construir algumas características mais interessantes em nossa aplicação. Uma vez que você acumulou uma coleção de bookmarks, é útil ser capaz de pesquisar através deles por tag. Em seguida, vamos implementar uma rota, a ação do controller, e um método localizador para pesquisar através de bookmarks por tag.
Idealmente, nós teríamos uma URL que se parece com
http://localhost:8765/bookmarks/tagged/funny/cat/gifs
. Isso deveria nos
permitir a encontrar todos os bookmarks que têm as tags ‘funny’, ‘cat’ e
‘gifs’. Antes de podermos implementar isso, vamos adicionar uma nova rota. Em
config/routes.php, adicione o seguinte na parte superior do arquivo:
Router::scope(
'/bookmarks',
['controller' => 'Bookmarks'],
function ($routes) {
$routes->connect('/tagged/*', ['action' => 'tags']);
}
);
O trecho acima define uma nova “rota” que liga o caminho /bookmarks/tagged/*
, a
BookmarksController::tags()
. Ao definir rotas, você pode isolar como
suas URLs parecerão, de como eles são implementadas. Se fôssemos visitar
http://localhost:8765/bookmarks/tagged
, deveriamos ver uma página de erro
informativa do CakePHP. Vamos implementar esse método ausente agora. Em
src/Controller/BookmarksController.php adicione o seguinte trecho:
public function tags()
{
$tags = $this->request->getParam('pass');
$bookmarks = $this->Bookmarks->find('tagged', [
'tags' => $tags
]);
$this->set(compact('bookmarks', 'tags'));
}
No CakePHP nós gostamos de manter as nossas ações do controller enxutas, e
colocar a maior parte da lógica de nossa aplicação nos modelos. Se você fosse
visitar a URL /bookmarks/tagged
agora, você veria um erro sobre o
método findTagged
não estar implementado ainda, então vamos fazer isso. Em
src/Model/Table/BookmarksTable.php adicione o seguinte:
public function findTagged(Query $query, array $options)
{
$bookmarks = $this->find()
->select(['id', 'url', 'title', 'description']);
if (empty($options['tags'])) {
$bookmarks
->leftJoinWith('Tags')
->where(['Tags.title IS' => null]);
} else {
$bookmarks
->innerJoinWith('Tags')
->where(['Tags.title IN ' => $options['tags']]);
}
return $bookmarks->group(['Bookmarks.id']);
}
Nós implementamos um método
localizador customizado. Este é um conceito
muito poderoso no CakePHP que lhe permite construir consultas reutilizáveis.
Em nossa pesquisa, nós alavancamos o método matching()
que nos habilita
encontrar bookmarks que têm uma tag ‘correspondente’.
Agora, se você visitar a URL /bookmarks/tagged
, o CakePHP irá mostrar um
erro e deixá-lo saber que você ainda não fez um arquivo view. Em seguida,
vamos construir o arquivo view para a nossa ação tags
. Em
src/Template/Bookmarks/tags.ctp coloque o seguinte conteúdo:
<h1>
Bookmarks tagged with
<?= $this->Text->toList(h($tags)) ?>
</h1>
<section>
<?php foreach ($bookmarks as $bookmark): ?>
<article>
<h4><?= $this->Html->link($bookmark->title, $bookmark->url) ?></h4>
<small><?= h($bookmark->url) ?></small>
<?= $this->Text->autoParagraph(h($bookmark->description)) ?>
</article>
<?php endforeach; ?>
</section>
O CakePHP espera que os nossos templates sigam a convenção de nomenclatura onde o nome do template é a versão minúscula e grifada do nome da ação do controller.
Você pode perceber que fomos capazes de utilizar as variáveis $tags
e
bookmarks
em nossa view. Quando usamos o método set()
em nosso
controller, automaticamente definimos variáveis específicas que devem ser
enviadas para a view. A view vai tornar todas as variáveis passadas
disponíveis nos templates como variáveis locais.
Em nossa view, usamos alguns dos helpers nativos do CakePHP. Helpers são usados para criar lógica re-utilizável para a formatação de dados, a criação de HTML ou outra saída da view.
Agora você deve ser capaz de visitar a URL /bookmarks/tagged/funny
e ver
todas os bookmarks com a tag ‘funny’.
Até agora, nós criamos uma aplicação básica para gerenciar bookmarks, tags e users. No entanto, todos podem ver as tags de todos os usuários. No próximo capítulo, vamos implementar a autenticação e restringir os bookmarks visíveis para somente aqueles que pertencem ao usuário atual.
Agora vá a Tutorial - Criando um Bookmarker - Parte 2 para continuar a construir sua aplicação ou mergulhe na documentação para saber mais sobre o que CakePHP pode fazer por você.
Depois de terminar a primeira parte deste tutorial, você deve ter uma aplicação muito básica. Neste capítulo iremos adicionar autenticação e restringir as bookmarks para que cada usuário possa ver/modificar somente aquelas tags que possuam.
No CakePHP, a autenticação é feita por Components (Componentes). Os Components podem ser considerados como formas de criar pedaços reutilizáveis de código relacionado a controllers com uma característica específica ou conceito. Os components também podem se ligar ao evento do ciclo de vida do controller e interagir com a sua aplicação. Para começar, vamos adicionar o AuthComponent a nossa aplicação. É essencial que cada método exija autenticação, por isso vamos acrescentar o AuthComponent em nosso AppController:
// Em src/Controller/AppController.php
namespace App\Controller;
use Cake\Controller\Controller;
class AppController extends Controller
{
public function initialize()
{
$this->loadComponent('Flash');
$this->loadComponent('Auth', [
'authenticate' => [
'Form' => [
'fields' => [
'username' => 'email',
'password' => 'password'
]
]
],
'loginAction' => [
'controller' => 'Users',
'action' => 'login'
]
]);
// Permite a ação display, assim nosso pages controller
// continua a funcionar.
$this->Auth->allow(['display']);
}
}
Acabamos de dizer ao CakePHP que queremos carregar os components Flash
e
Auth
. Além disso, temos a configuração personalizada do AuthComponent,
assim a nossa tabela users pode usar email
como username. Agora, se você for
a qualquer URL, você vai ser chutado para /users/login
, que irá
mostrar uma página de erro já que não escrevemos o código ainda.
Então, vamos criar a ação de login:
// Em src/Controller/UsersController.php
public function login()
{
if ($this->request->is('post')) {
$user = $this->Auth->identify();
if ($user) {
$this->Auth->setUser($user);
return $this->redirect($this->Auth->redirectUrl());
}
$this->Flash->error('Your username or password is incorrect.');
}
}
E em src/Template/Users/login.ctp adicione o seguinte trecho:
<h1>Login</h1>
<?= $this->Form->create() ?>
<?= $this->Form->input('email') ?>
<?= $this->Form->input('password') ?>
<?= $this->Form->button('Login') ?>
<?= $this->Form->end() ?>
Agora que temos um formulário de login simples, devemos ser capazes de efetuar login com um dos users que tenham senha criptografada.
Nota
Se nenhum de seus users tem senha criptografada, comente a linha
loadComponent('Auth')
. Então vá e edite o user, salvando uma nova
senha para ele.
Agora você deve ser capaz de entrar. Se não, certifique-se que você está usando um user que tenha senha criptografada.
Agora que as pessoas podem efetuar o login, você provavelmente vai querer
fornecer uma maneira de encerrar a sessão também. Mais uma vez, no
UsersController
, adicione o seguinte código:
public function logout()
{
$this->Flash->success('You are now logged out.');
return $this->redirect($this->Auth->logout());
}
Agora você pode visitar /users/logout
para sair e ser enviado à
página de login.
Se você não estiver logado e tentar visitar / usuários / adicionar você vai ser expulso para a página de login. Devemos corrigir isso se quisermos que as pessoas se inscrevam em nossa aplicação. No UsersController adicione o seguinte trecho:
public function beforeFilter(\Cake\Event\Event $event)
{
$this->Auth->allow(['add']);
}
O texto acima diz ao AuthComponent
que a ação add
não requer
autenticação ou autorização. Você pode querer dedicar algum tempo para limpar a
/users/add
e remover os links enganosos, ou continuar para a próxima
seção. Nós não estaremos construindo a edição do usuário, visualização ou
listagem neste tutorial, então eles não vão funcionar, já que o
AuthComponent
vai negar-lhe acesso a essas ações do controller.
Agora que os usuários podem se conectar, nós vamos querer limitar os
bookmarks que podem ver para aqueles que fizeram. Nós vamos fazer isso
usando um adaptador de ‘autorização’. Sendo os nossos requisitos
bastante simples, podemos escrever um código em nossa
BookmarksController
. Mas antes de fazer isso, vamos querer dizer ao
AuthComponent como nossa aplicação vai autorizar ações. Em seu AppController
adicione o seguinte:
public function isAuthorized($user)
{
return false;
}
Além disso, adicione o seguinte à configuração para Auth
em seu
AppController
:
'authorize' => 'Controller',
Seu método initialize
agora deve parecer com:
public function initialize()
{
$this->loadComponent('Flash');
$this->loadComponent('Auth', [
'authorize'=> 'Controller',//adicionado essa linha
'authenticate' => [
'Form' => [
'fields' => [
'username' => 'email',
'password' => 'password'
]
]
],
'loginAction' => [
'controller' => 'Users',
'action' => 'login'
],
'unauthorizedRedirect' => $this->referer()
]);
// Permite a ação display, assim nosso pages controller
// continua a funcionar.
$this->Auth->allow(['display']);
}
Vamos usar como padrão, negação do acesso, e de forma incremental conceder
acesso onde faça sentido. Primeiro, vamos adicionar a lógica de autorização
para os bookmarks. Em seu BookmarksController
adicione o seguinte:
public function isAuthorized($user)
{
$action = $this->request->params['action'];
// As ações add e index são permitidas sempre.
if (in_array($action, ['index', 'add', 'tags'])) {
return true;
}
// Todas as outras ações requerem um id.
if (!$this->request->getParam('pass.0')) {
return false;
}
// Checa se o bookmark pertence ao user atual.
$id = $this->request->getParam('pass.0');
$bookmark = $this->Bookmarks->get($id);
if ($bookmark->user_id == $user['id']) {
return true;
}
return parent::isAuthorized($user);
}
Agora, se você tentar visualizar, editar ou excluir um bookmark que não pertença a você, você deve ser redirecionado para a página de onde veio. No entanto, não há nenhuma mensagem de erro sendo exibida, então vamos corrigir isso a seguir:
// In src/Template/Layout/default.ctp
// Under the existing flash message.
<?= $this->Flash->render('auth') ?>
Agora você deve ver as mensagens de erro de autorização.
Enquanto view e delete estão trabalhando, edit, add e index tem alguns problemas:
Ao adicionar um bookmark, você pode escolher o user.
Ao editar um bookmark, você pode escolher o user.
A página de listagem mostra os bookmarks de outros users.
Primeiramente, vamos refatorar o formulário de adição. Para começar
remova o input('user_id')
a partir de src/Template/Bookmarks/add.ctp.
Com isso removido, nós também vamos atualizar o método add:
public function add()
{
$bookmark = $this->Bookmarks->newEntity();
if ($this->request->is('post')) {
$bookmark = $this->Bookmarks->patchEntity($bookmark, $this->request->getData());
$bookmark->user_id = $this->Auth->user('id');
if ($this->Bookmarks->save($bookmark)) {
$this->Flash->success('The bookmark has been saved.');
return $this->redirect(['action' => 'index']);
}
$this->Flash->error('The bookmark could not be saved. Please, try again.');
}
$tags = $this->Bookmarks->Tags->find('list');
$this->set(compact('bookmark', 'tags'));
}
Ao definir a propriedade da entidade com os dados da sessão, nós removemos qualquer possibilidade do user modificar algo que não pertenca a ele. Nós vamos fazer o mesmo para o formulário edit e action edit. Sua ação edit deve ficar assim:
public function edit($id = null)
{
$bookmark = $this->Bookmarks->get($id, [
'contain' => ['Tags']
]);
if ($this->request->is(['patch', 'post', 'put'])) {
$bookmark = $this->Bookmarks->patchEntity($bookmark, $this->request->getData());
$bookmark->user_id = $this->Auth->user('id');
if ($this->Bookmarks->save($bookmark)) {
$this->Flash->success('The bookmark has been saved.');
return $this->redirect(['action' => 'index']);
}
$this->Flash->error('The bookmark could not be saved. Please, try again.');
}
$tags = $this->Bookmarks->Tags->find('list');
$this->set(compact('bookmark', 'tags'));
}
Agora, nós precisamos apenas exibir bookmarks para o user logado.
Nós podemos fazer isso ao atualizar a chamada para paginate()
. Altere sua
ação index:
public function index()
{
$this->paginate = [
'conditions' => [
'Bookmarks.user_id' => $this->Auth->user('id'),
]
];
$this->set('bookmarks', $this->paginate($this->Bookmarks));
}
Nós também devemos atualizar a action tags()
e o método localizador relacionado,
mas vamos deixar isso como um exercício para que você conclua por sí.
Agora, adicionar novas tags é um processo difícil, pois o TagsController
proíbe todos os acessos. Em vez de permitir o acesso, podemos melhorar a
interface do usuário para selecionar tags usando um campo de texto separado por
vírgulas. Isso permitirá dar uma melhor experiência para os nossos
usuários, e usar mais alguns grandes recursos no ORM.
Porque nós queremos uma maneira simples de acessar as tags formatados para uma entidade, podemos adicionar um campo virtual/computado para a entidade. Em src/Model/Entity/Bookmark.php adicione o seguinte:
use Cake\Collection\Collection;
protected function _getTagString()
{
if (isset($this->_properties['tag_string'])) {
return $this->_properties['tag_string'];
}
if (empty($this->tags)) {
return '';
}
$tags = new Collection($this->tags);
$str = $tags->reduce(function ($string, $tag) {
return $string . $tag->title . ', ';
}, '');
return trim($str, ', ');
}
Isso vai nos deixar acessar a propriedade computada $bookmark->tag_string
.
Vamos usar essa propriedade em inputs mais tarde. Lembre-se de adicionar a
propriedade tag_string
a lista _accessible
em sua entidade.
Em src/Model/Entity/Bookmark.php adicione o tag_string
ao
_accessible
desta forma:
protected $_accessible = [
'user_id' => true,
'title' => true,
'description' => true,
'url' => true,
'user' => true,
'tags' => true,
'tag_string' => true,
];
Com a entidade atualizado, podemos adicionar uma nova entrada para as nossas
tags. Nas views add e edit, substitua tags._ids
pelo seguinte:
<?= $this->Form->input('tag_string', ['type' => 'text']) ?>
Agora que podemos ver as tags como uma string existente, vamos querer salvar
os dados também. Por marcar o tag_string
como acessível, o ORM irá
copiar os dados do pedido em nossa entidade. Podemos usar um método
beforeSave
para analisar a cadeia tag e encontrar/construir as
entidades relacionadas. Adicione o seguinte em
src/Model/Table/BookmarksTable.php:
public function beforeSave($event, $entity, $options)
{
if ($entity->tag_string) {
$entity->tags = $this->_buildTags($entity->tag_string);
}
}
protected function _buildTags($tagString)
{
$new = array_unique(array_map('trim', explode(',', $tagString)));
$out = [];
$query = $this->Tags->find()
->where(['Tags.title IN' => $new]);
// Remove tags existentes da lista de novas tags.
foreach ($query->extract('title') as $existing) {
$index = array_search($existing, $new);
if ($index !== false) {
unset($new[$index]);
}
}
// Adiciona tags existentes.
foreach ($query as $tag) {
$out[] = $tag;
}
// Adiciona novas tags.
foreach ($new as $tag) {
$out[] = $this->Tags->newEntity(['title' => $tag]);
}
return $out;
}
Embora esse código seja um pouco mais complicado do que o que temos feito até agora, ele ajuda a mostrar o quão poderosa a ORM do CakePHP é. Você pode facilmente manipular resultados da consulta usando os métodos de Collections (Coleções), e lidar com situações em que você está criando entidades sob demanda com facilidade.
Nós expandimos nossa aplicação bookmarker para lidar com situações de autenticação e controle de autorização/acesso básico. Nós também adicionamos algumas melhorias agradáveis à UX, aproveitando os recursos FormHelper e ORM.
Obrigado por dispor do seu tempo para explorar o CakePHP. Em seguida, você pode saber mais sobre o Models (Modelos), ou você pode ler os Utilizando o CakePHP.