-
Notifications
You must be signed in to change notification settings - Fork 0
Counters
increment() and decrement() adjust an integer counter and return the new
value. They are an InitPHP extension on top of PSR-16 (declared on
CacheInterface), and they behave identically
on every handler.
public function increment(string $key, int $offset = 1): int;
public function decrement(string $key, int $offset = 1): int;$cache->increment('views'); // 1 (key was missing → starts at 0)
$cache->increment('views'); // 2
$cache->increment('views', 10); // 12
$cache->decrement('views', 5); // 7
$cache->get('views'); // 7 — readable as a normal valueThe behaviour is the same for File, PDO, Redis, Memcache and WinCache:
- A missing or non-numeric item is treated as
0, so the result equals the offset:$cache->increment('fresh', 5); // 5 (was missing) $cache->set('label', 'hello'); $cache->increment('label', 3); // 3 (non-numeric → reset to 0, then +3)
- The new integer value is returned.
- The result is stored without an expiry. Incrementing a key that had a TTL drops that TTL.
-
decrement($key, $n)is exactlyincrement($key, -$n), so counters can go negative:$cache->set('stock', 3); $cache->decrement('stock', 5); // -2
These counters are implemented once in BaseHandler as a
read-modify-write over get() / set(). That makes them uniform and
compatible with values written by set(), but it means they are not atomic
under concurrency: two processes incrementing the same key at the same instant
can race and lose an update.
For most caches (page-view counters, soft rate limits, metrics) that is fine. If
you need strict atomic counters under heavy concurrency, use your backend's
native facility directly — for example phpredis INCRBY:
// Direct, atomic Redis counter (bypasses this library's envelope format)
$redis = new \Redis();
$redis->connect('127.0.0.1', 6379);
$redis->incrBy('atomic:counter', 1);Keep such native counters in their own keyspace. A value written with the library's
set()is a serialised envelope, not a bare integer, so the two styles must not share a key.
Because increment() stores without an expiry, it is not a drop-in tool for
a time-windowed limit: the first increment after a set($key, 0, $window) would
drop the window. For a soft fixed-window limiter, manage the window yourself with
get() / set() and a key that rotates per window:
function rateLimit(CacheInterface $cache, string $ip, int $max, int $window): bool
{
// The key changes every $window seconds, so each window self-expires.
$bucket = (int) floor(time() / $window);
$key = 'rate_' . hash('xxh3', $ip) . '_' . $bucket;
$count = (int) $cache->get($key, 0) + 1;
$cache->set($key, $count, $window); // re-set with the window TTL each hit
return $count <= $max;
}
if (!rateLimit($cache, $clientIp, 100, 60)) {
http_response_code(429);
exit('Too Many Requests');
}This stays correct with the library's semantics (the TTL lives on the set(),
not on increment()), though it shares the same non-atomic caveat — fine for a
soft limit. For a hard limit, use a native atomic counter with an expiry, e.g.
phpredis INCR + EXPIRE.
- Keys, TTL & Values — how TTLs and values behave.
- Recipes — more practical patterns.
- API Reference — the method signatures.
initphp/cache · MIT License · part of the InitPHP family
Source · Issues · Discussions · Packagist · Contributing · Security Policy
Getting Started
Core Concepts
Handlers
Guides
Other