Why ZealPHP?
PHP powers ~71% of the web (per W3Techs) — always as a worker behind a C-based HTTP server.
ZealPHP runs PHP as the HTTP server itself: coroutine-native, always-on, WebSocket/SSE/timers first-class.
The problem
In the traditional model (PHP-FPM, mod_php), the HTTP server is C — nginx or Apache. PHP is the worker that bridges in via FastCGI and exits per-request. Every request starts from scratch — no shared state, no persistent connections, no coroutines. Building a WebSocket server, streaming AI responses, or running background tasks requires leaving PHP entirely for Node.js, Go, or Python. ZealPHP collapses this: the HTTP server IS PHP, long-lived, with a coroutine per request.
Existing async PHP solutions are either too low-level (raw Swoole, ReactPHP, AMPHP), framework-locked (Laravel Octane), or not native PHP (FrankenPHP, RoadRunner). None provide a full-stack, coroutine-native framework with a migration path for existing PHP apps.
ZealPHP's approach
Coroutine-native, not event-loop
Write synchronous-looking code. Under the hood, every I/O call (file, curl, PDO, sleep) yields the event loop via OpenSwoole's coroutine hooks. Thousands of concurrent requests per worker, zero callback hell.
Full-stack framework, not a library
Routing, PSR-15 middleware, templating with streaming, WebSocket, SSE, shared memory,
task workers, sessions — all integrated. Write $app->route() and ship.
Not 12 packages wired together.
Legacy PHP bridge — LAMP-style file routing
Drop .php files in public/ and they route automatically — just like Apache.
session_start(), header(), $_GET, echo
all work unchanged via ext-zealphp. Drop files in api/ and they become REST endpoints.
Many traditional PHP patterns — including WordPress — run through the CGI worker bridge in compatibility mode, with documented limits. Migrate at your own pace — file by file, feature by feature.
PHP as the application server
HTTP, WebSocket, task workers, timers, shared memory, sessions — in one PHP application server,
started with php app.php. On one node you can skip the Redis/Supervisor/cron tier;
for cross-node deploys, ZealPHP's Store + pub/sub flip to a Redis/Valkey backend with one line of config.
Front it with nginx/Caddy/Traefik in production for TLS + horizontal scaling.
Competitive landscape
Every project below serves a different need. This comparison is about where ZealPHP fits — not about which is "best."
| Project | Model | Routing | WebSocket | Streaming | Shared Memory | Legacy PHP |
|---|---|---|---|---|---|---|
| ZealPHP | Coroutine | Built-in | Built-in | yield / SSE / stream() | Store + Counter | CGI worker |
| ReactPHP | Event loop | Manual | Via packages | Manual | No | No |
| AMPHP | Fiber | Manual | Via packages | Manual | No | No |
| FrankenPHP | Go worker | Via framework | Via framework | Via framework | No | Partial |
| RoadRunner | Go worker | Via framework | Go plugin | Via framework | No | No |
| Laravel Octane | Swoole/RR | Laravel | Via packages | Limited | Limited | No |
| Raw Swoole | Coroutine | Manual | Manual | Manual | Table / Atomic | No |
ZealPHP vs raw OpenSwoole — engine vs harness
The most common HN-shaped question: “it’s just OpenSwoole — why the extra layer?”
Same shape as “it’s just Node http — why Express?” You can write raw onRequest
handlers and ship them; people do. The catch is that every project that does ends up re-inventing
the same 12 things. OpenSwoole is the engine.
ZealPHP is the harness that lets you steer it without re-implementing the glue.
OpenSwoole gives you
the runtime — raw power
- HTTP server + WebSocket\Server primitives (
onRequest,onMessage,onOpen,onClose) - Coroutines:
go(),Channel,WaitGroup,Coroutine::getContext() Atomic+Table— lock-free shared memory across workersCoroutine\Http\Client+ DNS + sleep + file I/O hooksOpenSwoole\Runtime::enableCoroutine(HOOK_ALL)— PHP I/O yields the reactor automaticallyProcess\Pool+ master/manager/worker lifecycle- Timers:
Timer::tick(),Timer::after() Processfor sub-process fork + IPC pipes- FastCGI coroutine client (v22.1+)
ZealPHP adds on top
the harness — usable surface
- Routing —
route()+nsRoute+nsPathRoute+patternRoutewith reflection-based parameter injection (/routing) - PSR-15 middleware stack — 18 built-ins (CORS, ETag, Range, Compression, RateLimit, BasicAuth, HostRouter, ScopedMiddleware, …) covering common Apache mod_* / nginx behaviors (/middleware)
ext-zealphpoverrides —session_start(),header(),setcookie(),http_response_code(),headers_list(), the entiresession_*()family,flush(),apache_request_headers(),is_uploaded_file()all just work, routing to per-request state instead of mutating process globals- Coroutine-safe sessions —
CoSessionManagerwith per-coroutine isolation, no$_SESSIONraces across concurrent requests (/sessions) - Templating —
App::render/renderToString/renderStream/include/fragment— htmx-style named regions, streaming-Generator output, sub-template composition (/templates) - Universal return contract —
int= status,array= JSON,Generator= SSE/SSR stream,string= HTML,Closure= param-injected stream — one contract across route handler, public file, API closure, fallback, error handler, render(), include() (/responses#return-contract) - ZealAPI — file-based REST: drop
api/device/list.php→/api/device/listauto-routes; auth hooks (authChecker,adminChecker,usernameProvider) (/api) - CGI worker bridge —
cgiMode('pool' | 'proc' | 'fork' | 'fcgi')dispatches legacypublic/*.phpfiles with true global-scope isolation ('pool'is the default: a pre-spawned warm worker pool that keeps the interpreter in memory, ~1–3 ms;'proc'spawns on demand viaproc_open, ~30–50 ms;'fork'is the experimental Apache MPM prefork runner that forks a fresh child per request at ~1 ms, requirespcntl+posix;'fcgi'forwards to an external FastCGI / php-fpm pool) (/legacy-apps) - Pluggable Store + Counter backends — one API, three backends: Table (single-node, nanoseconds), Redis/Valkey (cross-node + persistence, ~ms), Tiered (L1 Table + L2 Redis with HMAC-signed cross-node L1 invalidation) (/store)
- Cross-host messaging —
Store::publish/App::subscribefor fire-and-forget pub/sub,publishReliable/subscribeReliablefor Streams-backed at-least-once delivery via consumer groups,WSRouterfor cross-server WebSocket routing, first-class WS rooms with federated membership (/pubsub) - Stream wrapper for
php://input— legacyfile_get_contents('php://input')in JSON APIs just works in long-running workers - CLI tooling —
php app.php start/stop/restart/status/logs+ daemonization + per-port PID files + log filters (--access,--debug,--server,--zlog) - Coroutine-safe superglobals & lifecycle presets —
App::mode(App::MODE_COROUTINE_LEGACY)runs legacy request-style PHP concurrently: the seven$_*superglobals (S1),$GLOBALS(S2), function-local statics (S5a), andrequire_oncestate (S7) all isolate per coroutine via ext-zealphp; per-requestdefine()isolation (S10) is available as a separate opt-in viaApp::defineIsolation(true). One-call lifecycle presets:App::mode(App::MODE_COROUTINE)(modern default),App::mode(App::MODE_LEGACY_CGI),App::mode(App::MODE_MIXED). (/coroutines#lifecycle-modes)
When raw OpenSwoole is the right choice: you’re building a custom binary-protocol server (your own message broker, database driver, ASR pipeline), you can’t install custom PHP extensions (locked-down shared host), or you’re explicitly building another framework. For everything else — HTTP, WebSocket, SSE, REST APIs, web apps with sessions, AI-streaming endpoints — the harness saves you weeks per project and keeps the migration door open to existing PHP code.
ZealPHP vs Laravel Octane
Two different problems:
- Laravel Octane accelerates an existing Laravel application by serving it from a long-running Swoole / RoadRunner / FrankenPHP worker. If you're on Laravel and want it faster, use Octane.
- ZealPHP is a framework-agnostic layer over OpenSwoole. Routing, middleware, WebSocket, SSE, shared memory, timers, and the legacy PHP bridge are exposed as first-class primitives — no Laravel kernel in between.
If you have a Laravel app, Octane is the right tool. If you're starting fresh, migrating non-Laravel PHP, or need lower-level coroutine primitives, ZealPHP is built for that.
The migration ladder
You don't have to learn a framework to start. Drop files in a folder. Upgrade when you need to.
setFallback() — your entire existing app runs unchanged on OpenSwoole
public/*.php — LAMP-style file routing. $_GET, session_start(), echo just work
api/*.php — drop a file, get a REST endpoint. ZealAPI auto-routes by filename
$app->route() — WebSocket, SSE, streaming when you're ready
superglobals(false) — full coroutine mode, thousands of concurrent requests
When to use ZealPHP
Good fit
- AI/LLM apps with streaming responses
- Real-time dashboards and live updates
- WebSocket apps (chat, collaboration)
- High-concurrency APIs — workloads where the I/O-concurrency-per-worker model pays off (measure before you commit)
- Migrating large PHP codebases to async
- LAMP-style PHP devs who want async without learning a framework
- Single-process deployments (no infra complexity)
Not the right fit
- Already invested in Laravel ecosystem
- Need shared hosting (requires CLI access)
- Building a custom protocol server
- Committed to AMPHP / Revolt / ReactPHP — Fiber libraries that drive Revolt's event loop, a separate scheduler from OpenSwoole's reactor (an app picks one)
- Need byte-for-byte Apache/nginx config replacement — ZealPHP covers the common .htaccess/nginx.conf patterns (rewrite, headers, auth, rate limit, MIME, etc.) but is not a drop-in replacement for every directive
Note:
OpenSwoole 26.2 uses PHP's zend_fiber API as its coroutine-context backend (better Xdebug interop) — but Fiber-driven libraries like AMPHP / Revolt still run on a separate scheduler from Swoole's reactor.
Benchmarks
Headline numbers: 117k req/s text, 106k JSON, 50k templated with 4 HTTP workers under the full PSR-15 middleware stack — 0 failures across 150k requests. Numbers are from one benchmark setup; real-world performance depends on payload size, I/O, OS limits, and tuning. Reproduce in 60s with scripts/bench_vs_express.sh. Full methodology, latency percentiles, caveats: /performance.
ab -n 50000 -c 200 -k
|
PERF.md
|
reproduce locally