Come usare gli eventi in Laravel

Gli eventi in Laravel sono un potente meccanismo per disaccoppiare le diverse parti dell'applicazione. Permettono di reagire a qualcosa che "accade" nel sistema (per esempio la registrazione di un utente o la creazione di un ordine) senza mescolare la logica di dominio con la logica accessoria, come l'invio di email o la scrittura di log.

Quando usare gli eventi

Gli eventi sono particolarmente utili quando:

  • Vuoi mantenere i tuoi controller e servizi snelli, delegando ad altri componenti attività secondarie.
  • Hai la stessa azione che deve scattare da più punti dell'applicazione (per esempio, loggare ogni volta che un utente effettua il login).
  • Vuoi rendere il codice più estensibile, in modo da poter aggiungere nuovi comportamenti in risposta a un evento senza toccare la logica principale.
  • Vuoi poter mettere in coda (queue) attività pesanti da eseguire in background, usando listener che implementano le code di Laravel.

Architettura: eventi, listener e EventServiceProvider

Il sistema di eventi di Laravel ruota intorno a tre concetti:

  1. Evento: una semplice classe che rappresenta qualcosa che è successo. Contiene di solito i dati necessari ai listener per lavorare (es. un modello $user).
  2. Listener: una classe che "ascolta" un determinato evento e, quando questo viene lanciato, esegue del codice (es. inviare una email di benvenuto).
  3. EventServiceProvider: il service provider che collega eventi e listener, dicendo a Laravel quali listener devono reagire a quali eventi.

Creare un evento

Per creare un evento puoi usare il comando Artisan make:event. Ad esempio, supponiamo di voler creare un evento per quando un utente si registra:

php artisan make:event UserRegistered

Questo comando genererà una classe evento, di solito in app/Events/UserRegistered.php. Un esempio di implementazione potrebbe essere:

<?php

namespace App\Events;

use App\Models\User;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class UserRegistered
{
    use Dispatchable, SerializesModels;

    public User $user;

    /**
     * Crea una nuova istanza dell'evento.
     */
    public function __construct(User $user)
    {
        $this->user = $user;
    }
}

In questo esempio l'evento contiene un solo attributo pubblico $user, che passeremo ai listener in modo che possano eseguire operazioni sul nuovo utente.

Creare un listener

Ora vogliamo reagire all'evento UserRegistered, per esempio inviando una email di benvenuto. Creiamo un listener con il comando Artisan:

php artisan make:listener SendWelcomeEmail --event=UserRegistered

Questo genererà una classe di listener in app/Listeners/SendWelcomeEmail.php simile a questa:

<?php

namespace App\Listeners;

use App\Events\UserRegistered;
use App\Mail\WelcomeMail;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Mail;

class SendWelcomeEmail implements ShouldQueue
{
    /**
     * Gestisce l'evento.
     */
    public function handle(UserRegistered $event): void
    {
        $user = $event->user;

        Mail::to($user->email)->send(new WelcomeMail($user));
    }
}

In questo esempio:

  • Il listener riceve un oggetto UserRegistered nel metodo handle.
  • Estrae l'utente dall'evento e gli invia una mail.
  • Implementa ShouldQueue, il che significa che il listener verrà eseguito tramite il sistema di code di Laravel (se configurato), evitando di rallentare la risposta HTTP.

Registrare eventi e listener in EventServiceProvider

Una volta creati evento e listener, dobbiamo dire a Laravel come collegarli. Questo avviene nel file app/Providers/EventServiceProvider.php.

Un esempio di configurazione:

<?php

namespace App\Providers;

use App\Events\UserRegistered;
use App\Listeners\SendWelcomeEmail;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
    /**
     * La mappa degli eventi con i loro listener.
     *
     * @var array<class-string, array<int, class-string>>
     */
    protected $listen = [
        UserRegistered::class => [
            SendWelcomeEmail::class,
        ],
    ];
}

In questo modo, ogni volta che l'evento UserRegistered viene lanciato, Laravel saprà che deve eseguire il listener SendWelcomeEmail.

Lanciare un evento

Per "lanciare" un evento, si utilizza la funzione helper event() oppure il metodo dispatch() sul singolo evento. Per esempio, dopo avere creato un nuovo utente nel tuo controller, potresti scrivere:

<?php

namespace App\Http\Controllers\Auth;

use App\Events\UserRegistered;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;

class RegisteredUserController extends Controller
{
    public function store(Request $request)
    {
        $validated = $request->validate([
            'name' => ['required', 'string'],
            'email' => ['required', 'email', 'unique:users,email'],
            'password' => ['required', 'confirmed'],
        ]);

        $user = User::create([
            'name' => $validated['name'],
            'email' => $validated['email'],
            'password' => bcrypt($validated['password']),
        ]);

        event(new UserRegistered($user));

        return redirect()->route('home');
    }
}

In alternativa, puoi usare il trait Dispatchable e chiamare il metodo statico dispatch() sull'evento:

UserRegistered::dispatch($user);

Eventi predefiniti di Laravel

Laravel espone anche una serie di eventi predefiniti, specialmente per il sistema di autenticazione e per Eloquent. Alcuni esempi:

  • Illuminate\Auth\Events\Login: emesso quando un utente effettua il login.
  • Illuminate\Auth\Events\Logout: emesso quando un utente effettua il logout.
  • Illuminate\Auth\Events\Registered: emesso quando un utente si registra tramite i metodi di autenticazione predefiniti.
  • Eventi Eloquent: creating, created, updating, updated, deleting, deleted, ecc.

Puoi collegarti a questi eventi usando la stessa logica: innanzitutto crei i listener, poi li registri in EventServiceProvider.

Eventi Eloquent: un esempio pratico

Supponiamo di voler fare qualcosa ogni volta che viene creato un ordine. Possiamo usare gli eventi Eloquent direttamente nel modello:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Order extends Model
{
    protected static function booted(): void
    {
        static::created(function (Order $order) {
            // Logica da eseguire quando un ordine viene creato
            \Log::info('Nuovo ordine creato', [
                'order_id' => $order->id,
            ]);
        });
    }
}

Questo approccio è comodo per logiche molto legate al ciclo di vita del modello, ma se la logica diventa complessa è spesso meglio spostarsi su eventi dedicati con listener separati.

Listener in coda (ShouldQueue)

Per mantenere l'applicazione reattiva è spesso preferibile eseguire in background operazioni costose, come l'invio di email o chiamate API esterne. Implementando l'interfaccia ShouldQueue su un listener, Laravel lo eseguirà tramite il sistema di code.

Nel nostro esempio SendWelcomeEmail implementa già ShouldQueue. Assicurati però di avere configurato una coda (database, Redis, ecc.) e di avere un worker in esecuzione:

php artisan queue:work

Puoi personalizzare il comportamento di messa in coda aggiungendo proprietà come $tries, $timeout o metodi come failed() nel listener.

Listener basati su closure

Per casi molto semplici, non hai necessariamente bisogno di creare una classe di listener. Puoi registrare un listener basato su closure nel metodo boot di EventServiceProvider:

<?php

namespace App\Providers;

use App\Events\UserRegistered;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Event;

class EventServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Event::listen(UserRegistered::class, function (UserRegistered $event) {
            Log::info('Nuovo utente registrato', [
                'user_id' => $event->user->id,
            ]);
        });
    }
}

Questa soluzione è pratica per piccole applicazioni o per logica temporanea, ma per progetti di medio-grandi dimensioni è in genere preferibile usare listener dedicati per mantenere il codice ordinato.

Broadcast degli eventi

Laravel supporta anche il "broadcasting" degli eventi verso il frontend in tempo reale (per esempio con Laravel Echo e Pusher o Ably). Per farlo, l'evento deve implementare l'interfaccia ShouldBroadcast o ShouldBroadcastNow.

Un esempio semplificato:

<?php

namespace App\Events;

use App\Models\Message;
use Illuminate\Broadcasting\Channel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Queue\SerializesModels;

class MessageSent implements ShouldBroadcast
{
    use SerializesModels;

    public Message $message;

    public function __construct(Message $message)
    {
        $this->message = $message;
    }

    public function broadcastOn(): Channel
    {
        return new Channel('chat');
    }
}

Una volta configurata l'infrastruttura di broadcast, l'evento potrà essere ascoltato in JavaScript e usato per aggiornare l'interfaccia utente in tempo reale.

Testare gli eventi

Per scrivere test automatizzati in Laravel, puoi verificare che un evento sia stato lanciato usando i metodi di faking offerti dal framework. Per esempio:

<?php

namespace Tests\Feature;

use App\Events\UserRegistered;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Event;
use Tests\TestCase;

class UserRegistrationTest extends TestCase
{
    use RefreshDatabase;

    public function test_event_user_registered_viene_lanciato(): void
    {
        Event::fake();

        $user = User::factory()->create();

        event(new UserRegistered($user));

        Event::assertDispatched(UserRegistered::class);
    }
}

Allo stesso modo, puoi testare che un listener venga eseguito o che un determinato effetto (es. una email) sia stato prodotto in seguito al lancio di un evento.

Buone pratiche

  • Non abusare degli eventi: se un comportamento è semplice e strettamente legato a una singola operazione, potrebbe essere sufficiente tenerlo nello stesso metodo, senza creare eventi.
  • Mantieni i listener focalizzati: un listener dovrebbe avere una responsabilità chiara e il codice al suo interno dovrebbe essere relativamente semplice. Se diventa troppo grande, valuta di estrarre ulteriori servizi.
  • Usa le code per operazioni costose: implementando ShouldQueue sui listener riduci il tempo di risposta delle richieste HTTP.
  • Sfrutta gli eventi predefiniti: prima di creare un nuovo evento, verifica se Laravel non ne mette già a disposizione uno adatto al tuo caso (soprattutto per autenticazione e Eloquent).
  • Nomina eventi e listener in modo chiaro: nomi come UserRegistered, OrderPaid, SendInvoiceEmail rendono il codice autoesplicativo.

Conclusione

Gli eventi in Laravel sono uno strumento fondamentale per mantenere l'applicazione modulare, estensibile e facilmente manutenibile. Usando in modo appropriato eventi, listener, code e broadcasting, puoi ottenere un'architettura pulita in cui ogni componente fa bene una sola cosa e reagisce in modo ordinato ai cambiamenti nello stato dell'applicazione.

Torna su