Run any PSR-15 application — Mezzio, Slim 4, or any framework that exposes a
Psr\Http\Server\RequestHandlerInterface — under ePHPm persistent worker
mode. One package serves them all.
In worker mode ePHPm keeps your framework bootstrapped in memory and dispatches each HTTP request to a long-lived PHP worker, avoiding per-request bootstrap cost.
ePHPm packages are distributed via their GitHub repositories (not Packagist).
Add every ePHPm repo in the dependency tree as a Composer vcs repository, then
require the adapter. This package pulls in ephpm/worker, so both repos are
listed — Composer does not resolve a VCS dependency's own VCS repositories
transitively, so each ePHPm package in the tree needs its own repositories
entry in your app's composer.json.
{
"repositories": [
{ "type": "vcs", "url": "https://github.com/ephpm/psr15-worker" },
{ "type": "vcs", "url": "https://github.com/ephpm/php-worker" }
],
"require": {
"ephpm/psr15-worker": "^0.1"
}
}Both ephpm/psr15-worker and its ephpm/worker dependency are tagged
v0.1.0, so ^0.1 resolves for each; each still needs its own repositories
entry because Composer does not resolve VCS repos transitively. Then:
composer updateThree lines. Hand your PSR-15 app to the worker and run the loop:
use Ephpm\Psr15\Worker;
$app = /* your PSR-15 RequestHandlerInterface: Slim App, Mezzio Application, ... */;
exit((new Worker($app))->run());run() blocks on the native Ephpm\Worker\take_request(), dispatches each
request through your handler, translates the PSR-7 response back to the engine
via Ephpm\Worker\send_response() (or send_response_stream() for large /
unknown-size bodies), and returns 0 when ePHPm signals shutdown. A handler
exception becomes a 500 so one bad request cannot kill the loop; engine-level
fatals are left to bubble so ePHPm can recycle the worker.
The engine hands adapters raw request material only — it never parses bodies
(Envelope::parsedBody() is always null, Envelope::files() is always empty)
and its query/cookie arrays are not url-decoded. This package fills the gap:
- Query string is re-parsed with
parse_str()(url-decoding plusa[]=bracket arrays). - Cookies have their names and values url-decoded.
application/x-www-form-urlencodedbodies (POST/PUT/PATCH/DELETE) are parsed intogetParsedBody().multipart/form-databodies are parsed into fields plus PSR-7UploadedFileInterfaceinstances; uploads are spooled to temp files that are removed automatically after each request.- JSON / raw bodies are left untouched — read
getBody()yourself. Set-Cookieresponse headers are sent as one wire header per cookie (never comma-joined); other repeated headers are comma-joined per RFC 9110.- Large responses stream. Bodies larger than 1 MiB — or whose size is unknown — are handed to the engine as a stream and forwarded to the client in 64 KiB chunks with backpressure, so big downloads keep memory flat.
Point ePHPm at the worker entrypoint and switch on worker mode in ephpm.toml:
[php]
mode = "worker"
worker_script = "vendor/ephpm/psr15-worker/bin/ephpm-worker"bin/ephpm-worker finds your project's vendor/autoload.php, loads a bootstrap
that returns a RequestHandlerInterface, and runs the loop. Under the engine
there is no CLI $argv, so name the bootstrap with the
EPHPM_WORKER_BOOTSTRAP environment variable (set it on the ePHPm server
process; the bootstrap file must return $app;):
EPHPM_WORKER_BOOTSTRAP=app/worker-bootstrap.php ephpm serve --config ephpm.tomlNote: worker_script must resolve to a file under document_root, so the
vendored bin/ephpm-worker only works when vendor/ lives inside the document
root. Prefer to keep everything in your project? Copy bin/ephpm-worker (or
one of the examples/) into your app and point worker_script at your copy —
a self-contained worker script that builds the app and calls
(new Worker($app))->run() itself needs no bootstrap variable at all.
- Slim 4 —
examples/slim.php - Mezzio —
examples/mezzio.php
These are documentation recipes; they require the respective frameworks and are not exercised in CI.
Response bodies stream: anything larger than 1 MiB (or of unknown size) is sent
through the engine's send_response_stream() with flat memory. The request
body is currently buffered as a string via Envelope::rawBody() — incremental
request-body consumption (Envelope::bodyStream()) is available from the
engine but not yet used by this adapter.
MIT — see LICENSE.