Download the PHP package phpdot/server without Composer
On this page you can find all versions of the php package phpdot/server. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download phpdot/server
More information about phpdot/server
Files in phpdot/server
Informations about the package server
phpdot/server
Swoole-native application server for PHP 8.5. One process owner, attachable transports, and a
PSR-15 handler in front — HTTP, WebSocket, SSE, and raw TCP served from a single Swoole master.
It builds its PSR-7 messages with phpdot/http, so there is no third-party PSR-7 implementation
under the hood.
Beyond request handling it owns the whole runtime: worker and task-worker pools, the full Swoole
lifecycle as typed listener interfaces and attribute-discovered #[ServerListener] classes, a
server-wide connection registry (WebSocket push, TCP broadcast), coroutine-safe timers, live
statistics, graceful signal handling with drain diagnostics, an orphan-reaping watchdog, and a
supervising file watcher for hot reload in development. An operational CLI ships in the box:
start/stop/restart/reload/status plus cluster visibility over an optional Redis registry,
with a unix control socket for CLI introspection — operational endpoints are the application's.
Table of Contents
- Requirements
- Installation
- Usage
- Feature overview
- Quick Start
- The CLI
- Protocols
- Transports
- Connection operations
- Lifecycle events
- Server listeners
- Task workers
- Timers
- Server control
- Statistics
- Control socket and cluster
- User processes and hot reload
- Configuration
- Operational behaviour
- Architecture
- Testing
- License
Requirements
| Requirement | Constraint |
|---|---|
| PHP | >= 8.5 |
| ext-swoole | >= 6.2 |
composer-runtime-api |
^2.2 |
phpdot/attribute |
^0.3 |
phpdot/console |
^0.3 |
phpdot/contracts |
^0.3 |
phpdot/http |
^0.3 |
psr/container |
^2.0 |
psr/http-factory |
^1.0 |
psr/http-message |
^2.0 |
psr/http-server-handler |
^1.0 |
symfony/console |
^8.0 |
Installation
Usage
Feature overview
- One master, many protocols — HTTP, WebSocket, and SSE share the primary port; raw TCP attaches on its own port. All driven by one PSR-15 handler aggregate.
- Self-contained PSR-7 — requests and responses are built with
phpdot/http; no separate PSR-7 implementation required. - Attachable transports —
HttpServer(primary) andTcpServerimplement a smallTransportcontract; theServerowns the process and wires them onto the Swoole master. - Typed lifecycle — eleven
On*Interfacehooks (start, shutdown, reload, worker and manager events) fanned out from a single registry. - Connection registry — a server-wide surface for WebSocket push/ping/disconnect and TCP
broadcast, exposed to real-time layers through
ConnectionSenderInterface. - Task workers — offload blocking work to a task pool, with callback or coroutine-await completion.
- Timers — coroutine-safe recurring and one-shot timers.
- Statistics — master/manager/worker PIDs, worker status, and full Swoole stats, plus a unix control socket beside the pid file for CLI introspection (never an HTTP endpoint).
- Operational CLI —
server:start(with-dand--watch),stop,restart,reload,status, andcluster:status, assembled by a config-drivenServerFactory. - Server listeners —
#[ServerListener]classes discovered by a subprocess scan (the pre-fork master never loads app classes, keeping them hot-reloadable), DI-constructed post-fork per worker, routed by their__invokeevent parameter type. - Cluster visibility — a Redis-backed node registry (optional
phpdot/redis) with heartbeats and TTL expiry. - Operational safety — fail-fast port checks, graceful SIGINT/SIGTERM teardown with real in-flight drain (BASE mode default), idle keep-alive close on worker exit, drain diagnostics, SSE cancellation on worker exit, and an orphan-reaping watchdog.
- Hot reload — the CLI's
--watchsupervises the server from outside: code edits reload workers, pre-fork edits (config, routes) drain and relaunch the whole process.
Quick Start
The CLI
In an application with phpdot/console, the command family is discovered automatically; the
ServerFactory reads the transport configs, attaches every enabled transport, subscribes
discovered listeners, and fails at creation time when nothing is enabled:
--watch turns the command into a small supervisor: it launches the server as a child process
and watches the files itself — reload-classified edits SIGUSR1 the child (all workers re-fork),
restart-classified edits SIGTERM it gracefully and relaunch fresh. Restart classification is
empirical, not guessed: at boot the child records every file it loaded before forking (a
preloaded.list beside the pid file), and the supervisor restarts for changes to any of them —
a pre-fork file is frozen into worker memory by fork inheritance, so a reload could never apply
its edits. Everything else reloads. Signals only ever target the pid proc_open returned.
Library consumers can skip the CLI entirely and drive ServerFactory::discover() / create()
— or the raw Server — themselves.
Protocols
The handler passed to serve() is the application's aggregate. It is always a PSR-15
RequestHandlerInterface; it opts into the other protocols by additionally implementing the
handler interfaces from phpdot/contracts:
| Protocol | Handler interface | Trigger |
|---|---|---|
| HTTP | Psr\Http\Server\RequestHandlerInterface |
every request |
| WebSocket | PHPdot\Contracts\Server\WebSocketHandlerInterface |
upgrade on the primary port |
| SSE | PHPdot\Contracts\Server\SseHandlerInterface |
Accept: text/event-stream |
| TCP | PHPdot\Contracts\Server\TcpHandlerInterface |
data on an attached TcpServer port |
A single object may implement all four — the same socket upgrades WebSocket connections, streams
SSE, and serves HTTP, while an attached TcpServer routes raw frames to the TCP hooks.
Transports
Server owns the process and the Swoole master; transports attach to it. HttpServer is always
the primary transport (it owns the main port). TcpServer adds a raw-TCP port alongside it:
TCP framing modes (TcpFraming): Eof (delimiter, default "\n"), Length (length-prefixed),
None (stream — primary-only). A non-primary TCP port requires framing; Swoole never fires
receive on an unframed added port.
Connection operations
ConnectionRegistry is the server-wide connection surface, and implements the
ConnectionSenderInterface seam a real-time layer depends on to reach clients without naming a
concrete server:
Lifecycle events
Subscribe any object to the lifecycle registry; each PHPdot\Server\Contract\On*Interface it
implements is fanned out from a single composite per Swoole event:
| Hook interface | Fires on |
|---|---|
OnStartInterface |
master start |
OnManagerStartInterface / OnManagerStopInterface |
manager process start / stop |
OnWorkerStartInterface / OnWorkerStopInterface |
worker start / stop |
OnWorkerExitInterface |
worker exit (drain) |
OnWorkerErrorInterface |
worker fatal error |
OnBeforeReloadInterface / OnAfterReloadInterface |
around a reload |
OnBeforeShutdownInterface / OnShutdownInterface |
around shutdown |
Server listeners
The typed On*Interface contracts are the low-level SPI. Application code uses the high-level
form instead: mark a class #[ServerListener], declare ONE __invoke whose parameter type is
the lifecycle event it wants, and drop it anywhere under a discovered path — no registration,
no subscription call:
Events: ServerStarted, WorkerStarted, WorkerExiting, ServerShutdown
(PHPdot\Server\Event\). The ListenerBridge fixes the class list pre-fork (the boot scan)
but constructs instances LAZILY through the container in whichever process first needs them —
post-fork in workers. A throwing listener is logged and isolated, never allowed to kill the
worker; a misdeclared one is skipped with a log line. Discovery paths are wired by the
application, exactly like console command discovery:
Task workers
Configure a task pool with ServerConfig(taskWorkerNum: N), then offload blocking work off the
request workers:
Timers
Coroutine-safe timers over Swoole's timer wheel:
Server control
Statistics
Control socket and cluster
The server serves NO operational endpoints of its own — introspection is CLI territory, and
health checks are the application's to route. Instead, whenever a pidFile is configured, a
unix-domain control socket appears beside it (server.pid → server.sock): a private,
filesystem-permissioned line that server:status queries for the master's live stats().
Nothing rides the network; a running pid whose socket cannot answer within a second is
reported as possibly wedged — the silence is itself a health signal. Unix socket paths cap at
~104 bytes, so keep pidFile reasonably shallow.
Health endpoints are the APPLICATION's responsibility (a route like any other — load
balancers speak HTTP, so the framework answers them; dot ships a HealthController on
/healthz). Reaching app code at all proves process, workers, routing, and kernel.
With phpdot/redis installed (a soft, optional coupling — the redis package knows nothing of
the server), the Cluster heartbeat publishes this node into a Redis registry every 5s with a
15s TTL: node_id (configured, or derived hostname:port), the master pid, uptime, and the
stats snapshot. server:cluster:status renders every node — ● fresh, ○ stale with "last
seen Ns ago" — and dead nodes simply expire off the table. Registry writes are best-effort:
a Redis outage never harms a serving node.
User processes and hot reload
ProcessManager (via $server->processes()) adds long-running user processes and drives the
file watcher for development hot reload:
A change under paths reloads the workers; a change under restart restarts the whole server
(WatchAction::Reload / Restart / Ignore is the resolved per-change decision).
Configuration
Four config DTOs, one per concern. In a dot application they hydrate from
config/server/{master,http,tcp,watch}.php via #[Config]; standalone consumers construct
them directly.
ServerConfig (#[Config('server.master')]) — process and pool tuning:
| Field | Default | Purpose |
|---|---|---|
workerNum |
null (CPU count) |
request workers |
taskWorkerNum |
0 |
task workers |
maxRequest |
100000 |
requests before a worker recycles — auto-forced to 0 when the handler speaks SSE/WS or a TCP transport is attached (recycling kills open streams and raw connections); override() is the escape hatch |
mode |
SWOOLE_BASE |
process model — BASE default: workers own their sockets, so stops DRAIN in-flight requests (PROCESS drops them) |
maxWaitTime |
3 |
drain seconds on reload/shutdown |
stopTimeout |
15 |
seconds server:stop waits before escalating to SIGKILL |
nodeId |
'' |
cluster identity; empty derives hostname:httpPort |
orphanWatchdog |
true |
reap the tree if the master dies ungracefully |
hookFlags |
SWOOLE_HOOK_ALL |
coroutine runtime hooks |
daemonize, pidFile, logFile, logLevel |
— | daemon / logging |
tcpNodelay, tcpKeepalive, backlog, bufferOutputSize, socketBufferSize, packageMaxLength |
— | socket tuning |
rawSettings |
[] |
any Swoole setting without a typed field |
HttpServerConfig (#[Config('server.http')]) — enabled, host, port, sockType,
serverSoftware, keepAlive (disable in development so idle sockets never pin exiting BASE
workers), http2, httpCompression (+ level and min length), the
httpParsePost / httpParseCookie / httpParseFiles parsing toggles, uploadTmpDir,
staticHandler (+ documentRoot, staticHandlerLocations), and the ssl* fields
(cert/key/CA, peer verification, protocols, ciphers). The server serves no operational
endpoints — health routes are the application's (see below).
TcpServerConfig (#[Config('server.tcp')]) — enabled (off by default), host, port,
sockType, framing, and the framing parameters (packageEof, packageLengthType,
lengthOffset, bodyOffset, packageMaxLength).
WatchConfig (#[Config('server.watch')]) — what --watch watches: paths (directories
scanned recursively, listed files watched as-is), extensions, excludes, restart (path
segments that need a full restart because they load pre-fork), depth, interval, debounce.
Operational behaviour
- Fail-fast ports —
serve()refuses to start when a listen port is held, naming the exact host:port. - Graceful signals — Ctrl+C (SIGINT) and SIGTERM tear the whole tree down cleanly, in PROCESS and BASE modes. BASE (the default) additionally drains in-flight requests to completion.
- Idle keep-alive close on worker exit — exiting BASE workers close their idle keep-alive sockets (sparing active requests, WS upgrades, and live streams), so reloads and shutdowns are never pinned to the max_wait_time force-kill by a browser's parked connections.
- Orphan watchdog (default on) — if the master dies without teardown, a watchdog process reaps the manager and workers instead of leaving an orphaned tree. macOS has no parent-death signal, so this is what prevents orphaned trees from poisoning the next boot.
- Drain diagnostics — a worker still pinned one second into shutdown logs exactly which coroutines and timers hold it, instead of a bare ERRNO 9101 force-kill.
- SSE cancellation — in-flight event streams are cancelled on worker exit so recycles never hang on a streaming loop.
Architecture
Testing
The package is standalone-testable (requires ext-swoole):
Integration tests boot real servers and assert raw bytes on the wire — HTTP parity, WebSocket
frames, SSE streams, TCP framing, signal handling, and orphan reaping. They build all PSR-7 inputs
and responses with phpdot/http; no external PSR-7 library is used.
License
MIT.
This repository is a read-only mirror, generated by CI from phpdot/monorepo. Pull requests and issues belong in the monorepo.
All versions of server with dependencies
ext-swoole Version >=6.2
php Version >=8.5
phpdot/attribute Version ^0.3
phpdot/console Version ^0.3
phpdot/contracts Version ^0.3
phpdot/http Version ^0.3
psr/container Version ^2.0
psr/http-factory Version ^1.0
psr/http-message Version ^2.0
psr/http-server-handler Version ^1.0
symfony/console Version ^8.0