waffle-commons / runtime
Runtime component for Waffle framework.
Requires
- php: ^8.5
- psr/container: ^2.0
- psr/http-message: ^1.1 || ^2.0
- waffle-commons/contracts: 0.1.0-beta2.1
- waffle-commons/http: 0.1.0-beta2.1
Requires (Dev)
- carthage-software/mago: ^1.29
- cyclonedx/cyclonedx-php-composer: ^6.2
- php-mock/php-mock-phpunit: ^2.15
- phpunit/phpunit: ^12.5
- vimeo/psalm: ^6.16
This package is auto-updated.
Last update: 2026-05-30 19:46:57 UTC
README
Waffle Runtime Component
Release:
v0.1.0-beta2ย |ยCHANGELOG.md
WaffleRuntime is the agnostic application runner. It owns the request loop in FrankenPHP worker mode and falls back gracefully to a single-shot execution under the classic PHP SAPI when frankenphp_handle_request() is unavailable.
The runtime contains no concrete framework dependencies โ it only knows about the KernelInterface, ResponseEmitterInterface, and the GlobalsFactory shape. Everything else is injected.
๐ฆ Installation
composer require waffle-commons/runtime
๐งฑ Surface
A single class: Waffle\Commons\Runtime\WaffleRuntime implementing Waffle\Commons\Contracts\Runtime\RuntimeInterface.
public function __construct( ?GlobalsFactory $globalsFactory = null, // defaults to new GlobalsFactory() ?ResponseEmitterInterface $emitter = null, // defaults to new ResponseEmitter() ); public function loop(KernelInterface $kernel, int $maxRequests = 500): void;
๐ Bootstrap (the entire public/index.php)
<?php declare(strict_types=1); use Waffle\Commons\Runtime\WaffleRuntime; use App\Factory\AppKernelFactory; require __DIR__ . '/../vendor/autoload.php'; define('APP_ROOT', dirname(__DIR__)); $kernel = AppKernelFactory::create(env: getenv('APP_ENV') ?: 'prod', debug: false); (new WaffleRuntime())->loop($kernel, maxRequests: 500);
๐ The loop contract
- Boot once.
$kernel->boot()->configure()runs exactly once when the FrankenPHP worker starts. - Iterate. Up to
$maxRequeststimes, the runtime callsfrankenphp_handle_request($handler)where$handler:- rebuilds a PSR-7
ServerRequestfrom the current superglobals (FrankenPHP repopulates them per request), - calls
$kernel->handle($request)(the hot path), - emits the response via the injected
ResponseEmitterInterface.
- rebuilds a PSR-7
- Garbage-collect periodically. Every 50 requests,
gc_collect_cycles()is called to keep long-running worker memory bounded. - Reset on exit. When the loop exits (max reached or FrankenPHP signaled stop),
$kernel->reset()clears request-scoped state.
If frankenphp_handle_request is not defined (classic SAPI), the runtime executes the handler once and exits โ no infinite loop.
๐ PHP 8.5 features used
final class WaffleRuntimeโ no inheritance.- Typed nullable constructor parameters with defaults built from
Waffle\Commons\Http\*factories. - First-class callable closure in the handler block.
- Typed
KernelInterface+ResponseEmitterInterface+GlobalsFactorydependencies.
๐งญ Architectural boundary (mago guard)
An active dependency perimeter is enforced on every CI run by vendor/bin/mago guard (bundled into composer mago; zero baselines). The rules live in mago.toml under [guard.perimeter] โ a forbidden use statement fails the build, not a reviewer.
Production code under Waffle\Commons\Runtime may depend only on:
Waffle\Commons\Runtime\**โ itselfWaffle\Commons\Contracts\**โ the shared contracts package, the primary Waffle dependencyWaffle\Commons\Http\**โ concrete PSR-7/17 request + response objects needed to drive the worker loopPsr\**โ PSR interfaces (PSR-7 / PSR-17)@global+Psl\**โ PHP core (including the FrankenPHPfrankenphp_handle_requestglobal) and the PHP Standard Library
Test code under WaffleTests\Commons\Runtime is unrestricted (@all); WaffleRuntimeWorkerModeTest is listed in [guard].excludes because it re-declares the production namespace to stub frankenphp_handle_request. Structural rules are guarded too: interfaces must be named *Interface, Exception\** classes must end in *Exception, and any Enum\** namespace may hold only enum declarations.
Contract-first, component-agnostic by construction: components compose through waffle-commons/contracts (plus the explicitly-permitted http), never ad-hoc through one another.
๐งช Testing
docker exec -w /waffle-commons/runtime waffle-dev composer tests
The WaffleRuntimeWorkerModeTest namespaces the production namespace to override frankenphp_handle_request via php-mock-phpunit; it is listed in mago.toml [guard].excludes.
๐ License
MIT โ see LICENSE.md.