Runtime component for Waffle framework.

Maintainers

Package info

github.com/waffle-commons/runtime

pkg:composer/waffle-commons/runtime

Statistics

Installs: 22

Dependents: 1

Suggesters: 0

Stars: 1

Open Issues: 0

0.1.0-beta2.1 2026-05-30 19:11 UTC

README

Discord PHP Version Require PHP CI codecov Latest Stable Version Latest Unstable Version Total Downloads Packagist License

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

  1. Boot once. $kernel->boot()->configure() runs exactly once when the FrankenPHP worker starts.
  2. Iterate. Up to $maxRequests times, the runtime calls frankenphp_handle_request($handler) where $handler:
    • rebuilds a PSR-7 ServerRequest from the current superglobals (FrankenPHP repopulates them per request),
    • calls $kernel->handle($request) (the hot path),
    • emits the response via the injected ResponseEmitterInterface.
  3. Garbage-collect periodically. Every 50 requests, gc_collect_cycles() is called to keep long-running worker memory bounded.
  4. 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 + GlobalsFactory dependencies.

๐Ÿงญ 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\** โ€” itself
  • Waffle\Commons\Contracts\** โ€” the shared contracts package, the primary Waffle dependency
  • Waffle\Commons\Http\** โ€” concrete PSR-7/17 request + response objects needed to drive the worker loop
  • Psr\** โ€” PSR interfaces (PSR-7 / PSR-17)
  • @global + Psl\** โ€” PHP core (including the FrankenPHP frankenphp_handle_request global) 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.