Eventos no PHP – PHP sem framework – parte 6

Tempo de leitura 5 minutos

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

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.

Projeto completo

Se você que mais séries de artigos por aqui, por favor, cadastre-se na newsletter.

Mais artigos desta série

Artigo anterior

Todos os artigos

Autor: Erik Figueiredo

Músico, gamer amador, tutor de programação, desenvolvedor freelancer full cycle, com foco em PHP (Laravel e CakePHP), Javascript (Front e Node.js), Dart (Front e Flutter) e infra.

4 comentários em “Eventos no PHP – PHP sem framework – parte 6”

  1. 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!

    1. 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.

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *