mpociot/phpws
mpociot/phpws is a lightweight, event-driven WebSocket server for PHP built on ReactPHP. Simple onOpen, onMessage, and onClose callbacks make it easy to add real-time features like chat, notifications, and live updates to your apps.
Architecture fit — a second runtime, and that is the decision. mpociot/phpws is a ReactPHP-based WebSocket server and client that runs as a single-threaded, long-lived event-loop daemon. It is categorically outside Laravel's PHP-FPM request/response lifecycle: it cannot be mounted as a route, a controller, or a queue worker. Adopting it means the platform team owns a new deploy topology unit to supervise, monitor, and page on. The TPM's first gate is therefore a requirements gate, not a code review: does the roadmap genuinely require a persistent socket tier (live notifications, chat, presence, live dashboards), or can SSE, polling, or managed pub/sub deliver the same product outcome? If a socket tier is real and the org accepts a second runtime, fit is moderate. If the need is "push a few browser updates," fit is weak and a managed service or Laravel Reverb is structurally better. Verdict: spike vehicle and reference implementation, not a production default.
Integration feasibility — everything around the socket is bespoke glue. There is no service provider, no broadcasting driver, no config publishing, no handshake auth hooks, no queue integration. The library gives event callbacks (onOpen, onMessage, onClose) and a working loop; everything a Laravel application actually needs is ours to design and own:
artisan ws:serve command to launch and manage the daemon.onOpen (session cookie or JWT verification) — no middleware pipeline exists to reuse.None of this appears in the "lightweight, framework-agnostic, minimal setup" pitch, and all of it belongs in the estimate before commitment. Feasible as a 1–2 week bounded spike; multi-week work before production traffic, and that estimate precedes any scaling effort.
Technical risk — high, concentrated, and nameable. The TPM's job is to put each item in the register before someone discovers it in a security review:
composer why-not fights and possible dead ends before the first line of product code.Key questions to close before approving.
Stack fit — the canonical seam is portable, which is the whole argument. The correct pattern is Laravel publishing domain events to Redis and a standalone WS daemon subscribing and fanning out to connected sockets. That seam is identical regardless of library. Because it is portable, the auth, TLS, proxy, and supervisor work invested during the spike survives a swap untouched — which is precisely why picking a maintained daemon is the rational default and this one is prior art.
Migration path — abstract first, spike second, swap third. Sequence defensively so the library decision stays reversible:
phpws. Define an internal Broadcaster interface (channel subscribe, message publish, presence track) with no library types leaking into domain code. This is the single highest-value TPM decision in the effort: it converts a risky dependency into a bounded experiment with a clean, pre-agreed exit, and makes every later transport swap a configuration change rather than a rewrite.phpws behind artisan ws:serve to prove the real-time UX and client behavior end to end. Non-production, no hardening, written acceptance criterion, explicit stop date, named owner.Compatibility — verify before writing product code. Clean install on PHP 8.2/8.3; ReactPHP constraint resolution against the current lockfile; Octane interaction, where the daemon must run as a separate process and never inside an Octane worker. Assume TLS termination, CORS/origin checks, handshake auth, and channel authorization are entirely absent and must be built. Treat the client side as unvalidated too: no reconnect/backoff, no heartbeat, no message ordering guarantees ship in the box.
Sequencing gates. Gate 1: requirements gate (socket tier justified). Gate 2: Broadcaster interface merged. Gate 3: compatibility spike passes install and Octane separation. Gate 4: demo acceptance criterion met. Gate 5: maintained target approved and funded in the same decision, so the swap is scheduled work rather than a contingency discovered mid-build.
Maintenance — we own it outright the day it ships. No upstream to escalate to, no release cadence to ride. If it is vendored, it needs a version pin, a security review, a named maintainer, and an explicit recurring time budget for patching. Frame it plainly: cheapest to prototype, most expensive to own.
Support — no safety net. Zero dependents means zero community debugging surface; every incident is diagnosed inside the team under time pressure, with no changelog to consult. That cost is invisible in the spike and permanent in production, and it should be stated in those terms in the risk register rather than discovered later.
Scaling — single process, no clustering story, none arriving. Horizontal scale requires the Redis publish/fan-out pattern plus sticky routing, connection accounting, and backpressure handling the library does not provide. Long-running event loops accumulate memory and require recycling discipline. The TPM decision is to declare the launch scale assumption up front: if multi-node fan-out is a launch requirement, this package is disqualified on day one.
Failure modes to write into the runbook before the spike ends.
onOpen path — the security-relevant failure, and the reason a handshake check belongs in the threat model, not the backlog.Ramp-up — the hidden workstream. The "pure PHP, easy integration" pitch understates the onboarding tax: an artisan ws:serve command, a supervisor with restart policy, a TLS-terminating proxy, handshake authentication, channel authorization, heartbeat, and event-loop discipline. Estimate it as multi-week before any scaling, gate the spike with an explicit acceptance criterion so it cannot quietly become production, and prefer maintained alternatives that reuse Laravel's broadcasting and auth conventions when the team lacks async/event-loop experience.
Decisions requested: approve the timeboxed spike; approve the Broadcaster abstraction as a build constraint on the spike; and approve the maintained production target now so the swap is scheduled, funded work rather than a contingency we rediscover at launch.
How can I help you explore Laravel packages today?