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
Comment | File | Size | Author |
---|---|---|---|
#60 | interdiff-55-60.txt | 1.13 KB | mpdonadio |
#60 | 2717207-60.patch | 11.27 KB | mpdonadio |
#55 | interdiff-50-55.txt | 542 bytes | mpdonadio |
#55 | 2717207-55.patch | 11.9 KB | mpdonadio |
#50 | interdiff-44-50.txt | 2.1 KB | mpdonadio |
Comments
Comment #2
dawehnerYeah we already converted quite some of the usecases for code that does unit testing, see https://www.drupal.org/node/2463059
Comment #3
BerdirWould 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?
Comment #4
dawehnerWell, I think this won't happen, because people will move to PSR-7, that doesn't have it, but you can always try.
Comment #5
alexpottSeems like the was another reason to create the constant.
Comment #6
alexpottBut that comment actually looks wrong for php5.6 at least... if I dump $_SERVER I see...
Comment #7
alexpottLooking at the PHP docs...
https://secure.php.net/manual/en/reserved.variables.server.php
Comment #8
dawehnerYeah, its always an int in our relevant PHP versions, see https://3v4l.org/VKWSU
Comment #9
mpdonadioWe 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.
Comment #10
mpdonadioSome more related issues, and the change record: The REQUEST_TIME constant can no longer be used in PHPUnit tests.
Comment #11
mpdonadioI'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.
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).
Comment #12
mpdonadioSomething like this.
Comment #13
catchQuite like #12 compared to
$this->requestStack->getCurrentRequest()->server->get('REQUEST_TIME');
.Comment #14
dawehnerCould we put the interface into a component at least?
Are you sure that not using {} is a good idea for the namespace?
Comment #15
tstoecklerI 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?
Comment #16
mpdonadio#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.
Comment #17
catchWe might as well add getRequestMicroTime() here too.
Re-scoping the title.
Comment #19
mpdonadio#17, good idea.
Triggering retest of my last patch. That passes locally
Comment #22
mpdonadioLet'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.
Comment #23
mpdonadioFixed a few nits when I was looking at this. Think this is ready for full review / comments.
Comment #26
mpdonadioSetting back to Needs Review. Fails in #23 have been from #2724871: Random failure in \Drupal\migrate_drupal_ui\Tests\d7\MigrateUpgrade7Test.
Comment #27
borisson_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.
/s/@inheritdoc/{@inheritdoc}/
This applies for several place in the patch, I won't mention all of them.
Needs a newline here.
I think we can only omit the @file docblock if the file only contains one class and nothing else, not sure about this though.
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.
Comment #28
mpdonadioAddresses #27.
Comment #29
borisson_Comment #30
Wim LeersShouldn't this be 8.2.0?
Comment #31
dawehnerWell, 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.
Comment #32
Wim LeersCan't tell if this is serious or making time travel references :P
Comment #33
alexpottNo 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.
Double spaces after a .
No need for the indenting after this just use a ; instead of { ... }
Also I'd put the test first.
Comment #34
alexpottPlus the change record needs be done.
Comment #35
mpdonadioThanks @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.
Comment #36
mpdonadioShould 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.
Comment #37
mpdonadioEvery single time.
Comment #38
mpdonadioBanner day. Uploaded wrong files. Look at these. Note to self, always clean up old patch files and interdiffs.
Comment #39
mpdonadioThe 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.
Comment #41
mpdonadioUpdated @deprecated to reflect 8.3.0. Added draft CR.
Comment #42
mpdonadioNoticed an unnecessary change that crept in.
Comment #43
hchonovWhat about "Provides a class for obtaining system time".
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.
Why did you remove the inheritdoc?
Not needed empty line.
Two empty lines after a function instead of one.
Beside those nitpicks I would really like to see this in core :)
+1
Comment #44
mpdonadioOK, #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.
Comment #45
hchonovFound one more nitpick, which could be removed on committing this.
"Mocks a the request" => "Mocks the request".
Comment #46
dawehnerWe are adding a new component inside
\Drupal\Component
so a composer.json file would be niceComment #47
mpdonadio@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?
Comment #48
dawehnerWeird, 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.
Comment #49
catchJust nits, but also just enough of them not to fix on commit:
Don't think this needs the 'in general'.
Should the examples use the symfony interface? Also do we have a standard for this?
fractional.
factional timestamps, shadow namespaces :)
Not sure this needs a comment.
Comment #50
mpdonadio#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.
Comment #51
hchonovWe do not need this, right?
Comment #52
mpdonadio#51, the docs are a little fuzzy here, https://www.drupal.org/node/1354#file
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.
Comment #53
dawehnerLet's add
runTestsInSeparateProcesses
to not cause side effects.Comment #54
mpdonadioNifty. Didn't realize that existed. Assume we also want a `@preserveGlobalState disabled` as a precaution, too?
Comment #55
mpdonadioIsolated test.
Comment #56
dawehnerOH, I always assumes that the separate process disables the global state automatically
Comment #57
mpdonadio#56, the handful of other tests that use `@runTestsInSeparateProcesse`s also use `@preserveGlobalState disabled`.
Comment #58
dawehnerIt doesn't hurt to have more safety involved.
Comment #59
catchDrupalCS doesn't like the @file, not sure how we handle the shadow namespace docs - maybe just drop that altogether.
Comment #60
mpdonadioOK, #59 is take care of. phpcs came up clean for me.
Comment #61
dawehnerYeah, we have used the shadow technique multiple times already without documenting it in those detaios.
Comment #63
catchCommitted/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
----------------------------------------------------------------------
Comment #65
jibranPatch to cherry-pick the service to 8.2.x. Please ignore the comment.