Problem/Motivation
In bootstrap.php we define the REQUEST_TIME constant so that PHPUnit test work. This is fine whilst we are not adding integration and functional test suites to PHPUnit - but we are:
#2232861: Create BrowserTestBase for web-testing on top of Mink
#2304461: KernelTestBaseTNG™
Both these issues do something like:
+++ b/core/includes/bootstrap.inc
@@ -86,7 +86,9 @@
-define('REQUEST_TIME', (int) $_SERVER['REQUEST_TIME']);
+if (!defined('REQUEST_TIME')) {
+ define('REQUEST_TIME', (int) $_SERVER['REQUEST_TIME']);
+}
@@ -114,7 +116,9 @@
-define('DRUPAL_ROOT', dirname(dirname(__DIR__)));
+if (!defined('DRUPAL_ROOT')) {
+ define('DRUPAL_ROOT', dirname(dirname(__DIR__)));
+}
Up to now when we were unit testing functionality that relies on the request time we would define the REQUEST_TIME constant at the start of the test, in core/tests/bootstrap.php. This would ensure that we do not get 'undefined constant' notices, and that the tests have a somewhat realistic value to work with. This works fine for proper unit tests that only load the class under test and do not actually perform a full bootstrap of Drupal.
However now that we are about to introduce running functional tests through PHPUnit this would mean that Drupal would be fully bootstrapped after PHPUnit runs our bootstrap.php. In the 'real' bootstrap the REQUEST_TIME constant would be defined again, throwing a notice that would fail the test, resulting in the workaround above.
Proposed resolution
No longer rely on the REQUEST_TIME constant In FooTest code so it can be removed from bootstrap.php and will never conflict with bootstrap.inc. Instead fallback to $_SERVER['request_time'] directly. In actual code use the information which is stored in the request object. Some code might run really early in the installer, so we have to fallback to the REQUEST_TIME from bootstrap.incthere.
Remaining tasks
User interface changes
API changes
This is about improving testability but there a couple of non changes to remove dependency on REQUEST_TIME where that code is already unit tested.
Beta phase evaluation
| Issue category | Task |
|---|---|
| Issue priority | Major because it is blocking to major issues. |
| Unfrozen changes | Unfrozen because it doesn't change API's. It changes OO code to be more testable and touches test related code which is not frozen. |
| Disruption | Minimal. It does not affect any API's but could break tests outside of core using REQUEST_TIME |
| Comment | File | Size | Author |
|---|---|---|---|
| #25 | 2459155.25.patch | 22.21 KB | hussainweb |
| #25 | interdiff-18-25.txt | 1.17 KB | hussainweb |
| #18 | 2459155.16.patch | 22.31 KB | pfrenssen |
| #16 | interdiff.txt | 1.79 KB | pfrenssen |
| #4 | 1-4-interdiff.txt | 2.11 KB | alexpott |
Comments
Comment #1
alexpottComment #3
pfrenssenComment #4
alexpottFixing the patch.
Comment #5
dawehnerComment #7
alexpottLast fix :)
Comment #8
neclimdulI'm ok with the change personally but we might want to make sure we bring this to the attention of DX minded people sooner rather then later and make sure everyone is on board.
I assume for review/testing that its only commented out?
Comment #9
Crell commentedIf it's in $_SERVER, then it should be in the request object, no? We should get it from there rather than the super-global.
We could arguably stick it into the container as a parameter, too, in which case services that need it can just get that injected. We do that for the root dir now, don't we? Or is that different because it doesn't change per-request?
Comment #10
alexpottThanks for the review @neclimdul.
I'm not sure what DX has to do with this. If you have code that you want to be unit testable and you have dependencies on
$_SERVERvariables use the request object.Comment #11
neclimdulBecause I didn't realize bootstrap.php is not bootstrap.inc.
could we just return (int) $_SERVER['REQUEST_TIME'];? I don't know there might be a reason not to but seems like it'd be fine to me.
Comment #12
neclimdulbeta eval to clarify
Comment #13
pfrenssenPatch didn't apply any more after #2457887: Use Utility\SafeMarkup class instead of Utility\String for placeholder(), checkPlain(),format() functions got in. Rerolled.
Comment #14
pfrenssenI was also confused about the difference between
bootstrap.incandbootstrap.php. @alexpott explained it to me. Updated issue summary.Comment #15
pfrenssenPatch looks good, a few nitpicks.
Seeing that this method is used twice (here and in MemoryBackend), might it be useful to put this in a trait so it can be reused easily in new code that relies on REQUEST_TIME?
The parentheses around the subtraction are not needed.
The line of documentation right above this still refers to the REQUEST_TIME constant.
These parentheses around the product are also not strictly needed, but this is OK as it helps with readability.
All unit tests are covered, we can be certain of this otherwise we would get failures with "Use of undefined constant REQUEST_TIME".
Comment #16
pfrenssenFixed those little nitpicks. They don't change the actual functionality so I suppose I am still allowed to RTBC this.
Comment #18
pfrenssenOops I uploaded the wrong patch when I was in a hurry to catch my train. This is the patch I was looking for.
Comment #19
mpdonadioDoes this need a CR to demonstrate the proper way to get this now in test and non-test code?
Comment #20
pfrenssenThis is only introduced in 8.0.x but indeed we might want to inform developers about the change. Added draft change record.
Comment #21
jhedstromThis still isn't using a trait from what I can tell.
Comment #22
pfrenssenSorry I forgot to mention this, @alexpott told me on IRC that he didn't think it was necessary to make a trait for that simple method.
Comment #23
jhedstromAh, back to RTBC then :)
Comment #24
hussainwebDo you think it is better to write this as:
It is not a blocker by any means, but I am thinking this is a quick change that saves a bunch of lines and makes the method easier to read.
Comment #25
hussainwebI didn't realize this was a Normal issue. I avoided putting it in a patch thinking not to block a critical. But this is a small change and makes the code easier to read, so here goes...
Comment #26
wim leersLooks fine. I don't think it makes it easier to read; it's equally legible for me in case of such a simple function. So I think #18 & #25 are equivalent.
Back to RTBC.
Comment #27
pfrenssenThis is actually major since it blocks two major issues: #2232861: Create BrowserTestBase for web-testing on top of Mink and #2304461: KernelTestBaseTNG™.
Comment #28
dawehner@hussainweb
It doesn't matter here, because we don't call it often, but in case you have code which is executed a lot of times, have a look at http://fabien.potencier.org/article/48/the-php-ternary-operator-fast-or-not
Comment #29
hussainweb@dawehner: Thanks for the links. I did come across this long back but completely forgot about it.
It was really a nitpick. It is not worth holding up a major issue for this. It's back to RTBC so there is nothing to do. I am now leaning towards #18 even though it is more verbose but I am fine with either now.
Comment #30
webchickChanges like this looks great:
Changes like this, not so much:
we've tried really, really hard over the D8 development cycle to avoid direct consultation of globals, especially superglobals.
However, in talking about this on IRC with alexpott, neclimdul, EclipseGc and Crell, it doesn't actually make the "pre-existing condition" any worse. These tests were already consulting the superglobal directly, just doing so behind a constant that we had to jankily define.
So I think this is fine for now, but we should get a follow-up to (quoting Crell) "I would really like to see if we can get the request time injectable, because then any service can just take an int as a constructor argument and all is right with the world."
Searching https://www.drupal.org/list-changes/published?keywords_description=REQUE... however I don't really see anything that covers the fact that you're not supposed to use REQUEST_TIME anymore, and the change record only covers the case we actually don't generally want people to do. It probably needs to be re-jiggered into something more like "REQUEST_TIME constant removed" and laying out the two replacement paths ("recommended" way to inject the request object, and "if you have to" way (for tests only) to consult the superglobal directly).
Once that's done, this is fine to go in.
Comment #31
Crell commentedAngie asked me to weigh in here.
Overall, this issue is reducing coupling, even if only very slightly, so I'm OK with it. We're replacing an implicit dependency on the Drupal environment with one for the PHP environment, which is a step up even if still not ideal.
It would be useful to have a follow-up for the non-test code in here to go a step further and try to inject the request time as an integer dependency from the container, which would therefore decouple those services from the RequestStack. Problem is, the container doesn't support non-object services or runtime-generated parameters. However... it does support runtime object services, so we could instead of an int stick a DateTime into the container for "now" and inject that. That would have the same net decoupling result. That sounds like a good follow-up once this is committed.
Comment #32
neclimdulI think the follow up is "as you unit test code, you can't use REQUEST_TIME anymore so things have to do something else"
Comment #33
webchickSo I thought I was waiting on a change record update mentioned in #30, but in grepping I see that it really is only unit tests where we no longer use REQUEST_TIME; it's still peppered throughout the code base in various places even with this patch (including KernelTestBase ones like /core/modules/system/src/Tests/Entity/EntityCrudHookTest.php).
So, sorry about the red herring.
Committed and pushed to 8.0.x. Thanks!