Laravel Version
12.28.1
PHP Version
8.3.26
Database Driver & Version
No response
Description
When using a closure in a job chain after batch jobs (as documented in the "Chains and Batches" section of the Queue documentation), the chain fails with a Call to undefined method Closure::getClosure() error.
This occurs specifically when:
- A job chain contains one or more batches
- A closure is placed in the chain after the batch(es)
- The batch completes successfully
- The
ChainedBatch callback attempts to dispatch the next item (the closure)
Steps To Reproduce
Create three simple job classes with the Batchable trait:
// app/Jobs/FlushPodcastCache.php
<?php
namespace App\Jobs;
use Illuminate\Bus\Batchable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
class FlushPodcastCache implements ShouldQueue
{
use Queueable, Batchable;
public function handle(): void
{
// Implementation
}
}
// app/Jobs/ReleasePodcast.php
<?php
namespace App\Jobs;
use Illuminate\Bus\Batchable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
class ReleasePodcast implements ShouldQueue
{
use Queueable, Batchable;
public function __construct(public int $id) {}
public function handle(): void
{
// Implementation
}
}
// app/Jobs/SendPodcastReleaseNotification.php
<?php
namespace App\Jobs;
use Illuminate\Bus\Batchable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
class SendPodcastReleaseNotification implements ShouldQueue
{
use Queueable, Batchable;
public function __construct(public int $id) {}
public function handle(): void
{
// Implementation
}
}
Then dispatch a chain with batches followed by a closure:
use App\Jobs\FlushPodcastCache;
use App\Jobs\ReleasePodcast;
use App\Jobs\SendPodcastReleaseNotification;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\Log;
Bus::chain([
new FlushPodcastCache,
Bus::batch([
new ReleasePodcast(1),
new ReleasePodcast(2),
]),
Bus::batch([
new SendPodcastReleaseNotification(1),
new SendPodcastReleaseNotification(2),
]),
function() {
Log::debug('Closure Run');
}
])->dispatch();
Expected Behavior
The closure should execute after all batches complete successfully, as documented in the Laravel Queue documentation under "Job Chaining" which explicitly shows that closures can be mixed with job classes in chains.
Actual Behavior
The chain processes successfully through all jobs and batches, but when ChainedBatch attempts to dispatch the closure, it throws:
Call to undefined method Closure::getClosure() at /app/vendor/laravel/framework/src/Illuminate/Queue/CallQueuedClosure.php:113
Stack Trace
[2025-10-30 17:31:09] local.ERROR: Call to undefined method Closure::getClosure() {"exception":"[object] (Error(code: 0): Call to undefined method Closure::getClosure() at /app/vendor/laravel/framework/src/Illuminate/Queue/CallQueuedClosure.php:113)
[stacktrace]
#0 /app/vendor/laravel/framework/src/Illuminate/Queue/Queue.php(208): Illuminate\\Queue\\CallQueuedClosure->displayName()
#1 /app/vendor/laravel/framework/src/Illuminate/Queue/Queue.php(164): Illuminate\\Queue\\Queue->getDisplayName()
#2 /app/vendor/laravel/framework/src/Illuminate/Queue/Queue.php(149): Illuminate\\Queue\\Queue->createObjectPayload()
#3 /app/vendor/laravel/framework/src/Illuminate/Queue/RedisQueue.php(283): Illuminate\\Queue\\Queue->createPayloadArray()
#4 /app/vendor/laravel/horizon/src/RedisQueue.php(89): Illuminate\\Queue\\RedisQueue->createPayloadArray()
#5 /app/vendor/laravel/framework/src/Illuminate/Queue/Queue.php(121): Laravel\\Horizon\\RedisQueue->createPayloadArray()
#6 /app/vendor/laravel/horizon/src/RedisQueue.php(47): Illuminate\\Queue\\Queue->createPayload()
#7 /app/vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php(246): Laravel\\Horizon\\RedisQueue->push()
#8 /app/vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php(230): Illuminate\\Bus\\Dispatcher->pushCommandToQueue()
#9 /app/vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php(80): Illuminate\\Bus\\Dispatcher->dispatchToQueue()
#10 laravel-serializable-closure://function (\\Illuminate\\Bus\\Batch $batch) use ($next) {
if (! $batch->cancelled()) {
\\Illuminate\\Container\\Container::getInstance()->make(\\Illuminate\\Contracts\\Bus\\Dispatcher::class)->dispatch($next);
}
}(4): Illuminate\\Bus\\Dispatcher->dispatch()
#11 [internal function]: Illuminate\\Bus\\ChainedBatch::{closure}()
#12 /app/vendor/laravel/serializable-closure/src/Serializers/Signed.php(43): call_user_func_array()
#13 [internal function]: Laravel\\SerializableClosure\\Serializers\\Signed->__invoke()
#14 /app/vendor/laravel/serializable-closure/src/SerializableClosure.php(39): call_user_func_array()
#15 /app/vendor/laravel/framework/src/Illuminate/Bus/Batch.php(458): Laravel\\SerializableClosure\\SerializableClosure->__invoke()
#16 /app/vendor/laravel/framework/src/Illuminate/Bus/Batch.php(280): Illuminate\\Bus\\Batch->invokeHandlerCallback()
#17 /app/vendor/laravel/framework/src/Illuminate/Bus/Batch.php(257): Illuminate\\Bus\\Batch->invokeCallbacks()
#18 /app/vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php(203): Illuminate\\Bus\\Batch->recordSuccessfulJob()
#19 /app/vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php(76): Illuminate\\Queue\\CallQueuedHandler->ensureSuccessfulBatchJobIsRecorded()
#20 /app/vendor/laravel/framework/src/Illuminate/Queue/Jobs/Job.php(102): Illuminate\\Queue\\CallQueuedHandler->call()
#21 /app/vendor/laravel/framework/src/Illuminate/Queue/Worker.php(451): Illuminate\\Queue\\Jobs\\Job->fire()
#22 /app/vendor/laravel/framework/src/Illuminate/Queue/Worker.php(401): Illuminate\\Queue\\Worker->process()
#23 /app/vendor/laravel/framework/src/Illuminate/Queue/Worker.php(187): Illuminate\\Queue\\Worker->runJob()
#24 /app/vendor/laravel/framework/src/Illuminate/Queue/Console/WorkCommand.php(148): Illuminate\\Queue\\Worker->daemon()
#25 /app/vendor/laravel/framework/src/Illuminate/Queue/Console/WorkCommand.php(131): Illuminate\\Queue\\Console\\WorkCommand->runWorker()
#26 /app/vendor/laravel/horizon/src/Console/WorkCommand.php(52): Illuminate\\Queue\\Console\\WorkCommand->handle()
#27 /app/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(36): Laravel\\Horizon\\Console\\WorkCommand->handle()
#28 /app/vendor/laravel/framework/src/Illuminate/Container/Util.php(43): Illuminate\\Container\\BoundMethod::Illuminate\\Container\\{closure}()
#29 /app/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(96): Illuminate\\Container\\Util::unwrapIfClosure()
#30 /app/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(35): Illuminate\\Container\\BoundMethod::callBoundMethod()
#31 /app/vendor/laravel/framework/src/Illuminate/Container/Container.php(836): Illuminate\\Container\\BoundMethod::call()
#32 /app/vendor/laravel/framework/src/Illuminate/Console/Command.php(211): Illuminate\\Container\\Container->call()
#33 /app/vendor/symfony/console/Command/Command.php(318): Illuminate\\Console\\Command->execute()
#34 /app/vendor/laravel/framework/src/Illuminate/Console/Command.php(180): Symfony\\Component\\Console\\Command\\Command->run()
#35 /app/vendor/symfony/console/Application.php(1073): Illuminate\\Console\\Command->run()
#36 /app/vendor/symfony/console/Application.php(356): Symfony\\Component\\Console\\Application->doRunCommand()
#37 /app/vendor/symfony/console/Application.php(195): Symfony\\Component\\Console\\Application->doRun()
#38 /app/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php(197): Symfony\\Component\\Console\\Application->run()
#39 /app/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1235): Illuminate\\Foundation\\Console\\Kernel->handle()
#40 /app/artisan(16): Illuminate\\Foundation\\Application->handleCommand()
#41 {main}
"}
Additional Context
- Closures work fine in chains when NOT following batches
- Using a job class instead of a closure works as expected
- The issue appears to be in how
ChainedBatch serializes/deserializes the closure for dispatch
- This follows the exact pattern documented at https://laravel.com/docs/12.x/queues#chains-and-batches
Laravel Version
12.28.1
PHP Version
8.3.26
Database Driver & Version
No response
Description
When using a closure in a job chain after batch jobs (as documented in the "Chains and Batches" section of the Queue documentation), the chain fails with a
Call to undefined method Closure::getClosure()error.This occurs specifically when:
ChainedBatchcallback attempts to dispatch the next item (the closure)Steps To Reproduce
Create three simple job classes with the
Batchabletrait:Then dispatch a chain with batches followed by a closure:
Expected Behavior
The closure should execute after all batches complete successfully, as documented in the Laravel Queue documentation under "Job Chaining" which explicitly shows that closures can be mixed with job classes in chains.
Actual Behavior
The chain processes successfully through all jobs and batches, but when
ChainedBatchattempts to dispatch the closure, it throws:Stack Trace
Additional Context
ChainedBatchserializes/deserializes the closure for dispatch