forked from laravel/framework
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix withoutEvents() not registering boot() listeners (laravel#33149)
If an Eloquent model class is instantiated for the first time inside a withoutEvents() Closure, any model boot() callbacks registering custom event listeners will be skipped. Instead of removing the dispatcher, replaced it with a null pattern implementation. Registration method calls still go through to the concrete dispatcher however fired event dispatch() calls become noop.
- Loading branch information
Showing
3 changed files
with
195 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
<?php | ||
|
||
namespace Illuminate\Events; | ||
|
||
use Illuminate\Contracts\Events\Dispatcher as DispatcherContract; | ||
use Illuminate\Support\Traits\ForwardsCalls; | ||
|
||
class NullDispatcher implements DispatcherContract | ||
{ | ||
use ForwardsCalls; | ||
|
||
/** | ||
* The underlying event dispatcher instance. | ||
*/ | ||
protected $dispatcher; | ||
|
||
/** | ||
* Create a new event dispatcher instance that does not fire. | ||
* | ||
* @param \Illuminate\Contracts\Events\Dispatcher $dispatcher | ||
* @return void | ||
*/ | ||
public function __construct(DispatcherContract $dispatcher) | ||
{ | ||
$this->dispatcher = $dispatcher; | ||
} | ||
|
||
/** | ||
* Don't fire an event. | ||
* | ||
* @param string|object $event | ||
* @param mixed $payload | ||
* @param bool $halt | ||
* @return void | ||
*/ | ||
public function dispatch($event, $payload = [], $halt = false) | ||
{ | ||
} | ||
|
||
/** | ||
* Don't register an event and payload to be fired later. | ||
* | ||
* @param string $event | ||
* @param array $payload | ||
* @return void | ||
*/ | ||
public function push($event, $payload = []) | ||
{ | ||
} | ||
|
||
/** | ||
* Don't dispatch an event. | ||
* | ||
* @param string|object $event | ||
* @param mixed $payload | ||
* @return array|null | ||
*/ | ||
public function until($event, $payload = []) | ||
{ | ||
} | ||
|
||
/** | ||
* Register an event listener with the dispatcher. | ||
* | ||
* @param string|array $events | ||
* @param \Closure|string $listener | ||
* @return void | ||
*/ | ||
public function listen($events, $listener) | ||
{ | ||
return $this->dispatcher->listen($events, $listener); | ||
} | ||
|
||
/** | ||
* Determine if a given event has listeners. | ||
* | ||
* @param string $eventName | ||
* @return bool | ||
*/ | ||
public function hasListeners($eventName) | ||
{ | ||
return $this->dispatcher->hasListeners($eventName); | ||
} | ||
|
||
/** | ||
* Register an event subscriber with the dispatcher. | ||
* | ||
* @param object|string $subscriber | ||
* @return void | ||
*/ | ||
public function subscribe($subscriber) | ||
{ | ||
return $this->dispatcher->subscribe($subscriber); | ||
} | ||
|
||
/** | ||
* Flush a set of pushed events. | ||
* | ||
* @param string $event | ||
* @return void | ||
*/ | ||
public function flush($event) | ||
{ | ||
return $this->dispatcher->flush($event); | ||
} | ||
|
||
/** | ||
* Remove a set of listeners from the dispatcher. | ||
* | ||
* @param string $event | ||
* @return void | ||
*/ | ||
public function forget($event) | ||
{ | ||
return $this->dispatcher->forget($event); | ||
} | ||
|
||
/** | ||
* Forget all of the queued listeners. | ||
* | ||
* @return void | ||
*/ | ||
public function forgetPushed() | ||
{ | ||
return $this->dispatcher->forgetPushed(); | ||
} | ||
|
||
/** | ||
* Dynamically pass method calls to the underlying dispatcher. | ||
* | ||
* @param string $method | ||
* @param array $parameters | ||
* @return mixed | ||
*/ | ||
public function __call($method, $parameters) | ||
{ | ||
return $this->forwardCallTo($this->dispatcher, $method, $parameters); | ||
} | ||
} |
52 changes: 52 additions & 0 deletions
52
tests/Integration/Database/EloquentModelWithoutEventsTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
<?php | ||
|
||
namespace Illuminate\Tests\Integration\Database; | ||
|
||
use Illuminate\Database\Eloquent\Model; | ||
use Illuminate\Database\Schema\Blueprint; | ||
use Illuminate\Support\Facades\Schema; | ||
|
||
/** | ||
* @group integration | ||
*/ | ||
class EloquentModelWithoutEventsTest extends DatabaseTestCase | ||
{ | ||
protected function setUp(): void | ||
{ | ||
parent::setUp(); | ||
|
||
Schema::create('auto_filled_models', function (Blueprint $table) { | ||
$table->increments('id'); | ||
$table->text('project')->nullable(); | ||
}); | ||
} | ||
|
||
public function testWithoutEventsRegistersBootedListenersForLater() | ||
{ | ||
$model = AutoFilledModel::withoutEvents(function () { | ||
return AutoFilledModel::create(); | ||
}); | ||
|
||
$this->assertNull($model->project); | ||
|
||
$model->save(); | ||
|
||
$this->assertEquals('Laravel', $model->project); | ||
} | ||
} | ||
|
||
class AutoFilledModel extends Model | ||
{ | ||
public $table = 'auto_filled_models'; | ||
public $timestamps = false; | ||
protected $guarded = ['id']; | ||
|
||
public static function boot() | ||
{ | ||
parent::boot(); | ||
|
||
static::saving(function ($model) { | ||
$model->project = 'Laravel'; | ||
}); | ||
} | ||
} |