E chegamos ao fim de mais um artigo, fechando com chave de ouro essa série com uma das dicas mais legais pra quem curte Single Responsability Principle, o Observer Pattern!!!
Imagine que você tem um determinado momento da aplicação em que quer executar algum código, mas não sabe bem o que pode acontecer ali, você quer a liberdade de “plugar” o que e quanta ações quiser.
Se você quer saber quando novos artigos forem publicados, por favor, cadastre-se na newsletter.
Um bom exemplo seria no momento em que persiste algo no banco de dados, quero dizer, o momento de criação de um registro.
Imagine que ao criar um usuário você quer enviar um email, ao cadastrar um pedido você vai processar o pagamento ou/e gerar um PDF com os dados do pedido.
E se eu tivesse uma solução em que seja possível fazer O QUE QUISER em um momento pré-determinado POR VOCÊ da execução do seu projeto? Seria legal?
Claro que eu tenho!
O que é Observer pattern
O Observer é um padrão de projeto de software que define uma dependência um-para-muitos entre objetos de modo que quando um objeto muda o estado, todos seus dependentes são notificados e atualizados automaticamente. Permite que objetos interessados sejam avisados da mudança de estado ou outros eventos ocorrendo num outro objeto.
https://pt.wikipedia.org/wiki/Observer
Observer pattern é bem simples, trocando em miúdos é o que eu já expliquei acima. Em determinado momento você executa uma ação (ões) que já foi (foram) registrada (s) préviamente, e com isso evitar que você inclua código que NÃO deveria estar ali naquele local e ainda te da possibilidades ilimitadas.
Um bom exemplo de Observer é o próprio Eloquent que usamos no nosso projeto, ele tem observers para as principais ações do CRUD:
- created: após a criação
- creating: antes da criação
- updated: após a atualização
- updating: antes da atualização
- saved: após salvar (criar ou atualizar)
- saving: antes de salvar (criar ou atualizar)
- deleted: após remover
- deleting: antes de remover
Claro que o Eloquent não fica só nisso, então segue o link da documentação oficial:
https://laravel.com/docs/6.x/eloquent#observers
Outro detalhe importante a ressaltar é que eu não fui a fundo no core do Laravel para garantir que é uma correta implementação do Observer Pattern, mas com certeza é na utilização. Até porque (não que eu queira ficar relembrando o passado), depois que o Rasmus Lerdorf mapeou o Laravel eu meio que desanime de entender o core dele (**ironia||piada** – contexto).
Criando um Observer para o Laravel Eloquent
Se você não leu os outros artigos e quer continuar sem ler, aqui os arquivos de exemplo:
https://github.com/erikfig/php-do-zero/tree/1cc950b710fe0d6470f8dd6eefb35e482f05e3c1
Criar um observer para um motor já pronto é MUITO fácil, é só uma classe que vai ficar dentro de modules/users/src/Observers/UserObserver.php
com o seguinte conteúdo.
<?php
namespace ErikFig\Framework\Users\Observers;
use ErikFig\Framework\Users\Models\User;
class UserObserver
{
public function created(User $user)
{
$this->log(__METHOD__);
}
public function creating(User $user)
{
$this->log(__METHOD__);
}
public function updated(User $user)
{
$this->log(__METHOD__);
}
public function updating(User $user)
{
$this->log(__METHOD__);
}
public function saved(User $user)
{
$this->log(__METHOD__);
}
public function saving(User $user)
{
$this->log(__METHOD__);
}
public function deleted(User $user)
{
$this->log(__METHOD__);
}
public function deleting(User $user)
{
$this->log(__METHOD__);
}
private function log($data)
{
$data .= PHP_EOL;
file_put_contents(__DIR__ . '/../../../../users.log', $data, FILE_APPEND);
}
}
Bem simples, quando eu executar QUALQUER tarefa de inclusão ou remoção de dados usando o model User será gerado um arquivo de log com o nome do método da classe UserObserver.
O método log é só pra não ter que copiar a mesma coisa em cada um dos outros métodos, ele não faz parte da implementação original do observer.
Agora, somente criar a classe não vai fazer nada, você precisa informar para o model que esta classe será usada, eu achei mais interessante isso ser feito na Register.php do módulo User:
<?php
namespace ErikFig\Framework\Users;
use Twig\Environment;
use ErikFig\Framework\Router;
use ErikFig\Framework\Users\Models\User; // carreguei o model
use ErikFig\Framework\Users\Observers\UserObserver; // carreguei o observer
class Register
{
public static function handle(Environment $twig, Router $router)
{
User::observe(UserObserver::class); // nova linha
$loader = $twig->getLoader();
// restante do arquivo
Só que não vai funcionar ainda, precisamos configurar o Eloquent para usar eventos.
Configurando o Eloquent para lidar com Observers (os eventos)
Quando eu comecei a escrever este artigo ainda estavamos na versão 6 do Laravel, agora na revisão para publicar eu detectei que o comando que usei não funcionaria mais, então eu vou informar aqui um que va rolar com o código original, mas também vou deixar um guia para lidar com isso de versão.
Precisamos instalar o pacote illuminate/events
e configura-lo no Eloquent para que o observer possa ser lido, caso contrário, nada acontecerá de diferente.
Para fazer essa instalação você terá que usar uma versão compatível para o illuminate/database
e para o illuminate/events
, tente rodar o comando assim:
composer require illuminate/events
Se der erro, abra o illuminate/database e verifique a versão dele:
{
"name": "erikfig/php-do-zero",
"authors": [
{
"name": "Erik Figueiredo",
"email": "erik.figueiredo@gmail.com"
}
],
"repositories": [
{
"type": "path",
"url": "./modules/users"
}
],
"require": {
"phpunit/phpunit": "^8.3",
"illuminate/database": "^6.10", <-- O ELOQUENT
"twig/twig": "^3.0",
"erikfig/module-users": "dev-master",
"illuminate/events": "6"
},
"autoload": {
"psr-4": {
"App\\": "app",
"ErikFig\\Framework\\": "src"
}
},
"minimum-stability": "dev",
"prefer-stable": true
}
A versão do meu é a 6.10, então eu pego a MAJOR version e faço assim:
composer require illuminate/events:6
Viu, é só adicionar um :{MAJOR-VERSION}
, qualquer dúvida estou a disposição.
Agora vamos configurar os eventos do Eloquent, altere o arquivo database.php que está na raiz do projeto:
<?php
use Illuminate\Container\Container; // carrego o Container
use Illuminate\Database\Capsule\Manager;
use Illuminate\Events\Dispatcher; // carrego o "expedidor" de eventos
$capsule = new Manager;
$capsule->addConnection([
'driver' => 'mysql',
'host' => 'localhost',
'database' => 'blog_php_sem_framework',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
]);
// adiciono esta linha, que ativa os eventos no Eloquent
$capsule->setEventDispatcher(new Dispatcher(new Container));
$capsule->setAsGlobal();
$capsule->bootEloquent();
Quase pronto, vamos criar um novo action e uma nova rota para cadastrar usuários e testar nossos eventos.
Testando os eventos do Eloquent
Agora vamos configurar uma URL para cadastro de usuários, eu vou usar uma alternativa mais simples e sem formulário, com foco em testar o Observer, fique a vontade para fazer mas que eu.
Criaremos um novo Action no UsersController.
<?php
namespace ErikFig\Framework\Users\Controllers;
use App\Controllers\AppController;
use ErikFig\Framework\Users\Models\User;
class UsersController extends AppController
{
public function index()
{
return $this->twig->render('users/index.html', ['users' => User::all()]);
}
// Novo action que cadastra usuários
public function create()
{
$user = new User;
$user->name = 'Erik';
$user->email = base64_encode(random_bytes(10)) . '@example.com';
$user->password = password_hash('secret', PASSWORD_DEFAULT);
$user->save();
return header('location: /users');
}
}
Adicionei uma nova rota no Register.php:
<?php
namespace ErikFig\Framework\Users;
use Twig\Environment;
use ErikFig\Framework\Router;
use ErikFig\Framework\Users\Models\User;
use ErikFig\Framework\Users\Observers\UserObserver;
class Register
{
public static function handle(Environment $twig, Router $router)
{
User::observe(UserObserver::class);
$loader = $twig->getLoader();
$loader->addPath(__DIR__ . '/../templates');
// nova rota
$router->get('/users/create', 'ErikFig\Framework\Users\Controllers\UsersController::create');
E até adicionei um link no template:
<h1>Usuários - módulo</h1>
<ul>
{% for user in users %}
<li>{{ user.name }}</li>
{% endfor %}
</ul>
<!-- LINK para cadastro de usuários -->
<a href="/users/create">criar novo</a>
Agora, se você tentar executar isso vai acabar lidando com um erro, o Eloquent espera que as tabelas tenham duas colunas de data para guardar quando o registro criado e atualizado, eu não criei elas, então preciso dizer para ele não fazer isso, bem simples, basta adicionar uma linha com public $timestamps = false;
<?php
namespace ErikFig\Framework\Users\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
public $timestamps = false;
}
Finalmente, quando clicar no link de cadastro de usuários deverá, além do registro no banco, ser criado um arquivo na raiz do projeto chamado users.log com o seguinte conteúdo:
ErikFig\Framework\Users\Observers\UserObserver::saving
ErikFig\Framework\Users\Observers\UserObserver::creating
ErikFig\Framework\Users\Observers\UserObserver::created
ErikFig\Framework\Users\Observers\UserObserver::saved
Indicando todos os eventos que foram disparados e a ordem em que foram executados.
Bom, é isso, agora você sabe MUITO bem como funciona o sistema de observadores e que na verdade é bem parecido com events e listeners que a maioria dos frameworks também dão suporte.
Se você curtiu esse artigo, por favor, compartilhe e indique para seus amigos, quem sabe eu não escrevo outro ou até enriqueço este mesmo falando mais sobre o Observer pattern ou até criando um sistema de eventos do ZERO aqui.
Fim da série?
Nada termina de verdade, tudo se transforma…
Obrigado por ler e acompanhar esta série desde o começo, eu não tenho palavras para descrever a satisfação que foi voltar a escrever no meu blog pessoal.
Se você que mais séries de artigos por aqui, por favor, cadastre-se na newsletter.
Minha questão não tem nada haver com o post, mas não encontrei outro da categoria da minha dúvida.
Na sua opinião qual é a melhor abordagem quando se trabalha com filas: Supervisor ou Tasks no ECS da AWS?
Nunca trabalhei com o Supervisor. Mas tenho vários workers que rodam no ECS e quando preciso escalar, aumento o número das Tasks do serviço que roda o worker.
Abraços!
Ambos são excelentes. Eu acabo preferindo, na maioria das vezes, o Supervisor simplesmente porque tenho mais prática com ele e já se encaixa no que eu costumo trabalhar.
Amei a série de artigos PHP Sem Frameworks, me ajudou bastante nos meus estudos.
Obrigado Erik!!!!
Obrigado Gustavo!
Olá! Primeiramente gostaria de agradecer pelo artigo, aprendi muito! Outra questão é que estou tendo um erro no illuminate/events, fiz o que você falou sobre incluir o MAJOR-VERSION porém continua dando erro de instalação no compose.lock (Installation failed, reverting ./composer.json and ./composer.lock to their original content.), também verifiquei que no código publicado no github não consta essa parte, poderia me dar um help?