Cache::forever

Stores an item in the cache with no expiration, removable only by forget, flush or eviction.

Signature
Cache::forever(string $key, mixed $value): bool
  • Added in Laravel 4.0

Parameters

ParameterTypeDescription
$$keystringThe cache key to write.
$$valuemixedThe value to store. It is serialized by the store, so it must be serializable.

Return value

booltrue when the store accepted the write, false when it did not.

Errors

Nothing for a normal write; the underlying store may throw a connection exception and an unserializable value raises an error from the serializer.

Cache::forever writes a value with no expiry. Nothing removes it except forget, a flush, or the store deciding to evict it.

When to reach for it

Reach for it when the value is invalidated by an event rather than by time: a compiled navigation tree rebuilt when a page is published, a currency table refreshed by a webhook, a computed permissions map cleared when roles change. In every one of those cases you should already know where the matching forget lives before you write the forever.

Cache::rememberForever($key, $callback) is the get-or-compute form and is usually what you actually want on a read path.

What trips people up

"Forever" is a promise you make, not one the store makes. Memcached is a bounded LRU: when it fills, items stored forever are evicted like any other, so code must still handle a miss. Redis with a maxmemory policy of allkeys-lru behaves the same way; only volatile-lru spares keys without a TTL, and then a cache full of them can push Redis into out-of-memory errors instead.

The other failure is orphaned data. A key written forever with a name that later changes shape — a version tag added, a tenant id inserted — leaves the old entry sitting in the store with nothing that ever deletes it.

Example

Example · php
<?php

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;

// No TTL: this survives until something removes it.
var_dump(Cache::forever('nav:main', ['Home', 'Docs', 'Pricing']));

// The get-or-compute form of the same thing.
$currencies = Cache::rememberForever('currencies', function () {
    return DB::table('currencies')->pluck('rate', 'code')->all();
});

print_r($currencies);

// Still write the read path as if it can miss, because it can.
$nav = Cache::get('nav:main', []);

echo count($nav), PHP_EOL;

// The matching invalidation, wherever the source data changes.
Cache::forget('nav:main');

When to use it

  • Hold a compiled navigation menu that is rebuilt only when a page is published or removed.
  • Cache a country or currency reference table refreshed by an occasional import command.
  • Store a rendered sitemap fragment that a model observer clears on save.

Frequently asked questions

What is the difference between Cache::forever and Cache::put without a TTL?

There is none — put with a null TTL delegates straight to forever. Calling forever explicitly is better style because it states the intent at the call site instead of leaving a reader to wonder whether the missing third argument was a mistake.

Can a Cache::forever entry disappear?

Yes. Memcached evicts items stored forever when it reaches its size limit, and Redis with an allkeys-lru or allkeys-random maxmemory policy does the same. Cache::flush and php artisan cache:clear also remove them, so the read path must tolerate a miss.

How do I remove something stored with Cache::forever?

Call Cache::forget($key) with the same key, normally from a model observer or the command that changes the underlying data. Cache::flush clears everything and ignores the configured cache prefix, so it is a poor choice on a store shared with other applications.

More examples

Pairing forever with the event that invalidates it

This is the shape a forever entry needs: one place that writes it, one place that clears it, and both naming the same key. Without the observer the menu is frozen at whatever it looked like the first time somebody loaded the site, and no amount of TTL tuning will explain the bug — there is no TTL.

Example · php
<?php

namespace App\Observers;

use App\Models\Page;
use Illuminate\Support\Facades\Cache;

class PageObserver
{
    public function saved(Page $page): void
    {
        Cache::forget('nav:main');

        echo "nav cache cleared for page {$page->id}", PHP_EOL;
    }

    public function deleted(Page $page): void
    {
        Cache::forget('nav:main');
    }
}

// Elsewhere, on the read path:
// $nav = Cache::rememberForever('nav:main', fn () => Page::navigationTree());

Why a forever entry can still be missing

Memcached evicts the least recently used item when it hits its memory limit, and an item with no expiry is not exempt. Indexing straight into the result of get() produces a null-array-access error in production long after the code was written. rememberForever restores the value on a miss, which is why it is the safer default on any read path.

Example · php
<?php

use Illuminate\Support\Facades\Cache;

Cache::store('memcached')->forever('settings', ['theme' => 'dark']);

// Days later, after the Memcached instance has filled up:
$settings = Cache::store('memcached')->get('settings');

var_dump($settings);   // NULL - evicted by the LRU, not expired

// So never write this:
// return Cache::get('settings')['theme'];

// Write this instead:
$settings = Cache::rememberForever('settings', function () {
    return ['theme' => 'dark'];
});

echo $settings['theme'], PHP_EOL;   // dark

Discussion

  • Be the first to comment on this lesson.