symfony/runtime
Symfony Runtime decouples PHP applications from global state by centralizing bootstrapping and execution in a runtime layer. It enables flexible entry points, better testability, and smoother integration with different environments and frameworks.
composer require symfony/runtime
index.php (HTTP entry point):
use Symfony\Component\Runtime\Runner;
use App\Kernel;
return Runner::run(new Kernel(), $_SERVER['APP_RUNTIME'] ?? 'http');
artisan (CLI entry point, artisan file):
use Symfony\Component\Runtime\Runner;
use App\Kernel;
return Runner::run(new Kernel(), 'cli');
Replace direct $_SERVER/$_ENV access in middleware or services with dependency-injected alternatives:
// Before (global state)
if ($_SERVER['HTTP_USER_AGENT'] === 'curl') { ... }
// After (testable, runtime-aware)
public function __invoke(Request $request) {
if ($request->headers->has('User-Agent: curl')) { ... }
}
index.php (HTTP)artisan (CLI).env (add APP_RUNTIME=http|cli|worker if needed)Workflow: Use APP_RUNTIME env var to switch between HTTP, CLI, or worker modes.
// config/runtime.php
return [
'default' => env('APP_RUNTIME', 'http'),
'runtimes' => [
'http' => App\Runtime\HttpRuntime::class,
'cli' => App\Runtime\CliRuntime::class,
'worker' => App\Runtime\WorkerRuntime::class,
],
];
Integration Tip: Extend Symfony\Component\Runtime\RunnerInterface for custom runtimes (e.g., RoadRunner workers).
Pattern: Replace global state with injected services.
// Before
$host = $_SERVER['HTTP_HOST'];
// After (in a service)
public function __construct(private Request $request) {}
public function getHost(): string {
return $this->request->getHost();
}
Laravel-Specific: Use Laravel’s container to bind runtime-specific services:
// app/Providers/AppServiceProvider.php
public function register() {
$this->app->bind(\Symfony\Component\HttpFoundation\Request::class, function () {
return Request::createFromGlobals();
});
}
Use Case: Load .env files per runtime (e.g., .env.worker).
// In your runtime class
use Symfony\Component\Runtime\SymfonyRuntime;
class WorkerRuntime extends SymfonyRuntime {
protected function getEnv(): array {
return array_merge(
parent::getEnv(),
$_ENV + ['APP_RUNTIME' => 'worker']
);
}
}
Pattern: Auto-detect FrankenPHP’s worker mode.
// No config needed—Symfony Runtime auto-detects FrankenPHP via $_SERVER.
Tip: Set APP_RUNTIME=frankenphp in .env for explicit control.
Workflow: Use Runner::run() for CLI commands.
// In artisan
return Runner::run(new Kernel(), 'cli', [
'command' => 'migrate',
]);
Legacy Middleware:
$_SERVER directly will break. Solution: Refactor to use Request objects or wrap in Runner.// Before (broken)
public function handle($request, Closure $next) {
if ($_SERVER['HTTP_X_API_KEY']) { ... }
}
// After (fixed)
public function handle(Request $request, Closure $next) {
if ($request->headers->has('X-API-KEY')) { ... }
}
Circular Dependencies:
Runner into services that are resolved during bootstrapping. Solution: Use lazy loading or factory methods.PHP Version Mismatch:
Custom Runtimes:
SymfonyRuntime requires overriding getEnv() and getArguments(). Tip: Start with a minimal implementation:
class CustomRuntime extends SymfonyRuntime {
protected function getEnv(): array {
return $_ENV + ['CUSTOM_VAR' => 'value'];
}
}
Check Runtime Class:
// Debug current runtime
dump($_SERVER['APP_RUNTIME'] ?? 'default');
Environment Variables:
Runtime::getEnv() to inspect loaded env vars:
dump(SymfonyRuntime::getEnv());
Worker Mode Issues:
if (php_sapi_name() === 'frankenphp-worker') {
$_SERVER['APP_RUNTIME'] = 'frankenphp';
}
Custom Runtime Classes:
Symfony\Component\Runtime\SymfonyRuntime for specialized behaviors (e.g., custom env loading).Runtime-Specific Config:
Runtime::getEnv():
$config = [
'timeout' => $_ENV['HTTP_TIMEOUT'] ?? 30,
'worker_timeout' => $_ENV['WORKER_TIMEOUT'] ?? 60,
];
Error Handling:
handleException() in custom runtimes:
class CustomRuntime extends SymfonyRuntime {
protected function handleException(Throwable $exception): void {
// Custom logging/handling
parent::handleException($exception);
}
}
Service Provider Order:
AppServiceProvider runs after runtime setup to avoid $_SERVER pollution.Artisan Commands:
$_SERVER will fail in CLI mode. Solution: Mock or inject dependencies.Testing:
Runner::run() in tests to isolate global state:
$response = Runner::run(new Kernel(), 'http', [], [
'HTTP_HOST' => 'test.local',
]);
WorkerRuntime (e.g., skip HTTP-specific services).How can I help you explore Laravel packages today?