Cache::put

Writes a value to the cache for a given number of seconds, or forever when the TTL is omitted.

Signature
Cache::put(string|array $key, mixed $value, DateTimeInterface|DateInterval|int|null $ttl = null): bool
  • Added in Laravel 4.0

Parameters

ParameterTypeDescription
$$keystring|arrayThe cache key. An array of key => value pairs writes them all, and the TTL then moves to the second argument.
$$valuemixedThe value to store. It is serialized by the store, so it must be serializable — closures and open resources are not.
$$ttloptionalDateTimeInterface|DateInterval|int|null= nullLifetime in seconds, or a date/interval to expire at. Omit or pass null to store the item indefinitely.

Return value

booltrue when the item was written. A zero or negative TTL forgets the key instead and returns the result of that removal (false when the key did not exist).

Errors

Nothing for a normal write; the underlying store may throw a connection exception, and a value that cannot be serialized raises an error from the serializer.

Changelog

  • PHP 5.8The integer TTL changed from minutes to seconds. Code written for 5.7 or earlier now expires items sixty times sooner.

Cache::put writes unconditionally, replacing whatever was under the key. The third argument is the interesting one.

The TTL argument

An integer is seconds, not minutes — this changed in Laravel 5.8 and is still the single most common cache bug in upgraded code. You may also pass any DateTimeInterface or DateInterval, which reads better for anything longer than a minute:

Cache::put('key', $value, now()->addMinutes(10));

Omitting the TTL entirely stores the item indefinitely — put quietly delegates to forever. That is easy to do by accident when a variable holding the TTL is null.

When to reach for it

Use put when you are the authority on the value: after a write to the database, when a webhook delivers fresh state, or when a scheduled command warms a cache. For read paths, prefer remember.

Use Cache::add() instead when the write must only happen if nobody got there first — add is atomic and returns false if the key already exists, which is how you implement a simple lock or initialise a counter.

What trips people up

A TTL of 0 or a negative number does not mean "forever" and does not mean "a moment"; it removes the key and returns whatever the removal returned.

Example

Example · php
<?php

use Illuminate\Support\Facades\Cache;

// Ten SECONDS, not ten minutes.
var_dump(Cache::put('report:daily', ['orders' => 42], 10));

// Clearer for longer lifetimes: expire at a moment in time.
Cache::put('report:weekly', ['orders' => 310], now()->addMinutes(10));

// No TTL at all -> stored indefinitely.
Cache::put('schema:version', 17);

// Zero or negative removes the key and reports the removal.
var_dump(Cache::put('report:daily', ['orders' => 42], 0));

// Write several keys at once; the TTL moves to the second argument.
Cache::put(['a' => 1, 'b' => 2], 60);

echo Cache::get('schema:version'), PHP_EOL;

When to use it

  • Refresh a cached settings blob immediately after an administrator saves the settings form.
  • Warm a dashboard aggregate from a scheduled command so no web request pays for the query.
  • Store a short-lived one-time code that must expire at a precise wall-clock time.

Frequently asked questions

Is the Cache::put TTL in seconds or minutes?

Seconds, since Laravel 5.8. Before 5.8 an integer meant minutes, so code carried over from an older application expires items sixty times sooner than its author intended; pass a Carbon instance such as now()->addMinutes(10) if you want the unit to be unambiguous.

What happens if I call Cache::put without a TTL?

The item is stored indefinitely — put delegates to forever, and only forget() or an eviction will remove it. This bites when the TTL comes from a variable that turned out to be null.

Why did Cache::put return false?

Because you passed a TTL of zero or a negative number, which forgets the key instead of writing it and returns the result of that removal. A false can also come from a store rejecting the write, such as Memcached refusing a value over its item size limit.

More examples

put versus add for a first-writer-wins guard

Two workers racing on put both succeed and the last one wins, which is useless as a guard. add performs a single atomic set-if-not-exists on the store, so exactly one worker sees true and the loser can back off. Use add, not put, whenever the return value is meant to decide who proceeds.

Example · php
<?php

use Illuminate\Support\Facades\Cache;

// put always overwrites.
Cache::put('import:running', 'worker-1', 300);
Cache::put('import:running', 'worker-2', 300);

echo Cache::get('import:running'), PHP_EOL;   // worker-2

Cache::forget('import:running');

// add is atomic and only writes when the key is absent.
var_dump(Cache::add('import:running', 'worker-1', 300));  // bool(true)
var_dump(Cache::add('import:running', 'worker-2', 300));  // bool(false)

echo Cache::get('import:running'), PHP_EOL;   // worker-1

Writing after a database change, with an explicit expiry

This is the write-through pattern: the request already has the new value in memory, so there is no reason to invalidate and force the next reader to recompute. Passing now()->addHour() and 3600 to the two calls stores exactly the same lifetime — the DateTime form is only easier to read.

Example · php
<?php

namespace App\Http\Controllers;

use App\Models\Setting;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;

class SettingsController extends Controller
{
    public function update(Request $request): array
    {
        $settings = Setting::updateFrom($request->validated());

        // Authoritative write: we just produced the fresh value.
        Cache::put('settings', $settings->toArray(), now()->addHour());

        // Same value on a named store, for a worker that reads elsewhere.
        Cache::store('redis')->put('settings', $settings->toArray(), 3600);

        echo 'cached until '.now()->addHour()->toDateTimeString(), PHP_EOL;

        return $settings->toArray();
    }
}

The minutes-to-seconds trap in upgraded code

The symptom is a cache that appears to do nothing under load: the key expires a minute after it is written, so almost every request recomputes. Nothing errors and nothing is logged. Passing a Carbon instance or an arithmetic expression makes the intended unit visible at the call site.

Example · php
<?php

use Illuminate\Support\Facades\Cache;

// Written for Laravel 5.7: the author meant 60 minutes.
Cache::put('heavy:aggregate', $rows = ['total' => 9], 60);

// On 5.8 and later this expires in 60 seconds.
echo 'expires in 60 seconds, not 60 minutes', PHP_EOL;

// The fix that cannot be misread:
Cache::put('heavy:aggregate', $rows, now()->addHour());

// Or be explicit about the unit:
Cache::put('heavy:aggregate', $rows, 60 * 60);

var_dump(Cache::get('heavy:aggregate'));

Discussion

  • Be the first to comment on this lesson.