API Index — Namespaces, Packages, Reports, Indices
Store
in package
Store — backend-agnostic key-value store.
Default backend is OpenSwoole\Table (single-node, in-memory,
lock-free, nanosecond latency — the hot path). Flip to Redis/Valkey
for cross-node + persistent shared state with one line in app.php:
Store::defaultBackend('redis'); // ZEALPHP_REDIS_URL env
Store::defaultBackend('redis', 'redis://cache:6379/0'); // explicit URL
Every existing call site keeps working unchanged — the static API is preserved verbatim.
Column types — prefer the backend-neutral Store::TYPE_* constants
for new code; OpenSwoole\Table::TYPE_* still works for BC:
Store::TYPE_INT — 1, 2, 4, or 8 bytes
Store::TYPE_FLOAT — 8 bytes
Store::TYPE_STRING — up to N bytes (specify max length)
IMPORTANT: Store::make() must be called BEFORE $app->run()
(before workers are forked). The Table backend's shared memory
segment is inherited on fork; the Redis backend's connection pool
is lazily built per worker.
Usage (identical across backends):
Store::make('sessions', 4096, [
'uid' => [Store::TYPE_STRING, 64],
'hits' => [Store::TYPE_INT, 4],
]);
Store::set('sessions', $id, ['uid' => 'alice', 'hits' => 0]);
Store::get('sessions', $id); // ['uid' => 'alice', ...]
Store::incr('sessions', $id, 'hits');
Store::count('sessions'); // O(1)
Table of Contents
Constants
- BACKEND_MEMCACHED : mixed = 'memcached'
- BACKEND_REDIS : mixed = 'redis'
- BACKEND_TABLE : mixed = 'table'
- BACKEND_TIERED : mixed = 'tiered'
- PREFER_AUTO : mixed = 'auto'
- PREFER_PHPREDIS : mixed = 'phpredis'
- PREFER_PREDIS : mixed = 'predis'
- TYPE_FLOAT : mixed = \OpenSwoole\Table::TYPE_FLOAT
- TYPE_INT : mixed = \OpenSwoole\Table::TYPE_INT
- TYPE_STRING : mixed = \OpenSwoole\Table::TYPE_STRING
Properties
- $backend : StoreBackend|null
-
$backendConfig
: array{kind: string, conn?: string|array
}
Methods
- clear() : void
- compareAndSet() : bool
- S-2 — optimistic compare-and-swap on a single Store row+column.
- count() : int
- decr() : int
- defaultBackend() : StoreBackend
- Get or set the process-wide default backend.
- del() : bool
- eval() : mixed
- Run a Lua script atomically on the Redis backend. KEYS are raw /
absolute (un-prefixed). Values are passed as
KEYS/ARGV(never interpolated into the script body). Throws on the Table backend. - evalScript() : mixed
- S-1 — execute a Redis Lua script atomically. Redis executes EVAL server-side as a single atomic operation: no other client can observe an intermediate state, no other command interleaves.
- exists() : bool
- get() : mixed
- Returns the row array when
$fieldis null, the field's scalar value when set, orfalseon miss (legacy BC —Store::gethas always returnedfalsefor missing keys; existing callers narrow viais_array()/is_scalar()and depend on that contract). - getStrict() : mixed
- Strict variant of get() — returns null on miss instead of false.
- hasSetOps() : bool
- True when the active backend supports direct SET primitives (sadd/srem/scard/sscanCursor/sdel). Use as the BC-safe guard before calling Set ops from generic / backend-portable code.
- incr() : int
- iterate() : Generator<string, array<string, scalar>>
-
iteratePaged()
: array{cursor: string, rows: array
>} - Paginated iteration (S-3). Returns one batch + an opaque next-cursor.
- make() : Table|null
- Create a named table. Returns the underlying
OpenSwoole\Tablewhen the backend istable, or null for redis (the raw object has no equivalent there — use the backend-neutral methods). - mget() : array<string, array<string, scalar>|null>
- Bulk read. Missing keys come back as
nullin the result map. - mset() : bool
- names() : array<int, string>
- ping() : bool
- Health check. True on Table backend (always reachable); on Redis, PINGs the pool and returns the result.
- publish() : int
- Fire-and-forget Redis pub/sub.
- publishReliable() : string
- Reliable variant via Redis Streams (XADD). Returns the Redis-generated message ID. Durable when Redis has AOF/RDB; at-least-once delivery via consumer groups (one consumer per worker by default).
- sadd() : int
- scard() : int
- sdel() : bool
- set() : bool
- srem() : int
-
sscanCursor()
: array{cursor: string, members: list
} - Paginated SSCAN. Returns one batch + an opaque next-cursor — same
cursor protocol as
iteratePaged. Cursor '0' starts a fresh scan; a returned cursor of '0' signals end-of-scan. - stats() : array<string, int>
- Per-worker operational stats — pool acquires, timeouts, clients created. Empty array on the Table backend (no stats surface).
- table() : Table|null
- Direct access to the underlying
OpenSwoole\Table. Only available on the table backend; throwsStoreExceptionon the redis backend (the raw object has no Redis equivalent — use the static methods). - tieredAdvisory() : string|null
- Advisory for a Tiered backend whose cross-node L1 invalidation is OFF.
- tieredBootChecks() : array<int, string>
- Boot-check advisories for the active Tiered backend. Returns the list of
advisory strings (currently 0 or 1) for the process-wide default backend
— empty when the backend is not Tiered, or when Tiered invalidation is
enabled with a secret. Mirrors
App::redisBootChecks()'s return shape so a boot harness can fold these into the same warning stream. - buildBackend() : StoreBackend
- buildTieredBackend() : TieredBackend
- Tiered backend facade: L1=TableBackend (in-process, ns latency) +
L2=RedisBackend (cross-node, source of truth). The L2 build path
reuses the same conn-opts shape as
'redis'so users only need to learn one config dialect. - memcachedServersFromEnv() : string
- poolOptsFromEnv() : array{prefer?: "auto"|"phpredis"|"predis"}
- Build pool options from environment. ZEALPHP_REDIS_PREFER picks the client lib — 'auto' (default), 'phpredis', or 'predis'. Use 'predis' for pub/sub subscribers in production until the phpredis SUBSCRIBE + HOOK_ALL spike has been benched in your environment.
- redisOrThrow() : RedisBackend
- Resolve the active backend to a RedisBackend (directly, or as the L2 of a Tiered/CircuitBreaker decorator). Throws when the active backend is Table — set ops require cross-node Redis primitives.
- redisUrlFromEnv() : string
Constants
BACKEND_MEMCACHED
public
mixed
BACKEND_MEMCACHED
= 'memcached'
BACKEND_REDIS
public
mixed
BACKEND_REDIS
= 'redis'
BACKEND_TABLE
public
mixed
BACKEND_TABLE
= 'table'
BACKEND_TIERED
public
mixed
BACKEND_TIERED
= 'tiered'
PREFER_AUTO
public
mixed
PREFER_AUTO
= 'auto'
PREFER_PHPREDIS
public
mixed
PREFER_PHPREDIS
= 'phpredis'
PREFER_PREDIS
public
mixed
PREFER_PREDIS
= 'predis'
TYPE_FLOAT
public
mixed
TYPE_FLOAT
= \OpenSwoole\Table::TYPE_FLOAT
TYPE_INT
public
mixed
TYPE_INT
= \OpenSwoole\Table::TYPE_INT
TYPE_STRING
public
mixed
TYPE_STRING
= \OpenSwoole\Table::TYPE_STRING
Properties
$backend
private
static StoreBackend|null
$backend
= null
$backendConfig
private
static array{kind: string, conn?: string|array}
$backendConfig
= ['kind' => 'table']
Methods
clear()
public
static clear(string $name) : void
Parameters
- $name : string
compareAndSet()
S-2 — optimistic compare-and-swap on a single Store row+column.
public
static compareAndSet(string $table, string $key, string $field, string $expected, string $new) : bool
Atomic across nodes (Lua-backed); returns true if the swap landed
(current value matched $expected), false otherwise.
// Increment 'hits' by 1 only if it's still at 42:
Store::compareAndSet('counters', 'user:42', 'hits', '42', '43');
Use when the "natural" Counter::compareAndSet shape doesn't fit (e.g., a Store row with mixed-type columns, not a standalone counter). Throws on Table backend.
Parameters
- $table : string
- $key : string
- $field : string
- $expected : string
- $new : string
Return values
boolcount()
public
static count(string $name) : int
Parameters
- $name : string
Return values
intdecr()
public
static decr(string $name, string $key, string $col[, int $by = 1 ]) : int
Parameters
- $name : string
- $key : string
- $col : string
- $by : int = 1
Return values
intdefaultBackend()
Get or set the process-wide default backend.
public
static defaultBackend([StoreBackendKind|string|null $kind = null ][, string|array<string, mixed> $conn = [] ]) : StoreBackend
Prefers StoreBackendKind (type-safe enum) but accepts the bare
string literal forms for BC: 'table' / 'redis'.
Parameters
- $kind : StoreBackendKind|string|null = null
-
enum or bare string; null to read current
- $conn : string|array<string, mixed> = []
-
redis URL string OR ['url'=>, 'pool_size'=>, 'prefix'=>, 'prefer'=>]
Return values
StoreBackenddel()
public
static del(string $name, string $key) : bool
Parameters
- $name : string
- $key : string
Return values
booleval()
Run a Lua script atomically on the Redis backend. KEYS are raw /
absolute (un-prefixed). Values are passed as KEYS/ARGV (never
interpolated into the script body). Throws on the Table backend.
public
static eval(string $script[, array<int, string> $keys = [] ][, array<int, string> $args = [] ]) : mixed
Parameters
- $script : string
- $keys : array<int, string> = []
- $args : array<int, string> = []
evalScript()
S-1 — execute a Redis Lua script atomically. Redis executes EVAL server-side as a single atomic operation: no other client can observe an intermediate state, no other command interleaves.
public
static evalScript(string $script[, array<int, string> $keys = [] ][, array<int, string> $args = [] ]) : mixed
This is the canonical "transaction" primitive on Redis — every MULTI/EXEC + WATCH pattern has a more efficient Lua equivalent.
Use cases:
-
Atomic "get current value, derive new value, set" without race windows (the CAS pattern, server-side).
-
Multi-key atomic updates (the MULTI/EXEC use case).
-
Conditional ops ("set only if condition holds").
// Atomic compare-and-swap on a hash field: Store::evalScript( "local v = redis.call('HGET', KEYS[1], ARGV[1]); " . "if v == ARGV[2] then return redis.call('HSET', KEYS[1], ARGV[1], ARGV[3]); end; " . "return 0;", ['zealstore:mytable:rowkey'], ['col', 'expected_old', 'new_value'], );
Throws StoreException on Table backend (Lua is Redis-server-side).
MULTI/EXEC + WATCH via the driver protocol is intentionally NOT exposed yet — the Lua approach above covers every documented use case more atomically (one round-trip, server-atomic) and works on both drivers without protocol-level glue. If your workload genuinely needs the deferred-pipeline shape, file an issue with the use case.
Parameters
- $script : string
- $keys : array<int, string> = []
-
Redis keys the script accesses (cluster-routing hint)
- $args : array<int, string> = []
-
ARGV values
exists()
public
static exists(string $name, string $key) : bool
Parameters
- $name : string
- $key : string
Return values
boolget()
Returns the row array when $field is null, the field's scalar
value when set, or false on miss (legacy BC — Store::get has
always returned false for missing keys; existing callers narrow
via is_array() / is_scalar() and depend on that contract).
public
static get(string $name, string $key[, string|null $field = null ]) : mixed
Parameters
- $name : string
- $key : string
- $field : string|null = null
getStrict()
Strict variant of get() — returns null on miss instead of false.
public
static getStrict(string $name, string $key[, string|null $field = null ]) : mixed
Recommended for new code. The legacy get() keeps returning false
on miss for BC with code written before v0.2.39 that uses === false
to detect misses; that BC contract is permanent.
New code that wants the unambiguous null-or-value contract — e.g. to
use ??-style fallbacks safely with stored falsy values — should
call getStrict() instead.
Returns null on miss; the scalar when $field is set and the row exists;
the row array (array<string, scalar>) otherwise.
Parameters
- $name : string
- $key : string
- $field : string|null = null
hasSetOps()
True when the active backend supports direct SET primitives (sadd/srem/scard/sscanCursor/sdel). Use as the BC-safe guard before calling Set ops from generic / backend-portable code.
public
static hasSetOps() : bool
Return values
boolincr()
public
static incr(string $name, string $key, string $col[, int $by = 1 ]) : int
Parameters
- $name : string
- $key : string
- $col : string
- $by : int = 1
Return values
intiterate()
public
static iterate(string $name) : Generator<string, array<string, scalar>>
Parameters
- $name : string
Return values
Generator<string, array<string, scalar>>iteratePaged()
Paginated iteration (S-3). Returns one batch + an opaque next-cursor.
public
static iteratePaged(string $name[, string $cursor = '0' ][, int $count = 100 ]) : array{cursor: string, rows: array>}
Use for large tables where draining the full generator is impractical
(e.g. paginated UI over a 100k-member room roster). When the returned
cursor is '0' the scan is complete.
$next = '0';
do {
$page = Store::iteratePaged('rooms:lobby:members', $next, 100);
foreach ($page['rows'] as $key => $row) { ... }
$next = $page['cursor'];
} while ($next !== '0');
Cursors are NOT stable across schema changes or clear(); resume within a single logically-consistent window. See StoreBackend interface for full contract.
Parameters
- $name : string
- $cursor : string = '0'
- $count : int = 100
Return values
array{cursor: string, rows: arraymake()
Create a named table. Returns the underlying OpenSwoole\Table
when the backend is table, or null for redis (the raw object
has no equivalent there — use the backend-neutral methods).
public
static make(string $name[, int $maxRows = 1024 ][, array<string, array{0: int, 1?: int}> $columns = [] ][, array<string, mixed> $opts = [] ]) : Table|null
Parameters
- $name : string
- $maxRows : int = 1024
- $columns : array<string, array{0: int, 1?: int}> = []
- $opts : array<string, mixed> = []
-
backend-specific: mode/ttl/etc.
Return values
Table|nullmget()
Bulk read. Missing keys come back as null in the result map.
public
static mget(string $name, array<int, string> $keys) : array<string, array<string, scalar>|null>
Parameters
- $name : string
- $keys : array<int, string>
Return values
array<string, array<string, scalar>|null>mset()
public
static mset(string $name, array<string, array<string, scalar>> $rows) : bool
Parameters
- $name : string
- $rows : array<string, array<string, scalar>>
Return values
boolnames()
public
static names() : array<int, string>
Return values
array<int, string>ping()
Health check. True on Table backend (always reachable); on Redis, PINGs the pool and returns the result.
public
static ping() : bool
Return values
boolpublish()
Fire-and-forget Redis pub/sub.
public
static publish(string $channel, string $payload) : int
Scope = the entire cluster. Every app instance connected to
the same Redis (every ZealPHP process on every host) that has
called App::subscribe('<this-channel>', ...) receives the
message. That's Redis pub/sub's native PUBLISH semantics —
there's no "local mode" or per-host limiting. If you only want a
specific server to receive the message, route by channel name
(e.g. ws:server:<server-id> — the pattern WSRouter::sendToClient
uses).
Returns the receiver count Redis itself reported — typically
(subscribed workers per instance) × (instances in the cluster).
A return of 0 means no subscriber was listening when the message
was published. Throws StoreException when the default backend
is not Redis (Table has no pub/sub semantics).
Pair with App::subscribe() to register handlers. Messages
published while a subscriber is mid-reconnect are LOST —
use publishReliable() for at-least-once delivery via Streams.
Parameters
- $channel : string
- $payload : string
Return values
intpublishReliable()
Reliable variant via Redis Streams (XADD). Returns the Redis-generated message ID. Durable when Redis has AOF/RDB; at-least-once delivery via consumer groups (one consumer per worker by default).
public
static publishReliable(string $stream, string $payload[, int|null $maxLen = null ]) : string
Pair with App::subscribeReliable() to register a consumer group handler. Throws StoreException when backend is not Redis.
Parameters
- $stream : string
- $payload : string
- $maxLen : int|null = null
Return values
stringsadd()
public
static sadd(string $key, string ...$members) : int
Parameters
- $key : string
- $members : string
Return values
intscard()
public
static scard(string $key) : int
Parameters
- $key : string
Return values
intsdel()
public
static sdel(string $key) : bool
Parameters
- $key : string
Return values
boolset()
public
static set(string $name, string $key, array<string, scalar> $row) : bool
Parameters
- $name : string
- $key : string
- $row : array<string, scalar>
Return values
boolsrem()
public
static srem(string $key, string ...$members) : int
Parameters
- $key : string
- $members : string
Return values
intsscanCursor()
Paginated SSCAN. Returns one batch + an opaque next-cursor — same
cursor protocol as iteratePaged. Cursor '0' starts a fresh scan;
a returned cursor of '0' signals end-of-scan.
public
static sscanCursor(string $key[, string $cursor = '0' ][, int $count = 100 ]) : array{cursor: string, members: list}
Parameters
- $key : string
- $cursor : string = '0'
- $count : int = 100
Return values
array{cursor: string, members: liststats()
Per-worker operational stats — pool acquires, timeouts, clients created. Empty array on the Table backend (no stats surface).
public
static stats() : array<string, int>
Return values
array<string, int>table()
Direct access to the underlying OpenSwoole\Table. Only available
on the table backend; throws StoreException on the redis backend
(the raw object has no Redis equivalent — use the static methods).
public
static table(string $name) : Table|null
Parameters
- $name : string
Return values
Table|nulltieredAdvisory()
Advisory for a Tiered backend whose cross-node L1 invalidation is OFF.
public
static tieredAdvisory(StoreBackend $backend) : string|null
Returns the warning string when $backend is Tiered (always Redis-L2)
AND enableInvalidation() has not been called, telling the operator
cross-node L1 coherence is OFF, how to turn it on, and the
enable-relative-to-make boot-order requirement. When invalidation IS
enabled but no HMAC secret is set, the message instead warns that any
Redis writer can forge an evict (the C2 trust-mode gap). Returns null
when invalidation is enabled with a secret — nothing to warn about.
Pure (no side effects) so a unit test can assert the decision; the
build path emits it via elog. Mirrors the shape of
App::redisBootChecks() / App::opcacheLegacyBootCheck().
Parameters
- $backend : StoreBackend
Return values
string|nulltieredBootChecks()
Boot-check advisories for the active Tiered backend. Returns the list of
advisory strings (currently 0 or 1) for the process-wide default backend
— empty when the backend is not Tiered, or when Tiered invalidation is
enabled with a secret. Mirrors App::redisBootChecks()'s return shape so
a boot harness can fold these into the same warning stream.
public
static tieredBootChecks() : array<int, string>
Return values
array<int, string>buildBackend()
private
static buildBackend(array{kind: string, conn?: string|array} $cfg) : StoreBackend
Parameters
-
$cfg
: array{kind: string, conn?: string|array
}
Return values
StoreBackendbuildTieredBackend()
Tiered backend facade: L1=TableBackend (in-process, ns latency) +
L2=RedisBackend (cross-node, source of truth). The L2 build path
reuses the same conn-opts shape as 'redis' so users only need to
learn one config dialect.
private
static buildTieredBackend(string|array<string, mixed> $conn) : TieredBackend
Recognised opts:
- 'url' / pool_size / prefix / prefer → forwarded to the L2 RedisBackend (same as 'redis')
- 'l1_ttl' (int seconds, default 5) → L1 freshness window
- 'invalidation_secret' (string|null) → cross-node L1 invalidation HMAC secret (defaults to env ZEALPHP_TIERED_INVALIDATION_SECRET)
Parameters
- $conn : string|array<string, mixed>
Return values
TieredBackendmemcachedServersFromEnv()
private
static memcachedServersFromEnv() : string
Return values
stringpoolOptsFromEnv()
Build pool options from environment. ZEALPHP_REDIS_PREFER picks the client lib — 'auto' (default), 'phpredis', or 'predis'. Use 'predis' for pub/sub subscribers in production until the phpredis SUBSCRIBE + HOOK_ALL spike has been benched in your environment.
private
static poolOptsFromEnv() : array{prefer?: "auto"|"phpredis"|"predis"}
Return values
array{prefer?: "auto"|"phpredis"|"predis"}redisOrThrow()
Resolve the active backend to a RedisBackend (directly, or as the L2 of a Tiered/CircuitBreaker decorator). Throws when the active backend is Table — set ops require cross-node Redis primitives.
private
static redisOrThrow(string $op) : RedisBackend
Parameters
- $op : string
Return values
RedisBackendredisUrlFromEnv()
private
static redisUrlFromEnv() : string