Problem/Motivation

index.php contains far too much logic. This file is not contained in /core - the more logic that is contained with in it there more susceptible it is to change during the 8.x release cycle. This means it is harder to give simple instructions on upgrading Drupal - i.e download and copy the core directory over should be what we are aiming for.

Proposed resolution

Use the same pattern in the front controller scripts as Symfony (see #13). Especially ensure that DrupalKernel can be instantiated and used in the same way as a Symfony application kernel. Also preserve the option to wrap the application kernel from within the front controller script.

DrupalKernel::createFromRequest() throws exceptions, e.g. when there is an invalid HOST header on the request. In order to get rid of the try...catch block in the front controller it is therefore necessary to replicate the initialization logic in DrupalKernel::handle() such that the kernel can be constructed without the static factory method. In order to make this work it is necessary to extract common initialization logic from DrupalKernel::createFromRequest() into various methods:

  1. Load bootstrap.inc in DrupalKernel::bootEnvironment()
  2. Find site path and initialize settings singleton in new DrupalKernel::initializeSettings() method
  3. Redirect users to install script in DrupalKernel::handle()

This results in the following front controller script:


/**
 * @file
 * The PHP page that serves all page requests on a Drupal installation.
 *
 * All Drupal code is released under the GNU General Public License.
 * See COPYRIGHT.txt and LICENSE.txt files in the "core" directory.
 */

use Drupal\Core\DrupalKernel;
use Symfony\Component\HttpFoundation\Request;

$autoloader = require_once __DIR__ . '/core/vendor/autoload.php';

$kernel = new DrupalKernel('prod', $autoloader);

$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();

$kernel->terminate($request, $response);

Remaining tasks

  • Agree location of logic
  • Review
  • Commit

User interface changes

None

API changes

None.

Beta phase evaluation

Reference: https://www.drupal.org/core/beta-changes
Issue category Task
Issue priority Major
Prioritized changes The main goal of this issue is reduce fragility by making it easier to upgrade Drupal by having less logic outside /core. @alexpott, as a framework manager, agrees that we should make this change before 8.0.0.
Disruption Disruptive for sites with custom front controllers because it will require a BC break, i.e. it would be necessary to manually replicate changes to the new front controller script.
CommentFileSizeAuthor
#124 patch-interdiff.txt1.66 KBznerol
#124 move_all_the_logic_out-2389811-124.patch15.21 KBznerol
#114 interdiff.txt3.14 KBznerol
#114 move_all_the_logic_out-2389811-114.patch15.19 KBznerol
#103 interdiff.txt1.07 KBznerol
#103 move_all_the_logic_out-2389811-103.patch14.14 KBznerol
#103 interdiff.txt1.07 KBznerol
#90 conflict-interdiff.txt1.28 KBznerol
#89 conflict-interdiff.txt1.65 KBznerol
#89 move_all_the_logic_out-2389811-89.patch14.1 KBznerol
#81 interdiff.txt938 bytesznerol
#81 move_all_the_logic_out-2389811-81.patch14.21 KBznerol
#79 interdiff.txt2.54 KBznerol
#79 move_all_the_logic_out-2389811-79.patch14.07 KBznerol
#77 move_all_the_logic_out-2389811-77.patch12.1 KBznerol
#74 move_all_the_logic_out-2389811-74.patch12.1 KBznerol
#72 move_all_the_logic_out-2389811-72.patch12.1 KBmpdonadio
#68 interdiff.txt1.29 KBznerol
#68 move_all_the_logic_out-2389811-68.patch13.32 KBznerol
#66 interdiff.txt1.66 KBznerol
#66 move_all_the_logic_out-2389811-64.patch12.03 KBznerol
#64 interdiff.txt1.66 KBznerol
#64 move_all_the_logic_out-2389811-60.patch12 KBznerol
#61 move_all_the_logic_out-2389811-60.patch12 KBznerol
#51 move_all_the_logic_out-2389811-51.patch10.73 KBznerol
#42 move_all_the_logic_out-2389811-42.patch10.75 KBhussainweb
#42 interdiff-41-42.txt553 byteshussainweb
#41 interdiff.txt527 bytesznerol
#41 move_all_the_logic_out-2389811-41.patch10.75 KBznerol
#36 move_all_the_logic_out-2389811-36.patch10.5 KBmpdonadio
#28 interdiff.txt6.83 KBznerol
#28 2389811-modern-frontcontroller-28.diff9.62 KBznerol
#25 interdiff.txt888 bytesznerol
#25 2389811-modern-frontcontroller-25.diff14.4 KBznerol
#22 interdiff.txt1.84 KBznerol
#22 2389811-modern-frontcontroller-22.diff15.27 KBznerol
#21 interdiff.txt4.07 KBznerol
#21 2389811-modern-frontcontroller-20.diff15.97 KBznerol
#15 move_all_the_logic_out-2389811-14.patch4.82 KBmpdonadio
#14 interdiff.txt2.67 KBznerol
#14 2389811-modern-frontcontroller-13.diff13.43 KBznerol
#13 2389811-modern-frontcontroller.diff12.72 KBznerol
#6 2389811.5.patch3.85 KBalexpott
#6 1-5-interdiff.txt1.05 KBalexpott
#1 2389811.1.patch3.84 KBalexpott
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

alexpott’s picture

Status: Active » Needs review
FileSize
3.84 KB
benjy’s picture

Yes, +1 to this. Putting the logic on \Drupal works for me, I can't think of a better place to put it.

One question, is "frontController" the best we can do? It makes sense but at the same time, it's different to how any other "controller" works in core, eg a class.

Couple of doc things;

  1. +++ b/core/lib/Drupal.php
    @@ -628,4 +633,45 @@ public static function accessManager() {
    +   *   The class loader. Normally Composer's ClassLoader, as included index.php,
    +   *   but may also be decorated; e.g.,
    

    "as included in index.php"

  2. +++ b/core/lib/Drupal.php
    @@ -628,4 +633,45 @@ public static function accessManager() {
    +      // Set the response code manually. Otherwise, this response will default to a
    +      // 200.
    

    Longer than 80chars.

Berdir’s picture

I think we had this a while ago (as a function) but it was reverted at some point.

tstoeckler’s picture

Yes, we had drupal_handle_request() in bootstrap.inc. I don't really care either way, as long as the code can be autoloaded.

cosmicdreams’s picture

@#2: "Front controller" is what the text books say we should name it:

from Wikipedia:

"The Front Controller Pattern is a software design pattern listed in several pattern catalogs. The pattern relates to the design of web applications. It "provides a centralized entry point for handling requests." Front controllers are often used in web applications to implement workflows."

/Drupal::frontcontroller should be our centralized entry point.

alexpott’s picture

FileSize
1.05 KB
3.85 KB

The reason I'm keep to move this out of index.php is that it keeps the instructions for a certain type of user (think dreamhost) simple. We should never really change anything outside of /core once 8.0.0 is out. This means that changing .htaccess, web.config, composer.json, composer.lock or any other file is acceptable and easy to maintain.

I can't think of a better name than frontController() open to suggestions.

mpdonadio’s picture

`frontController` is a noun, which implies that it is an object, or provides access to an object. Nearly everything else in that class does this, and the docblock does say that it is the "Static Service Container wrapper." Functions that don't provide access to objects, like ::l() and ::url() are really just helper functions for the objects.

`\Drupal\Core\DrupalKernel` may be a better place for this, and may also give us a chance to refactor that class / interface into something a little cleaner, and provide a middleware to bootstrap in a different manner if it wants to. I would also suggest `\Drupal\Core\Drupalkernel::processRequest()` and also returning the $response object that gets created.

znerol’s picture

With the current approach it is possible to wrap the application kernel from within the front controller script. Not sure whether this is relevant, now that we have stack middlewares, but they wrap the http kernel, not the application kernel.

Maybe introduce DrupalFrontController which implements the KernelInterface and TerminableInterface

$autoloader = require_once __DIR__ . '/core/vendor/autoload.php';
$front_controller = new DrupalFrontController($autoloader, 'prod');

$request = Request::createFromGlobals();
$response = $front_controller->handle($request);
$response->send();
$front_controller->terminate();

Looking at Request::createFromGlobals() and Response::send() I do not expect them to throw exceptions which could be meaningfully caught with the If you have just changed code message. Exceptions thrown in terminate() cannot influence the output because at this point in time the response is already sent. Therefore we could move the messy try/catch into DrupalFrontController::handle() and even write unit-tests for it.

See also the Symfony SE front controller script.

mpdonadio’s picture

I'll work on this if there is agreement on an approach. #8 seems like a good idea.

peterx’s picture

One common change in Drupal is to move the code out of Web root. The changes are not obvious. They could be made easier by defining the Drupal and Web roots at the start then documenting the change you can make when core is moved outside of Web root.

We do something similar in .htaccess with comments about changing redirections.

I suggest something like the following with a page somewhere on Drupal.org explaining how to change $drupal_root:

$web_root = __DIR__;
$drupal_root = __DIR__;
// $drupal_root = '/home/example/drupal';

... #8 code modified ...
$autoloader = require_once $drupal_root . '/core/vendor/autoload.php';

David_Rothstein’s picture

Title: Move all the logic out of index.php » Move all the logic out of index.php (again)

Déjà vu!

This was previously done earlier in the Drupal 8 cycle (#1831998: Reduce index.php for easier upgrading) but then was slowly whittled away and eventually removed. Given that history, I would suggest adding a code comment to index.php here explaining why we put no actual logic directly in the file - then maybe it will stick this time.

I would also suggest `\Drupal\Core\Drupalkernel::processRequest()` and also returning the $response object that gets created.

That makes more sense to me too, or possibly handleRequest()? - keeping in mind that the earlier issue that did this called it drupal_handle_request(). Either "handle" or "process" sounds good to me.

mpdonadio’s picture

I got bored last night, and wrote up a quick patch. I started down the road in #8, but it became apparent that 95% of KernelInterface wasn't needed, so I made DrupalFrontControllerInterface / DrupalFrontController and wired it up that way. Besides, it keeps the kernel doing kernely things, and the front controller as just the front controller. Just waiting for TestBot to give the OK of the POC patch.

znerol’s picture

I believe we should make it easy to use DrupalKernel in modern code. Therefore let's do the following:

  • Make DrupalKernel::createFromRequest() optional, i.e. move the initialization code to DrupalKernel::handle(). In the long run we probably should get rid of DrupalKernel::createFromRequest() completely.
  • Also move the try/catch block from the front controller to DrupalKernel::handle().

That makes it possible to use the following standard pattern in intex.php (and also http[s].php):

$autoloader = require_once __DIR__ . '/core/vendor/autoload.php';

$kernel = new DrupalKernel('prod', $autoloader);

$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->prepare($request); // <- Edit: removed in #14
$response->send();

$kernel->terminate($request, $response);

As a consequence some of the setup code now is duplicated in prepareLegacyRequest(). On the other hand, the installer-redirection logic now is inside the standard request-response flow. I think we do not need that logic in legacy requests anyway.

znerol’s picture

Fix run-test.sh, also Response::prepare() is not expected to be called from the front controller.

mpdonadio’s picture

Had this done before @znerol posted #13, but was waiting to see if anything major came out of TestBot before I posted it to the issue. I think this will come up green, and the exception handling works with my test scripts.

#13 has some static vs object context problems, so I couldn't try the HTTP exception handling with it, but it looks like the better thing to do.

The last submitted patch, 13: 2389811-modern-frontcontroller.diff, failed testing.

The last submitted patch, 14: 2389811-modern-frontcontroller-13.diff, failed testing.

benjy’s picture

+++ b/index.php
@@ -8,38 +8,13 @@
+// Craete a DrupalFrontController object and use that to process the request

Create is spelt wrong :) Looks pretty good to me.

znerol’s picture

znerol’s picture

Assigned: Unassigned » znerol
Issue tags: +SprintWeekend2015
znerol’s picture

Discussed this with @dawehner and @mpdonadio in IRC, conclusion is that we want to go with approach in #14, i.e. the front controller script should instantiate a kernel. Test failure in ConfigExportUITest is due to adventurous global tainting code in config_export_test. This is easy to rewrite properly. Interdiff is against #14.

znerol’s picture

#21 contains debugging stuff. Rerolling, interdiff is still against #14.

The last submitted patch, 21: 2389811-modern-frontcontroller-20.diff, failed testing.

Status: Needs review » Needs work

The last submitted patch, 22: 2389811-modern-frontcontroller-22.diff, failed testing.

znerol’s picture

Status: Needs work » Needs review
FileSize
14.4 KB
888 bytes

Bumped into #2409247: Generated proxy classes break DrupalKernelTest when running from the UI while trying to fix the test failures in DrupalKernelTest. Let's roll back the changes to that test for the moment.

jibran’s picture

+++ b/core/modules/config/src/Tests/ConfigExportUITest.php
index d0ceccd..0000000
--- a/core/modules/config/tests/config_export_test/config_export_test.info.yml

I think this is rebase gone wrong.

Berdir’s picture

No, just an incomplete interdiff ;)

znerol’s picture

Let's try to reduce code changes such that its not necessary to touch tests.

znerol’s picture

znerol’s picture

Assigned: znerol » Unassigned
Issue summary: View changes
jibran’s picture

+++ b/core/lib/Drupal/Core/DrupalKernel.php
@@ -749,6 +769,10 @@ public static function bootEnvironment() {
+    $core_root = dirname(dirname(dirname(__DIR__)));

Can't we use app variable here? Or is it not available yet?

znerol’s picture

Can't we use app variable here? Or is it not available yet?

bootEnvironment() is static, so regrettably no.

mpdonadio’s picture

Assigned: Unassigned » mpdonadio
Status: Needs review » Needs work
Issue tags: +Needs reroll

This needs a reroll due to #2221699: HTTP_HOST header cannot be trusted. I'll take care of it since I still have my testing scripts handy for checking the things that can't be handled by unit/web testing.

Damien Tournoud’s picture

All this sounds like a regression to me.

@znerol:

I believe we should make it easy to use DrupalKernel in modern code. Therefore let's do the following:

  • Make DrupalKernel::createFromRequest() optional, i.e. move the initialization code to DrupalKernel::handle(). In the long run we probably should get rid of DrupalKernel::createFromRequest() completely.
  • Also move the try/catch block from the front controller to DrupalKernel::handle().

DrupalKernel::createFromRequest() is that way for a reason: our whole processing, the kernel, the container, etc. all depends on the settings, and as a consequence all depend on the request. This is not "old" code (as opposed to "modern"), it is a conscious architecture decision.

If we want to reduce the amount of code in the front controller, the answer is to add a convenience function somewhere, not to butcher the architecture of the kernel.

mpdonadio’s picture

@damz, the patch in #15 is essentially what you described; a helper object rather than a helper method, though.

There was discussion this weekend about which approach was better on IRC, and we decided on this. I forget who was in on it, though.

mpdonadio’s picture

Assigned: mpdonadio » Unassigned
Status: Needs work » Needs review
FileSize
10.5 KB

Think this is good, and should come up green.

znerol’s picture

Issue tags: -Needs reroll

@Damien Tournoud: The very goal of this issue is to get rid of the try...catch in index.php. This is only possible if the front controller only calls code that either does not throw exceptions or is capable of handling them.

Several approaches have been proposed and explored:

  1. Copy paste the whole block into a static function in /Drupal (#1, #6)
  2. Introduce a DrupalFrontController class #8 (implemented in #15)
  3. Pack the logic into DrupalKernel::handle() #13ff

Option 1 is essentially what was there prior to the kernel-bootstrap patch but additionally removes the exception handling from the front controller. Whether or not to bury everything into one static function was discussed during review of said patch and it was a conscious architecture decision to better, bring our front controller closer inline with a Symfony kernel app. (@neclimdul #2016629-227: Refactor bootstrap to better utilize the kernel)

Option 2 is only marginally better in the current implementation. It would be acceptable if DrupalFrontController would implement the Symfony HttpKernelInterface and the TerminableInterface.

Option 3 just moves some bits of the Kernel around such that there is no need for DrupalFrontController at all. My statement about deprecating DrupalKernel::createFromRequest() was a bit premature, because we still need it in various places where it would be inconvenient to follow the front controller pattern.

Damien Tournoud’s picture

Status: Needs review » Needs work

@znerol: Given how DrupalKernel is currently structured, only one site path can be handled in a given instance.

Changing this to allow multiple independent requests to be processed (potentially at the same time) by a single instance of DrupalKernel is a relatively large undertaking, that is extremely far from being done by the current patch.

Looking at the current architecture, it is not even possible to do so properly, because several methods on DrupalKernelInterface are bound to a specific site anyway (->getServiceProviders, ->discoverServiceProviders, ->updateModules, ->handlePageCache, etc.).

That puts option #3 completely off the table. I suggest we go with #2, which sounds like the cleaner given the current state of the DrupalKernel architecture.

znerol’s picture

Status: Needs work » Needs review

Given how DrupalKernel is currently structured, only one site path can be handled in a given instance.

I completely agree. Neither of the three approaches attempts to change that. I assume that you misunderstood option #3 somehow. In fact it does the same as #2 but without introducing an additional front controller class.

Damien Tournoud’s picture

Status: Needs review » Needs work

@znerol: I understood option #3, and I'm saying it's not a practical option at all. The current patch tries to make DrupalKernel handle multiple independent requests and it is utterly insufficient.

Just as an example, in the implementation from #36, ->handle calls ->initializeSettings, which is going to detect the site path based on the request, but none of the rest of the call is actually re-entrant. If ever the site path changes between two requests, things are going to be utterly broken, because the container doesn't support that: the container is not going to be booted again, so you are going to end up with a new site path, but still the old list of modules, the old container, etc.

Let me re-iterate: Given how DrupalKernel is currently structured, only one site path can be handled in a given instance.

Changing that (which #3 is trying to do) is hard, and I suggest option #2 (adding a multi-site DrupalFrontController in front of the current DrupalKernel) is a better idea at this stage.

znerol’s picture

Status: Needs work » Needs review
FileSize
10.75 KB
527 bytes

Thanks for the clarification and the example. It seems to me that reusing the application kernel for multiple requests is a pretty exotic thing to do. I've never encountered a Symfony application which actually tried this. On the other hand, reusing the same http kernel for subrequests is quite common and that's also supported by Drupal.

That said, it is actually easy to protect against the scenario presented in #40. The only thing to do is to throw an exception if anyone tries to set the site path on a booted kernel.

Finally let me point out that even DrupalKernel::createFromRequest() does not protect against crazy people attempting to handle() different requests with the same Kernel.

hussainweb’s picture

It's like the tiniest nitpick ever, but here it goes. I hope it gets the issue going again. I would have really liked this in beta5/6. It would have given me more confidence in planning out the composer approach for building a site I am planning.

dawehner’s picture

Isn't that now a duplicate of an issue tstoeckler was working on recently and got rtbc?

znerol’s picture

@dawehner: Which one do you mean? #2406681: Add an autoload.php in the repo root to control the autoloader of front controllers? I do not see any significant overlap there.

Status: Needs review » Needs work

The last submitted patch, 42: move_all_the_logic_out-2389811-42.patch, failed testing.

Mile23’s picture

The front controller should grab the global and environmental parameters and inject them into the kernel.

The kernel should be an object that encapsulates all the behavior, but which requires that you inject parameters.

Basically the kernel is the object that forces you to start the injection of parameters for everything else.

Hence:

  1. +++ b/core/lib/Drupal/Core/DrupalKernel.php
    @@ -565,8 +541,46 @@ public function terminate(Request $request, Response $response) {
    +        $rebuild_path = $GLOBALS['base_url'] . '/rebuild.php';
    

    Superglobal in the kernel. Nope. :-)

  2. +++ b/core/lib/Drupal/Core/DrupalKernel.php
    @@ -743,6 +757,10 @@ public static function bootEnvironment() {
    +    // Include our bootstrap file.
    +    $core_root = dirname(dirname(dirname(__DIR__)));
    +    require_once $core_root . '/includes/bootstrap.inc';
    

    __DIR__ and require_once in a static kernel method. Nope. :-)

cosmicdreams’s picture

@47:

Are you saying that you would want the base_url and the core_root to be injected?

alexpott’s picture

  1. +++ b/core/lib/Drupal/Core/DrupalKernel.php
    @@ -213,37 +214,9 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
       public static function createFromRequest(Request $request, $class_loader, $environment, $allow_dumping = TRUE) {
    -    // Include our bootstrap file.
    -    $core_root = dirname(dirname(dirname(__DIR__)));
    -    require_once $core_root . '/includes/bootstrap.inc';
    

    @mile23 that is an existing issue.

  2. +++ b/core/lib/Drupal/Core/DrupalKernel.php
    @@ -561,8 +537,46 @@ public function terminate(Request $request, Response $response) {
    +    catch (Exception $e) {
    +      if ($catch === FALSE) {
    +        throw $e;
    +      }
    +      $message = 'If you have just changed code (for example deployed a new module or moved an existing one) read <a href="http://drupal.org/documentation/rebuild">http://drupal.org/documentation/rebuild</a>';
    +      if (Settings::get('rebuild_access', FALSE)) {
    +        $rebuild_path = $GLOBALS['base_url'] . '/rebuild.php';
    +        $message .= " or run the <a href=\"$rebuild_path\">rebuild script</a>";
    +      }
    +
    +      $response = new Response($message, 500);
    +    }
    

    The global is only used if something serious has gone wrong - and this generic exception catch completely breaks Drupal's exception handling so I think this should be removed too.

znerol’s picture

The global is only used if something serious has gone wrong - and this generic exception catch completely breaks Drupal's exception handling so I think this should be removed too.

I disagree. The standard exception handling is not affected by this change because that happens in the http kernel. What we are doing here is moving the stuff from index.php one layer down to DrupalKernel (the application kernel).

znerol’s picture

Status: Needs work » Needs review
FileSize
10.73 KB

Rerolled.

alexpott’s picture

@znerol yep it is unaffected I'm just pointing out that currently we register an exception handler in DrupalKernel (set_exception_handler('_drupal_exception_handler');) and it can never actually be hit because of this catch.

znerol’s picture

@alexpott: I do not quite understand. Does this patch alter the behavior in this regard?

alexpott’s picture

@znerol nope not at all. All I was trying to say is that...

+++ b/core/lib/Drupal/Core/DrupalKernel.php
@@ -561,8 +537,46 @@ public function terminate(Request $request, Response $response) {
+    catch (Exception $e) {
+      if ($catch === FALSE) {
+        throw $e;
+      }
+      $message = 'If you have just changed code (for example deployed a new module or moved an existing one) read <a href="http://drupal.org/documentation/rebuild">http://drupal.org/documentation/rebuild</a>';
+      if (Settings::get('rebuild_access', FALSE)) {
+        $rebuild_path = $GLOBALS['base_url'] . '/rebuild.php';
+        $message .= " or run the <a href=\"$rebuild_path\">rebuild script</a>";
+      }
+
+      $response = new Response($message, 500);
+    }

Is completely wrong both before and after this patch and therefore discussing whether or $GLOBALS['base_url'] should be in DrupalKernel or not is moot.

Crell’s picture

Why is the prepare() being moved inside the Kernel? I don't think I've ever seen that done inside an HttpKernelInterface implementation. (Although I admit I haven't surveyed all Symfony-using projects.) One method call is not a big expectation for the rare person writing a new front controller.

znerol’s picture

Symfony SE front controller script, Symfony HTTP Cache Kernel, laravel front controller script.

IMHO prepare() clearly belongs into the application kernel, and not the front controller script.

znerol’s picture

Also note, that in Symfony prepare() is called from a response listener for uncached requests.

Crell’s picture

Interesting. That must have changed at some point because that didn't used to be the default in Symfony SE. I withdraw my objection.

almaudoh’s picture

znerol’s picture

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

DrupalKernel::createFromRequest changed. The class loader stuff probably should go into the new initializeSettings() method.

znerol’s picture

Priority: Normal » Major
Status: Needs work » Needs review
FileSize
12 KB

I think this is at least major. If we ship with the current crap-loaded front-controller then sites will have a hard time to customize it in a sane way.

znerol’s picture

Issue tags: -Needs reroll

Status: Needs review » Needs work

The last submitted patch, 61: move_all_the_logic_out-2389811-60.patch, failed testing.

znerol’s picture

Status: Needs work » Needs review
FileSize
12 KB
1.66 KB

Status: Needs review » Needs work

The last submitted patch, 64: move_all_the_logic_out-2389811-60.patch, failed testing.

znerol’s picture

Status: Needs work » Needs review
FileSize
12.03 KB
1.66 KB

Uhm, that was the wrong patch. Ignore #64, interdiff is against #61.

Status: Needs review » Needs work

The last submitted patch, 66: move_all_the_logic_out-2389811-64.patch, failed testing.

znerol’s picture

Status: Needs work » Needs review
FileSize
13.32 KB
1.29 KB

Interesting. Seems like TestDiscovery also should be blowing up in HEAD.

Status: Needs review » Needs work

The last submitted patch, 68: move_all_the_logic_out-2389811-68.patch, failed testing.

mpdonadio’s picture

Status: Needs work » Needs review
FileSize
12.1 KB

Status: Needs review » Needs work

The last submitted patch, 72: move_all_the_logic_out-2389811-72.patch, failed testing.

znerol’s picture

Status: Needs work » Needs review
FileSize
12.1 KB

Weird. #72 looks fine. I cannot reproduce the test failures on my local machine.

Reupload.

webchick’s picture


use Drupal\Core\DrupalKernel;
use Symfony\Component\HttpFoundation\Request;

$autoloader = require_once 'autoload.php';

$kernel = new DrupalKernel('prod', $autoloader);

$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();

$kernel->terminate($request, $response);

Ahhhh. :) Much better.

znerol’s picture

alexpott’s picture

Status: Needs review » Needs work

Really liking this change.

+++ b/core/lib/Drupal/Core/DrupalKernel.php
@@ -380,6 +336,9 @@ public static function findSitePath(Request $request, $require_settings = TRUE)
   public function setSitePath($path) {
+    if ($this->booted) {
+      throw new \Exception('Site path cannot be changed after calling boot()');
+    }
     $this->sitePath = $path;
   }

This makes heaps of sense - can we add a unit test for it and should be be throwing a more specific exception?

znerol’s picture

Issue summary: View changes
Status: Needs work » Needs review
FileSize
14.07 KB
2.54 KB

I think we use LogicException in some other places.

alexpott’s picture

Status: Needs review » Needs work

Sorry should have caught this last time.

+++ b/core/lib/Drupal/Core/DrupalKernel.php
@@ -577,8 +536,46 @@ public function terminate(Request $request, Response $response) {
+    catch (Exception $e) {

Needs a leading slash ... we're in a class now :)

znerol’s picture

Status: Needs work » Needs review
FileSize
14.21 KB
938 bytes

Oh, right!

dawehner’s picture

Here is a question, maybe its just dump.

+++ b/core/lib/Drupal/Core/DrupalKernel.php
@@ -778,6 +775,10 @@ public static function bootEnvironment() {
+    $core_root = dirname(dirname(dirname(__DIR__)));
+    require_once $core_root . '/includes/bootstrap.inc';

So we have two places where we call bootEnvironment from: ::handler() and ::createFromRequest(), but both places have a kernel $kernel already. Given that I'm curious why bootEnvironment has to be static, because in case it isn't we could use $this->root instead.

znerol’s picture

bootEnvironment() is also called statically from within rebuild.php and I do not see an obvious way on how to fix that.

dawehner’s picture

Mh right, I just had a look at the patch file for itself, too bad.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

I like the general work forward.

xjm’s picture

Category: Bug report » Task

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 81: move_all_the_logic_out-2389811-81.patch, failed testing.

znerol’s picture

Status: Needs work » Needs review
FileSize
14.1 KB
1.65 KB

Reroll.

znerol’s picture

FileSize
1.28 KB

Proper conflict interdiff.

alexpott’s picture

Status: Needs review » Reviewed & tested by the community

The patch looks good after resolving the conflicts. Thanks @znerol.

Mile23’s picture

<?php

/**
 * @file
 * The PHP page that serves all page requests on a Drupal installation.
 *
 * All Drupal code is released under the GNU General Public License.
 * See COPYRIGHT.txt and LICENSE.txt files in the "core" directory.
 */

use Drupal\Core\DrupalKernel;
use Symfony\Component\HttpFoundation\Request;

$autoloader = require_once 'autoload.php';

$kernel = new DrupalKernel('prod', $autoloader);

$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();

$kernel->terminate($request, $response);

Aw yeah.

cosmicdreams’s picture

Hey, this looks awesome. I don't want to sidetrack things but I have a question that relates to the work here.

Would it be possible / crazy to convert the string ('prod') to a constant in this line:

$kernel = new DrupalKernel('prod', $autoloader);

That way we could pull index.php out of the git repo and have it vary for different environments. Does that make sense?

neclimdul’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/lib/Drupal/Core/DrupalKernel.php
    @@ -213,53 +214,9 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
    +    $kernel->initializeSettings($request);
    
    @@ -576,8 +536,46 @@ public function terminate(Request $request, Response $response) {
    +      $this->initializeSettings($request);
    

    I'm not sure why we're keeping createFromRequest still. If you use it though you'll hit settings initialization twice with is fairly expensive.

  2. +++ b/core/lib/Drupal/Core/DrupalKernel.php
    @@ -576,8 +536,46 @@ public function terminate(Request $request, Response $response) {
    +    // Adapt response headers to the current request.
    +    $response->prepare($request);
    

    Shouldn't this stay in the front controller

neclimdul’s picture

Sorry, I got distracted and forgot to follow up and on reading the issue on the prepare.

I disagree with moving it still. A listener and hard coded into the kernel have different effects on modularity and testing. Moving has no real benefit either.

Mile23’s picture

@cosmicdreams: Would it be possible / crazy to convert the string ('prod') to a constant in this line:

I'm pretty sure this isn't used anywhere other than perhaps simpletest, which has its own kernel subclass anyway.

I'd love to see the environment hooked into something useful, so we can support lightweight functional testing with fixtures, but that's out of scope here, too.

If you have a use-case in mind, create a follow-up, please. :-)

znerol’s picture

I disagree with moving it still. A listener and hard coded into the kernel have different effects on modularity and testing. Moving has no real benefit either.

Not sure about the benefit of that. In which case do we want to swap that out?

fgm’s picture

A case to consider is non-Drush / non-Console CLI applications, (e.g. Beanstalkd queue runner), for which anything HTTP-related is entirely irrelevant, and need to build fake headers for the CLI "request" in their own bootstrap.

The less HTTP they have to fake, the simpler and more robust it is for these.

znerol’s picture

Status: Needs work » Needs review

Re #94.1:

I'm not sure why we're keeping createFromRequest still. If you use it though you'll hit settings initialization twice with is fairly expensive.

We use that in the following cases for legacy scripts and in tests. In both cases it is desirable to initialize the settings.

$ git grep -l DrupalKernel::createFromRequest
core/authorize.php
core/modules/simpletest/src/BrowserTestBase.php
core/modules/simpletest/src/InstallerTestBase.php
core/modules/simpletest/src/WebTestBase.php
core/modules/statistics/statistics.php
core/modules/system/src/Tests/DrupalKernel/DrupalKernelTest.php
core/scripts/password-hash.sh

Given that createFromRequest() did not change in this patch but merely DrupalKernel::handle(), the important question is whether the latter method is used anywhere on an application kernel instance created with the static factory instead of directly with constructor. In order to find those cases, it is necessary to filter out occurrences where the http kernel is invoked (i.e. subrequests etc.).

$ git grep -i -- 'kernel.*->handle(' | grep -vEi 'http_?kernel'
core/modules/system/src/Tests/Routing/ExceptionHandlingTest.php:    $response = $kernel->handle($request);
core/modules/system/src/Tests/Routing/ExceptionHandlingTest.php:    $response = $kernel->handle($request);
core/modules/system/src/Tests/Routing/ExceptionHandlingTest.php:    $response = $kernel->handle($request)->prepare($request);
core/modules/system/src/Tests/Routing/ExceptionHandlingTest.php:    $response = $kernel->handle($request)->prepare($request);
core/modules/system/src/Tests/Routing/ExceptionHandlingTest.php:    $response = $kernel->handle($request)->prepare($request);
core/modules/system/tests/http.php:$response = $kernel->handle($request);
core/modules/system/tests/https.php:$response = $kernel->handle($request);
index.php:$response = $kernel->handle($request);

The only questionable file ExceptionHandlingTest.php is a false positive.

There is nothing in core which calls DrupalKernel::createFromRequest() followed by handle(). The reason why the static factory is still necessary is its usage in legacy scripts and drush.

Re #94.2:

Shouldn't this stay in the front controller

The purpose of the Response::prepare() is to tweak

[...] the Response to ensure that it is compliant with RFC 2616. Most of the changes are based on the Request that is "associated" with this Response.

For example it removes the response body if the incoming request used the HEAD verb. It's obvious that this should not be done before storing the response in the internal page cache. Therefore a response subscriber would only work for us if the page cache is disabled otherwise another mechanism is necessary. It would be possible to add another early middleware in order to make it pluggable.

I still do not think it is necessary to make it pluggable. If there is a technical reason to do it, then I'd prefer to move the call back to the front controller, because adding a new middleware for that single function call seems like overkill.

@all: Especially those with Symfony experience, please help us decide whether there are situations where running $response->prepare($request) inside DrupalKernel::handle() would be wrong.

Mile23’s picture

@znerol #99: There is nothing in core which calls DrupalKernel::createFromRequest() followed by handle(). The reason why the static factory is still necessary is its usage in legacy scripts and drush.

The first line of createFromRequest is this: $core_root = dirname(dirname(dirname(__DIR__)));. So automatically it's breaking isolation.

Semi-related: Reading over DrupalKernel right now it still kind of irks me that we can't inject DRUPAL_ROOT. The constructor says this: $this->root = dirname(dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__))));. Makes me itch. We could test the kernel with vfsstream if this were to change.

I think those legacy scripts and drush need to get broken and re-factored a bit, but I beat my head against that when it *wasn't* beta time and no one cared, so I'm not holding out hope.

For example it removes the response body if the incoming request used the HEAD verb. It's obvious that this should not be done before storing the response in the internal page cache.

I'm not sure that's the case. The render process can change headers, and the headers can be cached. Also, if parts of the response are cached we don't need to rebuild them on the next similar request.

@all: Especially those with Symfony experience, please help us decide whether there are situations where running $response->prepare($request) inside DrupalKernel::handle() would be wrong.

DrupalKernel::handle() is a shim for HttpKernel::handle(). I prefer the thin shim approach for overridden methods like this.

Of course, if there's an overriding design need, then yay. So what's the overriding design need?

@fgm mentions CLI apps having needs, but really, those should be addressed in other ways. For instance, if there's a need for CLI clients to clear the cache or run cron in an easy way, then let's build CliKernel and give it methods like clearCache($type='all');

znerol’s picture

I'm not sure that's the case. The render process can change headers, and the headers can be cached. Also, if parts of the response are cached we don't need to rebuild them on the next similar request.

The PageCache middleware stores/delivers the whole Response object including headers, status and content. The response event is emitted by the http kernel (i.e. the innermost kernel which is wrapped by several middlewares, including the page cache one). I.e., PageCache::set() is invoked with the result of HttpKernel::handle().

Response::prepare() adapts the response according to user agent characteristics and also removes the content if it was generated as a result of a HEAD request. Calling that before PageCache::set() would lead to a cache-poisoning issue. It follows that Response::prepare() cannot be in a response listener because that runs before PageCache::set() on a cache miss.

So really we have three options where to call that method:

  1. In the front controller (like in HEAD, but that deviates from Symfony front controllers)
  2. In the application kernel (patch)
  3. Inside a new middleware with priority > than page cache

It would be great if we could stick to the scope of this issue and concentrate on picking the right choice here.

neclimdul’s picture

Thanks znerol for addressing my concerns. I'm not sure I agree about prepare() still but I'm not sure its worth blocking.

Doing one more review, I had one more question. Currently if I want to customize/replace/or remove the hard coded catch message I could replace the front controller but now you have to replace DrupalKernel or patch it. Neither option is very reasonable. Should that not either be in the front controller or configurable in some way?

znerol’s picture

Should that not either be in the front controller or configurable in some way?

The point of this issue is to remove the try...catch from the front controller. What about another setting?

alexpott’s picture

In think changing this #103 is out-of-scope. Personally I'd love to remove the try catch but let's discuss that in another issue and just do the refactoring here.

neclimdul’s picture

I was actually mindful of scope when bringing it up but this would be a regression. Also the scope of this issue was to make maintenance and upgrades easier but adding the requirement of possibly having to maintain an alternative kernel or patching core is worse then the current situation.

alexpott’s picture

Status: Needs review » Needs work
+++ b/core/lib/Drupal/Core/DrupalKernel.php
@@ -599,8 +559,46 @@ public function terminate(Request $request, Response $response) {
+    catch (\Exception $e) {
+      if ($catch === FALSE) {
+        throw $e;
+      }
+      $message = Settings::get('rebuild_message', 'If you have just changed code (for example deployed a new module or moved an existing one) read <a href="http://drupal.org/documentation/rebuild">http://drupal.org/documentation/rebuild</a>');
+      if ($message && Settings::get('rebuild_access', FALSE)) {
+        $rebuild_path = $GLOBALS['base_url'] . '/rebuild.php';
+        $message .= " or run the <a href=\"$rebuild_path\">rebuild script</a>";
+      }
+
+      $response = new Response($message, 500);
+    }

This looks like it is has several problems compared with HEAD. Firstly if $catch is TRUE then it'll never be thrown again which means the error will not be caught be drupal's exception handler and therefore it won't be correctly logged.

Maybe the best thing to do is just leave this in index.php and file a separate issue to discuss it's removal or move to somewhere else - for example the exception handler itself.

znerol’s picture

Exceptions caught here are those which are thrown before our exception handler is installed (and before there is a container). We could add an error_log() though.

znerol’s picture

adding the requirement of possibly having to maintain an alternative kernel or patching core is worse then the current situation.

Could you please provide a use-case where this would be necessary with #103.

neclimdul’s picture

I'm not sure that is a problem. $catch is actually true by default and the $catch logic is actually run after the logging would have happened. In that respect it should be exactly the same as HEAD only one method call removed from its original location. Its the last thing run in the last handle method.
You would set it to FALSE to catch all exception in your front controller for what ever reason. Debugging or very specific logic I guess. Side note, I tried to rationalize this as an option instead of using a setting but its the behaviour of all exceptions all the way up the stack-middleware tree though so I think it would be fairly ineffective at targeting something specific like the "rebuild" message.

znerol’s picture

Exceptions caught here are those which are thrown before our exception handler is installed.

Ok, that's not quite true. Exception caught here are those which the exception handler fails to handle.

You would set it to FALSE to catch all exception in your front controller for what ever reason.

You set $catch to FALSE in unit tests. This is the one and only reason for that parameter. Perhaps it would be clearer if it would be named $return_response_whatever_it_takes = TRUE (this is what we want in production).

neclimdul’s picture

Yeah, alex corrected me on IRC. I don't know what I was thinking.

His concern then is actually probably legitimate then because if a random page(not the entire site) is throwing an exception its going to probably slip through without the logging.

znerol’s picture

Try this experiment. Turn off the database, add a breakpoint on Drupal\Core\Database\Driver\mysql\Connection::open() where the PDO object is constructed (line 92) and then step through the exception handling.

HEAD: The throw at the end of the catch block in index.php sends us to _drupal_exception_handler(), that throws again because there is no Database (and therefore no config) and sends us back to the front controller and then again back to the error handler and so fort until finally something breaks that loop - I have no idea what.

Because we do not throw again in the patch we do not enter that feedback loop. Alex is right indeed that in that case we side-step the exception handler. I think it is wrong to rethrow if $catch is FALSE, on the other hand we probably want to give the exception handler a chance to log something if possible. So perhaps we should simply forward to the exception handler from the catch clause and only add the rebuild message if that throws also.

znerol’s picture

Let's extract the exception handling code into its own method and give the Drupal exception handler a chance to log it.

znerol’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 114: move_all_the_logic_out-2389811-114.patch, failed testing.

mpdonadio’s picture

Status: Needs work » Needs review

Setting this back to Needs Review based on #114 being green after the re-queue from the testbot sadness today.

I manually tested both the hostname length protection and the trusted host mechanism, and both triggered the proper path through DrupalKernel::handleException()

webchick’s picture

Status: Needs review » Reviewed & tested by the community

Let's be bold, and see if this addresses alexpott's concerns.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 114: move_all_the_logic_out-2389811-114.patch, failed testing.

The last submitted patch, 114: move_all_the_logic_out-2389811-114.patch, failed testing.

znerol’s picture

Crell’s picture

Status: Needs review » Reviewed & tested by the community

Blindly re-RTBCing after a successful reroll...

alexpott’s picture

Issue summary: View changes

Yep my concerns are addressed - this is really great work work - thanks @znerol for persevering :)

Committed 90d6fb1 and pushed to 8.0.x. Thanks!

I've improved the beta evaluation.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

  • alexpott committed 90d6fb1 on 8.0.x
    Issue #2389811 by znerol, mpdonadio, alexpott, hussainweb, neclimdul:...
yched’s picture

w00t !! Awesome !

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.