The Cache

Cache::remember runs a closure once, stores what it returns, and serves that value until the TTL runs out.

SyntaxCache::remember(string $key, $ttl, Closure $callback): mixed

The cache is a key/value store with one API in front of several backends. You reach for it when a value costs more to produce than to keep: a report query, a count over a large table, a response from another service.

Cache::remember() is the shape you will use most. It takes a key, a time to live in seconds, and a closure. If the key is in the store, that value is returned and the closure never runs. If it is missing, the closure runs, its return value is written with that TTL, and you get it back.

Writing and reading by hand

Cache::put($key, $value, $seconds) writes, Cache::get($key, $default) reads, Cache::forget($key) deletes, and Cache::pull($key) reads and deletes in one step. put() with no TTL stores the value indefinitely, which is what Cache::forever() does. The global cache() helper is the same object in shorter clothes: cache('key') is Cache::get('key'), and cache(['key' => 'value'], 60) is Cache::put('key', 'value', 60).

Keys you can invalidate

Nothing in the framework knows when your cached value went out of date. Write the key so the code that changes the data can rebuild it: products.42.summary, not summary. Anything that changes the value, such as an id, a page number or a locale, belongs in the key. The store and the key prefix are set in config/cache.php; in Laravel 13 the default store is database, chosen by CACHE_STORE.

Example

Example · php
<?php
// app/Http/Controllers/ReportController.php

declare(strict_types=1);

namespace App\Http\Controllers;

use App\Models\Order;
use Illuminate\Support\Facades\Cache;
use Illuminate\View\View;

class ReportController extends Controller
{
    public function show(): View
    {
        // The query runs once, then the stored value is served for an hour.
        $revenue = Cache::remember('reports.revenue.daily', 3600, function (): float {
            return (float) Order::where('status', 'paid')->sum('total');
        });

        // Same thing with an arrow function: fn () => x is function () { return x; }
        $orders = Cache::remember(
            'reports.orders.daily',
            3600,
            fn (): int => Order::where('status', 'paid')->count(),
        );

        return view('reports.show', [
            'revenue' => $revenue,
            'orders' => $orders,
        ]);
    }
}

When to use it

  • Serve a dashboard total without re-running an expensive aggregate on every request.
  • Hold a third-party API response for a few minutes so a slow provider cannot slow your page.
  • Keep a rarely changing lookup, such as plan limits or country codes, out of the request path.
  • Count views or downloads in memory and write the total to the database later.

More examples

remember is a read, a miss check and a write

The two blocks are the same operation. Seeing the long form explains the null rule: the miss check is a null check, so a closure that returns null leaves you with a value that always looks absent. It also explains why there is no lock here, and why ten simultaneous misses run the query ten times.

Example · php
use Illuminate\Support\Facades\Cache;

// Shorthand.
$total = Cache::remember('stats.users', 600, fn (): int => User::count());

// The longhand it stands for.
$total = Cache::get('stats.users');

if ($total === null) {
    $total = User::count();

    Cache::put('stats.users', $total, 600);
}

A counter, initialised before it is incremented

Counting hits in the cache avoids a write per request. Initialise with add() first, as the documentation does: not every store will create a counter key that was never written. Flush the total to the database on a schedule if you need it to survive a cache clear.

Example · php
use Illuminate\Support\Facades\Cache;

// add() writes only when the key is absent, atomically, so a second
// request cannot reset a counter that is already running.
Cache::add('downloads.42', 0, now()->addHours(4));

Cache::increment('downloads.42');
Cache::increment('downloads.42', 5);

$downloads = (int) Cache::get('downloads.42', 0);

Forget the key where the data changes

A TTL alone means somebody sees the old summary for up to the TTL after an edit. Forgetting the key at the write site makes the next read rebuild it. This only works if the key is derivable from what you have in hand, which is the real reason to put the id in the key.

Example · php
// app/Http/Controllers/ProductController.php

public function update(UpdateProductRequest $request, Product $product): RedirectResponse
{
    $product->update($request->validated());

    Cache::forget("products.{$product->id}.summary");

    return redirect()->route('products.show', $product);
}

Discussion

  • Be the first to comment on this lesson.