phpdot/realtime

Real-time WebSocket engine — rooms, channels, presence, broadcast. Transport-agnostic: depends only on phpdot/contracts, never a concrete server.

Maintainers

Package info

github.com/phpdot/realtime

Issues

pkg:composer/phpdot/realtime

Transparency log

Statistics

Installs: 0

Dependents: 1

Suggesters: 0

Stars: 0

v0.1.0 2026-07-17 23:14 UTC

This package is auto-updated.

Last update: 2026-07-18 03:25:12 UTC


README

A real-time WebSocket engine for PHPdot — rooms, channels, presence, and broadcast over plain JSON frames, with a Socket.IO-style Hub/Socket API and an explicit-targeting broadcast operator. It is transport-agnostic: it reaches clients only through PHPdot\Contracts\Server\ConnectionSenderInterface, so it never names a concrete server. A pluggable adapter backs membership and presence — a Swoole\Table for a single node, or Redis for a multi-node cluster.

Table of Contents

Requirements

Requirement Constraint
PHP >= 8.5
ext-swoole >= 6.2
phpdot/contracts ^0.1
psr/http-message ^2.0

Installation

composer require phpdot/realtime

Usage

realtime carries no DI attributes on purpose — you bind the seam in your application. The Hub reaches clients through ConnectionSenderInterface (implemented by your server's connection registry), and the Adapter/Hub must be resolved once at bootstrap, before the server forks workers, because Swoole\Table is shared memory only when created pre-fork:

use PHPdot\Contracts\Server\ConnectionSenderInterface;
use PHPdot\Realtime\Adapter\TableAdapter;
use PHPdot\Realtime\Contract\Adapter;
use PHPdot\Realtime\Hub;

ConnectionSenderInterface::class => fn ($c) => $c->get(ConnectionRegistry::class),
Adapter::class => fn ($c) => new TableAdapter($c->get(ConnectionSenderInterface::class)),
Hub::class     => fn ($c) => new Hub($c->get(Adapter::class), $c->get(ConnectionSenderInterface::class)),

The Hub is the io: it owns the fd → Socket map, onConnection, emit, to/except/direct, room(), and the transport lifecycle (handleOpen / handleMessage / handleClose). Each Socket exposes the per-connection API (emit, broadcast, join/leave, on, disconnect), and RoomFacade answers presence queries (members(), count()).

Single node vs cluster

TableAdapter coordinates rooms and presence across the workers of one instance through a Swoole\Table. For several instances behind a load balancer, bind RedisAdapter instead: every node shares membership through Redis and relays broadcasts over pub/sub. RedisSubscriber runs the blocking SUBSCRIBE loop on a dedicated connection, and Maintenance\ClusterMaintenance heartbeats each node and reaps the membership of dead peers so leaked rooms and presence are reclaimed automatically.

Architecture

Application and transport callbacks drive the Hub, which resolves Sockets and fans events out through the Adapter. The adapter is the only thing that touches storage — a Swoole\Table (single node) or Redis (cluster) — and the Hub pushes frames to clients solely through ConnectionSenderInterface, so the engine never depends on a concrete server.

graph TD
    APP["Application + transport callbacks<br/><br/>onConnection / handleOpen / handleMessage / handleClose"]
    HUB["Hub (io)<br/><br/>fd → Socket map, emit, to/except/direct, room()"]
    SOCKET["Socket + BroadcastOperator + RoomFacade<br/><br/>per-connection API, explicit targeting, presence"]
    ADAPTER["Contract Adapter<br/><br/>TableAdapter (single node) / RedisAdapter (cluster)"]
    SENDER["Contracts Server ConnectionSenderInterface<br/><br/>the only path to a client — no concrete server"]

    APP --> HUB
    HUB --> SOCKET
    HUB --> ADAPTER
    HUB --> SENDER
    ADAPTER --> SENDER
Loading

Testing

composer install
composer test        # PHPUnit
composer analyse     # PHPStan, level max + strict rules
composer cs-check    # PHP-CS-Fixer
composer check       # All three

License

MIT.

This repository is a read-only mirror, generated by CI from phpdot/monorepo. Pull requests and issues belong in the monorepo.