Alpha ZealPHP is early-stage and under active development. APIs may change between minor versions until v1.0. Feedback and bug reports welcome on GitHub.
API Index — Namespaces, Packages, Reports, Indices

Room
in package

FinalYes

A first-class WebSocket room — cluster-wide membership, presence, fan-out + handler registration. Built on the existing v0.2.40 Store + pub/sub fabric:

  • Membership lives in the shared ws_room_members Store table (visible to every worker on every node).
    • Push / presence events flow over ws:room:{name} pub/sub channel.
    • One PSUBSCRIBE per worker covers EVERY room (no per-room subscriber proliferation).

Construct via WSRouter::room('chat.42') — don't new directly (instances need WSRouter::init() to have wired the PSUBSCRIBE). Room names must match /^[A-Za-z0-9_.-]+$/ (no : — it would collide ws_room_members keys and the ws:room:* channel; #247).

Usage:

$room = WSRouter::room('chat.42');
$room->join('alice');                            // SADD-equivalent + presence broadcast
$room->push(['from' => 'alice', 'msg' => 'hi']); // fans out across cluster
$room->size();                                   // cluster-wide member count
$room->members();                                // cluster-wide roster
$room->onMessage(function (array $msg) { ... }); // user-side handler
$room->onPresence(function (array $event) { ... }); // join/leave events
$room->leave('alice');                           // SREM-equivalent + presence broadcast

Federation: a publish from server-A reaches every server's PSUBSCRIBE. Each worker pushes to its local members (those in WSRouter's per-worker fd map). No double-delivery: workers without local members of this room skip the push entirely.

Table of Contents

Properties

$name  : string
$serverId  : string

Methods

__construct()  : mixed
isMember()  : bool
True if $clientId is a current member (cluster-wide check).
join()  : void
Add a client to this room. Idempotent — re-joining is cheap.
leave()  : void
Remove a client from this room. Idempotent. Broadcasts the leave event so peers update their local caches.
members()  : array<int, string>
Cluster-wide roster — list of client ids currently joined.
membersPaged()  : array{cursor: string, members: list}
Paginated roster — returns one SSCAN batch + an opaque next-cursor (WS-2). Use for very large rooms where members() would be too heavy. Cursor '0' starts a fresh walk; the returned cursor '0' signals end-of-scan.
name()  : string
onMessage()  : void
Register a handler for messages broadcast to this room.
onPresence()  : void
Register a handler for join/leave events on this room. The handler is called with the full event object: {type, client_id, ts}.
push()  : int
Broadcast a message to every member of this room across the cluster. Returns the receivers count Redis reported (1 per subscribed worker × every server with a pattern subscriber).
size()  : int
Cluster-wide member count (WS-2 hardened).
checkRoomRateLimit()  : bool
Per-room rate limit check. True = allowed, false = drop.
compositeKey()  : string
Composite key shape used in the ws_room_members Store table.
publish()  : int
readForbidden()  : bool
#234 — true when a roomAuthorizer is wired AND it denies a 'read' on this room for the current session principal. Roster reads (members/size/ isMember) consult this so an authorizer can block cross-tenant enumeration.

Properties

$name

private string $name

$serverId

private string $serverId

Methods

__construct()

public __construct(string $name, string $serverId) : mixed
Parameters
$name : string
$serverId : string

isMember()

True if $clientId is a current member (cluster-wide check).

public isMember(string $clientId) : bool
Parameters
$clientId : string
Return values
bool

join()

Add a client to this room. Idempotent — re-joining is cheap.

public join(string $clientId) : void

Broadcasts a presence event to every server's room subscriber so each worker can refresh its local-membership cache.

Parameters
$clientId : string

leave()

Remove a client from this room. Idempotent. Broadcasts the leave event so peers update their local caches.

public leave(string $clientId) : void
Parameters
$clientId : string

members()

Cluster-wide roster — list of client ids currently joined.

public members() : array<int, string>

Redis / Tiered backend: drains the per-room SET via SSCAN. For very large rooms (>10k members), use membersPaged() instead to avoid loading the full roster into memory.

Table backend: iterates the metadata table.

Return values
array<int, string>

membersPaged()

Paginated roster — returns one SSCAN batch + an opaque next-cursor (WS-2). Use for very large rooms where members() would be too heavy. Cursor '0' starts a fresh walk; the returned cursor '0' signals end-of-scan.

public membersPaged([string $cursor = '0' ][, int $count = 100 ]) : array{cursor: string, members: list}

Only useful on Redis / Tiered backends — on Table backend the iterate path returns the full roster in one batch (cursor '0').

Parameters
$cursor : string = '0'
$count : int = 100
Return values
array{cursor: string, members: list}

name()

public name() : string
Return values
string

onMessage()

Register a handler for messages broadcast to this room.

public onMessage(callable(array<string, mixed> $msg, string $room): void $handler) : void

Multiple handlers per room are allowed; all fire in order.

Parameters
$handler : callable(array<string, mixed> $msg, string $room): void

onPresence()

Register a handler for join/leave events on this room. The handler is called with the full event object: {type, client_id, ts}.

public onPresence(callable(array<string, mixed> $event, string $room): void $handler) : void
Parameters
$handler : callable(array<string, mixed> $event, string $room): void

push()

Broadcast a message to every member of this room across the cluster. Returns the receivers count Redis reported (1 per subscribed worker × every server with a pattern subscriber).

public push(array<string, mixed>|string $payload[, string|null $fromClientId = null ]) : int

Payload is JSON-encoded when array; sent as-is when string.

Parameters
$payload : array<string, mixed>|string
$fromClientId : string|null = null
Return values
int

size()

Cluster-wide member count (WS-2 hardened).

public size() : int

Redis / Tiered backend: O(1) SCARD on the per-room SET maintained by join/leave. Sub-millisecond regardless of cluster size.

Table backend: O(total members across ALL rooms) — iterates the ws_room_members metadata table and filters by room. Fine for single-node deployments where the iterate is in-process.

Return values
int

checkRoomRateLimit()

Per-room rate limit check. True = allowed, false = drop.

private checkRoomRateLimit() : bool

Sliding window via floor(time() / window) bucket. WS-7: the counter NAME is STABLE per room (no per-window suffix) — the window roll is handled by WSRouter::rateLimitAllow() resetting the SAME counter on boundary crossing, instead of allocating a brand-new named Atomic every window (which leaked one dead Atomic per elapsed window on the default Atomic Counter backend). Disabled when WSRouter::$roomRateLimitN === 0.

Return values
bool

compositeKey()

Composite key shape used in the ws_room_members Store table.

private static compositeKey(string $room, string $clientId) : string

#247 — length-prefix the room half so the key is an unambiguous function of the (room, client) pair even if a client id contains a :. The room name is already charset-validated (no :) at the WSRouter::room() chokepoint; prefixing its byte length here removes any residual ambiguity at the room↔client boundary (5:chat:42:alice can only decompose one way).

Parameters
$room : string
$clientId : string
Return values
string

publish()

private publish(array<string, mixed> $envelope) : int
Parameters
$envelope : array<string, mixed>
Return values
int

readForbidden()

#234 — true when a roomAuthorizer is wired AND it denies a 'read' on this room for the current session principal. Roster reads (members/size/ isMember) consult this so an authorizer can block cross-tenant enumeration.

private readForbidden() : bool

BC: with no authorizer the short-circuit returns false (reads unguarded) WITHOUT touching the session.

Return values
bool
On this page