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

Chatroom
in package

FinalYes

Lesson 22 — multi-room group chat (SQLite persistence, no Redis required).

Three pure-PHP responsibilities:

  1. Persist messages in the local SQLite (chatroom_messages table).
  2. Recall the last N messages on join (so a refresh shows history).
  3. List the rooms that have any traffic (the "lobby" view).

Fan-out to connected sockets is the WS handler's job (route/learn_chatroom.php). Single-server uses a local fd map; the lesson shows how to swap that for WSRouter::room() to scale to N nodes — same call sites, different fabric.

Table of Contents

Constants

DEFAULT_TAIL  : mixed = 50
MAX_MESSAGE_LEN  : mixed = 2000
MAX_USERNAME_LEN  : mixed = 32

Methods

broadcast_to_room()  : void
Fan-out helper: iterate the cluster-wide chatroom_fds Store table and push to every fd whose row's room matches. Works across workers (any worker can call $server->push($fd, ...) for any fd) AND across the cluster when the Store backend is Redis (the table iteration spans every node — federated chat for free).
listRooms()  : array<int, array{room: string, last_msg_at: int, count: int}>
Lobby — every room with at least one message + the latest activity timestamp.
recent()  : array<int, array{id: int, room: string, username: string, body: string, kind: string, created_at: int}>
Last $tail messages from this room in chronological order (oldest first).
saveMessage()  : array{id: int, room: string, username: string, body: string, kind: string, created_at: int}
Persist a message in the room. Trims user content + rejects empty bodies.
asInt()  : int
Coerce a mixed PDO column value to int, returning 0 on non-numeric input.
asString()  : string
Coerce a mixed PDO column value to string, returning '' on non-scalar input.
normalize()  : array{0: string, 1: string, 2: string}
Trim and clamp all three string inputs.
shapeMessage()  : array{id: int, room: string, username: string, body: string, kind: string, created_at: int}

Constants

DEFAULT_TAIL

private mixed DEFAULT_TAIL = 50

MAX_MESSAGE_LEN

private mixed MAX_MESSAGE_LEN = 2000

MAX_USERNAME_LEN

private mixed MAX_USERNAME_LEN = 32

Methods

broadcast_to_room()

Fan-out helper: iterate the cluster-wide chatroom_fds Store table and push to every fd whose row's room matches. Works across workers (any worker can call $server->push($fd, ...) for any fd) AND across the cluster when the Store backend is Redis (the table iteration spans every node — federated chat for free).

public static broadcast_to_room(mixed $server, string $room, array<string, mixed> $payload[, int $excludeFd = 0 ]) : void
Parameters
$server : mixed
$room : string
$payload : array<string, mixed>

JSON-serialisable message payload.

$excludeFd : int = 0

listRooms()

Lobby — every room with at least one message + the latest activity timestamp.

public static listRooms() : array<int, array{room: string, last_msg_at: int, count: int}>
Return values
array<int, array{room: string, last_msg_at: int, count: int}>

recent()

Last $tail messages from this room in chronological order (oldest first).

public static recent(string $room[, int $tail = self::DEFAULT_TAIL ]) : array<int, array{id: int, room: string, username: string, body: string, kind: string, created_at: int}>
Parameters
$room : string
$tail : int = self::DEFAULT_TAIL
Return values
array<int, array{id: int, room: string, username: string, body: string, kind: string, created_at: int}>

saveMessage()

Persist a message in the room. Trims user content + rejects empty bodies.

public static saveMessage(string $room, string $username, string $body[, string $kind = 'message' ]) : array{id: int, room: string, username: string, body: string, kind: string, created_at: int}

Returns the persisted row (with assigned id + server-side timestamp).

Parameters
$room : string
$username : string
$body : string
$kind : string = 'message'
Return values
array{id: int, room: string, username: string, body: string, kind: string, created_at: int}

asInt()

Coerce a mixed PDO column value to int, returning 0 on non-numeric input.

private static asInt(mixed $v) : int
Parameters
$v : mixed
Return values
int

asString()

Coerce a mixed PDO column value to string, returning '' on non-scalar input.

private static asString(mixed $v) : string
Parameters
$v : mixed
Return values
string

normalize()

Trim and clamp all three string inputs.

private static normalize(string $room, string $username, string $body) : array{0: string, 1: string, 2: string}

$room defaults to 'general' when empty; $username defaults to 'anonymous'. Both $username and $body are truncated to MAX_USERNAME_LEN and MAX_MESSAGE_LEN characters respectively.

Parameters
$room : string
$username : string
$body : string
Return values
array{0: string, 1: string, 2: string}

Trimmed [room, username, body].

shapeMessage()

private static shapeMessage(array<int|string, mixed> $row) : array{id: int, room: string, username: string, body: string, kind: string, created_at: int}
Parameters
$row : array<int|string, mixed>
Return values
array{id: int, room: string, username: string, body: string, kind: string, created_at: int}
On this page