Problem/Motivation

Drupal currently dives from the index.php (and other front-controllers) straight into all the code in DrupalKernel. Because this pattern is repeated in a few places (e.g. also in update.php and perhaps custom front-controllers) this places a very large burden on DrupalKernel to do a lot of things.

From Symfony's intention, a Kernel is really focused on handling a single request and response (and perhaps only its subresponses). This can be seen for example in Symfony's TerminableInterface that we implement. It should not handle larger application lifecycles. This problem becomes especially apparent in the work done to adopt Revolt in #3425210: Ensure asynchronous tasks that run beyond the length of a request have the chance to complete before process exit. That issue concerns itself with things that might happen outside of a request. However, without duplicating code to multiple front-controllers, there's no place to put this kind of logic.

Steps to reproduce

Proposed resolution

Symfony has solved this problem already using its Runtime component which allows for re-usable tasks that happen outside of a request. This can include things like configuring the environment and other set-up logic that's not tied to a specific request.

By implementing symfony/runtime we provide a clear separation of concerns between preparing the environment (runtime) and handling a request. We can also more easily re-use Symfony logic for the environment preparation.

The purpose of this issue s to enable removing the vast amount of Runtime set-up work that Drupal currently has in DrupalKernel with a bonus of making it easier to use things like FrankenPHP.

This change makes it a lot easier to solve a bunch of disparate issues:

A roadmap of how those are related can be viewed in https://web.archive.org/web/20250908093711/https://www.alexandervarwijk....

Remaining tasks

User interface changes

API changes

Drupal's shipped front-controllers (index.php/update.php) are updated to use symfony/runtime and to make those work the symfony/runtime plugin must be allowed to run in projects.

The old style index.php/update.php will still work with the current version of the DrupalKernel, but we'll likely want to make changes and move bootstrapping/environment logic out of DrupalKernel and into the Runtime, so that will not remain true forever.

This issue has treated the front-controllers as internal API to Drupal that people should not manually modify, thus changing it can happen in minor versions. The Symfony Runtime solves exactly the problem of end projects wanting more control over what happens in those front-controllers, so the possibility to change behaviour through a well-defined API is considered an API addition.

Data model changes

Release notes snippet

Issue fork drupal-3313404

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

bradjones1 created an issue. See original summary.

bradjones1’s picture

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

andypost’s picture

It is required to run Drupal using https://github.com/dunglas/frankenphp-drupal/issues/1 to get the best perf

el7cosmos’s picture

I created https://github.com/el7cosmos/drupal-runtime a while ago that works with non-persistent app servers at that time (e.g. php-fpm, nginx unit, apache httpd)

andypost’s picture

andypost’s picture

Issue tags: +DrupalCon Lille 2023
dpi’s picture

Another +1 for runtime.

I used it recently on https://www.drupal.org/project/sm with success. It cleans up setting up Symfony Console commands remarkably https://git.drupalcode.org/project/sm/-/blob/1.x/bin/sm?ref_type=heads

renrhaf’s picture

+1 it would allow to use worker mode to boost performances considerably

kingdutch made their first commit to this issue’s fork.

kingdutch’s picture

Status: Active » Needs review

Took some force-pushes (one day I'll get FunctionalTests working locally again) but the MR is now green across the board :raised_hands:

kingdutch’s picture

Issue summary: View changes
Issue tags: +Needs change record

Updated the issue summary to help reviewers.

kingdutch’s picture

Issue summary: View changes

Forgot to snip a reformatted paragraph, whoops.

nicxvan’s picture

For a start this will likely need framework manager review.

ghost of drupal past’s picture

I tried to understand how runtime works and I tried to contribute my fresh understanding: I submitted https://github.com/symfony/symfony-docs/pull/20909 and created https://app.infinitymaps.io/maps/mnJJ9FR4FND. I hope this helps someone.

donquixote’s picture

For now this MR adds a dependency and a bunch of new code.
That new code looks quite complex if you are not familiar with the symfony/runtime package (which I am not, at this point).

The supposed payoff is that we might later be able to split logic out of DrupalKernel.
But the current MR does not make this obvious.

We should have a proof-of-concept MR that shows how this change would help us to refactor and simplify DrupalKernel.
(This is the only reason stated in this issue, I don't see other benefits mentioned)

We also need to evaluate if there are alternatives (as in custom code) that do not add a dependency.

kingdutch’s picture

The quickest example I can give you is the pull request for #3425210: Ensure asynchronous tasks that run beyond the length of a request have the chance to complete before process exit. Other items I'm excited to work on, but they require a bit more puzzling. I hope that in addition I can add more information/rephrase my case and that can hopefully lead to a more concrete understanding (and possibly an updated issue summary).

DrupalKernel is currently a monolith and is responsible for:

  1. Setting PHP environment configuration in bootEnvironment
  2. Initializing settings and determining which multi-site if any we're in
  3. Initializing global variables for application context
  4. Handling requests

This worked for Drupal because, for the most part, the only use-case we were considering is the simple request response flow where there is a 1:1 mapping of process to request. However, in modern web applications we're seeing that that no longer holds true. A websocket server may have a long running application that handles many different requests. Similarly Drush might execute Drupal code and not have a request at all for its tasks. These kinds of use cases are discussed in #2218651: [meta] Make Drupal compatible with persistent app servers like ReactPHP, PHP-PM, PHPFastCGI, FrankenPHP, Swoole.

For the adoption of Revolt and enabling of asynchronous applications we must introduce a call to EventLoop::run. By definition, this call must be "global" because in an application there can only be a single EventLoop. The EventLoop has the task to schedule different cooperative tasks and calling EventLoop::run from within an EventLoop::run call causes a fatal error because their scheduling would conflict.

This was discussed in #3425210: Ensure asynchronous tasks that run beyond the length of a request have the chance to complete before process exit where I tried to find the best possible location. From Drupal's single-request perspective the most logical place would be in the Kernel at the end of handle and the end of terminate. However, this would cause a problem for developers who want to build multi-request applications, or applications in which a Drupal request handler may only be a small part. Their application can not function if EventLoop::run is buried within the Drupal Kernel. The logical conclusion for that then is that the EventLoop::run calls should be placed outside of the Kernel, but this would require it to be placed in index.php, update.php, the related scaffold files. Additionally, anyone that would build their own application that does not use those files would need to remember to include EventLoop::run in the right location for their calls of Drupal to work.

If we look at the list of responsibilities for DrupalKernel then we can see a similar tension for items 1 through 3. They're tasks that should really only happen once and they should happen in a specific manner for Drupal to work. The only consistent place that currently exists in Drupal to satisfy those requirements is DrupalKernel, because if we move them out, then we risk someone not doing the environment initialization separately.

To reduce the amount of responsibilities in DrupalKernel safely, we need a way to extract functionality from it while ensuring that it remains easy for ourselves (as contributors to Drupal Core) but also for application developers who integrate Drupal to properly set-up the environment that Drupal will run in.

We could write that code ourselves but symfony/runtime was created by the Symfony maintainers specifically to solve that problem:

What if we could decouple the bootstrapping logic of our apps from any global state?

This PR makes it possible via a new proposed symfony/runtime component.

The smart thing about the symfony/runtime component, which we would be hard-pressed to replicate ourselves without arriving at the same code, is that it decouples the runtime/global state and application in such a way that you can switch out either side. This puts Drupal in a position where Drupal can provide a runtime that would work in PHP-FPM, as I do in my proposed PR, but it can also provide a runtime that would let Drupal work within a console application, like the drupal CLI tool that is shipped with Drupal core.

Then anyone would be able to write a server or console application that uses Drupal and could use Drupal's out of the box runtime and be sure that their environment is set-up correctly. However, if they have specific needs and want the environment to be set-up in a different way then they can also change the runtime itself, while still use the Drupal PHP-FPM application or console tool.

We can already see an example of the necessity for a consistent environment in the Drupal CLI tool. We see that the InstallCommand and ServerCommand, both have a boot method which goes through steps 1-3 outlined at the start of this comment (and RecipeCommand and RecipeInfoCommand do the same through BootableCommandTrait).

I'm still puzzling a little bit of how to untangle DrupalKernel (though I think it's a worthwhile effort) but I'm confident that we can clean up the code for those commands as well.

  1. Move DrupalKernel::bootEnvironment into DrupalRuntime::__construct (different environments can swap out the runtime if needed): #2690035: [PP-1] Extract DrupalKernel::bootEnvironment into SAPI adapter
  2. Move DrupalKernel::findSitePath to DrupalRuntime. This should most likely be requestable as an argument from the main function and passed into the kernel since it's required for the Kernel to boot. That standardises the logic, but allows other tools to easily overwrite it if needed.
  3. Application root and initializeRequestGlobals would similarly be moved as per #2529170-104: [PP-1] Remove DrupalKernel::initializeRequestGlobals and replace base_root, base_url and base_path with a service
  4. By moving the site path detection out of the Kernel we can do the same for Settings::initialize from DrupalKernel::initializeSettings and make the settings available as front-controller argument from our DrupalRuntime:getArgument as well. That would make it trivial to write a Drush-like application that reads settings

I think there's more possible. For example some of the environment is now set based on drupal_valid_test_ua but we may want to switch out the runtime altogether in those. Some of that also requires untangling some existing global state before it becomes clearer.


I realise that's quite a long post which still assumes some knowledge of symfony/runtime. I think the main takeaway is the clear delineation and swapping mechanism that symfony/runtime provides between environment and application.

If environment is E and application is A, then the DrupalKernal is currently (EA), a very tightly coupled environment and application. With symfony/runtime (and the related stories to untangle DrupalKernel) this turns into

E
---- symfony/runtime
A

This makes it easy to change E for E' (in case you have different path requirements around file system structure or front-controller for example), but it also makes it easy to swap A for A' (in case of Drupal console or Drush for example).


The next steps that I'll work on:

1. Expand the PR for this issue to include updating the Drupal console script
2. Work on some of the related issues as stacked MRs to show how they would be implemented on top of the runtime.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new91 bytes

The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

kingdutch’s picture

I've outlined how this issue can make other issues (see "referenced by") easier in https://web.archive.org/web/20250908093711/https://www.alexandervarwijk....

The TL;DR is that it aids in decoupling the front-controller (index.php, update.php, or script entrypoint) from the environment. In this way, the end user can retain control over what the application does (e.g. user the DrupalKernel to handle a request; or use Drush to run a command) while still providing Drupal Core a place to split out responsibilities from the DrupalKernel that set-up the environment itself. This happens in a way where the end-user can easily use what's built-in to Drupal, but also replace these parts of they don't suit them (e.g. to use FrankenPHP or AMPHP).

kingdutch’s picture

Issue summary: View changes
Status: Needs work » Needs review

The latest commit converts the core scripts to use the Symfony Runtime pattern. The console runner behaviour is copied from Symfony's Runtime (since the generic runtime does not support applications). This provides us with input and output handling and adds environment and debug handling for free.

The bootstrap of Drupal in the scripts is not yet moved out of the front-controller. Rather than guess at a solution now this is reserved for https://www.drupal.org/project/drupal/issues/2363353 and https://www.drupal.org/project/drupal/issues/2529170 which can make these changes for the Kernel and the console at the same time in a structured manner.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new8.29 KB

The Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

kingdutch’s picture

Status: Needs work » Needs review

Coding standards got me.

The change to `DrupalRuntime` is kept as close to the `SymfonyRuntime` as possible to ensure consistent behaviour and make switching to that runtime in the future easier. The switching is not done as part of this issue since the SymfonyRuntime has a constructor which does a bunch of runtime configuration that is easier to discuss in a separate issue.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new6.57 KB

The Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

kingdutch’s picture

Status: Needs work » Needs review

PHPUnit doesn't use the repository root as working directory. This caused the PHPUnit test to fail to find the autoload_runtime.php file. To solve this the paths for the CLI front-controllers to autoload_runtime.php should be absolute, similar to autoload.php already being absolute in these scripts in the past.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new6.57 KB

The Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

kingdutch’s picture

Status: Needs work » Needs review

Accidentally committed the file mode changes for the scripts that I needed for testing :) Reverted those files back to non-executable.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new91 bytes

The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

shalini_jha made their first commit to this issue’s fork.

shalini_jha’s picture

Status: Needs work » Needs review

Moving this back to NR.

catch’s picture

Discussed this with @Kingdutch and @alexpott and confirmed that we think this is a good idea. Alex pointed out that we need to be a bit careful about bc for existing custom front controllers (like statistics.php which is now in contrib), but we are pretty sure those will continue to work as-is, at least until we explicitly deprecate something which could be much later. Removing the framework manager review tag given that.

kingdutch’s picture

Status: Needs review » Needs work
Issue tags: +Vienna2025

We worked on this in Vienna 🎉 Moving to "Needs Work" for the change record. Once that's written this can move back to "Needs review" and this should be good to go.

mxr576 made their first commit to this issue’s fork.

mxr576’s picture

Discussed this with @Kingdutch and @alexpott and confirmed that we think this is a good idea.

\o/

Once that's written this can move back to "Needs review" and this should be good to go.

Draft a change record, please review.

Also tried to fix CS issues on the MR, but I could not fix one of them for some reasons.

kingdutch’s picture

Status: Needs work » Needs review
Issue tags: -Needs change record

The change record is quite a bit longer than what I usually see, but it is comprehensive. I've tweaked the wording slightly to indicate that although this enables future maintainability improvements, we've not actually changed DrupalKernel yet. So those changes still need to materialize in future issues.

With the change record and passing tests this is now ready for review.

znerol’s picture

The php ini value for variables_order is set to GPCS by default in many (most?) Linux distros. That means that environment variables are not parsed into the $_ENV superglobal in many cases. They are still available from $_SERVER though.

The getenv() and putenv() C functions used by the PHP function with the same name are not thread-safe. As a result, concurrent access in multi-threaded environments may lead to hard to debug problems. There have been reports about this in the past (e.g. vlucas/phpdotenv#76 or laravel/framework#7354).

I suggest to try to avoid $_ENV, getenv and putenv and exclusively rely on $_SERVER as an environment variable source - at least initially.

mxr576’s picture

The change record is quite a bit longer than what I usually see,

I agree and had the same feeling as well, but I think this change definitely worth a proper introduction (and celebrating).

andypost’s picture

try to avoid $_ENV, getenv and putenv and exclusively rely on $_SERVER as an environment variable source

Strong ++ as requirement for FrankenPHP which is ZTS and help to #3437187: Add Caddyfile configuration

kingdutch’s picture

The getenv() and putenv() C functions used by the PHP function with the same name are not thread-safe. As a result, concurrent access in multi-threaded environments may lead to hard to debug problems. There have been reports about this in the past (e.g. vlucas/phpdotenv#76 or laravel/framework#7354).

It's unclear to me how putenv/getenv are relevant since they're not used in the code in question.

EDIT: The implementation of DrupalRuntime copies the use of putenv from SymfonyRuntime, which makes this question relevant. Addressed in #3313404-45: Use symfony/runtime for less bespoke bootstrap/compatibility with varied runtime environments

The php ini value for variables_order is set to GPCS by default in many (most?) Linux distros. That means that environment variables are not parsed into the $_ENV superglobal in many cases. They are still available from $_SERVER though.

The environment variables being available in $_SERVER depends on how PHP is run.

The page you linked specifies that this is true for CGI/FastCGI mode, but those are not the only modes to run PHP.

I suggest to try to avoid $_ENV, getenv and putenv and exclusively rely on $_SERVER as an environment variable source - at least initially.

Even Drupal's own docker image uses EGPCS when run through the command line. So I don't quite understand why Drupal should hinder functionality based on common linux distro configurations. The code that's being added is not forcing anyone to use environment variables, but it is providing users with the option.

The selection logic by Symfony would still prefer the server variable if one is set.

That selection logic is also why Drupal should check for the existence of $_ENV. If Drupal would only check/set $_SERVER, then we'd break the support for $_ENV that Symfony has, which means that Drupal's use of the Symfony Runtime component would no longer be compatible with the runtime documentation from Symfony (i.e. I can not overwrite the runtime with an environment variable).

The original issue which added $_ENV support in Symfony to ease developer's lives, https://github.com/symfony/symfony/pull/43833, mentioned that getting $_ENV into $_SERVER is not easy in all circumstances.

nicxvan’s picture

I'll preface this by saying I'm definitely speaking a bit outside of my expertise here.

I think we definitely need something like this to really enable the power of the revolt event loop and allow things like FrankenPHP to work.
I would like to question a few assumptions and decisions though:

We could write that code ourselves but symfony/runtime was created by the Symfony maintainers

We have seen now several times that Drupal does things very different than Symfony assumes and they do tend to lock things down for customization. Do we really gain all that much by not writing this bit ourselves? Looking at https://github.com/symfony/symfony/tree/7.3/src/Symfony/Component/Runtime vs how FrankenPHP uses it: https://github.com/php-runtime/frankenphp-symfony/blob/main/src/Runtime.php it feels like we're adding a lot of bloat and taking on assumptions from Symfony that we likely are not prepared to meet internally.

I also see we're using the resolver arguments which reactPhp and FrankenPhp don't seem to use. Search this page https://symfony.com/doc/current/components/runtime.html for What arguments will the end user use?. If we don't need this logic why are we adding this complexity if we don't need it?

Can we start with a simpler Drupal application object that meets what FrankenPHP needs instead of trying to provide the broadest support possible in the first case, especially since the environments we are targeting don't need some of these features?

If we do continue in this direction, have we tested what debugging looks like with this wrapper?

kingdutch’s picture

We have seen now several times that Drupal does things very different than Symfony assumes and they do tend to lock things down for customization. Do we really gain all that much by not writing this bit ourselves? Looking at https://github.com/symfony/symfony/tree/7.3/src/Symfony/Component/Runtime vs how FrankenPHP uses it: https://github.com/php-runtime/frankenphp-symfony/blob/main/src/Runtime.php it feels like we're adding a lot of bloat and taking on assumptions from Symfony that we likely are not prepared to meet internally.

The Runtime here is not the only thing that's being shared and re-used. It's also important to take note of the Runner class which actually uses the Kernel that it gets. The Runtime is only needed to wire up the Runner class (and in turn the Runner class may be re-used by more complex runtimes if more set-up is needed).

I also see we're using the resolver arguments which reactPhp and FrankenPhp don't seem to use. Search this page https://symfony.com/doc/current/components/runtime.html for What arguments will the end user use?. Why are we adding this complexity if we don't need it?

For this I want to point you to the blog post that I wrote (also linked in the IS). This issue started with a discussion on FrankenPHP, but I have no direct use for FrankenPHP myself and see Symfony's Runtime component as a clear path to modularize code that's currently in DrupalKernel because it lacks a better place. The runtime will provide us with a place to move those components.

The other reason that I don't introduce any resolving is to keep the issue focused and reviewable. My immediate follow-ups (though my energy is currently focused here) would be:

Can we start with a simpler Drupal application object that meets what FrankenPHP needs instead of trying to provide the broadest support possible in the first case, especially since the environments we are targeting don't need some of these features?

I've tried to come up with alternatives. However, I would end up recreating what Symfony already does for us and we'd either lose flexibility, or have the same level of flexibility but incompatible to what's already out there.

If we do continue in this direction, have we tested what debugging looks like with this wrapper?

Actually yes :D We discussed this a few months ago as part of the test change that was needed: https://git.drupalcode.org/project/drupal/-/merge_requests/11905/diffs#n...

For easier reference I've recreated a stack trace below

Exception: Issue #3313404 in Drupal\system\Controller\SystemController->systemAdminMenuBlockPage() (line 193 of core/modules/system/src/Controller/SystemController.php).

call_user_func_array(Array, Array) (Line: 123)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->{closure:Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber::wrapControllerExecutionInRenderContext():121}() (Line: 633)
Drupal\Core\Render\Renderer::{closure:Drupal\Core\Render\Renderer::executeInRenderContext():633}()
Fiber->start() (Line: 634)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 121)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext(Array, Array) (Line: 97)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->{closure:Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber::onController():96}() (Line: 183)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 76)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 53)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 28)
Drupal\Core\StackMiddleware\ContentLength->handle(Object, 1, 1) (Line: 118)
Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 92)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 53)
Drupal\Core\StackMiddleware\AjaxPageState->handle(Object, 1, 1) (Line: 54)
Drupal\Core\StackMiddleware\StackedHttpKernel->handle(Object, 1, 1) (Line: 716)
Drupal\Core\DrupalKernel->handle(Object) (Line: 37)
Drupal\Core\Runtime\DrupalKernelRunner->run() (Line: 29)
require('/Users/kingdutch/Projects/drupal-core/vendor/autoload_runtime.php') (Line: 13)
require_once('/Users/kingdutch/Projects/drupal-core/autoload_runtime.php') (Line: 16)
require('/Users/kingdutch/Projects/drupal-core/index.php') (Line: 71)

The main change is that the very bottom of the stack has changed and goes through the runner.

ghost of drupal past’s picture

Let's be clear: in my opinion if running frankenphp requires DrupalRuntime and DrupalKernelRunner then let's do it, yes, reluctantly but yes.

However, those are not particularly hard to understand. The hard to understand part is autoload_runtime.php and the code flow it creates. The problem is not the debug backtrace once Drupal booted but the long and circuitous route it takes to even get to the kernel in this MR.

Now, if we look at the suggested front end controllers, they end with return static function (). Observe the empty argument list. The typical symfony code here has a $context argument but DrupalKernel has different arguments than Symfony Kernel. So the entire resolver functionality is added and run on every Drupal request for absolutely no reason since there's nothing to resolve.

I would suggest the following template for autoload_drupal_runtime.php:

$autoloader = require_once __DIR__.'/autoload.php';
return static function($app) { 
  $runtimeClass = $_SERVER['APP_RUNTIME'] ?? $_ENV['APP_RUNTIME'] ?? %runtime_class%;
  $runtime = new $runtimeClass(($_SERVER['APP_RUNTIME_OPTIONS'] ?? $_ENV['APP_RUNTIME_OPTIONS'] ?? []) + %runtime_options%);
  exit($runtime->getRunner($app)->run());
};

The front end controller is

$runner = require_once 'autoload_drupal_runtime.php'; 
$runner(new DrupalKernel('prod', $autoloader));

This is very significantly more straightforward than re-including the front end controller to get the app closure, resolving it and then running the wrapped closure returned by the resolver just to get the application object back. You can read the code and know what's happening (in the php file, %runtime_class% becomes DrupalRuntime::class, see below how). I very seriously doubt anyone reading $app = $app(...$args); in the original can see $app ends up being the DrupalKernel object when index.php is running...

Should the kernel constructor change in the future this of course can be revisited as needed. But for now, I strongly suggest to keep it as simple as possible while still achieving the desired goals. Which this does because the only thing changing is how the $app object is passed to the runtime.

To use your own template while you do not control the top composer.json file I think the scaffold plugin could rerun the symfony runtime plugin to create your include from your template, just pass $composer in from Handler, I checked, Handler has $this->composer:

$extra = $composer->getPackage()->getExtra();
$savedExtra = $extra;
$extra['runtime']['autoload_template'] = path/to/our/template;
$extra['runtime']['runtime_class'] ??= DrupalRuntime::class;
$composer->getPackage()->setExtra($extra);
$path = path/to/symfony/autoload_runtime.php;
$tmpNam = tempnam('.', 'drupalruntime');
rename($path, $tmpNam); // Not sure whether it's needed, what's the event order? If the Symfony plugin regens this after your handler then this rigmarole is not needed. If needed, check for existence before renaming.
foreach ($composer->getPluginManager()->getPlugins() as $plugin) {
  if ($plugin instanceof Symfony\Component\Runtime\Internal\ComposerPlugin) {
    $plugin->updateAutoloadFile();
  }
}
rename($path, drupalruntime);
rename($tmpNam, $path); // if at all needed make sure to only run if the first rename did
$composer->getPackage()->setExtra($savedExtra);

On to a different topic. I believe DrupalRuntime::getArgument() could be dropped for now. Further, may I ask why is there a DrupalKernelRunner, why the Symfony HttpKernelRunner is not adequate? And if the HttpKernelRunner is adequate then why is SymfonyRuntime not? Could you point out the difference in functionality which makes these classes necessary?

kingdutch’s picture

Re #3313404-43: Use symfony/runtime for less bespoke bootstrap/compatibility with varied runtime environments

Now, if we look at the suggested front end controllers, they end with return static function (). Observe the empty argument list. The typical symfony code here has a $context argument but DrupalKernel has different arguments than Symfony Kernel.

The argument list is empty primarily to preserve the behavior of Drupal at the moment and to keep this issue focused on introducing symfony/runtime without rewriting anything else.

Once this lands #2501901: Allow configuring the environment via an environment variable could be solved with the following diff:

diff --git a/core/lib/Drupal/Core/Runtime/DrupalRuntime.php b/core/lib/Drupal/Core/Runtime/DrupalRuntime.php
index 802b920b445..3acabdaa9ad 100644
--- a/core/lib/Drupal/Core/Runtime/DrupalRuntime.php
+++ b/core/lib/Drupal/Core/Runtime/DrupalRuntime.php
@@ -69,7 +69,7 @@ public function getRunner(?object $application): RunnerInterface {
    */
   protected function getArgument(\ReflectionParameter $parameter, ?string $type): mixed {
     if ($type === 'string' && $parameter->name === 'environment') {
-      return $_ENV[$this->options['env_var_name']];
+      return $_ENV[$this->options['env_var_name']] ?? 'prod';
     }
 
     return parent::getArgument($parameter, $type);
diff --git a/core/scripts/drupal b/core/scripts/drupal
old mode 100644
new mode 100755
diff --git a/index.php b/index.php
index 588c6e5b138..e601076e39f 100644
--- a/index.php
+++ b/index.php
@@ -15,6 +15,6 @@
 $_ENV['APP_RUNTIME'] ??= $_SERVER['APP_RUNTIME'] ?? DrupalRuntime::class;
 require_once 'autoload_runtime.php';
 
-return static function () {
-  return new DrupalKernel('prod', require 'autoload.php');
+return static function (string $environment) {
+  return new DrupalKernel($environment, require 'autoload.php');
 };

That would be the most immediate introduction of using the resolver and wiring up arguments from the environment into the application.

My next step would be #2529170-104: [PP-1] Remove DrupalKernel::initializeRequestGlobals and replace base_root, base_url and base_path with a service which would be the following extra diff to the front-controller and more code added to the Runtime to actually instantiate the context:

diff --git a/index.php b/index.php
index e601076e39f..f9b4758c7a7 100644
--- a/index.php
+++ b/index.php
@@ -8,6 +8,7 @@
  * See COPYRIGHT.txt and LICENSE.txt files in the "core" directory.
  */
 
+use Drupal\Core\AppContext;
 use Drupal\Core\DrupalKernel;
 use Drupal\Core\Runtime\DrupalRuntime;
 
@@ -15,6 +16,6 @@
 $_ENV['APP_RUNTIME'] ??= $_SERVER['APP_RUNTIME'] ?? DrupalRuntime::class;
 require_once 'autoload_runtime.php';
 
-return static function (string $environment) {
-  return new DrupalKernel($environment, require 'autoload.php');
+return static function (string $environment, AppContext $context) {
+  return new DrupalKernel($environment, require 'autoload.php', TRUE, $context);
 };

In your example it's not clear how we'd do this kind of wiring of context from the runtime to the application. Enabling that wiring is a primary reason for introducing symfony/runtime.

On to a different topic. I believe DrupalRuntime::getArgument() could be dropped for now.

I think you're right that $environment is not yet wired in anywhere. I don't see an argument from myself in the commit in which I introduced the code of why I added "environment" specifically. I may have written it as example and accidentally committed it. I think you're right that we could remove it from this issue and introduce it in one of the two issues mentioned above.

Further, may I ask why is there a DrupalKernelRunner, why the Symfony HttpKernelRunner is not adequate? And if the HttpKernelRunner is adequate then why is SymfonyRuntime not? Could you point out the difference in functionality which makes these classes necessary?

You may absolutely ask! The commit hinted at it:

The change to `DrupalRuntime` is kept as close to the `SymfonyRuntime`
as possible to ensure consistent behaviour and make switching to that
runtime in the future easier. The switching is not done as part of this
issue since the SymfonyRuntime has a constructor which does a bunch of
runtime configuration that is easier to discuss in a separate issue.

Symfony's HttpKernelRunner configures some output buffering in various scenarios that Drupal currently doesn't do. It was not entirely clear to me what effect that would have on end-user applications and may cause unexpected behaviour. I erred on the cautious side and made sure we preserved Drupal's behaviour rather than adding that change into this issue for discussion.

Additionally the SymfonyRuntime class sets the error handler option which is then registered by the GenericRuntime. This would cause a change in behaviour of the default error handling from Drupal. This may be beneficial, but is again a bigger change than is necessary to introduce the Runtime component itself.

By introducing the Drupal specific classes we ensure that we make the minimum number of changes to introduce the component and the possibilities it provides. End-users are then free to experiment with swapping out Symfony's Runtime and using the HttpKernelRunner from Symfony. However, that then requires an active choice from site maintainers so that we don't break their sites unexpectedly.

If those things are things we want to change then we can go through a deprecation flow and educate site maintainers on how to opt-in.

kingdutch’s picture

Re #3313404-37: Use symfony/runtime for less bespoke bootstrap/compatibility with varied runtime environments:

In my previous comment (#3313404-40: Use symfony/runtime for less bespoke bootstrap/compatibility with varied runtime environments) I missed the use of putenv in getInput, though I'd argue that in this set-up the code (copied from the SymfonyRuntime class) does the most defensive thing it can: it sets using putenv, sets $_SERVER, and sets $_ENV, for the broadest set of compatibility.

This is used in console applications where explicit user flags should overwrite application settings. The understanding for those kinds of applications is also that any forking would happen after the runner has been kicked off, which means that the environment would be correct before that happens.

My argument of allowing $_ENV use in other places if it's available (since there are legit EGCPS use cases) remains valid IMO.

znerol’s picture

Thanks for #44, it puts the change nicely into a broader perspective.

The front controller (especially the main entry point index.php) could be viewed as application code. It would be great if subsequent refactoring could be made without changing it anymore. It might be possible to achieve this by using the following pattern:

return static function (AppContext $context) {
  return new DrupalKernel($context, require 'autoload.php');
};

The AppContext value object could start out as a very basic class only containing $environment = 'prod'. The DrupalKernel constructor can be changed to accept string|AppContext as its first parameter. If the caller passes the environment string, it is used as is, if the caller passes AppContext, the environment string is taken from there. Nothing else.

Then we can figure out the rest in subsequent refactorings without having to touch the front controllers anymore.


Regarding #45 and putenv: If that code path is called exclusively on the CLI, then this is fine.

Regarding $_SERVER and $_ENV: if it is unacceptable to drop $_ENV, then at least ensure that $_SERVER is used as the primary data source and use $_ENV only as a fallback (like its done in the symfony/runtime component):

% git grep '\$_SERVER.*??=' src/Symfony/Component/Runtime/
src/Symfony/Component/Runtime/Internal/autoload_runtime.template:if (is_string($_SERVER['APP_RUNTIME_OPTIONS'] ??= $_ENV['APP_RUNTIME_OPTIONS'] ?? [])) {
src/Symfony/Component/Runtime/Internal/autoload_runtime.template:$_SERVER['APP_RUNTIME'] ??= $_ENV['APP_RUNTIME'] ?? %runtime_class%;
src/Symfony/Component/Runtime/SymfonyRuntime.php:            $_SERVER[$envKey] ??= $_ENV[$envKey] ?? 'dev';
src/Symfony/Component/Runtime/SymfonyRuntime.php:            $_SERVER[$debugKey] ??= $_ENV[$debugKey] ?? !\in_array($_SERVER[$envKey], (array) ($options['prod_envs'] ?? ['prod']), true);
src/Symfony/Component/Runtime/Tests/phpt/autoload.php:$_SERVER['APP_RUNTIME_OPTIONS'] ??= [];
ghost of drupal past’s picture

So to convince you this will not be necessary in the future now I need to write the skeletons of all followups right here and now? There's no way to go ahead with a simple version and change if necessary.

fine.

At this point of bootstrap there is nothing but the superglobals. There's just nothing else. Sure, you can make a Request out of them with Request::createFromGlobals() but that doesn't change anything significantly. The only code possible is custom code pulling out something from these -- we can see this in DrupalRuntime::getArgument(). But instead of putting such code inside the runtime, just make a factory for DrupalKernel.

public static function create() {
  $environment = $_ENV[whatever] ?? prod;
  $request = Request::createFromGlobals(); // This is just a fancy new call, no problems calling it twice
   // copy initializeRequestGlobals
   // wrap the variables it creates in a value object and pass it in.
}

There is no change in what amount of code you need to write, all you would write into DrupalRuntime::getArgument(), put it into the factory instead.

I agree with keeping DrupalRuntime for the constructor override yay for less code to run. But using HttpKernelRunner won't introduce a change in behavior, those lines of code are copied out from Response::send() in order for newer Symfony to keep the behavior of older versions, I suspect eventually Response will drop them. Using HttpKernelRunner has a huge advantage: php-runtime/frankenphp-symfony inlines it and by not having a custom one you can ensure Drupal will stay compatible with it. Having a custom runtime class is irrelevant as that's not used for frankenphp or whatever.

Now the frankenphp or w/e runtime not using DrupalRuntime shows a huge advantage of not baking Drupal specific logic in DrupalRuntime: once DrupalKernel becomes reentrant you can just set the APP_RUNTIME env var to switch to an existing symfony runtime class and it just works. If your custom code is in DrupalRuntime then you need to write a Drupal specific version of frankenphp, swoole etc. Now what if it just worked with the existing Symfony runtimes...?

In yet other words: Drupal needs to work with (new $runtimeClass)->getRunner($app)->run();. This is the ecosystem part and both of us would dearly love if Drupal just worked with. Our disagreement is on how to create $app. I believe my way is equal effort to write, significantly easier to understand and provides much better compatibility with the ecosystem.

el7cosmos’s picture

+1 for SymfonyRuntime as default.

Additionally the SymfonyRuntime class sets the error handler option which is then registered by the GenericRuntime. This would cause a change in behaviour of the default error handling from Drupal. This may be beneficial, but is again a bigger change than is necessary to introduce the Runtime component itself.

I believe we can set the error_handler to false if wanted to.

nicxvan’s picture

I really think this needs an alternate MR with the other approach so we can compare.

ghost of drupal past’s picture

tl;dr: the recommended way to write front end controllers with symfony/runtime using its resolver introduces unnecessary indirection for Drupal. MR #14161 replaces this with an explicit glue object that preserves runtime compatibility (Roadrunner, FrankenPHP, etc.) while eliminating indirection and magic.

Before diving in, I'd like to thank kingdutch for his hard work, I have added very little here, mostly just shuffled some code around. And a disclaimer: while a tag1 client currently sponsors me to experiment with Roadrunner+Drupal to facilitate temporal.io integration this work doesn't favor roadrunner in any way or form.

To recap, symfony/runtime provides three mechanisms:

  1. Helper to create an app
  2. Selection of a runtime. I reviewed the four runtimes from php-runtime/runtime which provide runners for HttpKernelInterface: frankenphp, roadrunner, bref, swoole.
  3. Running the app.

We have no disagreements in #2 and #3. These are a requirement for the alternative app runners listed above. Where we do have a disagreement is in #1. Consider this (cut down) excerpt from the dex script from CLI entry point in Drupal Core (Dex):

require_once 'autoload_runtime.php';
return static function (array $context): Application {
  $baseDomain = $context['HOST'] ?? NULL;
  return $app;
};

The problems are clear: why does a front controller return a closure? How does it even run and what is $context? The answer: the front controller is re-included from autoload_runtime.php, and the parameters of the returned closure are later resolved through layers of indirection in the runtime’s getArgument method. As an example of the layers, check out ClosureResolver. An example of getArgument is included in MR #11905, this resolves a parameter called $environment allowing the first argument of DrupalKernel finally to be unhardwired from prod:

protected function getArgument(\ReflectionParameter $parameter, ?string $type): mixed {
    if ($type === 'string' && $parameter->name === 'environment') {
      return $_ENV[$this->options['env_var_name']];
    }
    return parent::getArgument($parameter, $type);
}

As you can see, the actual logic to figure out environment is standalone and doesn't rely on GenericRuntime which is no surprise because it doesn't provide anything that would help with such logic. Further, all GenericRuntime::getArgument itself has is a little code which similarly extracts values from superglobals. And none of the app runtimes listed above has a getArgument method. Given all that, it's very hard to see what all the added complexity buys Drupal.

Here's how the same code would look with MR #14161:

$glue = require_once 'autoload_runtime.php';
$baseDomain = $glue->appContext->getFromContext('HOST');
$glue($app);

We could compare index.php as well:

require_once 'autoload_runtime.php';

return static function (AppContext $appContext) {
  return new DrupalKernel($appContext, require 'autoload.php');
};

vs

$glue = require_once 'autoload_runtime.php';
$glue(new DrupalKernel($glue->appContext, $glue->autoloader));

(I called this glue because it glues symfony/runtime and Drupal together.)

This autoload_runtime.php contains a small anonymous class which runs the same code sans resolver as the Symfony autoload_runtime.php script making sure compatibility is kept with the ecosystem. The glue is an object so it can supply an AppContext and an autoloader to DrupalKernel, the three lines of code lifted from Symfony is split between __construct and __invoke. There's a lot less code to run and understand. There are no variables which just… appear as if from nowhere, there's no indirection, no closures.

The code figuring out the environment that was in DrupalRuntime::getArgument has been moved to AppContext::getEnvironment. Any future code which would figure out such pre-bootstrap values can be added to new methods on AppContext just as well. There's no difference in the logic, the only difference is how this logic is invoked and how the value is passed back. Let's see an example. I used AppContext::getFromContext which doesn't exist yet, it still needs to be added here or in a follow-up but it's super simple and it's worth taking a look. Everything except the return is copied from GeneralRuntime::getArgument:

public function getFromContext(string $key, $default = NULL) {
  $context = $_SERVER;
  if ($_ENV && !isset($_SERVER['PATH']) && !isset($_SERVER['Path'])) {
    $context += $_ENV;
  }
  return $context[$key] ?? $default;
}

So when you look at the first version of dex, you need to know $context has been set by symfony/runtime to be able to figure out what it is. And even if you do and read the documentation you will find it's "This is the same as $_SERVER + $_ENV" which, as we can see, is not always true. Finding out why $_ENV is ignored in some truly rare cases in the first version requires deep knowledge of symfony/runtime to find this code buried in GeneralRuntime::getArgument while in the second version it is immediately obvious where it comes from. Explicit is better than implicit. Even in a method this short, magic bites.

To close: all this is just for the core supplied front controllers. Nothing stops you from writing a Symfony style one.

And while I am sure MR #14161 is far better, should MR #11905 be chosen the next steps for that branch is removing all logic from DrupalRuntime to arrive to the same class as used in MR #14161: the very point of symfony/runtime is that the end user can pick a different app runtime and if you kept the logic in DrupalRuntime then you would need to write a separate runtime for each of the app servers, copying one half of DrupalRuntime, one half of the existing app runtime. To achieve this:

  1. Take AppContext from #14161.
  2. Introduce a Symfony\Runtime\Drupal\Core\AppContextRuntime with a single getArgument method which resolves the Drupal\Core\AppContext type. (See GenericRuntime::getRuntime on how this works.) This removes the need for DrupalRuntime::getArgument The index.php example shown is after this step, I showed it for a chance in equal comparison. Also, we might want to include this even in #14161 to facilitate custom Symfony-style front end controllers.
  3. Use the Symfony http runner. The Drupal HTTP runner was introduced to keep behavior but it turns out the Symfony httpfoundation component already runs the exact same code as the runner so it is not necessary. And for app runtime compatibility it is not desirable either, the app runtimes listed above provide their own runners which know how to run a kernel which implements HttpKernelInterface and TerminableInterface. This removes the need for DrupalRuntime::getRunner.

ghost of drupal past’s picture

Issue summary: View changes
StatusFileSize
new166 bytes
new144 bytes
ghost of drupal past’s picture

ghost of drupal past’s picture

Issue summary: View changes
ghost of drupal past’s picture

Issue summary: View changes
ghost of drupal past’s picture

Issue summary: View changes

nicxvan changed the visibility of the branch 3313404-use-symfonyruntime to hidden.

nicxvan’s picture

I will admit that a lot of this is new to me.

I do find many of the comments in 50 persuasive.

I reviewed both and where the changes were similar I compared them.

The code changes in 14161 were consistently smaller and less complex which I think is a benefit here.

I think it also shows that the argument resolving is not necessary, if a future state needs that we could always add it incrementally there.

14161 has my vote, but would love to see @kingdutch's review.

Also most of not all of the review comments on the original MR seem to be addressed in the latest MR as well.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new9.67 KB

The Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

ghost of drupal past’s picture

Status: Needs work » Needs review
Issue tags: +no-needs-review-bot

Both branches pass. The bot is mistaken.

ghost of drupal past’s picture

Note if my branch is chosen the composer plugin described and drafted in #3567080: Store settings.php outside of docroot by default and offered for use in #3562700: Allow certain Composer packages to register installer-specific services could be used here as well instead of the custom one.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.

kingdutch’s picture

Update and feedback addressed

I've updated my original PR with some of the feedback from ghost of drupal past to eliminate code we were duplicating from Symfony.

Most notably DrupalRuntime is now extending SymfonyRuntime which reduces the code duplication. I've set some default options to preserve the current behaviour of Drupal; in a follow-up issue we can decide to switch over to the SymfonyRuntime behaviour (or people can already opt-in themselves). This includes disabling dotenv and Symfony's error handler.

ghost of drupal past pointed out that Symfony's HttpFoundation Response object already included the same flush logic that SymfonyRunner has. So indeed we can easily use SymfonyRunner without any difference in behaviour. That's great and removes the need for DrupalHttpRunner!

Why DrupalRuntime and parameter provisioning are non-optional for me

Although I have nothing to offer in the fact that for people who come from older versions of Drupal it may feel weird that a front-controller only returns a function rather than running code procedurally. For me this is not much different than a main entry-point found in other languages such as C, Rust, and Java.

I want to pick out two remarks and reply to them because I think they illustrate to me that we're working towards two very different goals with the two pull requests.

ghost of drupal past stated:

Now, if we look at the suggested front end controllers, they end with return static function (). Observe the empty argument list. The typical symfony code here has a $context argument but DrupalKernel has different arguments than Symfony Kernel. So the entire resolver functionality is added and run on every Drupal request for absolutely no reason since there's nothing to resolve.

Nicxvan stated:

I think it also shows that the argument resolving is not necessary, if a future state needs that we could always add it incrementally there.

For me enabling the use of things like FrankenPHP is not the main goal of this issue but a bonus. The main purpose of this issue for me is to enable removing the vast amount of Runtime set-up work that Drupal currently has in DrupalKernel. Comparing the example applications for the runtime which don't need a lot of set-up with Drupal is comparing apples and oranges.

I've previously discussed this in the blogpost that I wrote but I believe the following things are currently responsibilities in DrupalKernel that shouldn't be there:

  • DrupalKernel::guessApplicationRoot
  • DrupalKernel::findSitePath
  • DrupalKernel::initializeSettings
  • DrupalKernel::loadLegacyIncludes
  • DrupalKernel::bootEnvironment (it literally has as comment in DrupalKernel::handle "Ensure sane PHP environment variables." which is synonym to "properly set-up the runtime")
  • DrupalKernel::initializeRequestGlobals

These are all logic that over the years have been pushed into DrupalKernel because we didn't want them in our front-controllers but they did need to happen before we called DrupalKernel::handle to make sure that a Drupal application works in a predictable environment.

Many of these functions also represent logic that needs to be invoked for other Drupal related applications (like Drush or the newer Drupal console) even if they might not need a kernel for request handling. We can see this in how prevalent DrupalKernel is in our codebase as it's essentially a god-class for all bootstrappable logic.

This set-up provides us two issues for improving how Drupal works:

  1. Any application that has a different (or no) request model (like Drush) needs to pick and choose what it gets from DrupalKernel so that its environment is correct and jump through hoops to get there
  2. The only way for developers to experiment with alternatives to these is to patch core; since DrupalKernel is so often referenced directly that it can not easily be swapped out

What the DrupalRuntime and argument resolution provides us is a way for us to move these responsibilities out of DrupalKernel in its current form and then allow contrib to experiment with improvements or provide substitutes that might make sense for their own application but not for Drupal core; without having to patch Drupal.

For example we may move a lot of these functions into DrupalRuntime and let DrupalKernel get them from the front-controller:

require_once 'autoload_runtime.php';

return static function (string $environment, ClassLoader $class_loader, string $app_root, string $site_path, Settings $settings) {
  return new DrupalKernel($environment, $class_loader, FALSE, $app_root, $site_path, $settings);
};

That would be a first step to move the logic out of DrupalKernel and make it easily available to any type of application.

With that step out of the way; anyone could overwrite DrupalRuntime in their own application, and change how that logic works. For example, Drupal's multi-site functionality could be entirely moved to a DrupalMultisiteRuntime and be opt-in. Similarly by changing being able to swap out the application root logic we can experiment with resolving blockers around making Drupal run from vendor/ rather than requiring Drupal's strict file layout on disk.

With the pull request as I originally proposed and have since improved with everyone's feedback, I see a clear path forward of not only enabling those alternative runtimes like FrankenPHP but also in simplifying DrupalKernel and making the logic that's currently locked up in it available to developers more easily. I'm also excited to use those new possibilities to start untangling DrupalKernel. Additionally when Drupal says "Drupal uses Symfony Runtime" then this is done in a way that's also understandable for a developer who might be used to Symfony but not to Drupal.

With the pull request as proposed by ghost of drupal past I see that we can start using other runtimes like FrankenPHP. However, I don't see a similar path for how we can move logic from DrupalKernel. We could move it into the AppContext class, but that's again a class that's hardcoded in some logic from Drupal and not easy to switch out.

For me there's real power in Symfony's getArgument logic and I think we'd be remiss if we left it out of scope.

ghost of drupal past changed the visibility of the branch drupal-runtime to hidden.

ghost of drupal past’s picture

I have hidden the drupal-runtime branch, please disregard #50 and sorry for holding up the issue.

nicxvan’s picture

I just want to leave a quick comment here for wayfinding: chx put together an issue here: #3576593: Create pre-bootstrap extension mechanism to hopefully begin address the goal @kingdutch posted around DrupalKernel.

    DrupalKernel::guessApplicationRoot
    DrupalKernel::findSitePath
    DrupalKernel::initializeSettings
    DrupalKernel::loadLegacyIncludes
    DrupalKernel::bootEnvironment (it literally has as comment in DrupalKernel::handle "Ensure sane PHP environment variables." which is synonym to "properly set-up the runtime")
    DrupalKernel::initializeRequestGlobals

I still have other thoughts on 63, but haven't quite found the words to formulate them yet.

davidwbarratt’s picture

Do we think this will land by Drupal 12?

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new98 bytes

The Needs Review Queue Bot tested this issue. The merge request has merge conflicts and cannot be merged. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

xjm’s picture

Since #3313404: Use symfony/runtime for less bespoke bootstrap/compatibility with varied runtime environments adds an additional usecase for the symfony/runtime dependency (which we already signed off on for this issue in #32), that approach would seem to result in less duplicated code and improved maintainability.

xjm’s picture

Issue tags: +DevDaysAthens2026
kim.pepper’s picture

@xjm re: #69 did you mean to link to this issue or another one?

alexpott made their first commit to this issue’s fork.

kingdutch’s picture

Status: Needs work » Needs review

Pipelines are green, there's currently no open feedback.

The permission issue question by larowlan was addressed by Chx: Composer already requires write access to those folders.

During Dev Days I added types to the new Composer scaffold class, but besides that the logic (e.g. around file system handling) is the same as for the existing one.

Only thing that would need to happen if the proposed PR is approved by core maintainers; is to make sure that #3552922: [PP-1]Add symfony/runtime dependency to core is merged first and the dependency commits are rebased/removed from this.

mradcliffe’s picture

Issue summary: View changes

I removed the remaining task since that was answered in #63 and #65. I added KingDutch’s statement about the purpose of the issue in #63 to the proposed resolution section.

I read through both change records. And have a few questions:

The deprecation messages mention to use "autoloader.php" the "Accessing the autoload global is deprecated" change record mentions to use "vendor/autoload.php". Is this the same? Should it be autoload_runtime.php instead as mentioned in "Drupal now uses symfony/runtime for bootstrap separation"?

kingdutch’s picture

Apparently #3552922: [PP-1]Add symfony/runtime dependency to core was never added as related issue, even though that's where the dependency would get added that is then used in the proposed MR.

The deprecation messages mention to use "autoloader.php" the "Accessing the autoload global is deprecated" change record mentions to use "vendor/autoload.php". Is this the same?

Yes, that's an error in the deprecation message. The filename is "autoload.php" and it contains the autoloader. I confused the concepts and have pushed a fix.

Should it be autoload_runtime.php instead as mentioned in "Drupal now uses symfony/runtime for bootstrap separation"?

The autoload_runtime.php file contains the Symfony runtime that performs logic to execute your front-controller similar to main entry point of languages like C/Java.

The autoload.php file contains the autoloader as dumped by Composer which knows how to look up a class when it's used and load the PHP file that it's contained in.

Thus autoload.php is the correct thing to reference when talking about the global that's being deprecated.

mradcliffe’s picture

Thank you for the clarification.

I did a manual test of merging both #3453474: CLI entry point in Drupal Core and #3313404: Use symfony/runtime for less bespoke bootstrap/compatibility with varied runtime environments together. I left a couple of comments about some differences between the two for the common file they touch. After resolving conflicts, things were still working well for CLI commands including running both php web/core/scripts/drupal and vendor/bin/dr.

mradcliffe’s picture

Status: Needs review » Reviewed & tested by the community

I am going to be bold and RTBC this.