OxPHP runtime adapter — run Symfony, Laravel and PSR-15 applications on the OxPHP server in both traditional and worker mode.
Extends symfony/runtime. Zero hard
dependencies on any framework or PSR-7 implementation — everything is
auto-detected when the container is resolved.
- PHP 8.4+
- OxPHP server (for production use; the runtime transparently falls back to
symfony/runtimedefaults when not running under the OxPHP SAPI) symfony/runtime^6.4 || ^7.0
composer require oxphp/runtimeThen set the runtime in .env:
APP_RUNTIME=OxPHP\Runtime\RuntimeYour public/index.php stays as it would be under symfony/runtime:
<?php
use App\Kernel;
require_once \dirname(__DIR__).'/vendor/autoload_runtime.php';
return fn(array $context) => new Kernel($context['APP_ENV'], (bool)$context['APP_DEBUG']);Return type from index.php |
Runner |
|---|---|
Psr\Http\Server\RequestHandlerInterface |
Psr15Runner |
Illuminate\Contracts\Http\Kernel |
LaravelRunner |
Symfony\Component\HttpKernel\HttpKernelInterface |
HttpKernelRunner |
Symfony\Component\HttpFoundation\Response |
HttpFoundationResponseRunner |
Psr\Http\Message\ResponseInterface |
Psr7ResponseRunner |
Any other type throws LogicException. Console applications are out of
scope — run them under the stock PHP CLI SAPI.
Mode is auto-detected via oxphp_is_worker(). Nothing to configure on
the runtime side. In worker mode:
- Your application boots once per worker thread.
oxphp_worker()drives the loop internally; the runtime wraps each request intry/catch(\Throwable)so a broken handler cannot poison the worker.- Between requests, OxPHP performs a soft reset (output buffers, headers, superglobals). Framework-specific state you want to reset on top of that goes through the resetter chain.
Register custom resetters via APP_RUNTIME_OPTIONS:
APP_RUNTIME_OPTIONS="{\"resetters\":[\"App\\\\Runtime\\\\EntityManagerResetter\"]}"Your class implements OxPHP\Runtime\Resetter\ResetterInterface:
use OxPHP\Runtime\Resetter\ResetterInterface;
final class EntityManagerResetter implements ResetterInterface
{
public function __construct(private readonly EntityManagerInterface $em) {}
public function reset(): void
{
$this->em->clear();
}
}Symfony users get the container's services_resetter (any service tagged
kernel.reset) wired up automatically — no registration needed.
Used only by PSR-15 and bare PSR-7 Response paths. Auto-detected in this order:
Nyholm\Psr7\Factory\Psr17Factory(recommended)GuzzleHttp\Psr7\HttpFactoryHttpSoft\Message\ServerRequestFactory(+ siblings)Laminas\Diactoros\ServerRequestFactory(+ siblings)
Override via APP_RUNTIME_OPTIONS:
APP_RUNTIME_OPTIONS="{\"psr17_factory\":\"My\\\\Factory\"}"StreamedResponse, StreamedJsonResponse, and PSR-7 bodies with
readable streams are streamed chunk-by-chunk — ideal for SSE and large
payloads.
Streamed callbacks are driven live: every echo inside a
StreamedResponse callback goes to the wire as soon as it happens —
chunks are never collected and replayed after the callback returns. The
runtime enables PHP's implicit flush while driving the callback, so the
engine pushes each chunk through the OxPHP SAPI's flush hook on write;
the callback does not need to call flush() itself. PSR-7 stream bodies
are flushed per chunk via oxphp_stream_flush(). An exception thrown
mid-stream closes the runtime's output buffers cleanly (bytes already
streamed stay sent, partial buffers are discarded) and never poisons the
worker.
For "respond early, keep working" patterns use oxphp_finish_request()
directly in your controller:
$response = new JsonResponse(['status' => 'accepted']);
// ... after sending ...
\oxphp_finish_request();
// background work continues; the client already got the responseMIT