Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ephpm/symfony-runtime

Run a Symfony / HttpKernel application under ePHPm persistent worker mode through Symfony's own Runtime component.

This package is a custom Symfony RuntimeInterface — the same extension point the FrankenPHP and RoadRunner runtimes use. Select it and your existing public/index.php front controller keeps the kernel bootstrapped in memory and services each HTTP request from a long-lived ePHPm worker, avoiding per-request framework bootstrap cost. No changes to your controllers, routing, or kernel.

How it works

Symfony's Runtime component splits an app into a resolver (build the app object) and a runner (run it). This package ships:

  • Ephpm\Symfony\Runtime — a subclass of SymfonyRuntime. It inherits the entire Symfony boot contract (APP_ENV/APP_DEBUG, .env loading via symfony/dotenv, the error handler, kernel-closure argument resolution) and changes only which runner serves an HttpKernelInterface.
  • Ephpm\Symfony\HttpKernelWorker — a RunnerInterface that loops on the native Ephpm\Worker\take_request(), builds a Symfony Request from each ePHPm request Envelope, dispatches it through HttpKernel::handle(), sends the Response back to the engine, then calls HttpKernel::terminate() so kernel.terminate post-response work runs.

Selecting this runtime is harmless off ePHPm: when the native worker primitives are absent (a normal PHP-FPM/CLI request, bin/console), the runtime falls back to Symfony's standard runner, so the same front controller works everywhere.

Install

ePHPm packages are distributed via their GitHub repositories (not Packagist); symfony/* come from Packagist as normal. 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 ePHPm repos are listed — Composer does not resolve a VCS dependency's own VCS repositories transitively, so each ePHPm package needs its own repositories entry in your app's composer.json.

{
  "repositories": [
    { "type": "vcs", "url": "https://github.com/ephpm/symfony-runtime" },
    { "type": "vcs", "url": "https://github.com/ephpm/php-worker" }
  ],
  "require": {
    "ephpm/symfony-runtime": "^0.1"
  }
}

Both ephpm/symfony-runtime 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 update

Wiring it into ePHPm

1. Select the runtime (recommended: composer.json)

Pin the runtime class the canonical Symfony way — in your app's composer.json, then regenerate vendor/autoload_runtime.php:

{
  "extra": {
    "runtime": { "class": "Ephpm\\Symfony\\Runtime" }
  }
}
composer dump-autoload

No environment variable is needed — the class is baked into vendor/autoload_runtime.php. Alternatively, set the APP_RUNTIME environment variable to Ephpm\Symfony\Runtime on the ePHPm server process (it overrides the composer.json default when exposed to PHP as $_SERVER/$_ENV).

Your public/index.php needs no changes — the standard Symfony front controller is runtime-agnostic:

<?php
// public/index.php
use App\Kernel;

require_once dirname(__DIR__).'/vendor/autoload_runtime.php';

return function (array $context) {
    return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
};

2. Switch on worker mode (ephpm.toml)

Point [php.worker] script at your front controller:

[php]
mode = "worker"
# concurrency = 0   # worker-thread pool size (0 = derived from CPU/cgroup quota)

[php.worker]
script = "public/index.php"

The worker knobs live in the [php.worker] table (script, max_requests, boot_timeout, populate_superglobals, stream_threshold); the pool size is [php] concurrency (a whole-server scheduling knob, 0 = auto). Leave populate_superglobals off — this adapter builds the Symfony Request from the request Envelope and does not read $_GET/$_POST/$_FILES.

[php.worker] script must resolve to a file under document_root, so keep public/index.php (or your chosen front controller) inside the document root.

Alternative: the explicit bin/ entrypoint

If you prefer an explicit worker script over the Runtime-component wiring (e.g. you can't set APP_RUNTIME/extra.runtime.class, or want an entry that doesn't depend on vendor/autoload_runtime.php), point [php.worker] script at the bundled bin/ephpm-symfony-worker and name a bootstrap that returns an HttpKernelInterface:

[php.worker]
script = "vendor/ephpm/symfony-runtime/bin/ephpm-symfony-worker"
EPHPM_SYMFONY_BOOTSTRAP=worker-kernel.php ephpm serve --config ephpm.toml
<?php
// worker-kernel.php
use App\Kernel;

require_once __DIR__.'/vendor/autoload.php';
(new Dotenv())->bootEnv(__DIR__.'/.env'); // if you use symfony/dotenv

return new Kernel($_SERVER['APP_ENV'] ?? 'prod', (bool) ($_SERVER['APP_DEBUG'] ?? false));

This path drives Ephpm\Symfony\HttpKernelWorker directly and never touches the Symfony Runtime resolver, so runtime selection is deterministic regardless of environment propagation.

What the worker does for you

ePHPm hands adapters raw request material only — it never parses request bodies (Envelope::parsedBody() is always null, Envelope::files() is always empty) and its query/cookie arrays are split on delimiters but not url-decoded. This adapter fills the gap, exactly like the sibling ephpm/psr15-worker:

  • Query string is re-parsed with parse_str() (url-decoding plus a[]= bracket arrays) into $request->query.
  • Cookies have their names and values url-decoded.
  • application/x-www-form-urlencoded bodies (POST/PUT/PATCH/DELETE) are parsed into $request->request.
  • multipart/form-data bodies are parsed into fields plus Symfony UploadedFile instances (in test mode, so isValid()/move() work over the spooled temp files — the same approach Symfony's own PSR-7 bridge uses); uploads are spooled to temp files removed automatically after each request. A name="photos[]" field becomes a list of UploadedFile, not a last-wins collapse.
  • JSON / raw bodies are left untouched — read $request->getContent() yourself.
  • Set-Cookie is emitted as one wire header per cookie (Symfony keeps cookies in a separate bag; each is rendered to its wire form). Other repeated response headers are comma-joined per RFC 9110.

Streaming

Responses stream when it pays to:

  • BinaryFileResponse is streamed straight from disk with flat memory, regardless of file size.
  • StreamedResponse (and any response whose getContent() is not a string) has its output captured into a memory-bounded temp spool, then streamed.
  • Plain string bodies larger than 1 MiB are spooled and streamed so the engine forwards them to the client in chunks with backpressure; smaller bodies are sent buffered.

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.

State isolation

Like every HttpKernel worker adapter (Octane, FrankenPHP, RoadRunner), the kernel and its services are reused across requests — that reuse is the whole point of worker mode. Your application owns its per-request state. Reset anything request-scoped in a reset/kernel.terminate listener, and rely on [php.worker] max_requests to recycle a worker periodically so slow growth in long-lived framework state is reclaimed with a clean reboot. This adapter does not reboot the kernel per request.

Error handling

A throwable that escapes HttpKernel::handle() (which normally renders its own error responses), or one from request/response translation, becomes a generic 500 so one bad request cannot kill the loop — unless a response was already sent, in which case it is rethrown so ePHPm recycles the worker rather than risking a double-send. kernel.terminate runs only after a successful send.

License

MIT — see LICENSE.

About

Symfony Runtime adapter to run Symfony/HttpKernel apps under ePHPm worker mode

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages