From a127e2320b35a9065e9c4ac37ae10780b516856b Mon Sep 17 00:00:00 2001 From: Josh Salway Date: Thu, 2 Apr 2026 09:56:12 +1000 Subject: [PATCH] [12.x] Fix closure in chain not dispatched after batch completes When Bus::chain contains a closure after a Bus::batch, the closure fails with 'Call to undefined method Closure::getClosure()'. This happens because attachRemainderOfChainToEndOfBatch() captures the next chain item (a CallQueuedClosure containing a SerializableClosure) directly in the batch's finally callback. When the batch serializes this callback, the nested SerializableClosure gets unwrapped to a raw Closure during the round-trip. Fix by re-serializing the next chain item to a string before capturing it in the finally closure. --- src/Illuminate/Bus/ChainedBatch.php | 4 +++- tests/Integration/Queue/JobChainingTest.php | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Bus/ChainedBatch.php b/src/Illuminate/Bus/ChainedBatch.php index 7dc1a39ecaab..19e77017a19e 100644 --- a/src/Illuminate/Bus/ChainedBatch.php +++ b/src/Illuminate/Bus/ChainedBatch.php @@ -130,9 +130,11 @@ protected function attachRemainderOfChainToEndOfBatch(PendingBatch $batch) $next->chainQueue = $this->chainQueue; $next->chainCatchCallbacks = $this->chainCatchCallbacks; + $next = serialize($next); + $batch->finally(function (Batch $batch) use ($next) { if (! $batch->cancelled()) { - Container::getInstance()->make(Dispatcher::class)->dispatch($next); + Container::getInstance()->make(Dispatcher::class)->dispatch(unserialize($next)); } }); diff --git a/tests/Integration/Queue/JobChainingTest.php b/tests/Integration/Queue/JobChainingTest.php index ba802b66cc37..986e4f65d100 100644 --- a/tests/Integration/Queue/JobChainingTest.php +++ b/tests/Integration/Queue/JobChainingTest.php @@ -470,6 +470,24 @@ public function testBatchCanBeAddedToChain() $this->assertEquals(['c1', 'c2', 'b1', 'b2', 'b3', 'b4', 'c3'], JobRunRecorder::$results); } + public function testClosureAfterBatchInChainIsDispatched() + { + Bus::chain([ + new JobChainingNamedTestJob('c1'), + Bus::batch([ + new JobChainingTestBatchedJob('b1'), + new JobChainingTestBatchedJob('b2'), + ]), + function () { + JobRunRecorder::record('closure-after-batch'); + }, + ])->dispatch(); + + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + + $this->assertContains('closure-after-batch', JobRunRecorder::$results); + } + public function testBatchInChainUsesCorrectQueue() { $otherQueue = $this->getQueueDriver() === 'redis' ? '{other}' : 'other';