Skip to content

Commit 6520552

Browse files
authored
[8.x] Queued closure listeners (#33463)
Queued closure listener support.
1 parent 12ee1c4 commit 6520552

File tree

8 files changed

+220
-1
lines changed

8 files changed

+220
-1
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@
9898
},
9999
"autoload": {
100100
"files": [
101+
"src/Illuminate/Events/functions.php",
101102
"src/Illuminate/Foundation/helpers.php",
102103
"src/Illuminate/Support/helpers.php"
103104
],

src/Illuminate/Events/CallQueuedListener.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22

33
namespace Illuminate\Events;
44

5+
use Illuminate\Bus\Queueable;
56
use Illuminate\Container\Container;
67
use Illuminate\Contracts\Queue\Job;
78
use Illuminate\Contracts\Queue\ShouldQueue;
89
use Illuminate\Queue\InteractsWithQueue;
910

1011
class CallQueuedListener implements ShouldQueue
1112
{
12-
use InteractsWithQueue;
13+
use InteractsWithQueue, Queueable;
1314

1415
/**
1516
* The listener class name.

src/Illuminate/Events/Dispatcher.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ public function listen($events, $listener = null)
7777
{
7878
if ($events instanceof Closure) {
7979
return $this->listen($this->firstClosureParameterType($events), $events);
80+
} elseif ($events instanceof QueuedClosure) {
81+
return $this->listen($this->firstClosureParameterType($events->closure), $events->resolve());
82+
} elseif ($listener instanceof QueuedClosure) {
83+
$listener = $listener->resolve();
8084
}
8185

8286
foreach ((array) $events as $event) {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace Illuminate\Events;
4+
5+
class InvokeQueuedClosure
6+
{
7+
/**
8+
* Handle the event.
9+
*
10+
* @param \Illuminate\Queue\SerializableClosure $closure
11+
* @param array $arguments
12+
* @return void
13+
*/
14+
public function handle($closure, array $arguments)
15+
{
16+
call_user_func($closure->getClosure(), ...$arguments);
17+
}
18+
19+
/**
20+
* Handle a job failure.
21+
*
22+
* @param \Illuminate\Queue\SerializableClosure $closure
23+
* @param array $arguments
24+
* @param array $catchCallbacks
25+
* @param \Throwable $exception
26+
* @return void
27+
*/
28+
public function failed($closure, array $arguments, array $catchCallbacks, $exception)
29+
{
30+
$arguments[] = $exception;
31+
32+
collect($catchCallbacks)->each->__invoke(...$arguments);
33+
}
34+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<?php
2+
3+
namespace Illuminate\Events;
4+
5+
use Closure;
6+
use Illuminate\Queue\SerializableClosure;
7+
8+
class QueuedClosure
9+
{
10+
/**
11+
* The underlying Closure.
12+
*
13+
* @var \Closure
14+
*/
15+
public $closure;
16+
17+
/**
18+
* The name of the connection the job should be sent to.
19+
*
20+
* @var string|null
21+
*/
22+
public $connection;
23+
24+
/**
25+
* The name of the queue the job should be sent to.
26+
*
27+
* @var string|null
28+
*/
29+
public $queue;
30+
31+
/**
32+
* The number of seconds before the job should be made available.
33+
*
34+
* @var \DateTimeInterface|\DateInterval|int|null
35+
*/
36+
public $delay;
37+
38+
/**
39+
* All of the "catch" callbacks for the queued closure.
40+
*
41+
* @var array
42+
*/
43+
public $catchCallbacks = [];
44+
45+
/**
46+
* Create a new queued closure event listener resolver.
47+
*
48+
* @param \Closure $closure
49+
* @return void
50+
*/
51+
public function __construct(Closure $closure)
52+
{
53+
$this->closure = $closure;
54+
}
55+
56+
/**
57+
* Set the desired connection for the job.
58+
*
59+
* @param string|null $connection
60+
* @return $this
61+
*/
62+
public function onConnection($connection)
63+
{
64+
$this->connection = $connection;
65+
66+
return $this;
67+
}
68+
69+
/**
70+
* Set the desired queue for the job.
71+
*
72+
* @param string|null $queue
73+
* @return $this
74+
*/
75+
public function onQueue($queue)
76+
{
77+
$this->queue = $queue;
78+
79+
return $this;
80+
}
81+
82+
/**
83+
* Set the desired delay for the job.
84+
*
85+
* @param \DateTimeInterface|\DateInterval|int|null $delay
86+
* @return $this
87+
*/
88+
public function delay($delay)
89+
{
90+
$this->delay = $delay;
91+
92+
return $this;
93+
}
94+
95+
/**
96+
* Specify a callback that should be invoked if the queued listener job fails.
97+
*
98+
* @param \Closure $closure
99+
* @return $this
100+
*/
101+
public function catch(Closure $closure)
102+
{
103+
$this->catchCallbacks[] = $closure;
104+
105+
return $this;
106+
}
107+
108+
/**
109+
* Resolve the actual event listener callback.
110+
*
111+
* @return \Closure
112+
*/
113+
public function resolve()
114+
{
115+
return function (...$arguments) {
116+
dispatch(new CallQueuedListener(InvokeQueuedClosure::class, 'handle', [
117+
'closure' => new SerializableClosure($this->closure),
118+
'arguments' => $arguments,
119+
'catch' => collect($this->catchCallbacks)->map(function ($callback) {
120+
return new SerializableClosure($callback);
121+
})->all(),
122+
]))->onConnection($this->connection)->onQueue($this->queue)->delay($this->delay);
123+
};
124+
}
125+
}

src/Illuminate/Events/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
],
1616
"require": {
1717
"php": "^7.3",
18+
"illuminate/bus": "^8.0",
1819
"illuminate/collections": "^8.0",
1920
"illuminate/container": "^8.0",
2021
"illuminate/contracts": "^8.0",

src/Illuminate/Events/functions.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Illuminate\Events;
4+
5+
use Closure;
6+
7+
if (! function_exists('Illuminate\Events\queueable')) {
8+
/**
9+
* Create a new queued Closure event listener.
10+
*
11+
* @param \Closure $closure
12+
* @return \Illuminate\Events\QueuedClosure
13+
*/
14+
function queueable(Closure $closure)
15+
{
16+
return new QueuedClosure($closure);
17+
}
18+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace Illuminate\Tests\Integration\Events;
4+
5+
use Illuminate\Events\CallQueuedListener;
6+
use Illuminate\Events\InvokeQueuedClosure;
7+
use function Illuminate\Events\queueable;
8+
use Illuminate\Support\Facades\Bus;
9+
use Illuminate\Support\Facades\Event;
10+
use Orchestra\Testbench\TestCase;
11+
12+
class QueuedClosureListenerTest extends TestCase
13+
{
14+
public function testAnonymousQueuedListenerIsQueued()
15+
{
16+
Bus::fake();
17+
18+
Event::listen(queueable(function (TestEvent $event) {
19+
//
20+
})->catch(function (TestEvent $event) {
21+
//
22+
})->onConnection(null)->onQueue(null));
23+
24+
Event::dispatch(new TestEvent);
25+
26+
Bus::assertDispatched(CallQueuedListener::class, function ($job) {
27+
return $job->class == InvokeQueuedClosure::class;
28+
});
29+
}
30+
}
31+
32+
class TestEvent
33+
{
34+
//
35+
}

0 commit comments

Comments
 (0)