From 5b99c8c523597517a51162c1831c2a779f4ddf29 Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 22 May 2026 21:57:25 +0100 Subject: [PATCH 1/2] 13.x Allow jobs to include context Update Job.php this would be useful Update RedisQueueTest.php lil test improvements no empty array... cs oop created should be last tbf --- src/Illuminate/Contracts/Queue/HasContext.php | 13 +++++++++++ src/Illuminate/Queue/Jobs/InspectedJob.php | 5 +++- src/Illuminate/Queue/Jobs/Job.php | 10 ++++++++ src/Illuminate/Queue/Queue.php | 13 +++++++++++ tests/Integration/Queue/RedisQueueTest.php | 20 ++++++++++++++++ tests/Queue/QueueDatabaseQueueUnitTest.php | 23 +++++++++++++++++++ 6 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 src/Illuminate/Contracts/Queue/HasContext.php diff --git a/src/Illuminate/Contracts/Queue/HasContext.php b/src/Illuminate/Contracts/Queue/HasContext.php new file mode 100644 index 000000000000..63e6f4dab177 --- /dev/null +++ b/src/Illuminate/Contracts/Queue/HasContext.php @@ -0,0 +1,13 @@ +payload()['uuid'] ?? null; } + /** + * Get the job context. + * + * @return array|null + */ + public function context() + { + return $this->payload()['context'] ?? null; + } + /** * Fire the job. * diff --git a/src/Illuminate/Queue/Queue.php b/src/Illuminate/Queue/Queue.php index e01e72c46038..26d107818da2 100755 --- a/src/Illuminate/Queue/Queue.php +++ b/src/Illuminate/Queue/Queue.php @@ -9,6 +9,7 @@ use Illuminate\Container\Container; use Illuminate\Contracts\Cache\Repository as Cache; use Illuminate\Contracts\Encryption\Encrypter; +use Illuminate\Contracts\Queue\HasContext; use Illuminate\Contracts\Queue\ShouldBeEncrypted; use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldQueueAfterCommit; @@ -185,6 +186,7 @@ protected function createObjectPayload($job, $queue) 'command' => $job, 'batchId' => $job->batchId ?? null, ], + 'context' => $this->getJobContext($job), 'createdAt' => Carbon::now()->getTimestamp(), ]); @@ -208,6 +210,17 @@ protected function createObjectPayload($job, $queue) ]); } + /** + * Get the context for the given job. + * + * @param object $job + * @return array|null + */ + protected function getJobContext($job) + { + return $job instanceof HasContext ? $job->context() : null; + } + /** * Get the display name for the given job. * diff --git a/tests/Integration/Queue/RedisQueueTest.php b/tests/Integration/Queue/RedisQueueTest.php index d3e61d080337..ad060019fb6c 100644 --- a/tests/Integration/Queue/RedisQueueTest.php +++ b/tests/Integration/Queue/RedisQueueTest.php @@ -4,6 +4,7 @@ use Illuminate\Container\Container; use Illuminate\Contracts\Events\Dispatcher; +use Illuminate\Contracts\Queue\HasContext; use Illuminate\Foundation\Testing\Concerns\InteractsWithRedis; use Illuminate\Queue\Events\JobQueued; use Illuminate\Queue\Events\JobQueueing; @@ -727,6 +728,25 @@ public function testAllReservedJobs($driver) $this->assertNotNull($reserved->first()->uuid); $this->assertInstanceOf(Carbon::class, $reserved->first()->createdAt); } + + #[DataProvider('redisDriverProvider')] + public function testJobContext($driver) + { + $default = config('queue.connections.redis.queue', 'default'); + $this->setQueue($driver, $default); + + $this->queue->push(new RedisContextJob); + + $this->assertSame(['task_id' => 42, 'type' => 'export'], $this->queue->pendingJobs()->first()->context); + } +} + +class RedisContextJob implements HasContext +{ + public function context(): array + { + return ['task_id' => 42, 'type' => 'export']; + } } class RedisQueueIntegrationTestJob diff --git a/tests/Queue/QueueDatabaseQueueUnitTest.php b/tests/Queue/QueueDatabaseQueueUnitTest.php index 9e235c47d63d..793690300f7a 100644 --- a/tests/Queue/QueueDatabaseQueueUnitTest.php +++ b/tests/Queue/QueueDatabaseQueueUnitTest.php @@ -4,6 +4,7 @@ use Illuminate\Bus\Batchable; use Illuminate\Container\Container; +use Illuminate\Contracts\Queue\HasContext; use Illuminate\Database\Connection; use Illuminate\Queue\DatabaseQueue; use Illuminate\Queue\Jobs\InspectedJob; @@ -356,6 +357,20 @@ public function testAllReservedJobs() $this->assertSame(2, $jobs->last()->attempts); } + public function testJobContext() + { + $queue = new DatabaseQueue($database = m::mock(Connection::class), 'table', 'default'); + $queue->setContainer(m::spy(Container::class)); + + $database->shouldReceive('table')->with('table')->andReturn($query = m::mock(stdClass::class)); + $query->shouldReceive('insertGetId')->once()->andReturnUsing(function ($array) { + $payload = json_decode($array['payload'], true); + $this->assertSame(['task_id' => 42, 'type' => 'export'], $payload['context']); + }); + + $queue->push(new ContextJob); + } + public function testGetLockForPoppingIsCached() { $database = m::mock(Connection::class); @@ -390,3 +405,11 @@ class MyBatchableJob { use Batchable; } + +class ContextJob implements HasContext +{ + public function context(): array + { + return ['task_id' => 42, 'type' => 'export']; + } +} From c7f5ee06e14430fee051d4476a94dd927f360660 Mon Sep 17 00:00:00 2001 From: Jack Bayliss Date: Wed, 27 May 2026 20:17:46 +0100 Subject: [PATCH 2/2] support the fake too --- .../Support/Testing/Fakes/QueueFake.php | 3 +++ tests/Support/SupportTestingQueueFakeTest.php | 21 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/Illuminate/Support/Testing/Fakes/QueueFake.php b/src/Illuminate/Support/Testing/Fakes/QueueFake.php index fe3df9b8dc9e..fa8265530bd7 100644 --- a/src/Illuminate/Support/Testing/Fakes/QueueFake.php +++ b/src/Illuminate/Support/Testing/Fakes/QueueFake.php @@ -6,6 +6,7 @@ use Closure; use Illuminate\Bus\UniqueLock; use Illuminate\Contracts\Cache\Repository as Cache; +use Illuminate\Contracts\Queue\HasContext; use Illuminate\Contracts\Queue\Queue; use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Events\CallQueuedListener; @@ -493,6 +494,7 @@ public function pendingJobs($queue = null): Collection ? (method_exists($data['job'], 'displayName') ? $data['job']->displayName() : get_class($data['job'])) : $data['job'], attempts: 0, + context: $data['job'] instanceof HasContext ? $data['job']->context() : null, createdAt: null, )); } @@ -534,6 +536,7 @@ public function allPendingJobs(): Collection ? (method_exists($data['job'], 'displayName') ? $data['job']->displayName() : get_class($data['job'])) : $data['job'], attempts: 0, + context: $data['job'] instanceof HasContext ? $data['job']->context() : null, createdAt: null, )); } diff --git a/tests/Support/SupportTestingQueueFakeTest.php b/tests/Support/SupportTestingQueueFakeTest.php index 66ed7062381b..a4ac59a19846 100644 --- a/tests/Support/SupportTestingQueueFakeTest.php +++ b/tests/Support/SupportTestingQueueFakeTest.php @@ -4,6 +4,7 @@ use BadMethodCallException; use Illuminate\Bus\Queueable; +use Illuminate\Contracts\Queue\HasContext; use Illuminate\Foundation\Application; use Illuminate\Queue\CallQueuedClosure; use Illuminate\Queue\Jobs\InspectedJob; @@ -540,6 +541,13 @@ public function testAllPendingJobs() $this->assertTrue($pending->contains(fn ($job) => $job->name === JobToFakeStub::class)); } + public function testContextIsIncluded() + { + $this->fake->push(new JobWithContextStub, '', 'foo'); + + $this->assertSame(['task_id' => 42, 'type' => 'export'], $this->fake->allPendingJobs()->first()->context); + } + public function testGetRawPushes() { $this->fake->pushRaw('some-payload', null, ['options' => 'yeah']); @@ -618,6 +626,19 @@ public function handle() } } +class JobWithContextStub implements HasContext +{ + public function handle() + { + // + } + + public function context(): array + { + return ['task_id' => 42, 'type' => 'export']; + } +} + class JobWithChainStub { use Queueable;