RetryCommand.php
TLDR
The RetryCommand.php
file is a class located in the Illuminate\Queue\Console
namespace. It is a command-line command used to retry failed jobs in a queue. The command is registered as queue:retry
. The file implements the handle
method to execute the command, and it also includes several helper methods to retrieve the job IDs, retry the job, reset payload attempts, and refresh the "retry until" timestamp.
Methods
handle
Executes the queue:retry
command. It retrieves the job IDs to be retried, pushes them back onto the queue, and dispatches the JobRetryRequested
event for each job. It also outputs the progress and status of each retry.
getJobIds
Retrieves the job IDs to be retried based on the provided arguments and options. It can fetch all job IDs, IDs for a specific queue, or IDs within a specified range.
getJobIdsByQueue
Retrieves the job IDs that belong to a specific queue. It checks if the queue.failer
object has an ids
method to directly retrieve the IDs or uses all
to get all failed jobs and filter them by the queue.
getJobIdsByRanges
Retrieves the job IDs within the provided range arguments. It checks the format of each range and generates an array of IDs based on the range.
retryJob
Retries a specific failed job by pushing it to the queue. It retrieves the connection, payload, and queue from the job object and uses the queue
object to push the job back onto the queue.
resetAttempts
Resets the payload attempts for a job. It receives the payload as a string, decodes it into an associative array, and checks if the attempts key exists. If it does, the attempts value is set to 0. The modified payload is then encoded back into a string.
refreshRetryUntil
Refreshes the "retry until" timestamp for a job. It receives the payload as a string, decodes it into an associative array, and checks if the command exists in the payload data. If it does, it tries to unserialize the command into an instance. If successful, it calls the retryUntil
method on the instance to obtain the retry timestamp or value. The retry value is then added to the payload and encoded back into a string.
Classes
None
<?php
namespace Illuminate\Queue\Console;
use DateTimeInterface;
use Illuminate\Console\Command;
use Illuminate\Contracts\Encryption\Encrypter;
use Illuminate\Queue\Events\JobRetryRequested;
use Illuminate\Support\Arr;
use RuntimeException;
use Symfony\Component\Console\Attribute\AsCommand;
#[AsCommand(name: 'queue:retry')]
class RetryCommand extends Command
{
/**
* The console command signature.
*
* @var string
*/
protected $signature = 'queue:retry
{id?* : The ID of the failed job or "all" to retry all jobs}
{--queue= : Retry all of the failed jobs for the specified queue}
{--range=* : Range of job IDs (numeric) to be retried (e.g. 1-5)}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Retry a failed queue job';
/**
* Execute the console command.
*
* @return void
*/
public function handle()
{
$jobsFound = count($ids = $this->getJobIds()) > 0;
if ($jobsFound) {
$this->components->info('Pushing failed queue jobs back onto the queue.');
}
foreach ($ids as $id) {
$job = $this->laravel['queue.failer']->find($id);
if (is_null($job)) {
$this->components->error("Unable to find failed job with ID [{$id}].");
} else {
$this->laravel['events']->dispatch(new JobRetryRequested($job));
$this->components->task($id, fn () => $this->retryJob($job));
$this->laravel['queue.failer']->forget($id);
}
}
$jobsFound ? $this->newLine() : $this->components->info('No retryable jobs found.');
}
/**
* Get the job IDs to be retried.
*
* @return array
*/
protected function getJobIds()
{
$ids = (array) $this->argument('id');
if (count($ids) === 1 && $ids[0] === 'all') {
$failer = $this->laravel['queue.failer'];
return method_exists($failer, 'ids')
? $failer->ids()
: Arr::pluck($failer->all(), 'id');
}
if ($queue = $this->option('queue')) {
return $this->getJobIdsByQueue($queue);
}
if ($ranges = (array) $this->option('range')) {
$ids = array_merge($ids, $this->getJobIdsByRanges($ranges));
}
return array_values(array_filter(array_unique($ids)));
}
/**
* Get the job IDs by queue, if applicable.
*
* @param string $queue
* @return array
*/
protected function getJobIdsByQueue($queue)
{
$failer = $this->laravel['queue.failer'];
$ids = method_exists($failer, 'ids')
? $failer->ids($queue)
: collect($failer->all())
->where('queue', $queue)
->pluck('id')
->toArray();
if (count($ids) === 0) {
$this->components->error("Unable to find failed jobs for queue [{$queue}].");
}
return $ids;
}
/**
* Get the job IDs ranges, if applicable.
*
* @param array $ranges
* @return array
*/
protected function getJobIdsByRanges(array $ranges)
{
$ids = [];
foreach ($ranges as $range) {
if (preg_match('/^[0-9]+\-[0-9]+$/', $range)) {
$ids = array_merge($ids, range(...explode('-', $range)));
}
}
return $ids;
}
/**
* Retry the queue job.
*
* @param \stdClass $job
* @return void
*/
protected function retryJob($job)
{
$this->laravel['queue']->connection($job->connection)->pushRaw(
$this->refreshRetryUntil($this->resetAttempts($job->payload)), $job->queue
);
}
/**
* Reset the payload attempts.
*
* Applicable to Redis and other jobs which store attempts in their payload.
*
* @param string $payload
* @return string
*/
protected function resetAttempts($payload)
{
$payload = json_decode($payload, true);
if (isset($payload['attempts'])) {
$payload['attempts'] = 0;
}
return json_encode($payload);
}
/**
* Refresh the "retry until" timestamp for the job.
*
* @param string $payload
* @return string
*
* @throws \RuntimeException
*/
protected function refreshRetryUntil($payload)
{
$payload = json_decode($payload, true);
if (! isset($payload['data']['command'])) {
return json_encode($payload);
}
if (str_starts_with($payload['data']['command'], 'O:')) {
$instance = unserialize($payload['data']['command']);
} elseif ($this->laravel->bound(Encrypter::class)) {
$instance = unserialize($this->laravel->make(Encrypter::class)->decrypt($payload['data']['command']));
}
if (! isset($instance)) {
throw new RuntimeException('Unable to extract job payload.');
}
if (is_object($instance) && ! $instance instanceof \__PHP_Incomplete_Class && method_exists($instance, 'retryUntil')) {
$retryUntil = $instance->retryUntil();
$payload['retryUntil'] = $retryUntil instanceof DateTimeInterface
? $retryUntil->getTimestamp()
: $retryUntil;
}
return json_encode($payload);
}
}