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:
- #3051459: Replace error and exception handlers with implementation from Symfony ErrorHandler component
- #2218651: [meta] Make Drupal compatible with persistent app servers like ReactPHP, PHP-PM, PHPFastCGI, FrankenPHP, Swoole
- #3446545: Allow specifying the environment for the kernel using an environment variables
- #1537198: Add a Production/Development toggle
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
| Comment | File | Size | Author |
|---|---|---|---|
| #20 | 3313404-nr-bot.txt | 91 bytes | needs-review-queue-bot |
Issue fork drupal-3313404
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
Comment #2
bradjones1Comment #4
andypostIt is required to run Drupal using https://github.com/dunglas/frankenphp-drupal/issues/1 to get the best perf
Comment #5
el7cosmosI 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)
Comment #6
andypostOne more report of usage!
https://github.com/dunglas/frankenphp-drupal/issues/1#issuecomment-17501...
Comment #7
andypostComment #8
andypostComment #9
dpiAnother +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
Comment #10
renrhaf+1 it would allow to use worker mode to boost performances considerably
Comment #13
kingdutchTook some force-pushes (one day I'll get FunctionalTests working locally again) but the MR is now green across the board :raised_hands:
Comment #14
kingdutchUpdated the issue summary to help reviewers.
Comment #15
kingdutchForgot to snip a reformatted paragraph, whoops.
Comment #16
nicxvan commentedFor a start this will likely need framework manager review.
Comment #17
ghost of drupal pastI 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.
Comment #18
donquixote commentedFor 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.
Comment #19
kingdutchThe 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:
bootEnvironmentThis 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 callingEventLoop::runfrom within anEventLoop::runcall 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
handleand the end ofterminate. 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 ifEventLoop::runis buried within the Drupal Kernel. The logical conclusion for that then is that theEventLoop::runcalls should be placed outside of the Kernel, but this would require it to be placed inindex.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 includeEventLoop::runin the right location for their calls of Drupal to work.If we look at the list of responsibilities for
DrupalKernelthen 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:
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
InstallCommandandServerCommand, both have abootmethod which goes through steps 1-3 outlined at the start of this comment (andRecipeCommandandRecipeInfoCommanddo the same throughBootableCommandTrait).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.
DrupalKernel::bootEnvironmentintoDrupalRuntime::__construct(different environments can swap out the runtime if needed): #2690035: [PP-1] Extract DrupalKernel::bootEnvironment into SAPI adapterDrupalKernel::findSitePathto 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.Settings::initializefromDrupalKernel::initializeSettingsand make the settings available as front-controller argument from ourDrupalRuntime:getArgumentas well. That would make it trivial to write a Drush-like application that reads settingsI think there's more possible. For example some of the environment is now set based on
drupal_valid_test_uabut 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
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.
Comment #20
needs-review-queue-bot commentedThe 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.
Comment #21
kingdutchI'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).Comment #22
kingdutchThe 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.
Comment #23
needs-review-queue-bot commentedThe 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.
Comment #24
kingdutchCoding 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.
Comment #25
needs-review-queue-bot commentedThe 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.
Comment #26
kingdutchPHPUnit doesn't use the repository root as working directory. This caused the PHPUnit test to fail to find the
autoload_runtime.phpfile. To solve this the paths for the CLI front-controllers toautoload_runtime.phpshould be absolute, similar toautoload.phpalready being absolute in these scripts in the past.Comment #27
needs-review-queue-bot commentedThe 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.
Comment #28
kingdutchAccidentally committed the file mode changes for the scripts that I needed for testing :) Reverted those files back to non-executable.
Comment #29
needs-review-queue-bot commentedThe 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.
Comment #31
shalini_jha commentedMoving this back to NR.
Comment #32
catchDiscussed 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.
Comment #33
kingdutchWe 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.
Comment #35
mxr576\o/
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.
Comment #36
kingdutchThe 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.
Comment #37
znerol commentedThe php ini value for variables_order is set to
GPCSby default in many (most?) Linux distros. That means that environment variables are not parsed into the$_ENVsuperglobal in many cases. They are still available from$_SERVERthough.The
getenv()andputenv()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,getenvandputenvand exclusively rely on$_SERVERas an environment variable source - at least initially.Comment #38
mxr576I agree and had the same feeling as well, but I think this change definitely worth a proper introduction (and celebrating).
Comment #39
andypostStrong ++ as requirement for FrankenPHP which is ZTS and help to #3437187: Add Caddyfile configuration
Comment #40
kingdutchIt'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
putenvfrom SymfonyRuntime, which makes this question relevant. Addressed in #3313404-45: Use symfony/runtime for less bespoke bootstrap/compatibility with varied runtime environmentsThe 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.
Even Drupal's own docker image uses
EGPCSwhen 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$_ENVthat 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
$_ENVsupport in Symfony to ease developer's lives, https://github.com/symfony/symfony/pull/43833, mentioned that getting$_ENVinto$_SERVERis not easy in all circumstances.Comment #41
nicxvan commentedI'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 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?
Comment #42
kingdutchThe 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).
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:
'prod'valueI'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.
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
The main change is that the very bottom of the stack has changed and goes through the runner.
Comment #43
ghost of drupal pastLet'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$contextargument butDrupalKernelhas different arguments than SymfonyKernel. 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:
The front end controller is
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:
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?
Comment #44
kingdutchRe #3313404-43: Use symfony/runtime for less bespoke bootstrap/compatibility with varied runtime environments
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:
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:
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.
I think you're right that
$environmentis 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.You may absolutely ask! The commit hinted at it:
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.
Comment #45
kingdutchRe #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
putenvingetInput, 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
$_ENVuse in other places if it's available (since there are legit EGCPS use cases) remains valid IMO.Comment #46
znerol commentedThanks 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:The
AppContextvalue object could start out as a very basic class only containing$environment = 'prod'. TheDrupalKernelconstructor can be changed to acceptstring|AppContextas its first parameter. If the caller passes the environment string, it is used as is, if the caller passesAppContext, 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
$_SERVERand$_ENV: if it is unacceptable to drop$_ENV, then at least ensure that$_SERVERis used as the primary data source and use$_ENVonly as a fallback (like its done in thesymfony/runtimecomponent):Comment #47
ghost of drupal pastSo 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
Requestout of them withRequest::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.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.Comment #48
el7cosmos+1 for
SymfonyRuntimeas default.I believe we can set the error_handler to false if wanted to.
Comment #49
nicxvan commentedI really think this needs an alternate MR with the other approach so we can compare.
Comment #50
ghost of drupal pasttl;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:
HttpKernelInterface: frankenphp, roadrunner, bref, swoole.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):
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 fromautoload_runtime.php, and the parameters of the returned closure are later resolved through layers of indirection in the runtime’sgetArgumentmethod. As an example of the layers, check out ClosureResolver. An example ofgetArgumentis included in MR #11905, this resolves a parameter called$environmentallowing the first argument ofDrupalKernelfinally to be unhardwired fromprod:As you can see, the actual logic to figure out
environmentis standalone and doesn't rely onGenericRuntimewhich is no surprise because it doesn't provide anything that would help with such logic. Further, allGenericRuntime::getArgumentitself has is a little code which similarly extracts values from superglobals. And none of the app runtimes listed above has agetArgumentmethod. 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:
We could compare index.php as well:
vs
(I called this glue because it glues symfony/runtime and Drupal together.)
This
autoload_runtime.phpcontains a small anonymous class which runs the same code sans resolver as the Symfonyautoload_runtime.phpscript 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__constructand__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::getArgumenthas been moved toAppContext::getEnvironment. Any future code which would figure out such pre-bootstrap values can be added to new methods onAppContextjust 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 usedAppContext::getFromContextwhich 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 thereturnis copied fromGeneralRuntime::getArgument:So when you look at the first version of
dex, you need to know$contexthas 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$_ENVis ignored in some truly rare cases in the first version requires deep knowledge of symfony/runtime to find this code buried inGeneralRuntime::getArgumentwhile 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
DrupalRuntimeto 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 inDrupalRuntimethen you would need to write a separate runtime for each of the app servers, copying one half ofDrupalRuntime, one half of the existing app runtime. To achieve this:Symfony\Runtime\Drupal\Core\AppContextRuntimewith a singlegetArgumentmethod which resolves theDrupal\Core\AppContexttype. (SeeGenericRuntime::getRuntimeon how this works.) This removes the need forDrupalRuntime::getArgumentThe 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.HttpKernelInterfaceandTerminableInterface. This removes the need forDrupalRuntime::getRunner.Comment #52
ghost of drupal pastComment #53
ghost of drupal pastComment #54
ghost of drupal pastComment #55
ghost of drupal pastComment #56
ghost of drupal pastComment #58
nicxvan commentedI 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.
Comment #59
needs-review-queue-bot commentedThe 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.
Comment #60
ghost of drupal pastBoth branches pass. The bot is mistaken.
Comment #61
ghost of drupal pastNote 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.
Comment #63
kingdutchUpdate 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
DrupalRuntimeis now extendingSymfonyRuntimewhich 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
mainentry-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:
Nicxvan stated:
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::guessApplicationRootDrupalKernel::findSitePathDrupalKernel::initializeSettingsDrupalKernel::loadLegacyIncludesDrupalKernel::bootEnvironment (it literally has as comment in DrupalKernel::handle "Ensure sane PHP environment variables." which is synonym to "properly set-up the runtime")DrupalKernel::initializeRequestGlobalsThese 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::handleto 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:
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
DrupalRuntimeand letDrupalKernelget them from the front-controller: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
DrupalRuntimein 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 fromvendor/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
getArgumentlogic and I think we'd be remiss if we left it out of scope.Comment #65
ghost of drupal pastI have hidden the drupal-runtime branch, please disregard #50 and sorry for holding up the issue.
Comment #66
nicxvan commentedI 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.
I still have other thoughts on 63, but haven't quite found the words to formulate them yet.
Comment #67
davidwbarratt commentedDo we think this will land by Drupal 12?
Comment #68
needs-review-queue-bot commentedThe 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.
Comment #69
xjmSince #3313404: Use symfony/runtime for less bespoke bootstrap/compatibility with varied runtime environments adds an additional usecase for the
symfony/runtimedependency (which we already signed off on for this issue in #32), that approach would seem to result in less duplicated code and improved maintainability.Comment #70
xjmComment #71
kim.pepper@xjm re: #69 did you mean to link to this issue or another one?
Comment #73
kingdutchPipelines 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.
Comment #74
mradcliffeI 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"?
Comment #75
kingdutchApparently #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.
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.
The
autoload_runtime.phpfile contains the Symfony runtime that performs logic to execute your front-controller similar tomainentry point of languages like C/Java.The
autoload.phpfile 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.phpis the correct thing to reference when talking about the global that's being deprecated.Comment #76
mradcliffeThank 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/drupalandvendor/bin/dr.Comment #77
mradcliffeI am going to be bold and RTBC this.