Eventos
O PivotPHP fornece um sistema de eventos simples, mas poderoso, que permite que você assine e escute vários eventos que ocorrem em sua aplicação. Isso proporciona uma ótima maneira de desacoplar vários aspectos da sua aplicação.
Uso Básico
Disparando Eventos
A maneira mais simples de disparar um evento é usando o dispatcher de eventos:
use PivotPHP\Events\Dispatcher;
// Disparar um evento simples
$dispatcher = new Dispatcher();
$dispatcher->dispatch('user.registered', $user);
// Usando o helper global
event('user.registered', $user);
// Disparar com múltiplos parâmetros
event('order.placed', [$order, $customer]);
Escutando Eventos
Registre listeners de eventos para responder aos eventos:
// Listener simples
$dispatcher->listen('user.registered', function($user) {
// Enviar email de boas-vindas
Mail::send(new WelcomeEmail($user));
});
// Múltiplos listeners para o mesmo evento
$dispatcher->listen('user.registered', function($user) {
// Registrar log
Log::info("Novo usuário registrado: {$user->email}");
});
$dispatcher->listen('user.registered', function($user) {
// Atualizar estatísticas
Stats::increment('users.registered');
});
Classes de Evento
Para eventos complexos, crie classes de evento dedicadas:
namespace App\Events;
class UserRegistered
{
public function __construct(
public User $user,
public array $additionalData = []
) {}
}
// Disparar objeto de evento
event(new UserRegistered($user, ['source' => 'api']));
// Escutar classe de evento
$dispatcher->listen(UserRegistered::class, function(UserRegistered $event) {
// Acessar dados do evento
$user = $event->user;
$source = $event->additionalData['source'] ?? 'web';
});
Listeners de Evento
Listeners Baseados em Classe
Crie classes de listener dedicadas para melhor organização:
namespace App\Listeners;
class SendWelcomeEmail
{
private Mailer $mailer;
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}
public function handle(UserRegistered $event): void
{
$this->mailer->send(
new WelcomeEmail($event->user)
);
}
}
// Registrar listener de classe
$dispatcher->listen(UserRegistered::class, SendWelcomeEmail::class);
// Ou com método específico
$dispatcher->listen(UserRegistered::class, [SendWelcomeEmail::class, 'handle']);
Listeners em Fila
Adie tarefas demoradas para jobs em background:
namespace App\Listeners;
use PivotPHP\Contracts\Queue\ShouldQueue;
use PivotPHP\Queue\InteractsWithQueue;
class ProcessUserAnalytics implements ShouldQueue
{
use InteractsWithQueue;
public function handle(UserRegistered $event): void
{
// Isso será executado em background
Analytics::track('user.registered', [
'user_id' => $event->user->id,
'source' => $event->additionalData['source'] ?? 'web'
]);
}
public function failed(UserRegistered $event, \Throwable $exception): void
{
// Lidar com falha
Log::error("Falha ao processar analytics para usuário {$event->user->id}");
}
}
Assinantes de Evento
Agrupe listeners de evento relacionados em classes assinantes:
namespace App\Listeners;
class UserEventSubscriber
{
public function handleUserRegistration(UserRegistered $event): void
{
// Lidar com registro
}
public function handleUserLogin(UserLoggedIn $event): void
{
// Atualizar último login
$event->user->update(['last_login' => now()]);
}
public function handleUserLogout(UserLoggedOut $event): void
{
// Limpar cache do usuário
Cache::forget("user.{$event->user->id}");
}
public function subscribe(Dispatcher $events): array
{
return [
UserRegistered::class => 'handleUserRegistration',
UserLoggedIn::class => 'handleUserLogin',
UserLoggedOut::class => 'handleUserLogout',
];
}
}
// Registrar assinante
$dispatcher->subscribe(UserEventSubscriber::class);
Listeners com Curinga
Escute múltiplos eventos com curingas:
// Escutar todos os eventos de usuário
$dispatcher->listen('user.*', function($event, $data) {
Log::info("Evento de usuário disparado: {$event}");
});
// Escutar todos os eventos
$dispatcher->listen('*', function($event, $data) {
// Registrar todos os eventos
EventLog::create([
'event' => $event,
'data' => json_encode($data),
'timestamp' => now()
]);
});
Prioridade de Evento
Controle a ordem de execução dos listeners:
// Prioridade maior executa primeiro (padrão é 0)
$dispatcher->listen('user.registered', function($user) {
// Validar dados do usuário primeiro
}, priority: 10);
$dispatcher->listen('user.registered', function($user) {
// Depois enviar email
}, priority: 5);
$dispatcher->listen('user.registered', function($user) {
// Por fim registrar log
}, priority: 0);
Interrompendo a Propagação de Evento
Evite que listeners adicionais sejam executados:
$dispatcher->listen('payment.processing', function($payment) {
if ($payment->amount > 10000) {
// Requer aprovação manual
$payment->setStatus('pending_approval');
// Parar outros listeners
return false;
}
});
$dispatcher->listen('payment.processing', function($payment) {
// Isso não será executado se amount > 10000
$payment->process();
});
Listeners Condicionais
Adicione listeners condicionalmente:
// Escutar apenas em produção
if (app()->environment('production')) {
$dispatcher->listen('error.occurred', function($error) {
// Enviar para serviço de rastreamento de erros
Sentry::captureException($error);
});
}
// Classe de listener condicional
class ConditionalListener
{
public function shouldHandle(UserRegistered $event): bool
{
// Lidar apenas com usuários premium
return $event->user->isPremium();
}
public function handle(UserRegistered $event): void
{
if (!$this->shouldHandle($event)) {
return;
}
// Lidar com registro de usuário premium
}
}
Provedor de Serviço de Eventos
Organize listeners de evento em um provedor de serviço:
namespace App\Providers;
use PivotPHP\Core\Core\ServiceProvider;
use PivotPHP\Events\Dispatcher;
class EventServiceProvider extends ServiceProvider
{
protected array $listen = [
UserRegistered::class => [
SendWelcomeEmail::class,
UpdateUserStatistics::class,
NotifyAdminOfNewUser::class,
],
OrderPlaced::class => [
ProcessPayment::class,
SendOrderConfirmation::class,
UpdateInventory::class,
],
];
protected array $subscribe = [
UserEventSubscriber::class,
PaymentEventSubscriber::class,
];
public function boot(): void
{
$dispatcher = $this->app->make(Dispatcher::class);
// Registrar listeners
foreach ($this->listen as $event => $listeners) {
foreach ($listeners as $listener) {
$dispatcher->listen($event, $listener);
}
}
// Registrar assinantes
foreach ($this->subscribe as $subscriber) {
$dispatcher->subscribe($subscriber);
}
// Registrar listeners dinâmicos
$this->registerDynamicListeners($dispatcher);
}
private function registerDynamicListeners(Dispatcher $dispatcher): void
{
// Eventos de modelo
User::created(function($user) use ($dispatcher) {
$dispatcher->dispatch(new UserCreated($user));
});
User::updated(function($user) use ($dispatcher) {
if ($user->wasChanged('email')) {
$dispatcher->dispatch(new UserEmailChanged($user));
}
});
}
}
Eventos Integrados
O PivotPHP dispara vários eventos integrados:
// Eventos do ciclo de vida da requisição
'request.received' => [$request]
'request.handled' => [$request, $response]
// Eventos de banco de dados
'database.query' => [$query, $bindings, $time]
'database.transaction.begin' => [$connection]
'database.transaction.commit' => [$connection]
'database.transaction.rollback' => [$connection]
// Eventos de cache
'cache.hit' => [$key, $value]
'cache.missed' => [$key]
'cache.write' => [$key, $value, $ttl]
'cache.delete' => [$key]
// Eventos de autenticação
'auth.login' => [$user, $remember]
'auth.logout' => [$user]
'auth.failed' => [$credentials]
Testando Eventos
Teste seus eventos e listeners:
use PivotPHP\Testing\TestCase;
use PivotPHP\Support\Facades\Event;
class UserRegistrationTest extends TestCase
{
public function test_user_registration_fires_event()
{
// Simular eventos
Event::fake();
// Executar registro
$response = $this->post('/register', [
'name' => 'João Silva',
'email' => 'joao@exemplo.com',
'password' => 'senha123'
]);
// Verificar que evento foi disparado
Event::assertDispatched(UserRegistered::class, function($event) {
return $event->user->email === 'joao@exemplo.com';
});
// Verificar que evento foi disparado exatamente uma vez
Event::assertDispatchedTimes(UserRegistered::class, 1);
}
public function test_welcome_email_is_sent()
{
Event::fake([UserRegistered::class]);
$user = User::factory()->create();
event(new UserRegistered($user));
Event::assertListening(
UserRegistered::class,
SendWelcomeEmail::class
);
}
}
Considerações de Performance
Eventos Assíncronos
Para melhor performance, processe eventos assincronamente:
class AsyncEventDispatcher extends Dispatcher
{
private Queue $queue;
public function dispatch($event, $payload = []): void
{
// Eventos críticos processados imediatamente
if ($this->isCritical($event)) {
parent::dispatch($event, $payload);
return;
}
// Enfileirar eventos não críticos
$this->queue->push(new ProcessEvent($event, $payload));
}
private function isCritical($event): bool
{
$critical = ['payment.failed', 'security.breach', 'system.error'];
return in_array($event, $critical) ||
$event instanceof CriticalEvent;
}
}
Agrupamento de Eventos
Agrupe múltiplos eventos para eficiência:
class BatchedEventDispatcher
{
private array $events = [];
private bool $batching = false;
public function batch(callable $callback): void
{
$this->batching = true;
$callback();
$this->batching = false;
$this->flushEvents();
}
public function dispatch($event, $payload = []): void
{
if ($this->batching) {
$this->events[] = [$event, $payload];
return;
}
// Disparar imediatamente se não estiver agrupando
parent::dispatch($event, $payload);
}
private function flushEvents(): void
{
foreach ($this->events as [$event, $payload]) {
parent::dispatch($event, $payload);
}
$this->events = [];
}
}
Melhores Práticas
- Use classes de evento: Para eventos complexos, crie classes dedicadas
- Mantenha listeners focados: Cada listener deve ter uma única responsabilidade
- Enfileire tarefas pesadas: Use listeners em fila para operações demoradas
- Lide com falhas: Implemente tratamento de erros nos listeners
- Teste eventos: Sempre teste o disparo e manipulação de eventos
- Documente eventos: Documente claramente quais eventos sua aplicação dispara
- Evite loops infinitos: Cuidado com eventos que disparam outros eventos
- Use type hints: Use type hints nos parâmetros de evento para melhor suporte da IDE