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-PM, ReactPHP, Swoole, etc. without any changes.
Installation
1
$ 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:
1 2 3 4 5 6 7 8
// public/index.php
use App\Kernel;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (array $context): Kernel {
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 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-plugins
option, the autoload_runtime.php
file won't be created.
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
#!/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): Application {
$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 SymfonyRuntime. It works excellent on most applications running with a webserver using PHP-FPM like Nginx or Apache.
The component also provides a 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:
1 2 3 4 5 6 7 8 9 10
{
"require": {
"...": "..."
},
"extra": {
"runtime": {
"class": "Symfony\\Component\\Runtime\\GenericRuntime"
}
}
}
If modifying the runtime class isn't enough, you can create your own runtime template:
1 2 3 4 5 6 7 8 9 10
{
"require": {
"...": "..."
},
"extra": {
"runtime": {
"autoload_template": "resources/runtime/autoload_runtime.template"
}
}
}
Symfony provides a runtime template file that you can use to create your own.
Using the Runtime
A Runtime is responsible for passing arguments into the closure and run the application returned by the closure. The SymfonyRuntime and 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:
1 2 3 4 5 6 7 8 9 10
// public/index.php
use Symfony\Bundle\FrameworkBundle\Console\Application;
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): Application {
// ...
};
The following arguments are supported by the SymfonyRuntime
:
- Request
- A request created from globals.
- InputInterface
- An input to read options and arguments.
- OutputInterface
- Console output to print to the CLI with style.
- Application
- An application for creating CLI applications.
- 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
andsession
.
Resolvable Applications
The application returned by the closure below is a Symfony Kernel. However, a number of different applications are supported:
1 2 3 4 5 6 7 8
// public/index.php
use App\Kernel;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return static function (): Kernel {
return new Kernel('prod', false);
};
The SymfonyRuntime
can handle these applications:
- HttpKernelInterface
- The application will be run with HttpKernelRunner like a "standard" Symfony application.
- Response
-
The Response will be printed by ResponseRunner:
1 2 3 4 5 6 7 8
// public/index.php use Symfony\Component\HttpFoundation\Response; require_once dirname(__DIR__).'/vendor/autoload_runtime.php'; return static function (): Response { return new Response('Hello world'); };
- Command
-
To write single command applications. This will use the ConsoleApplicationRunner:
1 2 3 4 5 6 7 8 9 10 11 12 13
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 static function (Command $command): Command { $command->setCode(static function (InputInterface $input, OutputInterface $output): void { $output->write('Hello World'); }); return $command; };
- Application
-
Useful with console applications with more than one command. This will use the ConsoleApplicationRunner:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
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 static function (array $context): Application { $command = new Command('hello'); $command->setCode(static function (InputInterface $input, OutputInterface $output): void { $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:
- RunnerInterface
-
The
RunnerInterface
is a way to use a custom application with the generic Runtime:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// public/index.php use Symfony\Component\Runtime\RunnerInterface; require_once dirname(__DIR__).'/vendor/autoload_runtime.php'; return static function (): RunnerInterface { 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:1 2 3 4 5 6 7 8 9 10 11 12
// public/index.php require_once dirname(__DIR__).'/vendor/autoload_runtime.php'; return static function (): callable { $app = static function(): int { echo 'Hello World'; return 0; }; return $app; };
void
-
If the callable doesn't return anything, the
SymfonyRuntime
will assume everything is fine:1 2 3 4 5
require_once dirname(__DIR__).'/vendor/autoload_runtime.php'; return function (): void { 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:
1 2 3 4 5 6 7
$_SERVER['APP_RUNTIME_OPTIONS'] = [
'project_dir' => '/var/task',
];
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
// ...
You can also configure extra.runtime
in composer.json
:
1 2 3 4 5 6 7 8 9 10
{
"require": {
"...": "..."
},
"extra": {
"runtime": {
"project_dir": "/var/task"
}
}
}
Then, update your Composer files (running composer dump-autoload
, for instance),
so that the vendor/autoload_runtime.php
files gets regenerated with the new option.
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.
dotenv_overload
(default:false
)-
To tell Dotenv whether to override
.env
vars with.env.local
(or other.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: the value of the env var defined bydebug_var_name
option-
(usually,
APP_DEBUG
), ortrue
if such env var is not defined) Toggles the debug mode of Symfony applications (e.g. to display errors) runtimes
-
Maps "application types" to a
GenericRuntime
implementation that knows how to deal with each of them. error_handler
(default: BasicErrorHandler or SymfonyErrorHandler forSymfonyRuntime
)- Defines the class to use to handle PHP errors.
env_var_name
(default:"APP_ENV"
)- Defines the name of the env var that stores the name of the configuration environment to use when running the application.
debug_var_name
(default:"APP_DEBUG"
)- Defines the name of the env var that stores the value of the debug mode flag to use when running the application.
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.
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 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 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 asint
. - The PHP engine is terminated 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 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 RunnerInterface:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
use Psr\Http\Message\ResponseInterface;
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
{
public function __construct(
private RequestHandlerInterface $application,
private int $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): ResponseInterface {
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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
use Symfony\Component\Runtime\GenericRuntime;
use Symfony\Component\Runtime\RunnerInterface;
class ReactPHPRuntime extends GenericRuntime
{
private int $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:
1 2 3 4 5
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (array $context): SomeCustomPsr15Application {
return new SomeCustomPsr15Application();
};