path = $path; $this->limit = $limit; $this->lockProviderResolver = $lockProviderResolver; } /** * Log a failed job into storage. * * @param string $connection * @param string $queue * @param string $payload * @param \Throwable $exception * @return int|null */ public function log($connection, $queue, $payload, $exception) { return $this->lock(function () use ($connection, $queue, $payload, $exception) { $id = json_decode($payload, true)['uuid']; $jobs = $this->read(); $failedAt = Date::now(); array_unshift($jobs, [ 'id' => $id, 'connection' => $connection, 'queue' => $queue, 'payload' => $payload, 'exception' => (string) mb_convert_encoding($exception, 'UTF-8'), 'failed_at' => $failedAt->format('Y-m-d H:i:s'), 'failed_at_timestamp' => $failedAt->getTimestamp(), ]); $this->write(array_slice($jobs, 0, $this->limit)); return $id; }); } /** * Get a list of all of the failed jobs. * * @return array */ public function all() { return $this->read(); } /** * Get a single failed job. * * @param mixed $id * @return object|null */ public function find($id) { return collect($this->read()) ->first(fn ($job) => $job->id === $id); } /** * Delete a single failed job from storage. * * @param mixed $id * @return bool */ public function forget($id) { return $this->lock(function () use ($id) { $this->write($pruned = collect($jobs = $this->read()) ->reject(fn ($job) => $job->id === $id) ->values() ->all()); return count($jobs) !== count($pruned); }); } /** * Flush all of the failed jobs from storage. * * @param int|null $hours * @return void */ public function flush($hours = null) { $this->prune(Date::now()->subHours($hours ?: 0)); } /** * Prune all of the entries older than the given date. * * @param \DateTimeInterface $before * @return int */ public function prune(DateTimeInterface $before) { return $this->lock(function () use ($before) { $jobs = $this->read(); $this->write($prunedJobs = collect($jobs)->reject(function ($job) use ($before) { return $job->failed_at_timestamp <= $before->getTimestamp(); })->values()->all()); return count($jobs) - count($prunedJobs); }); } /** * Execute the given callback while holding a lock. * * @param \Closure $callback * @return mixed */ protected function lock(Closure $callback) { if (! $this->lockProviderResolver) { return $callback(); } return ($this->lockProviderResolver)() ->lock('laravel-failed-jobs', 5) ->block(10, function () use ($callback) { return $callback(); }); } /** * Read the failed jobs file. * * @return array */ protected function read() { if (! file_exists($this->path)) { return []; } $content = file_get_contents($this->path); if (empty(trim($content))) { return []; } $content = json_decode($content); return is_array($content) ? $content : []; } /** * Write the given array of jobs to the failed jobs file. * * @param array $jobs * @return void */ protected function write(array $jobs) { file_put_contents( $this->path, json_encode($jobs, JSON_PRETTY_PRINT) ); } }