Home / Laravel / Storage Cache Store in Laravel 13.10.0

Storage Cache Store in Laravel 13.10.0

Laravel v13.10.0 introduces a storage cache driver backed by Laravel’s filesystem abstraction, making it possible to use an S3 disk (or any configured disk) as a key/value cache store without additional packages. It also adds a --stop-when-empty-for queue worker option, a WorkerIdle event, schedule group lifecycle callbacks, Schema::hasForeignKey(), and several queue testing improvements.

  • New storage cache driver backed by Laravel’s filesystem
  • --stop-when-empty-for queue worker option
  • WorkerIdle event for idle worker detection
  • Lifecycle and output callbacks on Schedule::group()
  • Schema::hasForeignKey() helper
  • queue:failed --json output option
  • assertPushedOnce() testing helper
  • Enum queue names in QueueFake
  • WorkerOptions passed to additional worker events

What’s New

Storage Cache Store

A new storage cache driver uses Laravel’s filesystem / Storage services to store cached values. This is primarily useful for using an existing S3 disk as a key/value cache — no Redis or Memcached required.

The default config/cache.php now includes a storage store entry:

'storage' => [
'driver' => 'storage',
'disk' => env('CACHE_STORAGE_DISK'),
'path' => env('CACHE_STORAGE_PATH', 'framework/cache/data'),
],

Point CACHE_STORAGE_DISK at any configured disk (including s3) and the cache driver will read and write values through Laravel’s filesystem layer. Each cached value is stored as a file containing a serialized payload with an expiration timestamp.

PR: #60131 by @taylorotwell

Queue Worker Idle Stop Option

queue:work now accepts a --stop-when-empty-for option that stops the worker after it has gone a configured number of seconds without processing any jobs:

php artisan queue:work --stop-when-empty-for=60

This stops the worker if no jobs have been processed for 60 seconds. It’s useful for short-lived workers, scaled-down environments, or any situation where you want workers to exit automatically when queues go quiet rather than running indefinitely.

PR: #60176 by @taylorotwell

Worker Idle Event

A new WorkerIdle event is dispatched when a queue worker checks for a job and finds the queue empty. This is distinct from JobPopping, which fires on every pop attempt regardless of whether a job was found. Listening to WorkerIdle lets you detect workers that are genuinely unused — useful for rebalancing worker capacity or logging idle time.

PR: #60134 by @jackbayliss

Worker Configuration Passed to Additional Worker Events

WorkerOptions (which includes the --name flag and other worker configuration) is now passed to the Pausing, Resuming, Interrupted, and Looping worker events. Previously these events did not include the worker’s configuration, making it harder to know which worker instance was involved in a listener.

PRs: #60135, #60153 by @jackbayliss

Schedule Group Lifecycle Callbacks

Schedule::group() now supports the same lifecycle and output callback methods available on individual events. This lets you attach callbacks once for an entire group instead of repeating them on each task:

Schedule::group(function (Schedule $schedule) {
$schedule->command('reports:generate');
$schedule->command('reports:email');
})->onFailure(function () {
// fires for any failing task in the group
})->onSuccess(function () {
// fires when each task in the group succeeds
});

PR: #60133 by @cosmastech

Scheduled Event Instance in Callbacks

Scheduled event callbacks (such as onSuccess, onFailure, and then) can now optionally receive the Event instance as a parameter. This gives the callback direct access to the event’s configuration — its command, output path, and other properties:

$schedule->command('reports:generate')
->onFailure(function (Event $event) {
Log::error("Scheduled task failed: {$event->command}");
});

PR: #60144 by @cosmastech

Schema Foreign Key Existence Helper

A new Schema::hasForeignKey() method checks whether a specific foreign key constraint exists on a table, complementing the existing getForeignKeys() and hasIndex() helpers:

if (! Schema::hasForeignKey('orders', ['user_id'])) {
Schema::table('orders', function (Blueprint $table) {
$table->foreign('user_id')->references('id')->on('users');
});
}

This is useful in migrations, package install scripts, and schema assertions where you want to avoid adding a foreign key that already exists.

PR: #60169 by @Tresor-Kasenda

JSON Output for Failed Jobs

The queue:failed Artisan command now accepts a --json flag, outputting failed jobs as JSON. Each entry includes id, connection, queue, class, and failed_at. An empty result returns []. This matches the --json support already available on route:list, db:show, queue:monitor, and other commands.

PR: #60168 by @Tresor-Kasenda

SQS Overflow Flush on Queue Clear

The SQS extended store (added in 13.9.0) now supports a flush_on_clear option. When enabled, running queue:clear will also call flush() on the configured overflow cache store after purging SQS, reclaiming storage immediately rather than waiting for TTL expiration. This matters for S3-backed stores where leftover objects incur ongoing cost:

'sqs' => [
// ...
'extended_store_options' => [
'enabled' => true,
'disk' => 's3',
'flush_on_clear' => true,
],
],

This option defaults to false to preserve existing behavior. Note that for most cache stores, flush() wipes the entire store — point overflow.store at a dedicated cache store (the new storage driver is a natural fit here) to avoid unintended data loss.

PR: #60138 by @Orrison

Assert Job Pushed Once

Queue::assertPushedOnce() is a more readable alternative to Queue::assertPushedTimes(JobClass::class, 1):

// before
Queue::assertPushedTimes(ProcessOrderJob::class, 1);
 
// after
Queue::assertPushedOnce(ProcessOrderJob::class);

PR: #60150 by @weshooper

Enum Queue Names in Fake Queue Assertions

QueueFake now normalizes enum queue names the same way the real queue driver does. Passing a UnitEnum case as a queue name to push(), size(), or pendingJobs() now works correctly, and assertions against enum queue names behave consistently with their string equivalents.

PR: #60161 by @Tresor-Kasenda

Cloud Request ID in Logs

For applications running on Laravel Cloud, the request ID is now output in log entries using a custom JSON formatter. It appears as a standalone field rather than being nested inside the Monolog context or extra blocks.

PR: #60156 by @jradtilbrook

Miscellaneous Fixes and Improvements

  • Fix starts_with/ends_with rejecting numeric values — these validation rules now correctly handle numeric input by casting to string before comparison, restoring behavior from Laravel 12 (#60120 by @aydinfatih)
  • URL encode paths for signed URLs — storage paths are now URL-encoded before being placed into signed route path segments, fixing temporary signed URLs that contain ?, &, or # characters (#60137 by @taylorotwell)
  • Delimit aggregate alias — SQL aggregate function aliases are now delimited to prevent conflicts with reserved words (#60140 by @willrowe)
  • Optimize Worker queue pause check — the queue worker’s pause check is now more efficient (#60109 by @jackbayliss)
  • Validate against line breaks in emails — email validation now rejects values containing line breaks (#60151 by @taylorotwell)

References

Source: https://laravel-news.com

Tagged:

Leave a Reply

Your email address will not be published. Required fields are marked *