diff --git a/src/Illuminate/Foundation/Bus/Dispatchable.php b/src/Illuminate/Foundation/Bus/Dispatchable.php index 1fef9872f833..391d83fc993b 100644 --- a/src/Illuminate/Foundation/Bus/Dispatchable.php +++ b/src/Illuminate/Foundation/Bus/Dispatchable.php @@ -87,6 +87,14 @@ public static function dispatchAfterResponse(...$arguments) return self::dispatch(...$arguments)->afterResponse(); } + /** + * Defer dispatching a command to its appropriate handler. + */ + public static function dispatchDefer(...$arguments) + { + return self::dispatch(...$arguments)->defer(); + } + /** * Set the jobs that should run if this job is successful. * diff --git a/src/Illuminate/Foundation/Bus/PendingDispatch.php b/src/Illuminate/Foundation/Bus/PendingDispatch.php index 443eb5eddf5a..4ca29686843f 100644 --- a/src/Illuminate/Foundation/Bus/PendingDispatch.php +++ b/src/Illuminate/Foundation/Bus/PendingDispatch.php @@ -27,6 +27,13 @@ class PendingDispatch */ protected $afterResponse = false; + /** + * Indicates if the job dispatch should be deferred. + * + * @var bool + */ + protected $defer = false; + /** * Create a new pending job dispatch. * @@ -163,6 +170,18 @@ public function afterResponse() return $this; } + /** + * Indicate that the job dispatch should be deferred. + * + * @return $this + */ + public function defer(): static + { + $this->defer = true; + + return $this; + } + /** * Determine if the job should be dispatched. * @@ -217,6 +236,8 @@ public function __destruct() return; } elseif ($this->afterResponse) { app(Dispatcher::class)->dispatchAfterResponse($this->job); + } elseif ($this->defer) { + defer(fn () => app(Dispatcher::class)->dispatch($this->job)); } else { app(Dispatcher::class)->dispatch($this->job); } diff --git a/tests/Bus/BusPendingDispatchTest.php b/tests/Bus/BusPendingDispatchTest.php index 99c4065cb5c5..7f6b8c665543 100644 --- a/tests/Bus/BusPendingDispatchTest.php +++ b/tests/Bus/BusPendingDispatchTest.php @@ -103,6 +103,14 @@ public function testAfterResponse() ); } + public function testDefer() + { + $this->pendingDispatch->defer(); + $this->assertTrue( + (new ReflectionClass($this->pendingDispatch))->getProperty('defer')->getValue($this->pendingDispatch) + ); + } + public function testGetJob() { $this->assertSame($this->job, $this->pendingDispatch->getJob()); diff --git a/tests/Integration/Queue/JobDispatchingTest.php b/tests/Integration/Queue/JobDispatchingTest.php index 441cb59dea97..e1dcb04c3a43 100644 --- a/tests/Integration/Queue/JobDispatchingTest.php +++ b/tests/Integration/Queue/JobDispatchingTest.php @@ -10,6 +10,7 @@ use Illuminate\Queue\Events\JobQueued; use Illuminate\Queue\Events\JobQueueing; use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Support\Defer\DeferredCallbackCollection; use Illuminate\Support\Facades\Bus; use Illuminate\Support\Facades\Config; use Orchestra\Testbench\Attributes\WithMigration; @@ -139,6 +140,21 @@ public function testUniqueJobLockIsReleasedForJobDispatchedAfterResponse() $this->assertFalse(UniqueJob::$ran); } + public function testDispatchDeferDelaysDispatchingUntilDeferredCallbacksAreRun() + { + Job::dispatchDefer('test'); + + $this->assertSame(1, $this->app[DeferredCallbackCollection::class]->count()); + $this->assertFalse(Job::$ran); + + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertFalse(Job::$ran); + + $this->app[DeferredCallbackCollection::class]->invoke(); + $this->runQueueWorkerCommand(['--once' => true]); + $this->assertTrue(Job::$ran); + } + public function testQueueMayBeNullForJobQueueingAndJobQueuedEvent() { Config::set('queue.default', 'database');