Problem/Motivation

We added the REQUEST_TIME constant while Drupal 7 was being developed, so that core/contrib could wouldn't have to call time() all the, er, time - since time() means syscalls.

However in PHP 5.1, $_SERVER['REQUEST_TIME'] was added, which works the same but without the need for a global constant.

In various places around core, we have access to the current Symfony request object directly or via the request stack, but extracting the request time is wieldy.

Proposed resolution

Create a time service and interface to expose the $_SERVER['REQUEST_TIME'], and abstract the time related functions.

Add a helper to \Drupal for getting the time service.

Defer replacement of REQUEST_TIME, $_SERVER['REQUEST_TIME'], time(), $request->server->get('REQUEST_TIME'), etc, to followup issues, so we can appropriately triage them into groups.

Remaining tasks

User interface changes

API changes

Data model changes

CommentFileSizeAuthor
#65 add_a_time_service_to-2717207-65-8.2.x-do-not-test.patch11.46 KBjibran
#60 interdiff-55-60.txt1.13 KBmpdonadio
#60 2717207-60.patch11.27 KBmpdonadio
#55 interdiff-50-55.txt542 bytesmpdonadio
#55 2717207-55.patch11.9 KBmpdonadio
#50 interdiff-44-50.txt2.1 KBmpdonadio
#50 2717207-50.patch11.76 KBmpdonadio
#44 interdiff-42-44.txt3.24 KBmpdonadio
#44 2717207-44.patch11.82 KBmpdonadio
#42 interdiff-41-42.txt449 bytesmpdonadio
#42 2717207-42.patch11.8 KBmpdonadio
#41 interdiff-39-41.txt548 bytesmpdonadio
#41 2717207-41.patch12.24 KBmpdonadio
#39 interdiff-28-39.txt13.11 KBmpdonadio
#39 2717207-39.patch12.24 KBmpdonadio
#38 interdiff-28-38.txt12.71 KBmpdonadio
#38 2739290-38.patch21.29 KBmpdonadio
#36 interdiff-28-36.txt12.55 KBmpdonadio
#36 2717207-36.patch11.68 KBmpdonadio
#28 interdiff-23-28.txt2.78 KBmpdonadio
#28 2717207-28.patch7.55 KBmpdonadio
#23 interdiff-22-23.txt1.72 KBmpdonadio
#23 2717207-23.patch7.08 KBmpdonadio
#22 interdiff-16-22.txt3.18 KBmpdonadio
#22 2717207-22.patch7.47 KBmpdonadio
#16 interdiff-12-16.txt8.56 KBmpdonadio
#16 2717207-16.patch6 KBmpdonadio
#12 2717207-12.patch5.23 KBmpdonadio
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

catch created an issue. See original summary.

dawehner’s picture

Yeah we already converted quite some of the usecases for code that does unit testing, see https://www.drupal.org/node/2463059

Berdir’s picture

Would it be worth to open an upstream issue to sugesting adding $request->getRequestTime() or so? Looking at our code base, knowing that seems like a pretty common requirement?

dawehner’s picture

Well, I think this won't happen, because people will move to PSR-7, that doesn't have it, but you can always try.

alexpott’s picture

/**
 * Time of the current request in seconds elapsed since the Unix Epoch.
 *
 * This differs from $_SERVER['REQUEST_TIME'], which is stored as a float
 * since PHP 5.4.0. Float timestamps confuse most PHP functions
 * (including date_create()).
 *
 * @see http://php.net/manual/reserved.variables.server.php
 * @see http://php.net/manual/function.time.php
 */
define('REQUEST_TIME', (int) $_SERVER['REQUEST_TIME']);

Seems like the was another reason to create the constant.

alexpott’s picture

But that comment actually looks wrong for php5.6 at least... if I dump $_SERVER I see...

     "REQUEST_TIME_FLOAT" => 1462262200.1395,
     "REQUEST_TIME" => 1462262200,
alexpott’s picture

Looking at the PHP docs...

'REQUEST_TIME'
The timestamp of the start of the request. Available since PHP 5.1.0.
'REQUEST_TIME_FLOAT'
The timestamp of the start of the request, with microsecond precision. Available since PHP 5.4.0.

https://secure.php.net/manual/en/reserved.variables.server.php

dawehner’s picture

Yeah, its always an int in our relevant PHP versions, see https://3v4l.org/VKWSU

mpdonadio’s picture

We have an issue to remove the cast in the define() and some other random casts in #1209470: REQUEST_TIME is a float with microseconds on PHP 5.4.

mpdonadio’s picture

mpdonadio’s picture

I'm fairly against the proposed resolution. If we went through the trouble of using modern PHP OO techniques in Drupal 8, then is seems counterintuitive to go back to using a superglobal, especially one that changes.

I thought I made an issue about this, but maybe it was just a conversation I had (or the voices in my head). I think we should introduce a time service, to abstract away $_SERVER['REQUEST_TIME'], time(), microtime(), and maybe a few others.

interface TimeInterface {
  public function getRequestTime();
  public function getCurrentTime();
  public function getCurrentMicroTime();
}

Give the time service the request stack to get the request time from the current request, and then provide thin wrappers around the PHP functions for the rest. Then classes that need these can use mocking for sane unit testing. Just not sure how we could provide full test coverage when by definition, some of these functions don't return the same value of successive calls (have never tried the namespace shenanigans for shadowing the system calls).

mpdonadio’s picture

Status: Active » Needs review
FileSize
5.23 KB

Something like this.

catch’s picture

Quite like #12 compared to $this->requestStack->getCurrentRequest()->server->get('REQUEST_TIME');.

dawehner’s picture

  1. +++ b/core/lib/Drupal/Core/Datetime/Time.php
    diff --git a/core/lib/Drupal/Core/Datetime/TimeInterface.php b/core/lib/Drupal/Core/Datetime/TimeInterface.php
    new file mode 100644
    
    new file mode 100644
    index 0000000..8917b91
    
    index 0000000..8917b91
    --- /dev/null
    
    --- /dev/null
    +++ b/core/lib/Drupal/Core/Datetime/TimeInterface.php
    
    +++ b/core/lib/Drupal/Core/Datetime/TimeInterface.php
    +++ b/core/lib/Drupal/Core/Datetime/TimeInterface.php
    @@ -0,0 +1,28 @@
    
    @@ -0,0 +1,28 @@
    +<?php
    +
    

    Could we put the interface into a component at least?

  2. +++ b/core/tests/Drupal/Tests/Core/Datetime/TimeTest.php
    @@ -0,0 +1,101 @@
    +// Set up our shadow namespace so we can mock global system calls.
    +
    +namespace Drupal\Core\Datetime;
    +
    +/**
    + * Shadowed time() system call that lives in our namespace.
    + *
    + * @returns int
    + */
    +function time() {
    +  return 12345678;
    +}
    +
    +/**
    

    Are you sure that not using {} is a good idea for the namespace?

tstoeckler’s picture

I can see the value of wrapping $this->requestStack->getCurrentRequest()->server->get('REQUEST_TIME') for DX purposes, and would also greatly prefer this over a superglobal. Having written this, would a simple $this->requestStack->getCurrentTime() which does the same not solve the same problem?

I think wrapping time() and microtime() is a nice idea for unit testing, but is a separate issue, no?

mpdonadio’s picture

#14.1 - Think the whole thing can move to component. Had it in \Core\ because it was exposed as a service, but we expose some Symfony classes directly, so why not this. There is nothing really Drupal specific about it.

#14.2 - Good idea, and makes the file read better. I think this would be the only case in core that does this, so not really sure how to document it.

#15 - It's a tough call, an may lean towards YAGNI. However, the $_SERVER['REQUEST_TIME'] vs REQUEST_TIME vs $this->requestStack->getCurrentRequest()->server->get('REQUEST_TIME') vs time() are all related. I would suggest, we rescope this to introduce the time service and deprecate REQUEST_TIME. Then as followups, we can work on standardizing core to use the time service: 1, replace $_SERVER['REQUEST_TIME'] / REQUEST_TIME usages / $this->requestStack->getCurrentRequest()->server->get('REQUEST_TIME') usages (maybe split out the later, as it may affect a whole bunch of injections). 2, replace time() with the proper service method (there are a lot of places where time() is called when it should be the request time. 3, replace usages of microtime. Issues exist already for some of these.

catch’s picture

Title: Use $_SERVER['REQUEST_TIME'] instead of REQUEST_TIME » Add a time service to provide consistent interaction with time()/microtime() and superglobals
+++ b/core/lib/Drupal/Component/Datetime/Time.php
@@ -0,0 +1,47 @@
+  public function getRequestTime() {

We might as well add getRequestMicroTime() here too.

Re-scoping the title.

Status: Needs review » Needs work

The last submitted patch, 16: 2717207-16.patch, failed testing.

mpdonadio’s picture

#17, good idea.

Triggering retest of my last patch. That passes locally

../vendor/phpunit/phpunit/phpunit tests/Drupal/Tests/Component/Datetime/TimeTest.php
PHPUnit 4.8.11 by Sebastian Bergmann and contributors.

...

Time: 215 ms, Memory: 6.00Mb

OK (3 tests, 3 assertions)

The last submitted patch, 16: 2717207-16.patch, failed testing.

The last submitted patch, 16: 2717207-16.patch, failed testing.

mpdonadio’s picture

Issue summary: View changes
Status: Needs work » Needs review
Issue tags: +Needs change record
FileSize
7.47 KB
3.18 KB

Let's try this. Also added @deprecated for REQUEST_TIME, tweaked some comments, and #17.

If we are happy with this general approach and just need polish the patch, I'll write up a change record to announce the new service.

mpdonadio’s picture

Fixed a few nits when I was looking at this. Think this is ready for full review / comments.

Status: Needs review » Needs work

The last submitted patch, 23: 2717207-23.patch, failed testing.

The last submitted patch, 23: 2717207-23.patch, failed testing.

mpdonadio’s picture

Status: Needs work » Needs review
borisson_’s picture

Status: Needs review » Needs work

We're implementing date facets and we'd like to be able to use this patch to make our tests somewhat easier. I did find some nitpicks related to coding standards, everything else looks great.

  1. +++ b/core/lib/Drupal/Component/Datetime/Time.php
    @@ -0,0 +1,54 @@
    +   * @inheritdoc
    

    /s/@inheritdoc/{@inheritdoc}/

    This applies for several place in the patch, I won't mention all of them.

  2. +++ b/core/lib/Drupal/Component/Datetime/Time.php
    @@ -0,0 +1,54 @@
    +}
    \ No newline at end of file
    

    Needs a newline here.

  3. +++ b/core/tests/Drupal/Tests/Component/Datetime/TimeTest.php
    @@ -0,0 +1,126 @@
    +<?php
    +
    

    I think we can only omit the @file docblock if the file only contains one class and nothing else, not sure about this though.

  4. +++ b/core/tests/Drupal/Tests/Component/Datetime/TimeTest.php
    @@ -0,0 +1,126 @@
    +      $this->requestStack->expects($this->any())
    +                         ->method('getCurrentRequest')
    +                         ->willReturn($request);
    

    I don't think we do this indentation in core, but there's an open issue about this as far as I know. I think it should only be 2 spaces for the method/willReturn lines.

mpdonadio’s picture

Status: Needs work » Needs review
FileSize
7.55 KB
2.78 KB

Addresses #27.

borisson_’s picture

Status: Needs review » Reviewed & tested by the community
Wim Leers’s picture

+++ b/core/includes/bootstrap.inc
@@ -82,6 +82,9 @@
+ * @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0.

Shouldn't this be 8.2.0?

dawehner’s picture

Well, with this service you can mock the time and theoretically be able to go back in time. The actual version, we deprecate, then doesn't matter that much.

You are right though, we can fix that on commit or someone uploads a new patch.

Wim Leers’s picture

Well, with this service you can mock the time and theoretically be able to go back in time. The actual version, we deprecate, then doesn't matter that much.

Can't tell if this is serious or making time travel references :P

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/lib/Drupal/Component/Datetime/Time.php
    @@ -0,0 +1,54 @@
    +
    +class Time implements TimeInterface {
    

    No docs. I think this needs some documentation calling on all developers to use this instead of any other way of getting the time so that we can change the time at will during testing. I support this approach completely - the first software product I ever worked on was a billing system and all calls to time had to go through a central time service that could be modified at will - it's a great way to sensibly test things that change over time. Or maybe this should be on the interface.

  2. +++ b/core/tests/Drupal/Tests/Component/Datetime/TimeTest.php
    @@ -0,0 +1,139 @@
    + * shadow namespace for overriding PHP global functions within the test.  PHP
    

    Double spaces after a .

  3. +++ b/core/tests/Drupal/Tests/Component/Datetime/TimeTest.php
    @@ -0,0 +1,139 @@
    +namespace Drupal\Component\Datetime {
    ...
    +namespace Drupal\Tests\Component\Datetime {
    

    No need for the indenting after this just use a ; instead of { ... }

    Also I'd put the test first.

alexpott’s picture

Plus the change record needs be done.

mpdonadio’s picture

Assigned: Unassigned » mpdonadio

Thanks @alexpott. In taking a quick look at some other habit-breaking classes/services (Url, LinkGenerator, etc), I'll add some code samples to the docs to the functions on the interface, and a better overview comment to the interface and the concrete class. The CR will pretty much follow these examples.

mpdonadio’s picture

Should address #33. Was going to ping @jhodgdon for a look, but just realized she stepped down :(

Comments and proofreading greatly appreciated. Once we are happy with these docs, I will adapt for CR.

mpdonadio’s picture

Status: Needs work » Needs review

Every single time.

mpdonadio’s picture

FileSize
21.29 KB
12.71 KB

Banner day. Uploaded wrong files. Look at these. Note to self, always clean up old patch files and interdiffs.

mpdonadio’s picture

FileSize
12.24 KB
13.11 KB

The interwebs have officially beaten me today. Sorry for the noise.

Should address #33. Was going to ping @jhodgdon for a look, but just realized she stepped down :(

Comments and proofreading greatly appreciated. Once we are happy with these docs, I will adapt for CR.

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

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

mpdonadio’s picture

Assigned: mpdonadio » Unassigned
Issue tags: -Needs change record
FileSize
12.24 KB
548 bytes

Updated @deprecated to reflect 8.3.0. Added draft CR.

mpdonadio’s picture

Noticed an unnecessary change that crept in.

hchonov’s picture

  1. +++ b/core/lib/Drupal/Component/Datetime/Time.php
    @@ -0,0 +1,57 @@
    + * Provides a class obtaining system time.
    

    What about "Provides a class for obtaining system time".

  2. +++ b/core/lib/Drupal/Component/Datetime/TimeInterface.php
    @@ -0,0 +1,149 @@
    +   * Using the time service, rather than other methods, is especially
    +   * important to create tests that run that require predictable timestamps.
    

    What about "Using the time service, rather than other methods, is especially important when creating tests, which require predictable timestamps."

    This sentence is repeated for each of the methods, so I would put it as a documentation for the interface itself.

  3. +++ b/core/tests/Drupal/Tests/Component/Datetime/TimeTest.php
    @@ -0,0 +1,135 @@
    +  protected function setUp() {
    

    Why did you remove the inheritdoc?

  4. +++ b/core/tests/Drupal/Tests/Component/Datetime/TimeTest.php
    @@ -0,0 +1,135 @@
    +
    

    Not needed empty line.

  5. +++ b/core/tests/Drupal/Tests/Component/Datetime/TimeTest.php
    @@ -0,0 +1,135 @@
    +
    +
    

    Two empty lines after a function instead of one.

Beside those nitpicks I would really like to see this in core :)
+1

mpdonadio’s picture

OK, #43 should be taken care of, but not sure what extra space was meant in (4) and I left the duplicated docs on each method. Similar things happen in other classes (Url comes to mind). Having the docs on each method makes sense when you Cmd-B to a definition and want to read the docs, and also for api.drupal.org so you can see everything in one place when reading the docs for a method on a single webpage.

hchonov’s picture

Status: Needs review » Reviewed & tested by the community

Found one more nitpick, which could be removed on committing this.

+++ b/core/tests/Drupal/Tests/Component/Datetime/TimeTest.php
@@ -0,0 +1,137 @@
+    // Mocks a the request stack getting the current request.

"Mocks a the request" => "Mocks the request".

dawehner’s picture

+++ b/core/lib/Drupal.php
new file mode 100644
index 0000000..cc6f2a2

index 0000000..cc6f2a2
--- /dev/null

--- /dev/null
+++ b/core/lib/Drupal/Component/Datetime/Time.php

+++ b/core/lib/Drupal/Component/Datetime/Time.php
+++ b/core/lib/Drupal/Component/Datetime/Time.php
@@ -0,0 +1,57 @@

We are adding a new component inside \Drupal\Component so a composer.json file would be nice

mpdonadio’s picture

@dawehner, we have one already in that component, http://cgit.drupalcode.org/drupal/tree/core/lib/Drupal/Component/Datetim...

Do we want/need to update the description?

dawehner’s picture

Weird, I swear I opened up my IDE to look it up whether there is one. Thank you for pointing it out. Nope, I don't think we really need an update.

catch’s picture

Status: Reviewed & tested by the community » Needs work

Just nits, but also just enough of them not to fix on commit:

  1. +++ b/core/lib/Drupal/Component/Datetime/TimeInterface.php
    @@ -0,0 +1,149 @@
    +   * In general, this method should be used to obtain the current system time
    

    Don't think this needs the 'in general'.

  2. +++ b/core/lib/Drupal/Component/Datetime/TimeInterface.php
    @@ -0,0 +1,149 @@
    +   * $request_time = REQUEST_TIME;
    +   * $request_time = $requestStack->getCurrentRequest()->server->get('REQUEST_TIME');
    +   * $request_time = $request->server->get('REQUEST_TIME');
    +   * @endcode
    

    Should the examples use the symfony interface? Also do we have a standard for this?

  3. +++ b/core/lib/Drupal/Component/Datetime/TimeInterface.php
    @@ -0,0 +1,149 @@
    +   *  A Unix timestamp with a factional portion.
    

    fractional.

  4. +++ b/core/tests/Drupal/Tests/Component/Datetime/TimeTest.php
    @@ -0,0 +1,137 @@
    + * checking the global namespace. So, the shadow namespace gets used to mock
    

    factional timestamps, shadow namespaces :)

  5. +++ b/core/tests/Drupal/Tests/Component/Datetime/TimeTest.php
    @@ -0,0 +1,137 @@
    + * Set up the real namespace for the test.
    

    Not sure this needs a comment.

mpdonadio’s picture

#44 should be addressed, other than #2. Not sure what we want to do here. All of those uses are scattered through core (essentially, but not verbatim). Ideally, as a followup (or two or three), we want to replace all of those with the time service.

hchonov’s picture

+++ b/core/tests/Drupal/Tests/Component/Datetime/TimeTest.php
@@ -0,0 +1,134 @@
+ * @file

We do not need this, right?

mpdonadio’s picture

#51, the docs are a little fuzzy here, https://www.drupal.org/node/1354#file

The @file doc block MUST be present for all PHP files, with one exception: files that contain a namespaced class/interface/trait, whose file name is the class name with a .php extension, and whose file path is closely related to the namespace (under PSR-4 or a similar standard), SHOULD NOT have a @file documentation block.

That particular file has two namespaces in it, one of which doesn't match the filename. One is for the test class, which matches the filename, the other is for the globals that are shadowed for testing purposes.

dawehner’s picture

+++ b/core/tests/Drupal/Tests/Component/Datetime/TimeTest.php
@@ -0,0 +1,134 @@
+/**
+ * @coversDefaultClass \Drupal\Component\Datetime\Time
+ * @group Datetime
+ */
+class TimeTest extends UnitTestCase {

Let's add runTestsInSeparateProcesses to not cause side effects.

mpdonadio’s picture

Nifty. Didn't realize that existed. Assume we also want a `@preserveGlobalState disabled` as a precaution, too?

mpdonadio’s picture

Isolated test.

dawehner’s picture

+++ b/core/tests/Drupal/Tests/Component/Datetime/TimeTest.php
@@ -22,6 +22,11 @@
+ * @preserveGlobalState disabled

OH, I always assumes that the separate process disables the global state automatically

mpdonadio’s picture

#56, the handful of other tests that use `@runTestsInSeparateProcesse`s also use `@preserveGlobalState disabled`.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

It doesn't hurt to have more safety involved.

catch’s picture

Status: Reviewed & tested by the community » Needs work

DrupalCS doesn't like the @file, not sure how we handle the shadow namespace docs - maybe just drop that altogether.

FILE: ...ites/8.x/core/tests/Drupal/Tests/Component/Datetime/TimeTest.php
----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
 3 | ERROR | [x] Namespaced classes, interfaces and traits should not
   |       |     begin with a file doc comment
----------------------------------------------------------------------
PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY
----------------------------------------------------------------------
mpdonadio’s picture

Status: Needs work » Needs review
FileSize
11.27 KB
1.13 KB

OK, #59 is take care of. phpcs came up clean for me.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

Yeah, we have used the shadow technique multiple times already without documenting it in those detaios.

  • catch committed b09f922 on 8.3.x
    Issue #2717207 by mpdonadio, dawehner, catch, alexpott: Add a time...
catch’s picture

Status: Reviewed & tested by the community » Fixed

Committed/pushed to 8.3.x, thanks!

Fixed this on commit:

FILE: ...8.x/core/lib/Drupal/Component/Datetime/TimeInterface.php
----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
74 | ERROR | [x] Return comment indentation must be 3 spaces, found
| | 2 spaces
----------------------------------------------------------------------
PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY
----------------------------------------------------------------------

Status: Fixed » Closed (fixed)

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

jibran’s picture

Patch to cherry-pick the service to 8.2.x. Please ignore the comment.