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-09-02 15:05:45 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 terminate the process in place of returning
- 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 terminating the process
None of the implementations call exit() or die(). Their callers do:
bin/hello.php and public/index.php both end with exit($front->run()), so
the decision to end the process belongs to the code that started 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, for a different
cause. Stream operations there can fail: a stream can be closed, or open and
refuse the write, and a report of a failure must not itself become a second
failure.
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.
See the Front-Interop interface package for the full specification.