Caching

Jetonomy\Cacheincludes/class-cache.php — is the single object-cache wrapper every model routes through (group jetonomy, default TTL 300s). Do not add per-service wp_cache_* calls or a second cache group; extend this wrapper instead.

API

get() / set() / delete()

Cache::get( string $key )
Cache::set( string $key, $value, int $ttl = 0 ): void   // ttl 0 = DEFAULT_TTL (300s)
Cache::delete( string $key ): void

Thin wrappers over wp_cache_get/set/delete() scoped to the jetonomy group.

delete_many()

Cache::delete_many( string[] $keys ): void

Busts several keys in one call — for writers that must invalidate more than one key whose value changed (e.g. a space row is served under both space:{id} and space:slug:{slug}, so a single write invalidates both).

remember() / remember_object()

Cache::remember( string $key, callable $callback, int $ttl = 0 )
Cache::remember_object( string $key, callable $callback, int $ttl = 0 ): ?object

Standard cache-aside: return the cached value, or call $callback(), cache it, and return it. Use remember_object() for any read with an ?object return contract — some persistent object-cache backends (Redis/Memcached) materialise a cached null as '' on the next read, which remember() alone would hand back as a string and fatal a typed caller. remember_object() coerces any non-object hit back to null.

flush()

Cache::flush(): void

Flushes the whole jetonomy group. Not a per-write path — this is for one-shot admin/CLI/import recomputes whose set-based writes can't name the rows they touched, and the admin "Clear cache" action. Guarded by wp_cache_supports( 'flush_group' ); falls back to a full wp_cache_flush() on a persistent drop-in without group support, and is a no-op with no persistent cache (nothing to flush).

is_persistent()

Cache::is_persistent(): bool

Wraps wp_using_ext_object_cache().

Invalidation rule: bust after the write, in the write method

Every write method busts the exact keys whose value it changed, immediately after the database write completes — never before. Busting first is a re-prime race: a concurrent read between the bust and the write re-caches the stale value, and it stays stale for the full TTL.

A set-based UPDATE ... WHERE id IN (...) cannot invalidate what it doesn't name — bust each affected id explicitly, or call Cache::flush() for a one-shot admin/CLI/import path that can't enumerate the rows cheaply.

Member-visible state a user just changed (their own post count, a space they just made private) must never rely on TTL expiry alone — bust the exact key.

Reference pattern: Space::bust_cache()

includes/models/class-space.php:

/**
 * Bust every object-cache key that serves a space row: the id key and,
 * when given, the slug->id mapping key. Row data lives once, under
 * space:{id}; find_by_slug() caches only the stable slug->id mapping under
 * space:slug:{slug}. Callers bust AFTER the DB write.
 */
public static function bust_cache( int $id, ?string $slug = null ): void {
    $keys = [ "space:{$id}" ];
    if ( ! empty( $slug ) ) {
        $keys[] = "space:slug:{$slug}";
    }
    Cache::delete_many( $keys );
}

Callers invoke it after every write that changes a space row: update() (old + new slug on rename), the hard delete() override, and both counter increments (id-only — the slug mapping doesn't change on a count bump). This is the model to follow for any new cached row: one bust_cache( $id, ...) helper on the model, called from every writer, after the write, never a listener on a do_action (a listener misses any caller that doesn't fire the action — REST, AJAX, CLI, import, and Abilities callers all mutate a space, and only busting inside the model methods catches all of them).

public static function update( int $id, array $data ): bool {
    $old_slug = ( parent::find( $id )->slug ?? null );
    $result   = parent::update( $id, $data );
    self::bust_cache( $id, $old_slug );
    if ( ! empty( $data['slug'] ) && $data['slug'] !== $old_slug ) {
        Cache::delete( "space:slug:{$data['slug']}" ); // rename invalidates the new key too
    }
    return $result;
}