Problem/Motivation

DrupalKernel has a mixture of concrete and static methods which all need to use the Drupal file system.

Concrete methods already have $this->root which they can use to discover the root directory for Drupal. However, $this->root is set based on assumptions about where Drupal core sits in the file system.

Static methods still rely on DRUPAL_ROOT which, itself, is set based on assumptions about Drupal's location in the file system.

These assumptions make it tricky to mock or test the kernel in many ways, and also is generally inelegant.

Proposed resolution

To solve these issues, we should inject the application root directory into the DrupalKernel object on creation, and also inject the root into the various static methods.

Since much calling code does not know how to do this, we can make these injections optional, allowing the kernel object to make a reasonable guess about the root directory path.

This allows testing code to not have to deal with the DRUPAL_ROOT constant. It also means that we can eventually untangle the need for legacy includes.

Remaining tasks

User interface changes

API changes

Data model changes

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Mile23 created an issue. See original summary.

Mile23’s picture

Le patch.

Mile23’s picture

Status: Active » Needs review

Status: Needs review » Needs work

The last submitted patch, 2: 2631362_2.patch, failed testing.

Mile23’s picture

Status: Needs work » Needs review
FileSize
9.19 KB
1.63 KB

Ah, if only I could get the local testbot working....

Status: Needs review » Needs work

The last submitted patch, 5: 2631362_5.patch, failed testing.

Mile23’s picture

Status: Needs work » Needs review
FileSize
9.19 KB
621 bytes

Three's a charm?

tstoeckler’s picture

Status: Needs review » Needs work

Yay, this is quite an awesome patch.

  1. +++ b/core/lib/Drupal/Core/DrupalKernel.php
    @@ -252,15 +252,18 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
    +   * @param string|null $app_root
    +   *   (optional) The path to the application root as a string, or NULL. If
    +   *   NULL, the application root will be computed. Defaults to NULL.
    

    The "|null" can be dropped, it's not that NULL is an explicit value with a semantic meaning, it's just that the parameter is optional. Similarly the "or NULL" and the "Default to NULL" can be dropped.

    All of this applies to multiple hunks in this patch

  2. +++ b/core/lib/Drupal/Core/Test/TestRunnerKernel.php
    @@ -20,15 +20,15 @@ class TestRunnerKernel extends DrupalKernel {
    -  public function __construct($environment, $class_loader) {
    ...
    +  public function __construct($environment, $class_loader, $allow_dumping = TRUE, $app_root = NULL) {
    

    Seems the $allow_dumping parameter is added needlessly.

hussainweb’s picture

Status: Needs work » Needs review
FileSize
3.09 KB
9.11 KB

Great patch, @Mile23. I am changing the comments as per suggestions in #8.1. I am not too sure about the second point. I think there is value in keeping the parameters consistent in inheriting classes.

tstoeckler’s picture

Hmm... I missed the inheritance part. Still though, if we are not actually using the parameter I don't think we should declare it. Otherwise I think we should be passing it on to the parent.

hussainweb’s picture

FileSize
860 bytes
9.12 KB

And I missed the parameter. I was sure I saw it being passed. Anyway, it seems it has to be FALSE and I have changed the default parameter to FALSE. I think the inherited docblock is now inaccurate. Any suggestions?

tstoeckler’s picture

Yes, that way it makes sense, IMO, thanks. Not sure about the docblock.

Mile23’s picture

Status: Needs review » Needs work
  1. +++ b/core/lib/Drupal/Core/DrupalKernel.php
    @@ -252,15 +252,18 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
    +   * @param string $app_root
    +   *   (optional) The path to the application root as a string. If omitted or
    +   *   NULL, the application root will be computed.
    
    @@ -277,12 +280,28 @@ public static function createFromRequest(Request $request, $class_loader, $envir
    +   * @param string $app_root
    +   *   (optional) The path to the application root as a string. If omitted or
    +   *   NULL, the application root will be computed.
    ...
    +    if ($app_root === NULL) {
    
    @@ -324,6 +343,9 @@ public function __construct($environment, $class_loader, $allow_dumping = TRUE)
    +   * @param string $app_root
    +   *   (optional) The path to the application root as a string. If omitted or
    +   *   NULL, the application root will be computed.
    

    See the === ?

    It means we need to document string|null, because the type of the parameter matters to the code.

  2. +++ b/core/lib/Drupal/Core/Test/TestRunnerKernel.php
    @@ -20,15 +20,15 @@ class TestRunnerKernel extends DrupalKernel {
    -  public static function createFromRequest(Request $request, $class_loader, $environment = 'test_runner', $allow_dumping = TRUE) {
    -    return parent::createFromRequest($request, $class_loader, $environment);
    +  public static function createFromRequest(Request $request, $class_loader, $environment = 'test_runner', $allow_dumping = TRUE, $app_root = NULL) {
    +    return parent::createFromRequest($request, $class_loader, $environment, $allow_dumping, $app_root);
    

    I'm pretty sure the reason we override createFromRequest is so we can insert our new defaults for optional parameters. Otherwise we don't really need it. So yay, that stays. But....

  3. +++ b/core/lib/Drupal/Core/Test/TestRunnerKernel.php
    @@ -20,15 +20,15 @@ class TestRunnerKernel extends DrupalKernel {
    -  public function __construct($environment, $class_loader) {
    -    parent::__construct($environment, $class_loader, FALSE);
    +  public function __construct($environment, $class_loader, $allow_dumping = FALSE, $app_root = NULL) {
    +    parent::__construct($environment, $class_loader, $allow_dumping, $app_root);
    

    On the other hand, the reason we pass FALSE to parent in the constructor is so we can explicitly always turn off dumping.

    So we do need to keep the FALSE, but also add the $app_root parameter.

Mile23’s picture

Status: Needs work » Needs review
FileSize
9.19 KB
3.89 KB

Reverted the string|null in docblocks, re-added FALSE to TestRunnerKernel::__construct().

Also changed visibility of guessApplicationRoot() to be private, so that we don't end up with callers just calling it as an input to the factory, or worse using it to discover the app root.

hussainweb’s picture

@Mile23: I see the ===, but isn't that used to set the value to a default guessed application root? In other words, NULL has no semantic meaning for the parameter, it is just used to keep it optional. This is what happens in rest of the Drupal code where it is not documented as NULL, jut optional.

For the $allow_dumping, there is an advantage in using the variable instead of literal FALSE. In normal usage, callers would skip this parameter and it would remain FALSE (which is default). However, what if a caller explicitly wanted to pass TRUE to the parent. I know that is not in the scope of this issue but we are effectively blocking it here and not even documenting why we are not using $allow_dumping. This could lead to confusion later.

I'd suggest changing it back to the parameter OR documenting why we are using FALSE and create a follow-up.

Mile23’s picture

FileSize
9.28 KB
3.6 KB

However, what if a caller explicitly wanted to pass TRUE to the parent.

Right. Exactly. :-) The thing is that it's there to force the kernel to never allow dumping. It's the version of the kernel that runs the tests, so it shouldn't dump its container; it should always rebuild it on the next test run, and it should remain isolated from the tests.

I don't necessarily agree with it, but that's how it's designed. http://cgit.drupalcode.org/drupal/tree/core/lib/Drupal/Core/Test/TestRun...

Changing it is out of scope here. File a follow-up if you'd like.

OK, so also reverted docblocks so that now we have string instead of string|null.

dawehner’s picture

Thank you for keeping the changes as much in scope as possible!
I like getting the app root more sane, thanks a lot for that!

  1. +++ b/core/lib/Drupal/Core/DrupalKernel.php
    @@ -277,12 +280,28 @@ public static function createFromRequest(Request $request, $class_loader, $envir
    +  private static function guessApplicationRoot() {
    

    we never use private functions, let's just keep using protected here

  2. +++ b/core/lib/Drupal/Core/Test/TestRunnerKernel.php
    @@ -78,7 +81,7 @@ public function boot() {
    -    include_once DRUPAL_ROOT . '/core/includes/file.inc';
    +    include_once $this->getAppRoot() . '/core/includes/file.inc';
    

    In other places we used $this->root, what about keep doing that?

Mile23’s picture

we never use private functions, let's just keep using protected here

Is there a reason we never use private methods, other than we never use them?

The reason it's private is in #14.

In an ideal world that method wouldn't exist, because we'd require the injected app root rather than allowing it to be optional. Then no one would ever guess. But that would be a major refactoring and a bit of an API change. So having the guess be private discourages others from relying on the guess.

dawehner’s picture

Is there a reason we never use private methods, other than we never use them?

Well, the thing is, that some people maybe want to change its behaviour. For such a flexible system as Drupal it is sometimes impossible to know.

Mile23’s picture

Well, the thing is, that some people maybe want to change its behaviour.

If they want to change its behavior then they can submit a patch. I made it private so people wouldn't use it outside the kernel. See #14.

dawehner’s picture

Make it protected and deal with it. Its not that hard. You just waste your personal time ... people will block the commit etc.

Mile23’s picture

So I shouldn't use private because an unknown person might think private is bad, and wouldn't be convinced by #14, and leaving it alone will waste *my* time.

Ok, phantom reviewer, this one's for you...

Here's a patch that says protected instead. If anyone other than the phantom reviews it, I'd really appreciate it if you could say something like, "Why is this method protected? It looks like it should be private, since that will encourage people to walk the path of dependency injection, an idiom we've adopted here in the Drupal project with the release of Drupal 8, and basically the raison d'etre of this patch."

Thanks.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.0-beta1 was released on March 2, 2016, which means new developments and disruptive changes should now be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Mile23’s picture

Rerolled for 8.2.x.

We really should just force the injection of DRUPAL_ROOT without giving an option not to.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

We really should just force the injection of DRUPAL_ROOT without giving an option not to.

Well, this is not really an option in Drupal 8.x ...

In general I really like this patch. As a small followup we could even create try to replace the DRUPAL_ROOT constant with a call to the kernel.

  • catch committed 331cff9 on 8.2.x
    Issue #2631362 by Mile23, hussainweb: Inject DRUPAL_ROOT into...
catch’s picture

Status: Reviewed & tested by the community » Fixed

Nice patch.

Committed/pushed to 8.2.x, thanks!

Forgot to add commit credit before commit, but did it after.

Status: Fixed » Closed (fixed)

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