diff --git a/src/Illuminate/Events/Dispatcher.php b/src/Illuminate/Events/Dispatcher.php index 35c37f430d33..a56566f7dd94 100755 --- a/src/Illuminate/Events/Dispatcher.php +++ b/src/Illuminate/Events/Dispatcher.php @@ -83,6 +83,13 @@ class Dispatcher implements DispatcherContract */ protected $deferringEvents = false; + /** + * The specific events to defer (null means defer all events). + * + * @var array|null + */ + protected $deferringSpecificEvents = null; + /** * Create a new event dispatcher instance. * @@ -258,12 +265,6 @@ public function until($event, $payload = []) */ public function dispatch($event, $payload = [], $halt = false) { - if ($this->deferringEvents) { - $this->deferredEvents[] = func_get_args(); - - return null; - } - // When the given "event" is actually an object we will assume it is an event // object and use the class as the event name and this event itself as the // payload to the handler, which makes object based events quite simple. @@ -272,6 +273,12 @@ public function dispatch($event, $payload = [], $halt = false) ...$this->parseEventAndPayload($event, $payload), ]; + if ($this->shouldDeferEvent($event)) { + $this->deferredEvents[] = func_get_args(); + + return null; + } + // If the event is not intended to be dispatched unless the current database // transaction is successful, we'll register a callback which will handle // dispatching this event on the next successful DB transaction commit. @@ -792,15 +799,18 @@ public function setTransactionManagerResolver(callable $resolver) * Execute the given callback while deferring events, then dispatch all deferred events. * * @param callable $callback + * @param array|null $events * @return mixed */ - public function defer(callable $callback) + public function defer(callable $callback, ?array $events = null) { $wasDeferring = $this->deferringEvents; $previousDeferredEvents = $this->deferredEvents; + $previousDeferringSpecificEvents = $this->deferringSpecificEvents; $this->deferringEvents = true; $this->deferredEvents = []; + $this->deferringSpecificEvents = $events; try { $result = $callback(); @@ -815,9 +825,21 @@ public function defer(callable $callback) } finally { $this->deferringEvents = $wasDeferring; $this->deferredEvents = $previousDeferredEvents; + $this->deferringSpecificEvents = $previousDeferringSpecificEvents; } } + /** + * Determine if the given event should be deferred. + * + * @param string $event + * @return bool + */ + protected function shouldDeferEvent(string $event) + { + return $this->deferringEvents && ($this->deferringSpecificEvents === null || in_array($event, $this->deferringSpecificEvents)); + } + /** * Gets the raw, unprepared listeners. * diff --git a/src/Illuminate/Support/Facades/Event.php b/src/Illuminate/Support/Facades/Event.php index 748f0890f0c9..dd6ec19a24f9 100755 --- a/src/Illuminate/Support/Facades/Event.php +++ b/src/Illuminate/Support/Facades/Event.php @@ -21,7 +21,7 @@ * @method static void forgetPushed() * @method static \Illuminate\Events\Dispatcher setQueueResolver(callable $resolver) * @method static \Illuminate\Events\Dispatcher setTransactionManagerResolver(callable $resolver) - * @method static mixed defer(callable $callback) + * @method static mixed defer(callable $callback, ?array $events = null) * @method static array getRawListeners() * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) diff --git a/tests/Events/EventsDispatcherTest.php b/tests/Events/EventsDispatcherTest.php index 3f308a751508..56d97e52c58d 100755 --- a/tests/Events/EventsDispatcherTest.php +++ b/tests/Events/EventsDispatcherTest.php @@ -98,6 +98,84 @@ public function testDeferNestedEvents() $this->assertSame(['inner', 'outer1', 'outer2'], $_SERVER['__event.test']); } + public function testDeferSpecificEvents() + { + $_SERVER['__event.test'] = []; + $d = new Dispatcher; + + $d->listen('foo', function ($foo) { + $_SERVER['__event.test'][] = $foo; + }); + + $d->listen('bar', function ($bar) { + $_SERVER['__event.test'][] = $bar; + }); + + $d->defer(function () use ($d) { + $d->dispatch('foo', ['deferred']); + $d->dispatch('bar', ['immediate']); + + $this->assertSame(['immediate'], $_SERVER['__event.test']); + }, ['foo']); + + $this->assertSame(['immediate', 'deferred'], $_SERVER['__event.test']); + } + + public function testDeferSpecificNestedEvents() + { + $_SERVER['__event.test'] = []; + $d = new Dispatcher; + + $d->listen('foo', function ($foo) { + $_SERVER['__event.test'][] = $foo; + }); + + $d->listen('bar', function ($bar) { + $_SERVER['__event.test'][] = $bar; + }); + + $d->defer(function () use ($d) { + $d->dispatch('foo', ['outer-deferred']); + $d->dispatch('bar', ['outer-immediate']); + + $this->assertSame(['outer-immediate'], $_SERVER['__event.test']); + + $d->defer(function () use ($d) { + $d->dispatch('foo', ['inner-deferred']); + $d->dispatch('bar', ['inner-immediate']); + + $this->assertSame(['outer-immediate', 'inner-immediate'], $_SERVER['__event.test']); + }, ['foo']); + + $this->assertSame(['outer-immediate', 'inner-immediate', 'inner-deferred'], $_SERVER['__event.test']); + }, ['foo']); + + $this->assertSame(['outer-immediate', 'inner-immediate', 'inner-deferred', 'outer-deferred'], $_SERVER['__event.test']); + } + + public function testDeferSpecificObjectEvents() + { + $_SERVER['__event.test'] = []; + $d = new Dispatcher; + + $d->listen(DeferTestEvent::class, function () { + $_SERVER['__event.test'][] = 'DeferTestEvent'; + }); + + $d->listen(ImmediateTestEvent::class, function () { + $_SERVER['__event.test'][] = 'ImmediateTestEvent'; + }); + + $d->defer(function () use ($d) { + $d->dispatch(new DeferTestEvent()); + $d->dispatch(new ImmediateTestEvent()); + + $this->assertSame(['ImmediateTestEvent'], $_SERVER['__event.test']); + }, [DeferTestEvent::class]); + + $this->assertSame(['ImmediateTestEvent', 'DeferTestEvent'], $_SERVER['__event.test']); + } + public function testHaltingEventExecution() { unset($_SERVER['__event.test']); @@ -787,3 +865,11 @@ public function handle() $_SERVER['__event.test'][] = 'handle-3'; } } + +class DeferTestEvent +{ +} + +class ImmediateTestEvent +{ +} diff --git a/tests/Integration/Events/DeferEventsTest.php b/tests/Integration/Events/DeferEventsTest.php index 9fd065a5b22f..861e31c441db 100644 --- a/tests/Integration/Events/DeferEventsTest.php +++ b/tests/Integration/Events/DeferEventsTest.php @@ -77,6 +77,32 @@ public function testDeferMultipleModelEvents() $this->assertEquals('multiple_models_response', $response); $this->assertSame(['saved:TestModel', 'created:AnotherTestModel'], $_SERVER['__model_events']); } + + public function testDeferSpecificModelEvents() + { + $_SERVER['__model_events'] = []; + + TestModel::creating(function () { + $_SERVER['__model_events'][] = 'creating'; + }); + + TestModel::saved(function () { + $_SERVER['__model_events'][] = 'saved'; + }); + + $response = Event::defer(function () { + $model = new TestModel(); + $model->fireModelEvent('creating'); + $model->fireModelEvent('saved'); + + $this->assertSame(['creating'], $_SERVER['__model_events']); + + return 'specific_model_defer_result'; + }, ['eloquent.saved: '.TestModel::class]); + + $this->assertEquals('specific_model_defer_result', $response); + $this->assertSame(['creating', 'saved'], $_SERVER['__model_events']); + } } class TestModel extends Model @@ -94,3 +120,11 @@ public function fireModelEvent($event, $halt = true) return parent::fireModelEvent($event, $halt); } } + +class DeferTestEvent +{ +} + +class AnotherDeferTestEvent +{ +}