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-beta4
Requires (Dev)
- carthage-software/mago: ^1.29
- cyclonedx/cyclonedx-php-composer: ^6.2
- igor-php/igor-php: ^0.7.0
- php-mock/php-mock-phpunit: ^2.15
- phpunit/phpunit: ^12.5
- vimeo/psalm: ^6.16
This package is auto-updated.
Last update: 2026-06-14 07:49:20 UTC
README
Waffle Runtime Component
Release:
0.1.0-beta4ย |ย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 knows only the contracts interfaces KernelInterface, ResponseEmitterInterface, and GlobalsFactoryInterface. The concrete GlobalsFactory / ResponseEmitter (from http) are injected by the application.
๐ฆ Installation
composer require waffle-commons/runtime
๐งฑ Surface
A single class: Waffle\Commons\Runtime\WaffleRuntime implementing Waffle\Commons\Contracts\Runtime\RuntimeInterface.
public function __construct( GlobalsFactoryInterface $globalsFactory, // required โ wired by the app bootstrap ResponseEmitterInterface $emitter, // required โ wired by the app bootstrap ); public function loop(KernelInterface $kernel, int $maxRequests = 500): void;
๐ Bootstrap (the entire public/index.php)
<?php declare(strict_types=1); use Waffle\Commons\Http\Emitter\ResponseEmitter; use Waffle\Commons\Http\Factory\GlobalsFactory; 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); // The app wires the concrete http factory + emitter into the agnostic runtime. (new WaffleRuntime(new GlobalsFactory(), new ResponseEmitter()))->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 constructor parameters injected via
contractsinterfaces (no concrete defaults). - First-class callable closure in the handler block.
- Typed
KernelInterface+ResponseEmitterInterface+GlobalsFactoryInterfacedependencies.
๐งญ 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 only Waffle dependencyPsr\**โ 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, 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.