front-interop / impl
Reference implementations for Front-Interop.
Requires
- php: >=8.4
- front-interop/interface: 1.x@dev
Requires (Dev)
- pds/composer-script-names: ^1.0
- pds/skeleton: ^1.0
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^11.0
- pmjones/php-styler: 0.x@dev
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-07 20:52:18 UTC
README
Reference implementations of the Front-Interop interface for PHP 8.4+.
These implementations are intentionally naive; they exist to showcase the FrontController contract itself. Production front controllers would be obtained from an IOC container and would compose a request/response system, a router/dispatcher, and so on.
Installation
composer require front-interop/impl
Usage
Three reference implementations cover three execution contexts.
ConsoleFrontController
A command-line front controller, reading $argv and writing to $stdout and
$stderr. Both streams are optional; they default to php://stdout and
php://stderr.
use FrontInterop\Impl\ConsoleFrontController; $front = new ConsoleFrontController($argv); exit($front->run());
That is bin/hello.php, apart from the autoloader. Run it with:
php bin/hello.php World
RequestFrontController
An HTTP front controller, reading an injected query array and writing HTML to
$output. That stream is optional; it defaults to php://output.
use FrontInterop\Impl\RequestFrontController; $front = new RequestFrontController($_GET); exit($front->run());
That is public/index.php, apart from the autoloader. Run composer hello
at the package root, then visit:
http://localhost:8080/index.php?name=World
FrankenFrontController
A front controller for FrankenPHP, wrapping a RequestFrontController in
a frankenphp_handle_request() worker loop.
use FrontInterop\Impl\FrankenFrontController; $front = new FrankenFrontController(100); exit($front->run());
It runs only under the FrankenPHP binary in worker mode, because
frankenphp_handle_request() does not exist elsewhere.
Classes
| Interface | Implementation |
|---|---|
| FrontController | ConsoleFrontController, RequestFrontController, FrankenFrontController |
All classes are in the FrontInterop\Impl namespace. The three concrete
classes extend AFrontController for the helpers they share.
Conformance
Three of the Front-Interop directives for run() shape these implementations:
- return a status between
0and254 - do not end
run()by callingexit()ordie() - do not let a Throwable escape
Returning a status
ConsoleFrontController returns 1 when no name is given and 0 once it has
written its greeting.
RequestFrontController returns 0 after emitting its page, and 1 from
error(). A 0 does not mean that nothing went wrong: it returns 0 with a
422 when no name is given, because the request was handled and a response
was emitted. That counts as success in an HTTP context.
FrankenFrontController returns the status from the last request handled,
set only by RequestFrontController::run(). It returns 0 if the runtime
ends the loop before any request is handled.
The error() method in every controller returns 1.
Not calling exit()
None of the implementations call exit() or die(). The scripts that start
them do: bin/hello.php and public/index.php both end with
exit($front->run()). The script receives the status and decides what to do
with it.
FrankenFrontController bounds its worker loop with $requestMax so that
run() returns after that many requests. A 0 maximum defers the decision to
the runtime. The loop ends when the runtime returns false.
Not letting a Throwable escape
Each implementation of run() wraps its work in a try/catch block. The
catch sends the Throwable to caught(), which reports through error() and
then releases the Throwable. (The release is guarded because the destructor
runs at that moment, and a throw there would reach the caller after run()
had already computed its status.)
Each error() implementation guards its own writes too. A stream can be closed,
or open and refuse the write, and a throw from error() would escape run().
ConsoleFrontController writes to stderr and falls back to log() only when
that write fails. It tests what fwrite() returns, because a stream that
refuses the write reports false rather than throwing.
RequestFrontController always logs, then attempts the response separately,
so a failed log does not prevent the 500. FrankenFrontController logs and
returns 1.
The directives bind run() alone. ConsoleFrontController and
RequestFrontController throw InvalidArgumentException from __construct()
when a stream argument is not a stream resource. Construction is outside them.
Testing
composer test runs the suite. Most tests need nothing beyond PHP.
Two tests in FrankenServerTest start a real frankenphp binary in worker
mode and send it HTTP requests. They look for the binary on the PATH, or
at the path in the FRANKENPHP_BIN environment variable, and skip when they
find none. They also skip on Windows. Set REQUIRE_FRANKENPHP=1 to make a
missing binary a failure; CI sets it on Ubuntu with PHP 8.4. On macOS,
brew install dunglas/frankenphp/frankenphp installs the binary.
See the Front-Interop interface package for the full specification.