The Runtime Component

5.3 version
Symfony 5.3 is backed by JoliCode.

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

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:

<?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:

  1. It instantiates a Symfony\Component\Runtime\RuntimeInterface;
  2. The callable (returned by public/index.php) is passed to the Runtime, whose job is to resolve the arguments (in this example: array $context);
  3. Then, this callable is called to get the application (App\Kernel);
  4. 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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
    "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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
    "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:

  1. The main entry point returns a callable (the “app”) that wraps the application;
  2. 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.
  3. The app callable is invoked with its arguments, it will return an object that represents the application.
  4. 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.
  5. The RunnerInterface::run(object $application) is called and it returns the exit status code as int.
  6. 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.