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:
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:
php artisan make:listener CreateTeamListener
Registered
Event as a parameter, and the Listener will auto-listen for that Eventapp/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.
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.
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:
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.
In the following lesson, let's discuss a pattern you use daily without even noticing it: Builder.