yiisoft/yii-runner
Base runner infrastructure for Yii applications, providing a common interface and helpers for bootstrapping the dependency injection container and executing an app. Pair it with HTTP or console runner packages to create clean, environment-specific entry scripts.
This is not a Laravel package. yiisoft/yii-runner is the abstract bootstrap layer for Yii3 applications: it loads layered config, builds a PSR-11 container via yiisoft/config, wires an error handler, and hands control to either an HTTP kernel (yiisoft/yii-http), a console kernel (yiisoft/yii-console), or a long-running worker (RoadRunner/Swoole). There is no service provider, no facade, no config/runner.php to publish. As a Laravel developer you adopt it in one of two situations: you are working inside a Yii3 codebase, or you want a second, framework-agnostic process (queue consumer, cron, webhook endpoint) bootstrapping from the same Composer tree as a Laravel app.
Look first at these files, in order:
src/Runner.php — the abstract run() contract; everything else is a variation on it.src/ConfigTrait.php — the config layering, rootPath, debug, environment, merge-plan logic. This is the actual value of the package.src/ErrorHandler.php — register() / restore(), error-to-exception conversion.src/RunnerHelper.php — how YII_ENV / YII_DEBUG are resolved from the environment.Then read the concrete siblings, because the base package alone is inert: yiisoft/yii-runner-http, yiisoft/yii-runner-console, and yiisoft/yii-runner-roadrunner. The yiisoft/app template is the canonical wiring example.
Minimal adoption (equivalent of public/index.php + bootstrap/app.php):
// public/index.php
declare(strict_types=1);
require dirname(__DIR__) . '/vendor/autoload.php';
$runner = new \Yiisoft\Yii\Runner\Http\HttpRunner(
rootPath: dirname(__DIR__),
debug: (bool) ($_ENV['YII_DEBUG'] ?? false),
environment: $_ENV['YII_ENV'] ?? 'prod',
);
$runner->run();
Layout it expects: config/ (app config), config/packages/ (per-vendor-package config fragments), config/params.php, optional config/environments/{env}/, and a generated config/.merge-plan.php.
First real use case: a Laravel app that needs a standalone CLI worker for a domain library. You keep Laravel for the web tier, but run the worker through a small Yii runner so the library gets a DI container and config without dragging in Illuminate\Foundation. If you only want the container and not the request lifecycle, skip the runner entirely and use yiisoft/config's ConfigLoader + yiisoft/di directly.
Config layering instead of a single config/app.php. The runner merges config from the app, from each installed package's config/ fragment, and from the environment directory. In Laravel terms this is mergeConfigFrom() for every package, automatically, plus config:cache semantics baked into the boot. The merge plan file records which fragments were discovered; when you composer require a Yii package, the plan changes and the config graph grows without you editing the app config.
Parameter indirection. config/params.php values are referenced as ${param} in service definitions and can come from env. Use this for the same things you'd use env() for, but resolve them at container-build time so the compiled definitions are static — important under RoadRunner/Swoole, where the container is built once and reused across requests.
Same config, multiple entry points. This is the biggest architectural win and the closest analogue to Laravel's Kernel split. bin/console, public/index.php, and a RoadRunner worker all construct a different runner subclass over the same config/ directory. Adding a queue consumer or a health-check endpoint is a new 10-line entry file, not a new bootstrap.
Custom runner for bespoke lifecycles. For a cron job or a worker that owns its own loop:
final class WorkerRunner extends \Yiisoft\Yii\Runner\Runner
{
use \Yiisoft\Yii\Runner\ConfigTrait;
public function run(): void
{
$container = $this->loadConfig();
$container->get(\App\Worker::class)->run();
// loop, signal handling, graceful shutdown
}
}
ConfigTrait::loadConfig() is the extension point; everything the stock runners do is downstream of it. That trait is also the honest answer to "can I reuse this from Laravel?" — you can call it from an Artisan command to get a PSR-11 container, but do not call run() there, since run() owns the process and emits the response/terminates.
Cross-framework sharing. Anything you build on this should be PSR-7/PSR-11 only, never Illuminate\Http\Request. Then both sides can consume it: Laravel wraps it, Yii feeds it natively. In practice, share the domain service and the config contract; let each framework keep its own HTTP layer.
Environment matrix. Treat YII_ENV (prod, dev, test) and YII_DEBUG as the two switches. YII_DEBUG controls error verbosity, stack traces, and (in some runners) container definition checks; it should never be true outside local/test. Keep it out of .env.example defaults and set it explicitly in your deploy config.
composer require yiisoft/yii-runner gives you abstractions. You need a concrete runner (-http, -console, -roadrunner) or your own subclass. Zero dependents on Packagist means zero copy-paste examples from the community — read the sibling packages' source, they are short.YII_ENV, not APP_ENV. RunnerHelper reads YII_ENV / YII_DEBUG. If you're booting this inside a Laravel process expecting Laravel's env vars to carry over, they won't. Bridge them explicitly in whatever constructs the runner.ErrorHandler::register() replaces the PHP error handler and installs a shutdown function. Calling it in a process that already has Laravel's handler registered produces confusing "handled twice" behavior and lost exception reports. In a hybrid process, use register() / restore() around the Yii-side call, or skip it entirely and let the host framework handle errors.config/ fragment is silently ignored. Symptom: a service definition "not found" that you can plainly see in the vendor directory. Regenerate the plan; if you deploy with a build artifact, rebuild it in CI, not on the server.php artisan config:cache, but with no command to run. Same discipline as Octane: no mutable singletons, no request-scoped state in container-shared services.debug: true in a worker means stack traces in logs and, depending on the error handler, possibly in responses. Bind it to an env var that is set to false by the deploy config rather than computed from a hostname check.rootPath, debug, environment, configDirectory, vendorDirectory) and have gained parameters across releases. Pin the version and read the concrete runner's __construct rather than trusting a blog snippet.Illuminate\Http\Request.(new ConfigTrait impl)->loadConfig()) and var_dump the resulting definitions. Faster than reading merge plans by hand when a parameter resolves to the wrong value.How can I help you explore Laravel packages today?