skip to content

In Laravel 13, which cache store does a fresh app use, how does CACHE_STORE select it, and how does the failover store behave?

level: juniorimportance: should knowfreq 55%

answer

  1. config/cache.php: default plus stores
  2. skeleton .env: CACHE_STORE=database
  3. cache and cache_locks tables
  4. Cache::store('redis') for a named store
  5. failover: database, then array

basics

~20 s

A Laravel 13 skeleton uses the database store: CACHE_STORE in .env feeds the default key of config/cache.php, which names one entry in its stores array. The failover store tries its listed stores in order and moves on only when one throws.

solid answer

~40 s

`config/cache.php` has a `default` key read from `env('CACHE_STORE', 'database')` and a `stores` array of named stores, each with a `driver` (`array`, `database`, `file`, `memcached`, `redis`, `dynamodb`, `storage`, `octane`, `session`, `failover`, `null`). The skeleton's `.env.example` sets `CACHE_STORE=database`, backed by the `cache` and `cache_locks` tables from its default migration; `phpunit.xml` switches tests to `array`. `Cache::store('redis')` reaches any other named store; an unknown name throws `InvalidArgumentException`. The shipped `failover` store lists `database` then `array`: each operation goes to the first store and falls to the next only if it throws, dispatching `CacheFailedOver`. A miss is not a failure, so failover is not a second cache tier.

code

ini · 5 lines
ini
# .env for production on a single Redis host
CACHE_STORE=redis
REDIS_HOST=10.0.0.5
REDIS_CACHE_DB=1
CACHE_PREFIX=fx-converter-cache-

go deeper

for a junior

Recall that CACHE_STORE picks a named store from config/cache.php, that a new app uses database, and that Cache::store('redis') reaches another store.

for a middle

Explain driver versus store, what the database store needs (the cache table), why tests use array, and what the failover store does on an exception versus a miss.

for a senior

Judge what failover to array really buys: pages keep rendering but the cache goes cold per process. Choose a shared fallback and alert on CacheFailedOver.

for a principal

Weigh staying on the database store against adding Redis for cache traffic, including the operational cost of another dependency and what failover must preserve.

## Stores, drivers and the default Laravel separates two ideas that are easy to blur: - A **driver** is an implementation: `database`, `redis`, `file`, `memcached`, `dynamodb`, `array`, `storage`, `octane`, `session`, `failover` or `null`. - A **store** is a named entry in the `stores` array of `config/cache.php` that picks a driver and gives it options. You can define several stores on the same driver — two Redis stores on different connections, for example. The `default` key chooses which store the `Cache` facade and the `cache()` helper use when you do not name one. In the Laravel 13 skeleton it reads `env('CACHE_STORE', 'database')`, and `.env.example` sets `CACHE_STORE=database`. A currency-converter app that starts on SQLite therefore caches exchange rates in the `cache` table of its own database from day one, with no Redis server needed. ## What each common store needs | Store (driver) | Where data lives | Needs | |---|---|---| | `database` | the `cache` table (locks in `cache_locks`) | the skeleton's `0001_01_01_000001_create_cache_table` migration, or `php artisan make:cache-table` | | `redis` | Redis, on the `cache` connection (`REDIS_CACHE_DB`, default `1`) | phpredis or predis and a Redis server | | `file` | `storage/framework/cache/data` | a writable directory; per-server only | | `memcached` | Memcached servers in `memcached.servers` | the Memcached PECL extension | | `array` | PHP memory of the current process | nothing; lost when the process ends | | `dynamodb` | a DynamoDB table (`DYNAMODB_CACHE_TABLE`) | AWS credentials and the table | The skeleton's `phpunit.xml` sets `CACHE_STORE=array`, so tests never touch a real store and start empty every time. ## Using more than one store ```php $rates = Cache::store('redis')->remember('fx:rates:EUR', 600, $fetch); Cache::store('file')->put('fx:last-sync', now()->timestamp); ``` `Cache::store($name)` returns a repository for that named store and caches the instance for the rest of the process. Asking for a name that is not in `stores` throws `InvalidArgumentException` with the message *Cache store [name] is not defined.* ## The failover store Laravel 13's `config/cache.php` ships this entry: ```php 'failover' => [ 'driver' => 'failover', 'stores' => ['database', 'array'], ], ``` It only takes effect when you make it the default (`CACHE_STORE=failover`) or call `Cache::store('failover')`. Its behaviour, from `FailoverStore::attemptOnAllStores()`: 1. Run the operation (`get`, `put`, `add`, `lock`, `flush`…) on the first listed store. 2. If that call **throws** — the database is unreachable, Redis timed out — record the failure, dispatch `Illuminate\Cache\Events\CacheFailedOver` (once, while the store keeps failing) and try the next store. 3. The first store that does not throw supplies the result. If every store throws, the last exception is rethrown. Two consequences matter in production: - **A miss is not a failure.** If the database store answers `null`, that `null` is the answer; failover never looks in the `array` store for it. It is a resilience switch, not a two-level cache. - **Falling back to `array` means no shared cache.** The `array` store lives in one process, so while the database is down each request or worker starts empty and every rate lookup hits the provider. Failover keeps pages rendering instead of throwing; it does not keep the cache warm. Pointing the second entry at a shared store (another Redis, the file store on a single server) changes that trade-off. ## Upgrade notes - Older apps may still set `CACHE_DRIVER`. The Laravel 13 skeleton and framework configs read only `CACHE_STORE`, so a leftover `CACHE_DRIVER` does nothing unless your own `config/cache.php` still references it. - The Laravel 13 key prefix defaults to `Str::slug(APP_NAME).'-cache-'` (hyphens); earlier releases used underscores. Set `CACHE_PREFIX` explicitly if two deployments must share keys. ## Choosing a store for the converter - **One server, low traffic:** the `database` default is fine. Rates are read far more often than written, and there is nothing extra to operate. - **Several web servers:** avoid `file`, because each server would hold its own copy and invalidating one key would leave the others stale. `database`, `redis` and `memcached` are shared by every server that points at them. - **High read volume:** `redis` or `memcached` keep cache reads off the primary database, and both support tags, which the `database` store does not. - **Tests and one-off scripts:** `array`, which disappears with the process. - **Disabling caching temporarily:** the `null` driver accepts writes and always misses, which is useful for proving whether a bug is cache-related. Whatever you pick, switching later is a one-line `.env` change as long as application code talks to the `Cache` facade rather than to a driver-specific client.

  • In Laravel, if the first store of a failover cache returns null for a key, does failover check the next store?
    No. `FailoverStore` moves on only when the underlying call throws. A `null` from a healthy store is a legitimate miss and is returned as-is, so the second store is never consulted for reads. Failover protects availability; it is not a read-through hierarchy.
  • In Laravel 13, why do tests usually run with CACHE_STORE=array?
    The skeleton's `phpunit.xml` sets `CACHE_STORE=array`. The array store keeps values in the current process only, so every test starts with an empty cache, needs no table or server, and cannot leak state into another test run. Tests that need a real store's behaviour must override it explicitly.

saying these in an interview costs you the question

  • A fresh Laravel 13 app caches to the file store by default
  • The failover store reads the next store whenever the first one misses
  • In a new Laravel 13 app, setting CACHE_DRIVER in .env selects the cache store
  • The array store shares cached values between requests and workers
  • Cache::store() with an unknown name silently falls back to the default store