Eventos no CakePHP 2 — Usando o Observer Pattern (Padrão Observador)

Danilo Santos
6 min readSep 22, 2016

--

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!

CakePHP’s Observer Pattern (O sistema de eventos)

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:

--

--