Cache::increment
Adds an amount to an integer cache item, atomically on Redis and Memcached.
Cache::increment(string $key, int $value = 1): int|bool- Added in Laravel 4.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$$key | string | The cache key holding the counter. |
$$valueoptional | int= 1 | How much to add. Pass a negative number, or use decrement(), to go the other way. |
Return value
int|boolThe new value after the increment, or false when the store could not increment the item — Memcached returns false for a key that does not yet exist.
Errors
Nothing directly; on Redis, incrementing a key holding a non-numeric string raises a Predis or PhpRedis exception from the server error.
Cache::increment adds to an integer already in the cache and returns the new total. Cache::decrement is the mirror image. Both take an optional amount.
Atomicity is driver-dependent
This is the whole reason to use the method, and it is not uniform:
- Redis and Memcached increment server-side in a single command. Safe under any concurrency.
- Database wraps a read and an update in a transaction with a row lock. Safe, but it costs a lock.
- File and array read the value, add to it in PHP and write it back. Two processes doing that at once lose a count, and no error is raised.
So a counter under the file driver undercounts silently. That is fine for a rough view count on a single-process dev box and wrong for anything that gates behaviour.
What trips people up
The key must already exist. Redis creates it at zero and increments, but Memcached returns false for a missing key and never initialises it. The documented fix is to seed the counter first with Cache::add($key, 0, $ttl), which is atomic and a no-op when another process got there first.
Incrementing does not extend the TTL. A counter seeded with an hour's lifetime resets an hour later no matter how much traffic it saw.
Example
<?php
use Illuminate\Support\Facades\Cache;
$key = 'views:article:19';
// Seed atomically. add() is a no-op if another process already did it.
Cache::add($key, 0, now()->addHours(4));
// Each call returns the NEW total.
var_dump(Cache::increment($key)); // int(1)
var_dump(Cache::increment($key, 5)); // int(6)
var_dump(Cache::decrement($key, 2)); // int(4)
echo Cache::get($key), PHP_EOL; // 4
// The TTL is still the one set by add() - incrementing does not renew it.
// On Memcached, skipping the add() above makes this return false:
var_dump(Cache::increment('never:written'));When to use it
- Count requests per user per minute for a hand-rolled throttle.
- Track article views cheaply and flush the totals to the database on a schedule.
- Decrement a limited stock of promo codes so two buyers cannot claim the last one.
Frequently asked questions
Is Cache::increment atomic?
On Redis and Memcached yes, because the increment happens server-side in one command, and the database driver is safe too since it wraps the read and update in a transaction with a row lock. The file and array drivers read and write in PHP with no lock, so concurrent increments lose counts without any error.
Why does Cache::increment return false?
Usually because the key does not exist and you are on Memcached, which will not create a missing key. Seed it first with Cache::add($key, 0, $ttl) as the documentation shows; add is atomic, so it is safe even if several processes call it at once.
Does incrementing reset the cache key's expiry?
No. The TTL set when the key was created continues to run down, so a counter seeded for an hour expires an hour later regardless of traffic. If you want a rolling window, put the time period into the key name so each window gets its own entry.
More examples
A per-minute throttle whose window actually expires
Folding the minute into the key is what makes the window work: the old key ages out on its own and the new one is seeded by add on its first hit. A single fixed key cannot be reset, because increment leaves the TTL untouched — you would need an extra put to re-arm it, and that put would race with other requests.
<?php
use Illuminate\Support\Facades\Cache;
function tooManyRequests(int $userId, int $limit = 60): bool
{
// A key per window means the counter expires with the window.
$key = 'throttle:'.$userId.':'.now()->format('YmdHi');
Cache::add($key, 0, 120); // seed, plus a little slack
return Cache::increment($key) > $limit;
}
var_dump(tooManyRequests(7)); // bool(false)
// The mistake this avoids: one fixed key, seeded once.
// Cache::add('throttle:7', 0, 60);
// Cache::increment('throttle:7'); // never resets the TTL, and after
// // 60s the count silently restartsWhy the file driver is not safe for counters
The file and array stores do a read-modify-write in userland with no lock around the pair, so concurrent increments silently lose counts — the total is simply low, with no exception and nothing in the logs. Redis INCRBY and the Memcached increment command are single server-side operations; the database store buys safety with a row lock inside a transaction.
<?php
use Illuminate\Support\Facades\Cache;
// The file store implements increment in PHP:
// $current = read($key);
// write($key, $current + $value);
// Two workers interleaving those four steps both read 41 and both write 42.
Cache::store('file')->add('counter', 0, 600);
Cache::store('file')->increment('counter');
echo Cache::store('file')->get('counter'), PHP_EOL; // 1 - correct when alone
// Redis performs the whole operation server-side, so it cannot interleave.
Cache::store('redis')->increment('counter');
// The database store is also safe: it wraps the read and the update
// in a transaction that locks the row.
Cache::store('database')->increment('counter');
echo 'use redis, memcached or database for counters that matter', PHP_EOL;
Discussion