-
Notifications
You must be signed in to change notification settings - Fork 0
Custom Handlers
The five built-in handlers are not special — they extend
BaseHandler, which provides everything except the
storage primitives. Writing your own backend (APCu, DynamoDB, an in-memory store
for tests…) means implementing six methods and declaring your defaults.
You inherit, for free:
- option handling (
getOption,setOptions, theprefixdefault, theoptionInt/optionFloat/optionStringhelpers); - key validation and prefixing via
$this->name($key); - TTL normalisation via
$this->ttlToSeconds($ttl); - the bulk methods
getMultiple/setMultiple/deleteMultiple; - the
increment()/decrement()counters.
You implement the six abstract methods: get, set, delete, clear, has,
isSupported.
Dependency-free, complete, and handy for tests (see Testing):
<?php
declare(strict_types=1);
namespace App\Cache;
use DateInterval;
use InitPHP\Cache\BaseHandler;
final class ArrayHandler extends BaseHandler
{
/** @var array<string, array{expires: int|null, value: mixed}> */
private array $store = [];
public function get(string $key, mixed $default = null): mixed
{
$name = $this->name($key); // validates + prefixes
if (!$this->alive($name)) {
return $default;
}
return $this->store[$name]['value'];
}
public function set(string $key, mixed $value, null|int|DateInterval $ttl = null): bool
{
$name = $this->name($key);
$seconds = $this->ttlToSeconds($ttl); // null | int (may be <= 0)
if ($seconds !== null && $seconds <= 0) {
unset($this->store[$name]); // expired-on-write → delete
return true;
}
$this->store[$name] = [
'expires' => $seconds === null ? null : time() + $seconds,
'value' => $value,
];
return true;
}
public function delete(string $key): bool
{
unset($this->store[$this->name($key)]);
return true;
}
public function clear(): bool
{
$this->store = [];
return true;
}
public function has(string $key): bool
{
return $this->alive($this->name($key));
}
public function isSupported(): bool
{
return true;
}
private function alive(string $name): bool
{
if (!isset($this->store[$name])) {
return false;
}
$expires = $this->store[$name]['expires'];
if ($expires !== null && $expires < time()) {
unset($this->store[$name]);
return false;
}
return true;
}
}Use it like any built-in handler:
use InitPHP\Cache\Cache;
$cache = Cache::create(App\Cache\ArrayHandler::class, ['prefix' => 'test_']);
$cache->set('k', 'v');
$cache->get('k'); // "v"Declare defaults in $handlerOptions, gate availability in isSupported(), and
lean on the helpers:
<?php
declare(strict_types=1);
namespace App\Cache;
use DateInterval;
use InitPHP\Cache\BaseHandler;
final class ApcuHandler extends BaseHandler
{
/** @var array<string, mixed> */
protected array $handlerOptions = [
'default_ttl' => 0, // 0 = no expiry, used when set() gets no TTL
];
public function get(string $key, mixed $default = null): mixed
{
$value = apcu_fetch($this->name($key), $ok);
return $ok ? $value : $default;
}
public function set(string $key, mixed $value, null|int|DateInterval $ttl = null): bool
{
$name = $this->name($key);
$seconds = $this->ttlToSeconds($ttl);
if ($seconds !== null && $seconds <= 0) {
apcu_delete($name); // already-expired → delete
return true;
}
if ($seconds === null) {
$seconds = $this->optionInt('default_ttl', 0);
}
return apcu_store($name, $value, $seconds);
}
public function delete(string $key): bool
{
apcu_delete($this->name($key)); // a missing key still counts as deleted
return true;
}
public function clear(): bool
{
return apcu_clear_cache(); // clears the whole APCu cache (not prefix-scoped)
}
public function has(string $key): bool
{
return apcu_exists($this->name($key));
}
public function isSupported(): bool
{
return extension_loaded('apcu') && (bool) ini_get('apc.enabled');
}
}To stay consistent with the built-in handlers and PSR-16:
-
Always run keys through
$this->name($key)— it validates (throwingInvalidArgumentExceptionfor bad keys) and applies the prefix. -
Normalise TTLs with
$this->ttlToSeconds($ttl)and treat a<= 0result as "delete the item, returntrue". -
nullis a value, not a miss. Make sure a storednullreads back asnullandhas()returnstruefor it. (A serialised envelope, as Redis and Memcache use, is the usual trick when the backend can't tellfalsefrom a miss.) -
Return
boolfromset/delete/clear, and the value (or the caller's default) fromget. -
Gate
isSupported()on whatever your backend needs; the factory calls it and throws aCacheExceptionwhen it returnsfalse.
Get those right and the inherited increment(), decrement() and the
*Multiple() methods just work.
- Testing — use a custom in-memory handler in your test suite.
-
API Reference — every
BaseHandlerhelper.
initphp/cache · MIT License · part of the InitPHP family
Source · Issues · Discussions · Packagist · Contributing · Security Policy
Getting Started
Core Concepts
Handlers
Guides
Other