The Runtime Component
The Runtime Component
The Runtime Component decouples the bootstrapping logic from any global state to make sure the application can run with runtimes like PHP-FPM, ReactPHP, Swoole, etc. without any changes.
New in version 5.3: The Runtime component was introduced in Symfony 5.3.
Installation
$ composer require symfony/runtime
Note
If you install this component outside of a Symfony application, you must require the vendor/autoload.php
file in your code to enable the class autoloading mechanism provided by Composer. Read this article for more details.
Usage
The Runtime component abstracts most bootstrapping logic as so-called runtimes, allowing you to write front-controllers in a generic way. For instance, the Runtime component allows Symfony’s public/index.php
to look like this:
<?php
// public/index.php
use App\Kernel;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (array $context) {
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
};
So how does this front-controller work? At first, the special autoload_runtime.php
file is automatically created by the Composer plugin in the component. This file runs the following logic:
- It instantiates a
Symfony\Component\Runtime\RuntimeInterface
; - The callable (returned by
public/index.php
) is passed to the Runtime, whose job is to resolve the arguments (in this example:array $context
); - Then, this callable is called to get the application (
App\Kernel
); - At last, the Runtime is used to run the application (i.e. calling `$kernel->handle(Request::createFromGlobals())->send()).
Caution
If you use the Composer --no-scripts
option, make sure your Composer version is >=2.1.3
; otherwise the autoload_runtime.php
file won’t be created.
To make a console application, the bootstrap code would look like:
#!/usr/bin/env php
<?php
// bin/console
use App\Kernel;
use Symfony\Bundle\FrameworkBundle\Console\Application;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (array $context) {
$kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
// returning an "Application" makes the Runtime run a Console
// application instead of the HTTP Kernel
return new Application($kernel);
};
Selecting Runtimes
The default Runtime is Symfony\Component\Runtime\SymfonyRuntime
. It works excellent on most applications running with a webserver using PHP-FPM like Nginx or Apache.
The component also provides a Symfony\Component\Runtime\GenericRuntime
, which uses PHP’s $_SERVER
, $_POST
, $_GET
, $_FILES
and $_SESSION
superglobals. You may also use a custom Runtime (e.g. to integrate with Swoole or AWS Lambda).
Use the APP_RUNTIME
environment variable or by specifying the extra.runtime.class
in composer.json
to change the Runtime class:
{
"require": {
"...": "..."
},
"extra": {
"runtime": {
"class": "Symfony\\Component\\Runtime\\GenericRuntime"
}
}
}
Using the Runtime
A Runtime is responsible for passing arguments into the closure and run the application returned by the closure. The Symfony\Component\Runtime\SymfonyRuntime
and Symfony\Component\Runtime\GenericRuntime
supports a number of arguments and different applications that you can use in your front-controllers.
Resolvable Arguments
The closure returned from the front-controller may have zero or more arguments:
<?php
// public/index.php
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (InputInterface $input, OutputInterface $output) {
// ...
};
The following arguments are supported by the SymfonyRuntime
:
Symfony\Component\HttpFoundation\Request
A request created from globals.
Symfony\Component\Console\Input\InputInterface
An input to read options and arguments.
Symfony\Component\Console\Output\OutputInterface
Console output to print to the CLI with style.
Symfony\Component\Console\Application
An application for creating CLI applications.
Symfony\Component\Command\Command
For creating one line command CLI applications (using `Command::setCode()).
And these arguments are supported by both the SymfonyRuntime
and GenericRuntime
(both type and variable name are important):
array $context
This is the same as $_SERVER
+ $_ENV
.
array $argv
The arguments passed to the command (same as $_SERVER['argv']
).
array $request
With keys query
, body
, files
and session
.
Resolvable Applications
The application returned by the closure below is a Symfony Kernel. However, a number of different applications are supported:
<?php
// public/index.php
use App\Kernel;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function () {
return new Kernel('prod', false);
};
The SymfonyRuntime
can handle these applications:
Symfony\Component\HttpKernel\HttpKernelInterface
The application will be run with Symfony\Component\Runtime\Runner\Symfony\HttpKernelRunner
like a “standard” Symfony application.
Symfony\Component\HttpFoundation\Response
The Response will be printed by Symfony\Component\Runtime\Runner\Symfony\ResponseRunner
:
<?php
// public/index.php
use Symfony\Component\HttpFoundation\Response;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function () {
return new Response('Hello world');
};
Symfony\Component\Console\Command\Command
To write single command applications. This will use the Symfony\Component\Runtime\Runner\Symfony\ConsoleApplicationRunner
:
<?php
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (Command $command) {
$command->setCode(function (InputInterface $input, OutputInterface $output) {
$output->write('Hello World');
});
return $command;
};
Symfony\Component\Console\Application
Useful with console applications with more than one command. This will use the Symfony\Component\Runtime\Runner\Symfony\ConsoleApplicationRunner
:
<?php
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (array $context) {
$command = new Command('hello');
$command->setCode(function (InputInterface $input, OutputInterface $output) {
$output->write('Hello World');
});
$app = new Application();
$app->add($command);
$app->setDefaultCommand('hello', true);
return $app;
};
The GenericRuntime
and SymfonyRuntime
also support these generic applications:
Symfony\Component\Runtime\RunnerInterface
The RunnerInterface
is a way to use a custom application with the generic Runtime:
<?php
// public/index.php
use Symfony\Component\Runtime\RunnerInterface;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function () {
return new class implements RunnerInterface {
public function run(): int
{
echo 'Hello World';
return 0;
}
};
};
callable
Your “application” can also be a callable
. The first callable will return the “application” and the second callable is the “application” itself:
<?php
// public/index.php
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function () {
$app = function() {
echo 'Hello World';
return 0;
};
return $app;
};
void
If the callable doesn’t return anything, the SymfonyRuntime
will assume everything is fine:
<?php
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function () {
echo 'Hello world';
};
Using Options
Some behavior of the Runtimes can be modified through runtime options. They can be set using the APP_RUNTIME_OPTIONS
environment variable:
<?php
$_SERVER['APP_RUNTIME_OPTIONS'] = [
'project_dir' => '/var/task',
];
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
// ...
You can also configure extra.runtime.options
in composer.json
:
{
"require": {
"...": "..."
},
"extra": {
"runtime": {
"project_dir": "/var/task"
}
}
}
The following options are supported by the SymfonyRuntime
:
env
(default: APP_ENV
environment variable, or "dev"
)
To define the name of the environment the app runs in.
disable_dotenv
(default: false
)
To disable looking for .env
files.
dotenv_path
(default: .env
)
To define the path of dot-env files.
use_putenv
To tell Dotenv to set env vars using `putenv() (NOT RECOMMENDED).
prod_envs
(default: [“prod”]`)
To define the names of the production envs.
test_envs
(default: [“test”]`)
To define the names of the test envs.
Besides these, the GenericRuntime
and SymfonyRuntime
also support these options:
debug
(default: APP_DEBUG
environment variable, or true
)
Toggles displaying errors.
runtimes
Maps “application types” to a GenericRuntime
implementation that knows how to deal with each of them.
error_handler
(default: Symfony\Component\Runtime\Internal\BasicErrorHandler
or Symfony\Component\Runtime\Internal\SymfonyErrorHandler
for SymfonyRuntime
)
Defines the class to use to handle PHP errors.
Create Your Own Runtime
This is an advanced topic that describes the internals of the Runtime component.
Using the Runtime component will benefit maintainers because the bootstrap logic could be versioned as a part of a normal package. If the application author decides to use this component, the package maintainer of the Runtime class will have more control and can fix bugs and add features.
Note
Before Symfony 5.3, the Symfony bootstrap logic was part of a Flex recipe. Since recipes are rarely updated by users, bug patches would rarely be installed.
The Runtime component is designed to be totally generic and able to run any application outside of the global state in 6 steps:
- The main entry point returns a callable (the “app”) that wraps the application;
- The app callable is passed to
RuntimeInterface::getResolver(), which returns a
Symfony\Component\Runtime\ResolverInterface`. This resolver returns an array with the app callable (or something that decorates this callable) at index 0 and all its resolved arguments at index 1. - The app callable is invoked with its arguments, it will return an object that represents the application.
- This application object is passed to
RuntimeInterface::getRunner(), which returns a
Symfony\Component\Runtime\RunnerInterface`: an instance that knows how to “run” the application object. - The `RunnerInterface::run(object $application) is called and it returns the exit status code as int.
- The PHP engine is exited with this status code.
When creating a new runtime, there are two things to consider: First, what arguments will the end user use? Second, what will the user’s application look like?
For instance, imagine you want to create a runtime for ReactPHP:
What arguments will the end user use?
For a generic ReactPHP application, no special arguments are typically required. This means that you can use the Symfony\Component\Runtime\GenericRuntime
.
What will the user’s application look like?
There is also no typical React application, so you might want to rely on the PSR-15 interfaces for HTTP request handling.
However, a ReactPHP application will need some special logic to run. That logic is added in a new class implementing Symfony\Component\Runtime\RunnerInterface
:
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use React\EventLoop\Factory as ReactFactory;
use React\Http\Server as ReactHttpServer;
use React\Socket\Server as ReactSocketServer;
use Symfony\Component\Runtime\RunnerInterface;
class ReactPHPRunner implements RunnerInterface
{
private $application;
private $port;
public function __construct(RequestHandlerInterface $application, int $port)
{
$this->application = $application;
$this->port = $port;
}
public function run(): int
{
$application = $this->application;
$loop = ReactFactory::create();
// configure ReactPHP to correctly handle the PSR-15 application
$server = new ReactHttpServer(
$loop,
function (ServerRequestInterface $request) use ($application) {
return $application->handle($request);
}
);
// start the ReactPHP server
$socket = new ReactSocketServer($this->port, $loop);
$server->listen($socket);
$loop->run();
return 0;
}
}
By extending the GenericRuntime
, you make sure that the application is always using this ReactPHPRunner
:
use Symfony\Component\Runtime\GenericRuntime;
use Symfony\Component\Runtime\RunnerInterface;
class ReactPHPRuntime extends GenericRuntime
{
private $port;
public function __construct(array $options)
{
$this->port = $options['port'] ?? 8080;
parent::__construct($options);
}
public function getRunner(?object $application): RunnerInterface
{
if ($application instanceof RequestHandlerInterface) {
return new ReactPHPRunner($application, $this->port);
}
// if it's not a PSR-15 application, use the GenericRuntime to
// run the application (see "Resolvable Applications" above)
return parent::getRunner($application);
}
}
The end user will now be able to create front controller like:
<?php
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (array $context) {
return new SomeCustomPsr15Application();
};
This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.