Skip to content
Snippets Groups Projects
Unverified Commit ceab480a authored by Alexander Varwijk's avatar Alexander Varwijk
Browse files

Issue #3313404: Use symfony/runtime for less bespoke bootstrap/compatibility...

Issue #3313404: Use symfony/runtime for less bespoke bootstrap/compatibility with varied runtime environments

A custom runtime is provided which knows how to create the Drupal
specific runner that will use the DrupalKernel. The Drupal specific
runner is currently the same as Symfony's HttpKernelRunner. However, it
provides us with a place to put runtime specific code such as Revolt's
eventloop.
parent 1a2079a4
No related branches found
No related tags found
1 merge request!11905Issue #3313404: Use symfony/runtime for less bespoke bootstrap/compatibility...
Showing
with 349 additions and 43 deletions
<?php
/**
* @file
* Includes the autoload_runtime created by the Symfony Runtime component.
*
* This file was generated by drupal-scaffold.
*
* @see composer.json
* @see index.php
* @see core/install.php
* @see core/rebuild.php
*/
return require __DIR__ . '/vendor/autoload_runtime.php';
......@@ -65,6 +65,7 @@
"drupal/core-vendor-hardening": true,
"php-http/discovery": true,
"phpstan/extension-installer": true,
"symfony/runtime": true,
"tbachert/spi": false
}
},
......
......@@ -496,7 +496,7 @@
"dist": {
"type": "path",
"url": "core",
"reference": "7e8f42a2a16fa8db35c42d6ba0c7bcc9b3508588"
"reference": "e4656f4b8ebdebf158c97e202a7bd3f34a25058b"
},
"require": {
"asm89/stack-cors": "^2.3",
......@@ -542,6 +542,7 @@
"symfony/process": "^7.2",
"symfony/psr-http-message-bridge": "^7.2",
"symfony/routing": "^7.2",
"symfony/runtime": "^7.2",
"symfony/serializer": "^7.2",
"symfony/validator": "^7.2",
"symfony/yaml": "^7.2",
......@@ -3739,6 +3740,85 @@
],
"time": "2024-11-25T11:08:51+00:00"
},
{
"name": "symfony/runtime",
"version": "v7.2.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/runtime.git",
"reference": "8e8d09bd69b7f6c0260dd3d58f37bd4fbdeab5ad"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/runtime/zipball/8e8d09bd69b7f6c0260dd3d58f37bd4fbdeab5ad",
"reference": "8e8d09bd69b7f6c0260dd3d58f37bd4fbdeab5ad",
"shasum": ""
},
"require": {
"composer-plugin-api": "^1.0|^2.0",
"php": ">=8.2"
},
"conflict": {
"symfony/dotenv": "<6.4"
},
"require-dev": {
"composer/composer": "^2.6",
"symfony/console": "^6.4|^7.0",
"symfony/dotenv": "^6.4|^7.0",
"symfony/http-foundation": "^6.4|^7.0",
"symfony/http-kernel": "^6.4|^7.0"
},
"type": "composer-plugin",
"extra": {
"class": "Symfony\\Component\\Runtime\\Internal\\ComposerPlugin"
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Runtime\\": "",
"Symfony\\Runtime\\Symfony\\Component\\": "Internal/"
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Enables decoupling PHP applications from global state",
"homepage": "https://symfony.com",
"keywords": [
"runtime"
],
"support": {
"source": "https://github.com/symfony/runtime/tree/v7.2.3"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-12-29T21:39:47+00:00"
},
{
"name": "symfony/serializer",
"version": "v7.2.0",
......
......@@ -54,6 +54,7 @@
"symfony/process": "~v7.2.0",
"symfony/psr-http-message-bridge": "~v7.2.0",
"symfony/routing": "~v7.2.0",
"symfony/runtime": "~v7.2.3",
"symfony/serializer": "~v7.2.0",
"symfony/service-contracts": "~v3.5.1",
"symfony/string": "~v7.2.0",
......
<?php
namespace Drupal\Composer\Plugin\Scaffold;
use Composer\IO\IOInterface;
use Composer\Util\Filesystem;
use Drupal\Composer\Plugin\Scaffold\Operations\ScaffoldResult;
/**
* Generates an 'autoload_runtime.php' that includes the Symfony_runtime loader.
*
* @internal
*/
final class GenerateAutoloadRuntimeReferenceFile {
/**
* This class provides only static methods.
*/
private function __construct() {
}
/**
* Generates the autoload_runtime file at the specified location.
*
* This only writes a bit of PHP that includes the autoload_runtime file that
* Composer generated. Drupal does this so that it can guarantee that there
* will always be an `autoload_runtime.php` file in a well-known location.
*
* @param \Composer\IO\IOInterface $io
* IOInterface to write to.
* @param string $package_name
* The name of the package defining the autoload_runtime file
* (the root package).
* @param string $web_root
* The path to the web root.
* @param string $vendor
* The path to the vendor directory.
*
* @return \Drupal\Composer\Plugin\Scaffold\Operations\ScaffoldResult
* The result of the autoload_runtime file generation.
*/
public static function generateAutoloadRuntime(IOInterface $io, $package_name, $web_root, $vendor) {
$autoload_runtime_path = static::autoloadRuntimePath($package_name, $web_root);
// Calculate the relative path from the webroot (location of the project
// autoload_runtime.php) to the vendor directory.
$fs = new Filesystem();
$relative_autoload_path = $fs->findShortestPath($autoload_runtime_path->fullPath(), "$vendor/autoload_runtime.php");
file_put_contents($autoload_runtime_path->fullPath(), static::autoLoadRuntimeContents($relative_autoload_path));
return new ScaffoldResult($autoload_runtime_path, TRUE);
}
/**
* Determines whether or not the autoload_runtime file has been committed.
*
* @param \Composer\IO\IOInterface $io
* IOInterface to write to.
* @param string $package_name
* The name of the package defining the autoload_runtime file
* (the root package).
* @param string $web_root
* The path to the web root.
*
* @return bool
* True if autoload_runtime.php file exists and has been committed to the
* repository
*/
public static function autoloadRuntimeFileCommitted(IOInterface $io, $package_name, $web_root) {
$autoload_runtime_path = static::autoloadRuntimePath($package_name, $web_root);
$autoload_runtime_file = $autoload_runtime_path->fullPath();
$location = dirname($autoload_runtime_file);
if (!file_exists($autoload_runtime_file)) {
return FALSE;
}
return Git::checkTracked($io, $autoload_runtime_file, $location);
}
/**
* Generates a scaffold file path object for the autoload_runtime file.
*
* @param string $package_name
* The name of the package defining the autoload_runtime file
* (the root package).
* @param string $web_root
* The path to the web root.
*
* @return \Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath
* Object wrapping the relative and absolute path to the destination file.
*/
protected static function autoloadRuntimePath($package_name, $web_root) {
$rel_path = 'autoload_runtime.php';
$dest_rel_path = '[web-root]/' . $rel_path;
$dest_full_path = $web_root . '/' . $rel_path;
return new ScaffoldFilePath('autoload_runtime', $package_name, $dest_rel_path, $dest_full_path);
}
/**
* Builds the contents of the autoload_runtime file.
*
* @param string $relative_autoload_runtime_path
* The relative path to the runtime loader in vendor.
*
* @return string
* Return the contents for the autoload_runtime.php.
*/
protected static function autoLoadRuntimeContents($relative_autoload_runtime_path) {
$relative_autoload_runtime_path = preg_replace('#^\./#', '', $relative_autoload_runtime_path);
return <<<EOF
<?php
/**
* @file
* Includes the autoload_runtime created by the Symfony Runtime component.
*
* This file was generated by drupal-scaffold.
*
* @see composer.json
* @see index.php
* @see core/install.php
* @see core/rebuild.php
*/
return require __DIR__ . '/{$relative_autoload_runtime_path}';
EOF;
}
}
......@@ -172,6 +172,11 @@ public function scaffold() {
if (!GenerateAutoloadReferenceFile::autoloadFileCommitted($this->io, $this->rootPackageName(), $web_root)) {
$scaffold_results[] = GenerateAutoloadReferenceFile::generateAutoload($this->io, $this->rootPackageName(), $web_root, $this->getVendorPath());
}
// The same is done for the autoload_runtime file that loads the Symfony
// runtime.
if (!GenerateAutoloadRuntimeReferenceFile::autoloadRuntimeFileCommitted($this->io, $this->rootPackageName(), $web_root)) {
$scaffold_results[] = GenerateAutoloadRuntimeReferenceFile::generateAutoloadRuntime($this->io, $this->rootPackageName(), $web_root, $this->getVendorPath());
}
// Add the managed scaffold files to .gitignore if applicable.
$gitIgnoreManager = new ManageGitIgnore($this->io, getcwd());
......
......@@ -37,7 +37,8 @@
"drupal/core-vendor-hardening": true,
"phpstan/extension-installer": true,
"dealerdirect/phpcodesniffer-composer-installer": true,
"php-http/discovery": true
"php-http/discovery": true,
"symfony/runtime": true
},
"sort-packages": true
},
......
......@@ -35,7 +35,8 @@
"drupal/core-project-message": true,
"phpstan/extension-installer": true,
"dealerdirect/phpcodesniffer-composer-installer": true,
"php-http/discovery": true
"php-http/discovery": true,
"symfony/runtime": true
},
"sort-packages": true
},
......
......@@ -9,14 +9,12 @@
*/
use Drupal\Core\DrupalKernel;
use Symfony\Component\HttpFoundation\Request;
use Drupal\Core\Runtime\DrupalRuntime;
$autoloader = require_once 'autoload.php';
// @todo Replace once https://github.com/symfony/symfony/issues/60249 is fixed.
$_ENV['APP_RUNTIME'] ??= DrupalRuntime::class;
require_once 'autoload_runtime.php';
$kernel = new DrupalKernel('prod', $autoloader);
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
return static function () {
return new DrupalKernel('prod', require 'autoload.php');
};
......@@ -8,10 +8,12 @@
* See COPYRIGHT.txt and LICENSE.txt files in the "core" directory.
*/
use Drupal\Core\Runtime\DrupalRuntime;
use Drupal\Core\Update\UpdateKernel;
use Symfony\Component\HttpFoundation\Request;
$autoloader = require_once 'autoload.php';
// @todo Replace once https://github.com/symfony/symfony/issues/60249 is fixed.
$_ENV['APP_RUNTIME'] ??= DrupalRuntime::class;
require_once 'autoload_runtime.php';
// Disable garbage collection during test runs. Under certain circumstances the
// update path will create so many objects that garbage collection causes
......@@ -21,10 +23,6 @@
gc_disable();
}
$kernel = new UpdateKernel('prod', $autoloader, FALSE);
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
return static function () {
return new UpdateKernel('prod', require 'autoload.php', FALSE);
};
......@@ -29,6 +29,7 @@
"symfony/mailer": "^7.2",
"symfony/mime": "^7.2",
"symfony/routing": "^7.2",
"symfony/runtime": "^7.2",
"symfony/serializer": "^7.2",
"symfony/validator": "^7.2",
"symfony/process": "^7.2",
......
<?php
namespace Drupal\Core\Runtime;
use Drupal\Core\DrupalKernelInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\TerminableInterface;
use Symfony\Component\Runtime\RunnerInterface;
/**
* The Drupal Kernel Runner.
*
* This is used by the DrupalRuntime to process a single request with a
* DrupalKernel in the context of a webserver use-case such as PHP-FPM with
* nginx or Apache.
*/
class DrupalKernelRunner implements RunnerInterface {
/**
* Create a new DrupalKernelRunner instance.
*
* @param \Drupal\Core\DrupalKernelInterface $kernel
* The DrupalKernel that should be used to handle the request.
* @param \Symfony\Component\HttpFoundation\Request $request
* The request that's being handled.
*/
public function __construct(
private DrupalKernelInterface $kernel,
private Request $request,
) {
}
/**
* {@inheritdoc}
*/
public function run(): int {
$response = $this->kernel->handle($this->request);
$response->send();
if ($this->kernel instanceof TerminableInterface) {
$this->kernel->terminate($this->request, $response);
}
return 0;
}
}
<?php
declare(strict_types=1);
namespace Drupal\Core\Runtime;
use Drupal\Core\DrupalKernelInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Runtime\GenericRuntime;
use Symfony\Component\Runtime\RunnerInterface;
/**
* The custom Drupal framework runtime.
*
* This runtime understands how to provide the arguments that Drupal
* front-controllers need to instantiate the DrupalKernel.
*/
class DrupalRuntime extends GenericRuntime {
/**
* {@inheritdoc}
*/
public function getRunner(?object $application): RunnerInterface {
if ($application instanceof DrupalKernelInterface) {
return new DrupalKernelRunner($application, Request::createFromGlobals());
}
return parent::getRunner($application);
}
}
......@@ -83,6 +83,7 @@ final class ComposerPluginsValidator implements EventSubscriberInterface {
'drupal/core-project-message' => '*',
'phpstan/extension-installer' => '^1.1',
PhpTufValidator::PLUGIN_NAME => '^1',
'symfony/runtime' => "*",
];
/**
......
......@@ -41,7 +41,8 @@
"minimum-stability": "stable",
"config": {
"allow-plugins": {
"drupal/core-composer-scaffold": false
"drupal/core-composer-scaffold": false,
"symfony/runtime": false
}
}
}
......@@ -484,9 +484,10 @@ public static function providerAllowedPlugins(): array {
[
'example/plugin-a' => TRUE,
'example/plugin-b' => FALSE,
// The scaffold plugin is explicitly disallowed by the fake_site
// fixture.
// The scaffold and runtime plugins are explicitly disallowed by the
// fake_site fixture.
'drupal/core-composer-scaffold' => FALSE,
'symfony/runtime' => FALSE,
],
],
];
......
......@@ -247,7 +247,7 @@ public function testLoggerException(): void {
// Find fatal error logged to the error.log
$errors = file(\Drupal::root() . '/' . $this->siteDirectory . '/error.log');
$this->assertCount(10, $errors, 'The error + the error that the logging service is broken has been written to the error log.');
$this->assertCount(16, $errors, 'The error + the error that the logging service is broken has been written to the error log.');
$this->assertStringContainsString('Failed to log error', $errors[0], 'The error handling logs when an error could not be logged to the logger.');
$expected_path = \Drupal::root() . '/core/modules/system/tests/modules/error_service_test/src/MonkeysInTheControlRoom.php';
......
......@@ -122,6 +122,7 @@ public function testManageGitIgnore(): void {
/.gitattributes
/.ht.router.php
/autoload.php
/autoload_runtime.php
/index.php
/robots.txt
/update.php
......
......@@ -9,14 +9,12 @@
*/
use Drupal\Core\DrupalKernel;
use Symfony\Component\HttpFoundation\Request;
use Drupal\Core\Runtime\DrupalRuntime;
$autoloader = require_once 'autoload.php';
// @todo Replace once https://github.com/symfony/symfony/issues/60249 is fixed.
$_ENV['APP_RUNTIME'] ??= DrupalRuntime::class;
require_once 'autoload_runtime.php';
$kernel = new DrupalKernel('prod', $autoloader);
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
return static function () {
return new DrupalKernel('prod', require 'autoload.php');
};
......@@ -8,10 +8,12 @@
* See COPYRIGHT.txt and LICENSE.txt files in the "core" directory.
*/
use Drupal\Core\Runtime\DrupalRuntime;
use Drupal\Core\Update\UpdateKernel;
use Symfony\Component\HttpFoundation\Request;
$autoloader = require_once 'autoload.php';
// @todo Replace once https://github.com/symfony/symfony/issues/60249 is fixed.
$_ENV['APP_RUNTIME'] ??= DrupalRuntime::class;
require_once 'autoload_runtime.php';
// Disable garbage collection during test runs. Under certain circumstances the
// update path will create so many objects that garbage collection causes
......@@ -21,10 +23,6 @@
gc_disable();
}
$kernel = new UpdateKernel('prod', $autoloader, FALSE);
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
return static function () {
return new UpdateKernel('prod', require 'autoload.php', FALSE);
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment