Eventos no CakePHP 2 — Usando o Observer Pattern (Padrão Observador)
Há alguns dias precisei pesquisar sobre “Eventos no CakePHP v2.x”. Meu ponto de partida foi a própria documentação do framework, o www.cakephp.org, porém não fiquei satisfeito com o resultado e continuei minha busca até que encontrei um excelente artigo no Blog do desenvolvedor Andy Carter, segue link: http://andy-carter.com/blog/events-in-cakephp-2-using-the-observer-pattern.
Como gostei muito do artigo, solicitei permissão ao Andy para fazer a tradução para o português e, aqui estamos. Vamos começar!
Aparentemente “Eventos” é uma funcionalidade do framework CakePHP, frequentemente ignorada por quem o usa. Introduzida na versão 2.1, o sistema de eventos fornece um meio de aplicar lógica ao que normalmente não pertence ao model (modelo) ou controller (controlador). Desenvolver em CakePHP consiste basicamente de fat models e skinny controllers (modelos gordos e controladores magros), mas existem algumas coisas que não pertencem a eles, portanto não deveriam ser colocadas no model só para deixá-los mais “gordos”.
Se você não é familiarizado com a ideia de fat models e skinny controllers, é basicamente isso: models contém a lógica/regra de negócio, coisas como recuperar, salvar e deletar dados; enquanto que os controllers preparam estes dados para serem exibidos para o usuário final através das views. No entanto, há trechos de códigos que não encaixam na organização normal do padrão MVC. Por exemplo:
Imagine que temos um User Model (entidade usuário) e queremos enviar um e-mail para o novo usuário (user) após o registro dele ser criado na nossa base de dados. O envio do e-mail não tem relação com a view, então também não pertence ao controller e não está diretamente relacionado ao trabalho com a base de dados (lógica/regra de negócio). Este envio é uma reação à lógica/regra de negócio da criação de um novo registro na base de dados, nosso novo usuário. É nessa parte que os Eventos aparecem.
CakePHP Observer Pattern (Padrão Observador)
Você deve ter ouvido falar do sistema de Eventos do CakePHP como o Observer Pattern (comum à abordagem de projeto MVC).
No Observer Pattern nós temos subjects (assuntos) que podem ser os models ou controllers no CakePHP; estes subjects disparam os Eventos.
Então nós temos observers (referenciado no CakePHP como listeners) que são “anexados” aos nossos subjects e “escutam” por algum Evento disparado pela aplicação.
Desenvolvendo com esse padrão de projeto nós podemos separar essas ações do código da aplicação. Se desenvolvermos plugins para o CakePHP, isto pode nos ajudar a deixar mais facilmente extensível para outros projetos. O código guardado em um Event Listener (“escutador” de Eventos) pode ser facilmente estendido ou sobrescrito no CakePHP devido a maneira como ele é “anexado” usando as configurações do App.
Disparando um Evento
Continuando com nosso exemplo do cadastro de um novo usuário, nosso User Model é o nosso subject (assunto) e queremos disparar um Evento depois da criação do usuário. Podemos fazer isso dentro do callback afterSave quando um novo registro/usuário for criado. Veja abaixo:
<?php
App::uses('CakeEvent', 'Event');
class User extends AppModel {
public function afterSave($created, $options = array()) {
parent::afterSave($created, $options);
if ($created === true) {
$Event = new CakeEvent('Model.User.created', $this, array(
'id' => $this->id,
'data' => $this->data[$this->alias]
));
$this->getEventManager()->dispatch($Event);
}
}
}
A primeira coisa que quero que vocês notem neste exemplo é que usamos “App::uses()” para incluir o CakeEvent, e criamos uma nova instância dele no nosso método “afterSave()”. Estamos nomeando nosso Evento como “Model.User.created”, passando o subject ($this) e associando alguns dados com o Evento (o terceiro parâmetro do CakeEvent).
Nomeamos o Evento “Model.User.created”, porém pode ser o nome que você desejar. O mais importante aqui é que este nome será usado no nosso listener (“escutador”) para disparar algum trecho de código. No entanto, recomendo o uso de pseudo name-spacing que usamos, isto é, basicamente o tipo do subject (‘Model’), o objeto do subject (‘User’) e para finalizar um nome descritivo para o Evento (‘created’).
Finalmente o método “dispatch()” notifica todos os listeners anexados do nosso Evento. Veremos como anexar listeners daqui a pouco!
Como você pode ver, nosso User Model não tem referência alguma com envio de e-mail ao novo usuário. Como foi falado anteriormente, ele não tem nada a ver com a interação com a nossa base de dados. Ao invés disso, estamos usando um listener para “observar” quando o novo usuário é criado graças ao recente Evento que foi disparado.
O Listener (“escutador”)
O lugar recomendado para manter nossos Event Listeners é no diretório app/Lib/Event, apesar disso a estrutura padrão de arquivos do CakePHP não vem configurada para isso, então você precisará criar o diretório Event se ele ainda não existir.
Ao contrário dos Models e dos Controllers, o CakePHP não estabelece uma convenção de nome para os listeners. Particularmente eu recomendo o uso do CamelCase no singular para o nome das classes, terminando com ‘Listener’ (e nomear o arquivo de acordo). Para nosso exemplo iremos criar um UserListener e salvá-lo em app/Lib/Event/UserListener.php.
CakePHP Event Listeners implementa a interface “CakeEventListener” que requer a presença de um método “implementedEvents()”. Então, nosso listener precisa, no mínimo, ficar parecido com algo assim:
<?php // In app/Lib/Event/UserListener.php
App::uses('CakeEventListener', 'Event');
class UserListener implements CakeEventListener {
public function implementedEvents() {
}
}
O método “implementedEvents()” é onde iremos mapear os nomes dos Eventos para os métodos que o listener estará observando. Para nosso exemplo nós temos o Evento “Model.User.created” que queremos escutar e enviar um e-mail de confirmação para o novo usuário quando este for disparado.
public function implementedEvents() {
return ['Model.User.created' => 'sendConfirmationEmail'];
}
Agora precisamos criar o método “sendConfirmationEmail()” no nosso listener.
<?php // In app/Lib/Event/UserListener.php
App::uses('CakeEventListener', 'Event');
App::uses('CakeEmail', 'Network/Email');class UserListener implements CakeEventListener {
public function implementedEvents() {
return ['Model.User.created' => 'sendConfirmationEmail'];
}public function sendConfirmationEmail(CakeEvent $Event) {
$Email = new CakeEmail('smtp');
$Email->from(array(
'no@no-replay.com.br' => 'Your Site'
));
$Email->to($Event->data['data']['email']);
$Email->subject('Your account has been created');
$Email->emailFormat('html');
$Email->template('new_user');
$Email->viewVars(array(
'data' => $Event->data['data']
));
$Email->send();
return;
}
}
Como estamos usando o “CakeEmail”, também precisamos incluí-lo no início do arquivo listener (observe no início do código acima).
App::uses('CakeEmail', 'Network/Email');
Os métodos mapeados no “implementedMethods()” precisam ser métodos da classe listener em questão, então “sendConfirmationEmail()” é um método do nosso “UserListener”. Os métodos tem um único parâmetro que é o “CakeEvent”. Como você pode ver no exemplo acima, este método contém todos os dados que passamos quando disparamos o Evento no nosso Model. Neste caso, teremos “$Event->data[‘id’]” e “$Event->data[‘data’]”. Qualquer dado modificado em “$Event->data” será acessível no nosso subject (Ex.: o Model) depois do Evento ser disparado.
Outra coisa a ser notada sobre Listeners é que eles não são associados diretamente com um Model. Então, apesar de nosso “UserListener” estar sendo anexado ao nosso Model User, ele não carrega o Model. Se você quiser usar um Model dentro de um listener, precisará usar “ClassRegistry::init()”.
Anexando os Listeners (“escutadores”)
A última coisa que precisamos fazer é “anexar” nossos listeners a alguma coisa para que os Eventos possam disparar. Tenho visto pessoas anexarem seus listeners diretamente do Model/Controller, porém uma melhor abordagem é fazer o anexamento através das configurações do App, criando um arquivo events.php.
// In app/Config/events.php
App::uses('ClassRegistry', 'Utility');
App::uses('UserListener', 'Event');
$User = ClassRegistry::init('User');
$User->getEventManager()->attach(new UserListener());
O trecho de código acima está carregando o listener que criamos e então anexando-o ao gerenciamento de Evento do User Model.
Obs.: No caso do nosso exemplo não é necessário, mas também é possível (e em alguns casos desejável) anexar de forma global os nossos listeners na aplicação. Conforme abaixo:
// In app/Config/events.php
App::uses('CakeEventManager', 'Event');
App::uses('UserListener', 'Lib/Event');
CakeEventManager::instance()->attach(new UserListener());
Por fim, precisamos ter certeza que o CakePHP sabe a respeito do nosso novo arquivo de configuração de Eventos, então precisamos carregá-lo no arquivo bootstrap.php (podendo ser colocado na última linha do arquivo).
// In app/Config/bootstrap.php
// Load the global event listeners.
require_once APP . 'Config' . DS . 'events.php';
Feito isso, no momento em que um novo usuário é criado, o CakePHP irá chamar o método definido no nosso listener e enviar o e-mail de boas vindas.
Conclusão
Você pode ser um dos muitos que vieram através da documentação oficial do CakePHP Events e ficaram assustados com Eventos em CakePHP devido as explicações confusas. Espero que este artigo tenha explicado melhor o que é o sistema de Eventos e como ele funciona, e possivelmente o tenha motivado a começar a usar.
Isso aconteceu comigo! rsrsrs
Fiquem conscientes de que trabalhar com Eventos pode deixar o processo de debug um pouco mais complicado, então não usem de forma descontrolada. No entanto, quando usado da maneira correta pode conduzir você a escrever melhor o seu código e a deixá-lo reutilizável. Eventos podem fazer muito mais do que o descrito aqui (veja a documentação oficial), então vá e verifique o que podem fazer por você.
Link para o artigo original: http://andy-carter.com/blog/events-in-cakephp-2-using-the-observer-pattern
Resolvi, por conta própria, gravar um pequeno tutorial sobre o artigo para deixar a compreensão ainda mais fácil. Com certeza existem pessoas que preferem vídeos também. Segue abaixo: