Back to Course |
Design Patterns in Laravel 11

Observer Pattern with Events/Listeners

One of the most widely used design patterns in Laravel is Observer.

Actually, there are two Laravel features for this pattern:

The general idea is this:

  1. The "main" code is happening in an "active" way: in Controller, for example
  2. Then there are other "subscribed" classes observing what happened, waiting to be activated when needed

Event/Listener: Practical Example

Look at a typical Event/Listener example from Laravel Breeze:

app/Http/Controllers/Auth/RegisteredUserController.php:

use Illuminate\Auth\Events\Registered;
 
// ...
 
class RegisteredUserController extends Controller
{
public function store(Request $request): RedirectResponse
{
// ... validation
 
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
]);
 
event(new Registered($user));

The registration Controller fires the event Registered that can be listened to by others.

Inside that Registered Event class, which comes from Laravel core, we have $user as a property.

Illuminate/Auth/Events/Registered.php:

use Illuminate\Queue\SerializesModels;
 
class Registered
{
use SerializesModels;
 
public $user;
 
public function __construct($user)
{
$this->user = $user;
}
}

And then, for example, we want to create a listener to auto-create a Team for that new user.

Then, all we need to do:

  • Generate the Listener with php artisan make:listener CreateTeamListener
  • Pass the Registered Event as a parameter, and the Listener will auto-listen for that Event

app/Listeners/CreateTeamListener.php:

use App\Models\Team;
use Illuminate\Auth\Events\Registered;
 
// ...
 
class CreateTeamListener
{
public function handle(Registered $event): void
{
// Access the user using $event->user...
Team::create([
'name' => $event->user->name . "'s Team",
'user_id' => $event->user->id,
]);
}
}

With recent Laravel changes, we don't even need to register that Listener anywhere. The event discovery will take care of it.


Observer Example: The Same Pattern?

Eloquent Observers just happens to be a more convenient "wrapper" implementation of events/listeners.

For example, if we create the Observer for the same case of auto-creating the team for the new user, we do this:

php artisan make:observer UserObserver --model=User

And then fill the Observer with this code:

namespace App\Observers;
 
use App\Models\Team;
use App\Models\User;
 
class UserObserver
{
public function created(User $user): void
{
Team::create([
'name' => $user->name . "'s Team",
'user_id' => $user->id,
]);
}
}

There are events like created(), deleting(), and others, which are listened to in the form of methods in the Observer class.

Doesn't it look similar to the Event/Listener? It's just that Eloquent automatically takes care of firing the Event for you.

The only difference from listeners is that we do need to register the Observers, they are not auto-discovered. Recently, they release a more convenient way of doing it, with ObservedBy attribute on the Model:

app/Models/User.php:

use App\Observers\UserObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
 
#[ObservedBy([UserObserver::class])]
class User extends Authenticatable
{
// ...
}

So, structurally, Observers are in a different folder from Listeners, but from the design pattern perspective, they follow the same idea of "passively listening for the changes".

Actually, there's even a third alternative: you can also describe those methods in the Model itself in a booted() method.

app/Models/User.php:

class User extends Model
{
protected static function booted(): void
{
static::created(function (User $user) {
Team::create([
'name' => $user->name . "'s Team",
'user_id' => $user->id,
]);
});
}
}

In that way, you don't even need a separate Observer class. So, it's your personal choice whether to make your Model "fatter" or separate that into Observer class.


When to Use This Pattern

Use Observers when you want to separate ("hide"?) extra logic to be happening on Eloquent models.

Use Listeners to listen to specific events, not necessarily related to Eloquent models.

Also, you may use Listeners with Eloquent models, too, with the idea that you actively call the Event, which will serve two purposes for future developers:

  • It would allow them to clearly see that something is happening under the hood (Observers are not clearly seen)
  • It would allow them to create more Listener classes for the same Event

I will add my personal opinion here: I have been avoiding this pattern lately because both Observers and Listeners require extra effort to understand what is happening "in the background".

Yes, it's a separation of concerns, but I started preferring a more "active" way of doing it, like actively calling Services/Jobs from the Controller. Again, as usual in Laravel, it's a personal preference.


Next: Builder

In the following lesson, let's discuss a pattern you use daily without even noticing it: Builder.