WordPress object-cache
drop-in backed by ePHPm's in-process KV store via the
ephpm_kv_* SAPI functions. The same wp_cache_get() / wp_cache_set() /
wp_cache_flush() API WordPress core already uses, served from memory in the
same OS process — zero socket round-trips, zero RESP parsing, no Redis or
Memcached daemon to run.
// Anywhere in WordPress, once wp-content/object-cache.php is in place:
wp_cache_set('user:42', ['name' => 'Alice'], 'users', 3600);
wp_cache_get('user:42', 'users'); // ['name' => 'Alice']
wp_cache_incr('hits:home'); // 1
wp_cache_flush(); // really clears the whole storeEach call resolves to a direct C function call into the Rust DashMap backing ePHPm's KV store. There's no Redis daemon, no Memcached daemon, and there's no socket even in-process; this is the same code path a Rust handler would take.
Unlike the other ePHPm cache adapters, wp_cache_flush() actually works —
the KV SAPI exposes ephpm_kv_flush_all(), so a flush clears the entire store
rather than being a documented no-op.
- Requirements
- Install
- How the drop-in finds its classes
- Supported features
wp_cache_flush()actually works- Non-persistent groups
- Multisite
- Verifying the cache is live
- Testing without ePHPm
- Troubleshooting
- How it works
- License
- PHP 8.2+
- WordPress with object-cache drop-in support (every supported WP release).
This package does not depend on any WordPress core Composer package — it
implements the
WP_Object_Cachesurface WordPress calls, and runs its tests on plainphp-cli. - The ePHPm runtime — the global
ephpm_kv_*SAPI functions are registered by ePHPm's embedded PHP. If you run WordPress under PHP-FPM, Apache mod_php, or the stock PHP CLI, those functions don't exist andSapiKvOps::__construct()throws. The drop-in degrades gracefully in that case (see Troubleshooting); for local development without ePHPm see Testing without ePHPm.
You can confirm the SAPI is present from any PHP file with:
var_dump(function_exists('ephpm_kv_get')); // expect bool(true)If you get false, you're not running inside ePHPm.
composer require ephpm/cache-wordpressThen activate the drop-in by copying it into wp-content/:
cp vendor/ephpm/cache-wordpress/dropin/object-cache.php wp-content/object-cache.php(Symlinking works too if your deployment allows it.) WordPress loads
wp-content/object-cache.php automatically during wp_start_object_cache() —
no plugin to activate, no wp-config.php edit required in the common case.
That's it. On the next request WordPress's entire object cache — post, term, user, options, and transient lookups — is served from ePHPm's in-process KV store.
WordPress loads object-cache.php very early — before plugins and, usually,
before any Composer autoloader has run. The drop-in therefore loads the
package's classes itself, trying these locations in order:
- A
EPHPM_CACHE_AUTOLOADconstant pointing at avendor/autoload.php. Define it inwp-config.phpif your layout is unusual:define('EPHPM_CACHE_AUTOLOAD', '/srv/app/vendor/autoload.php');
WP_CONTENT_DIR/vendor/autoload.phpABSPATH/vendor/autoload.phpvendor/autoload.phprelative to the drop-in (and a couple of parent climbs), covering the case where the drop-in still lives inside the installed package.- As a last resort, it
require_onces the package'ssrc/*.phpfiles directly, resolved relative to the drop-in's own location.
If none of those resolve the Ephpm\Cache\WordPress\ObjectCache class, the
drop-in does nothing — WordPress falls back to its built-in non-persistent
runtime cache and a notice is written to the PHP error log, rather than fataling
the site. So a misconfigured autoload path degrades to "no persistent cache,"
never to a white screen.
Standard composer require layouts (WordPress at the project root with
vendor/ alongside wp-content/) are picked up by case 2 or 3 with no extra
configuration.
The drop-in defines every wp_cache_* function WordPress core calls, each
delegating to the ObjectCache engine:
| Function(s) | Backed by ePHPm KV |
|---|---|
wp_cache_get, wp_cache_set, wp_cache_add, wp_cache_replace |
yes |
wp_cache_delete |
yes |
wp_cache_get_multiple, wp_cache_set_multiple, wp_cache_add_multiple, wp_cache_delete_multiple |
yes |
wp_cache_incr, wp_cache_decr |
yes (atomic via ephpm_kv_incr_by) |
wp_cache_flush |
yes — clears the whole store |
wp_cache_flush_runtime |
yes (runtime array only) |
wp_cache_flush_group |
partial (see below) |
wp_cache_add_global_groups, wp_cache_add_non_persistent_groups |
yes |
wp_cache_switch_to_blog |
yes (multisite) |
wp_cache_close, wp_cache_init |
yes |
wp_cache_reset (deprecated) |
alias of flush_runtime |
wp_cache_supports($feature) |
yes |
wp_cache_supports() returns true for add_multiple, set_multiple,
get_multiple, delete_multiple, flush_runtime, and flush_group, so core
and well-behaved plugins take their batched/fast paths.
Like core's own WP_Object_Cache, every value set during a request is served
back from an in-request runtime array for the rest of that request; the KV store
is consulted only on a runtime miss (or with $force = true).
The KV SAPI offers no key-enumeration or prefix-scan primitive, so a persistent
group cannot be selectively dropped from the store without tracking every key
ever written to it. flush_group() therefore:
- always clears that group from the runtime array, and
- returns
trueonly for non-persistent groups (where the runtime array is the whole story); - returns
falsefor persistent groups — the in-store entries remain and age out via their TTLs (or a fullwp_cache_flush()).
This is deliberately honest: rather than silently pretend a partial flush happened, the return value tells the caller whether the persistent tier was actually cleared.
Most object-cache drop-ins that sit on a key/value store with no SCAN or
FLUSHALL have to make wp_cache_flush() a no-op (the sibling
ephpm/cache-laravel store, for instance, returns false from flush() and
documents the "bump the prefix" workaround).
This package doesn't have that problem. ePHPm's KV SAPI exposes
ephpm_kv_flush_all(), which clears the entire effective store in one call.
wp_cache_flush() clears both the runtime array and the persistent store, so
plugins and wp cache flush behave exactly as WordPress expects.
Note:
ephpm_kv_flush_all()clears the whole KV store, not just keys this object cache wrote. If you also use the KV store directly (or viaephpm/predis-connection) inside the same ePHPm instance, awp_cache_flush()will drop those keys too. Use separate ePHPm instances / stores if you need isolation.
WordPress marks some cache groups as non-persistent — cheap-to-recompute data
that shouldn't pollute a shared backend (counts, plugins, theme_json,
and others, plus whatever plugins register). For those groups this drop-in keeps
everything in the per-request runtime array and never writes to the KV
store:
wp_cache_add_non_persistent_groups(['counts', 'plugins']);
wp_cache_set('comments', 5, 'counts'); // runtime only — no KV write
wp_cache_get('comments', 'counts'); // 5, from the runtime arrayget/set/add/replace/delete/incr/decr on a non-persistent group
operate purely on the runtime array. The test suite asserts this with a spy
backend: a non-persistent group produces zero calls to the KV ops.
Non-global groups are namespaced by blog id so a network's per-site caches don't
collide; global groups (registered via wp_cache_add_global_groups()) share a
single namespace across the whole network. Stored keys look like:
wp:<blog_id>:<group>:<key> // per-site
wp:global:<group>:<key> // global group
WordPress calls wp_cache_switch_to_blog() as it switches sites; the drop-in
updates the blog prefix accordingly, so switch_to_blog(2) and
switch_to_blog(3) see independent caches while users/site-options stay shared.
Drop a tiny check into any template or a must-use plugin:
wp_cache_set('__healthcheck', '1', 'default', 5);
assert(wp_cache_get('__healthcheck', 'default') === '1');If this round-trips you've confirmed the SAPI is loaded, the KV store is up, TTL parsing works, and the drop-in is wired. You can also check that WordPress considers the cache persistent:
var_dump(wp_using_ext_object_cache()); // expect bool(true)The ObjectCache engine takes an optional KvOpsInterface, so you can exercise
it anywhere — including standard PHPUnit suites on plain php-cli, with no
WordPress and no ePHPm runtime:
use Ephpm\Cache\WordPress\InMemoryKvOps;
use Ephpm\Cache\WordPress\ObjectCache;
$cache = new ObjectCache(new InMemoryKvOps());
$cache->set('foo', 'bar', 'default', 60);
assert($cache->get('foo', 'default') === 'bar');InMemoryKvOps is for tests only — values live in PHP arrays, there is no
eviction policy, no memory limit, and TTL is best-effort lazy expiry. Don't use
it in production.
Run the suite with:
composer install
composer testWordPress fell back to its built-in runtime cache because the drop-in couldn't
find the package classes. Check the PHP error log for
ephpm object-cache.php: could not locate ... and either install via Composer
in a standard layout or set EPHPM_CACHE_AUTOLOAD in wp-config.php (see
How the drop-in finds its classes).
You're not running under the ePHPm runtime, so SapiKvOps refused to construct.
Run WordPress through the ephpm binary, or for local development point the
engine at InMemoryKvOps (tests) — see
Testing without ePHPm.
ephpm_kv_flush_all() clears the entire KV store, including keys written by
other consumers (e.g. ephpm/predis-connection) on the same instance. If you
need isolation, give WordPress its own ePHPm instance/store.
Expected — see the
wp_cache_flush_group() limitation. Use a
full wp_cache_flush() or rely on TTLs for persistent groups.
By design — ePHPm's KV is an in-process DashMap, not a durable store. Treat it as a tier-1 cache. See the ePHPm KV docs for the durability/replication story.
ePHPm runs PHP inside the same OS process as the KV store via the embed SAPI. The
store itself is a Rust DashMap plus TTL management.
ePHPm registers a small set of host functions (ephpm_kv_get, ephpm_kv_set,
ephpm_kv_incr_by, ephpm_kv_expire, ephpm_kv_ttl, ephpm_kv_pttl,
ephpm_kv_del, ephpm_kv_exists, ephpm_kv_flush_all) into PHP's global
function table. Calling one is a direct C function call into Rust — no socket, no
protocol parser, no value serialization beyond what userland code already does.
This package wraps those functions in a WP_Object_Cache-shaped engine
(Ephpm\Cache\WordPress\ObjectCache) and a thin object-cache.php drop-in that
defines the global wp_cache_*() functions WordPress core calls. The rest of
WordPress's caching stack — WP_Query, term/meta caches, the options API,
transients backed by the object cache — keeps working unchanged because it all
routes through those functions.
Integer-looking values are stored as their raw string so the SAPI's atomic
incr/decr keep operating on native integers; everything else is
PHP-serialize()d.
See ephpm.dev/architecture/kv-store/ for the architecture and ephpm.dev/guides/kv-from-php/ for the underlying SAPI surface.
MIT — see LICENSE.