Skip to content

Commit feffcd6

Browse files
[12.x] Deferred Events (#56556)
* Adding Event::defer() * Fix CS * formatting --------- Co-authored-by: Taylor Otwell <[email protected]>
1 parent 48bc5ee commit feffcd6

File tree

4 files changed

+208
-0
lines changed

4 files changed

+208
-0
lines changed

src/Illuminate/Events/Dispatcher.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,20 @@ class Dispatcher implements DispatcherContract
6969
*/
7070
protected $transactionManagerResolver;
7171

72+
/**
73+
* The currently deferred events.
74+
*
75+
* @var array
76+
*/
77+
protected $deferredEvents = [];
78+
79+
/**
80+
* Indicates if events should be deferred.
81+
*
82+
* @var bool
83+
*/
84+
protected $deferringEvents = false;
85+
7286
/**
7387
* Create a new event dispatcher instance.
7488
*
@@ -244,6 +258,12 @@ public function until($event, $payload = [])
244258
*/
245259
public function dispatch($event, $payload = [], $halt = false)
246260
{
261+
if ($this->deferringEvents) {
262+
$this->deferredEvents[] = func_get_args();
263+
264+
return null;
265+
}
266+
247267
// When the given "event" is actually an object we will assume it is an event
248268
// object and use the class as the event name and this event itself as the
249269
// payload to the handler, which makes object based events quite simple.
@@ -768,6 +788,36 @@ public function setTransactionManagerResolver(callable $resolver)
768788
return $this;
769789
}
770790

791+
/**
792+
* Execute the given callback while deferring events, then dispatch all deferred events.
793+
*
794+
* @param callable $callback
795+
* @return mixed
796+
*/
797+
public function defer(callable $callback)
798+
{
799+
$wasDeferring = $this->deferringEvents;
800+
$previousDeferredEvents = $this->deferredEvents;
801+
802+
$this->deferringEvents = true;
803+
$this->deferredEvents = [];
804+
805+
try {
806+
$result = $callback();
807+
808+
$this->deferringEvents = false;
809+
810+
foreach ($this->deferredEvents as $args) {
811+
$this->dispatch(...$args);
812+
}
813+
814+
return $result;
815+
} finally {
816+
$this->deferringEvents = $wasDeferring;
817+
$this->deferredEvents = $previousDeferredEvents;
818+
}
819+
}
820+
771821
/**
772822
* Gets the raw, unprepared listeners.
773823
*

src/Illuminate/Support/Facades/Event.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
* @method static \Illuminate\Events\Dispatcher setQueueResolver(callable $resolver)
2323
* @method static \Illuminate\Events\Dispatcher setTransactionManagerResolver(callable $resolver)
2424
* @method static array getRawListeners()
25+
* @method static mixed defer(callable $callback)
2526
* @method static void macro(string $name, object|callable $macro)
2627
* @method static void mixin(object $mixin, bool $replace = true)
2728
* @method static bool hasMacro(string $name)

tests/Events/EventsDispatcherTest.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,67 @@ public function testBasicEventExecution()
3737
$this->assertSame('barbar', $_SERVER['__event.test']);
3838
}
3939

40+
public function testDeferEventExecution()
41+
{
42+
unset($_SERVER['__event.test']);
43+
$d = new Dispatcher;
44+
$d->listen('foo', function ($foo) {
45+
$_SERVER['__event.test'] = $foo;
46+
});
47+
48+
$result = $d->defer(function () use ($d) {
49+
$d->dispatch('foo', ['bar']);
50+
$this->assertArrayNotHasKey('__event.test', $_SERVER);
51+
52+
return 'callback_result';
53+
});
54+
55+
$this->assertEquals('callback_result', $result);
56+
$this->assertSame('bar', $_SERVER['__event.test']);
57+
}
58+
59+
public function testDeferMultipleEvents()
60+
{
61+
$_SERVER['__event.test'] = [];
62+
$d = new Dispatcher;
63+
$d->listen('foo', function ($value) {
64+
$_SERVER['__event.test'][] = $value;
65+
});
66+
$d->listen('bar', function ($value) {
67+
$_SERVER['__event.test'][] = $value;
68+
});
69+
$d->defer(function () use ($d) {
70+
$d->dispatch('foo', ['foo']);
71+
$d->dispatch('bar', ['bar']);
72+
$this->assertSame([], $_SERVER['__event.test']);
73+
});
74+
75+
$this->assertSame(['foo', 'bar'], $_SERVER['__event.test']);
76+
}
77+
78+
public function testDeferNestedEvents()
79+
{
80+
$_SERVER['__event.test'] = [];
81+
$d = new Dispatcher;
82+
$d->listen('foo', function ($foo) {
83+
$_SERVER['__event.test'][] = $foo;
84+
});
85+
86+
$d->defer(function () use ($d) {
87+
$d->dispatch('foo', ['outer1']);
88+
89+
$d->defer(function () use ($d) {
90+
$d->dispatch('foo', ['inner']);
91+
$this->assertSame([], $_SERVER['__event.test']);
92+
});
93+
94+
$this->assertSame(['inner'], $_SERVER['__event.test']);
95+
$d->dispatch('foo', ['outer2']);
96+
});
97+
98+
$this->assertSame(['inner', 'outer1', 'outer2'], $_SERVER['__event.test']);
99+
}
100+
40101
public function testHaltingEventExecution()
41102
{
42103
unset($_SERVER['__event.test']);
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
3+
namespace Illuminate\Tests\Integration\Events;
4+
5+
use Illuminate\Database\Eloquent\Model;
6+
use Illuminate\Support\Facades\Event;
7+
use Orchestra\Testbench\TestCase;
8+
9+
class DeferEventsTest extends TestCase
10+
{
11+
public function testDeferEvents()
12+
{
13+
unset($_SERVER['__event.test']);
14+
15+
Event::listen('foo', function ($foo) {
16+
$_SERVER['__event.test'] = $foo;
17+
});
18+
19+
$response = Event::defer(function () {
20+
Event::dispatch('foo', ['bar']);
21+
22+
$this->assertArrayNotHasKey('__event.test', $_SERVER);
23+
24+
return 'callback_result';
25+
});
26+
27+
$this->assertEquals('callback_result', $response);
28+
$this->assertSame('bar', $_SERVER['__event.test']);
29+
}
30+
31+
public function testDeferModelEvents()
32+
{
33+
$_SERVER['__model_event.test'] = [];
34+
35+
TestModel::saved(function () {
36+
$_SERVER['__model_event.test'][] = 'saved';
37+
});
38+
39+
$response = Event::defer(function () {
40+
$model = new TestModel();
41+
$model->fireModelEvent('saved', false);
42+
43+
$this->assertSame([], $_SERVER['__model_event.test']);
44+
45+
return 'model_event_response';
46+
});
47+
48+
$this->assertEquals('model_event_response', $response);
49+
$this->assertContains('saved', $_SERVER['__model_event.test']);
50+
}
51+
52+
public function testDeferMultipleModelEvents()
53+
{
54+
$_SERVER['__model_events'] = [];
55+
56+
TestModel::saved(function () {
57+
$_SERVER['__model_events'][] = 'saved:TestModel';
58+
});
59+
60+
AnotherTestModel::created(function () {
61+
$_SERVER['__model_events'][] = 'created:AnotherTestModel';
62+
});
63+
64+
$response = Event::defer(function () {
65+
$model1 = new TestModel();
66+
$model1->fireModelEvent('saved');
67+
68+
$model2 = new AnotherTestModel();
69+
$model2->fireModelEvent('created');
70+
71+
// Events should not have fired yet
72+
$this->assertSame([], $_SERVER['__model_events']);
73+
74+
return 'multiple_models_response';
75+
});
76+
77+
$this->assertEquals('multiple_models_response', $response);
78+
$this->assertSame(['saved:TestModel', 'created:AnotherTestModel'], $_SERVER['__model_events']);
79+
}
80+
}
81+
82+
class TestModel extends Model
83+
{
84+
public function fireModelEvent($event, $halt = true)
85+
{
86+
return parent::fireModelEvent($event, $halt);
87+
}
88+
}
89+
90+
class AnotherTestModel extends Model
91+
{
92+
public function fireModelEvent($event, $halt = true)
93+
{
94+
return parent::fireModelEvent($event, $halt);
95+
}
96+
}

0 commit comments

Comments
 (0)