Sometimes we use saveQuietly() to persist data without triggering Eloquent events. But after saving, we may want to manually fire a specific event – for example, only created without creating.
Laravel doesn’t provide this out of the box, so we need to build it ourselves.
What saveQuietly Does
saveQuietly() temporarily removes the event dispatcher, saves the model, then restores it. So none of the events (creating, created, updating, updated) will fire.
1
2
3
4
5
| // Simplified from Laravel source
public function saveQuietly(array $options = [])
{
return static::withoutEvents(fn () => $this->save($options));
}
|
The problem: if I have validation or side effects in the creating event but want to skip creating in certain scenarios, save the model, and then manually fire created to notify other listeners – how do I do that?
Add a fire Method via Builder Macro
Define the macro in AppServiceProvider’s boot method:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| // app/Providers/AppServiceProvider.php
use Illuminate\Database\Eloquent\Builder;
public function boot(): void
{
Builder::macro('fire', function (string $event) {
/** @var Builder $this */
$model = $this->getModel();
$dispatcher = $model::getEventDispatcher();
// Eloquent event naming format: "eloquent.{event}: App\Models\User"
return $dispatcher->dispatch(
"eloquent.{$event}: " . get_class($model),
$model
);
});
}
|
Usage is straightforward:
1
2
3
4
5
6
7
8
9
10
11
| $user = new User([
'name' => 'Recca',
'email' => 'recca@example.com',
'password' => Hash::make('password'),
]);
// Save quietly without firing any events
$user->saveQuietly();
// Manually fire the created event afterward
$user->newQuery()->fire('created');
|
Test Verification
Write a test to confirm that saveQuietly doesn’t fire events, but fire can trigger them manually:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
| namespace Tests\Feature;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Support\Facades\Hash;
use Tests\TestCase;
class EloquentFireEventTest extends TestCase
{
use RefreshDatabase;
use WithFaker;
public function test_save_quietly_does_not_fire_event(): void
{
$callback = \Mockery::spy(fn () => null);
User::creating($callback);
$user = new User([
'name' => $this->faker->name,
'email' => $this->faker->email,
'password' => Hash::make('password'),
]);
$user->saveQuietly();
// saveQuietly does not trigger creating
$callback->shouldNotHaveBeenCalled();
}
public function test_fire_dispatches_event_manually(): void
{
$callback = \Mockery::spy(fn () => null);
User::created($callback);
$user = new User([
'name' => $this->faker->name,
'email' => $this->faker->email,
'password' => Hash::make('password'),
]);
$user->saveQuietly();
// Manually fire the created event
$user->newQuery()->fire('created');
$callback->shouldHaveBeenCalled()->once();
}
}
|
Alternative Approach
If calling $user->newQuery()->fire(...) every time feels verbose, you can add a trait directly to the Model:
1
2
3
4
5
6
7
8
9
10
| trait FiresEvents
{
public function fireModelEvent(string $event): mixed
{
return static::getEventDispatcher()->dispatch(
"eloquent.{$event}: " . static::class,
$this
);
}
}
|
Then you can simply call $user->fireModelEvent('created'). Note that Laravel’s Model already has a protected method called fireModelEvent, so watch out for naming conflicts – you might want to use dispatchModelEvent instead.