Laravel provides more than one way to wire up an observer to listen to model events.
The oldest way, going all the way back to Laravel 5 days, was to manually register the observer in the event service provider:
public function boot()
{
User::observe(UserObserver::class);
}
Then in Laravel 9, a property was added to the event service provider that did the same thing:
protected $observers = [
User::class => UserObserver::class,
];
And most recently, in Laravel 10, we got a new attribute-based syntax right within the model:
#[ObservedBy([UserObserver::class])]
class User extends Authenticatable
{
// rest of model here
}
All three of these approaches still work today, and they all do the same thing, but there is a small gotcha with the attribute-based syntax. I ran into this recently when I was trying to test a model observer was wired up correctly.
This test passes with the first two ways of wiring an observer, but fails if you use the attribute syntax:
public function testWiredUpSuccessfully(): void
{
Event::fake();
Event::assertListening(
sprintf("eloquent.created: %s", User::class),
sprintf('%s@created', UserObserver::class)
);
}
Why does this fail with the attribute syntax? The first two methods are wired up in the service provider, which happens anytime the app is booted.
But the attribute syntax only gets wired up when a model is booted. In this basic wiring test, no model is created.
Adding one line to the test fixes the issue: User::bootHasEvents()
.
This is only ever going to be an issue in a very basic "wiring test" like this, but it's important to know.
And don't get me wrong, I like the newer attribute syntax, but this initially caught me by surprise when my tests failed.
Here to help,
Joel
P.S. Thank you to everyone who bought a copy of our Laravel tips ebook during last week's launch!