oxphp / runtime
OxPHP runtime adapter — run Symfony, Laravel and PSR-15 apps on the OxPHP server, in traditional and worker mode.
Requires
- php: >=8.4
- symfony/runtime: ^6.4 || ^7.0 || ^8.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.58
- illuminate/contracts: ^11.0
- illuminate/http: ^11.0
- nyholm/psr7: ^1.8
- phpunit/phpunit: ^11.0
- psr/http-server-handler: ^1.0
- symfony/dependency-injection: ^6.4 || ^7.0 || ^8.0
- symfony/http-kernel: ^6.4 || ^7.0 || ^8.0
Suggests
- guzzlehttp/psr7: Alternative PSR-17 factory
- httpsoft/http-message: Alternative PSR-17 factory
- illuminate/contracts: To run Laravel applications
- laminas/laminas-diactoros: Alternative PSR-17 factory
- nyholm/psr7: Recommended PSR-17 factory for PSR-15 handlers
- symfony/http-kernel: To run Symfony HttpKernel applications
README
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.
Requirements
- 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
Installation
composer require oxphp/runtime
Then set the runtime in .env:
APP_RUNTIME=OxPHP\Runtime\Runtime
Your 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']);
Supported application types
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.
Traditional vs worker mode
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.
Resetters
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.
PSR-17 factory
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\"}"
Streaming
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 response
License
MIT