Problem/Motivation
Date objects in Drupal are of type DrupalDateTime, which inherits from DateTimePlus, which is a wrapper around PHP's native DateTime class. Yet, sometimes we need to work directly with the DateTime object when dealing with libraries that expect DateTimeInterface.
For example, I'm working with datetime field data and the Recurr library, but I run into various problems when I the use field's DrupalDateTime object because it doesn't implement DateTimeInterface.
// DrupalDateTime object that doesn't implement DateTimeInterface.
$node->field_some_date_field[0]->date;
Unfortunately, there's no way to access the protected $dateTimeObject directly. Thus, I'd have to rebuild a new DateTime object from various pieces of the DateTimePlus object, and that's just sloppy.
Proposed resolution
I propose a simple DateTimePlus::getPhpDateTime() method to return the protected DateTime object.
// DateTime object that *does* implement DateTimeInterface.
$node->get('field_some_date_field')->date->getPhpDateTime();
Remaining tasks
Agree on the method name#6 - #10Write patch#2 - #15Add tests#12Write change record#20Answering the question on #32, "Is there a reason we don't implement DateTimeInterface?"#42
User interface changes
None
API changes
New method DateTimePlus::getPhpDateTime()
Data model changes
None
| Comment | File | Size | Author |
|---|---|---|---|
| #58 | interdiff-56-58.txt | 1.63 KB | mpdonadio |
| #58 | 2906229-58.patch | 3.99 KB | mpdonadio |
| #56 | interdiff-50-56.txt | 2.59 KB | mpdonadio |
| #56 | 2906229-56.patch | 5.02 KB | mpdonadio |
| #50 | 2906229-50.patch | 6.29 KB | mpdonadio |
Comments
Comment #2
joelstein commentedComment #3
manuel garcia commentedComment #4
wengerkI'm working & review the proposed solution from the DrupalConEu at Vienna.
Comment #5
wengerkI agree with @joelstein. I just fixed the DocBlock comment which was confusing in the return statement.
The method return a
\DateTimeand notDateTimeonly.Comment #6
sutharsan commentedI have checked the patch: it applies and the code style is correct.
I would suggest to change the method name to be
getPhpDateTime. This is more descriptive for example when used in this combination:DateTimeInterface::getDateTimeis used in:Comment #7
sutharsan commentedBack to Needs work to discuss (and change).
Comment #8
wengerkCompletely agree with @sutharsan.
I think
getPhpDateTime()& even the previousgetDateTimeObject()are both quite confusing, thegetPhpDateTime()is quite better but I don't like the idea of an acronym into the Class name, maybe something like :getPrimitiveDateTime()would do the job, but the DateTime in PHP is not a primitive so .... ?toDateTime()?MomentJS has faced the same kind of problem and resolve it with a
toDate(). http://momentjs.com/docs/#/displaying/as-javascript-date/I don't know, maybe the
getPhpDateTime()is the more self-explaining method.Edited
Plus, when calling somes methods (E.g
modify) of aDrupalDateTimeit return aDateTimeobject, so when used in a Service you better use the hintDateTimeinstead ofDrupalDateTime. It make sens to add a feature to retrieve aDateTime.Comment #9
marcvangendLet's get this issue going again, we only need to agree on the name, right?
My vote goes to
getPhpDateTime.Reasoning:
- At first,
getDateTimeObjectseems to make sense, but that's only because I already know that the protected property is named DateTimePlus::$dateTimeObject. We shouldn't assume a developer has that kind of knowledge. Also, DateTimePlus is already an object that contains date and time info, so getDateTimeObject doesn't make clear how it is different from what you already have.- Methods that look like toSomeType are usually conversions from some type to another, eg. an object to a string. That is not the case here: we're not converting the object, we're getting a property.
- In fact the most logical name IMHO would be
getDateTimebecause it gets a DateTime object, but as Sutharsan pointed out, that name is (over) used already, and it's too late to rename those getDateTime methods now. The way I see it,getPhpDateTimeis most similar, only a little more explicit in which kind of date-and-time object it is returning.Comment #10
sutharsan commented@marcvangend, thanks for your explanation and your vote. With 2 votes +1 and one undecided, I conclude that we will name the method
getPhpDateTime.Needs work to change the name and add a (unit) test.
Comment #11
wengerk@sutharsan working on it - submit my patch during the afternoon. (UTC+1).
Comment #12
wengerkHere a rerolled patch with a test
testGetPhpDateTime& the renamed method asgetPhpDateTime.Comment #13
sutharsan commentedThanks for adding the test.
Not use if adding a method needs a change record.
One small remark on the documentation:
To me, the second line does not add value to the first.
Comment #14
sutharsan commentedAnswering my own question: https://www.drupal.org/core/gates#documentation
Comment #15
wengerkThanks @Sutharsan, rerolled & remove unnecessary comment.
By the way, how can I help for the "Needs change record" tag ?
Comment #16
wengerkComment #17
manuel garcia commentedGlad to see progress on this, good work!
@wengerk for the change record, on the right sidebar you will find a "add change notice" link, which will take you to the form to add one for the current issue.
Documentation on how to write one: https://www.drupal.org/contributor-tasks/draft-change-record
Also, looking at other change records helps if you want to see some examples: https://www.drupal.org/list-changes/drupal
Comment #18
gambry@wengerk here the guidelines to draft a change record.
What the Change Record needs to document is the addition of this method to the DateTimePlus class, describing what it does and maybe adding a code example of the usage.
Also when you upload a new patch, adding an interdiff will help reviewers better understanding what your changes are compared to previous patches.
Amazing job here, thanks everyone!
Leaving Needs Work due the required change record, patch looks RTBC to me.
UPDATE: crossposted! :D
Comment #19
manuel garcia commentedhaha @gambry++
Comment #20
wengerkI wrote the API change record here https://www.drupal.org/node/2936388
PS: It's my first API change record hope I do it the right way.
Comment #21
wengerkComment #22
manuel garcia commentedBrilliant, thank you @wengerk -
RTBCing.
Comment #24
sutharsan commentedAdjusting issue title and summary to match actual solution.
Comment #25
sutharsan commentedApplied the issue summary template and added extra summary data.
Comment #26
sutharsan commented@wengerk, thanks for the change record. I've made some small adjustments and added an example for working with a date field.
Comment #28
catchI think this should be 'wrapped by this class' instead of 'this class wraps around'.
Not sure this needs a comment other than the @covers? But should it use the fully qualified class name here?
Comment #29
sutharsan commentedAgree, modified.
Yes, @covers added, title removed.
No clue what you mean.
\DateTimeZone? Yes Further I see no FCN.Comment #30
colanChanged the comment there to specify that the test covers
DateTimePlus, which is where the method actually exists. It's more general.I also updated the change record to point to 8.6.x, and referenced the original New Datetime API change record.
I think we can RTBC this now.
Comment #31
sutharsan commentedGood catch. I agree with the FQCN in
@covers, since the@coversDefaultClassof the test class is of a different class.Comment #32
larowlanDevil's advocate: Is there a reason we don't implement DateTimeInterface?
Comment #33
gambryNo, there isn't! We should use DateTimeInterface indeed.
Comment #34
manuel garcia commentedRe #32, #33:
I'm no expert on dates, and I hope I'm understanding the question correctly, but in any case, here's my analysis:
DateTimeInterfaceis meant for classes that deal withDrupalDateTimeobjects, eg thegetDateTimeandsetDateTimemethods defined by the interface both return and expect aDrupalDateTimeobject.So to me it'd make no sense to have
DrupalDateTimeitself implementDateTimeInterface.For context, currently the two classes implement the interface it in core are:
\Drupal\Core\TypedData\Plugin\DataType\Timestamp\Drupal\Core\TypedData\Plugin\DataType\DateTimeIso8601Comment #35
gambryHey @Manuel Garcia, the suggestion is not for DateTimePlus to implement DateTimeInterface but for the
getPhpDateTime()docblock to be@return \DateTimeInterface, instead of \DateTime.Comment #36
manuel garcia commentedOh I see @gambry - well, again
DateTimeInterfaceis for dealing withDrupalDateTimeobjects, andDateTimePlushas no knowledge ofDrupalDateTime, so I don't think we can guarantee thatgetPhpDateTime()will be returning something implementingDateTimeInterface?One could argue that the only thing extending
DateTimePlusin core is indeedDrupalDateTime, but well, I don’t know. I'm going to get out of the way now, let other chip in here :)Comment #37
gambryAttached what we are trying to achieve.
getPhpDateTime()return a \DateTime object instance, however the correct way to document this is to tell IDE and developers we are returning an instance of its interface.Comment #38
gambry\DateTimeInterface is a PHP interface, not a Drupal one.
Yes, we are returning the
protected $dateTimeObject, ...which now that I check it's actually documented as \DateTime as well.@larowlan should we change the dockblock of
$dateTimeObjecttoo?It appears to me we were doing correctly "something wrong" in 30. So we either change the docblock var type in there too, or we revert to patch in #30 and we fix both dockblocks in a follow up.
Comment #39
manuel garcia commentedConfused by this comment, https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21TypedData... clearly uses
DrupalDateTimefor its setter, and its getter is specifying that it returns aDrupalDateTimeobject?Comment #40
gambryHey @Manuel Garcia, I always assumed #32 was referring to \DateTimeInterface , not \Drupal\Core\TypedData\Type\DateTimeInterface.
However after giving a thorough reading #32 does say "implement", so probably referring to
DateTimePlusimplementing\DateTimeInterface.I think this is a bigger question, probably for the DateTime maintainer. To fully implement \DateTimeInterface the class only need the __wakeup() method.
I'm so reverting this issue to the state it was at #32, adding the question to the IS tasks and pinging @mpdonadio and @jhedstrom to give their feedback.
Comment #41
manuel garcia commented/me hides under a rock
Comment #42
gambry..and we may not need component maintainers in here (a chat with Berdir was enough! Kudos++).
From DateTimeInterface PHP doc page:
So we can't implement that.
Back to RTBC, current patch is again #30. If we want to change the doblocks from
\DateTimetoDateTimeInterfacewe can do it in a follow-up.Comment #43
mpdonadioSo what happens if DTP or DDT (or anything that extends these) overrides a method, and the returned \DateTime object is used directly instead of the overridden method?
Comment #44
gambryI'm not sure what you mean @mpdonadio . The idea of making available the \DateTime object is for being able to use external libraries expecting a \DateTimeInterface object, and those libraries don't know (and don't have to know) anything about DTP, DDT or anything extending these.
And won't use nor be affected by any overridden method.
Said that because we are returning the \DateTime object, any change to that will affect of course the future behaviour of the decorating class the object belongs to, so either DTP, DDT or whatever.
This is a tool for the developer, and they should use it responsibly.
Comment #45
mpdonadioLet's say we need to create DrupalDateTime::getTimeZone() and DrupalDateTime::setTimeZone() for some reason. Something like this would then not behave as expected:
I guess another way to say this is that by adding this new method which allows modification, we are breaking the Proxy pattern, and I am wondering if this is really a subtle BC break. Perhaps
may be better.
Quick review
We should do the test somewhere other than 'UTC'.
Can we add assertions to make sure the timestamps are the same and the time zones are the same?
Comment #46
gambryYes. That's what I meant with the developer should use it responsibly. And returning the cloned object ensures things won't break.
However what if some libraries want to actually manipulate the \DateTime instance?
(with all the danger may come with)
Do you think something like this will still acceptable?
Comment #47
mpdonadioI am going to polish #32 based on some of the thoughts above. Should have something tonight.
Comment #48
mpdonadioThis addresses my concerns.
The test is on both DateTimePlusTest and DrupalDateTimeTest on purpose. The components are supposed to be usable outside of Drupal, so the test needs to live there, and we have been adding the equivalent tests to DrupalDateTimeTest to prevent regressions.
Comment #49
gambryThe comment correctly says "Altering it (\DateTime object) does not change the DateTimePlus object." however we are doing the opposite? Changing $date and verifying $datetime hasn't changed?
Same as above.
Also same thing on DrupalDateTimeTest changes.
With the
clonein place that's pretty safe now. Thanks for that @mpdonadioComment #50
mpdonadioNice catch. Renamed the local vars to make this read better, and added a few more assertions to make things more explicit.
Comment #51
gambryThanks. That looks perfect.
My only question is what's the benefit of having the test for both DateTimePlus and DrupalDateTime?
The method lives in DateTimePlus, so its test is the logic place where you would have it tested. But DrupalDateTime doesn't override the method, nor the proxied \DateTime object so I'm not sure what the benefit is?
Setting this to RTBC anyway as currently DrupalDateTimeTest behaves like this for all other tests, and it's more a personal knowledge question than an issue.
Also updating the CR to reflect #45 changes about the new
$cloneargument.Thanks!!!
Comment #52
alexpottI prefer to start more conservative. At the moment we only have use-cases that need the clone. And to be honest if you need to update a DrupalDateTime object using the referenced \DateTime then just create a new DrupalDateTime object. We should always return the clone.
Comment #53
pounardWouldn't it be better to just make DateTimePlus implement the \DateTimeInterface? it's almost API compatible already. Then core should use everywhere \DateTimeInterface instead of DateTimePlus or DrupalDateTime and the code would be perfect (by perfect, I mean flexible and extensible).
Comment #54
mpdonadioSee #42 about why we can't implement \DateTimeInterface.
Comment #55
pounardThanks for answering, I didn't know that! Hmm. I am very, very angry at Drupal 8 for implementing this terrible class which is DateTimePlus, and even angrier to DrupalDateTime (seriously, why Drupal, why... there is no good reason on earth) but now I'm also angry at PHP :)
Comment #56
mpdonadio#52 is addressed.
Comment #58
mpdonadioOops.
Comment #59
gambryThe CR needs updates to reflect changes requested on #52 and addressed on #56 - #58.
Comment #60
mpdonadioUpdated the CR.
Comment #61
gambryThanks a lot @mpdonadio!
Comment #62
alexpottCrediting people who reviewed the patch and influenced it.
Committed 8a5dba5 and pushed to 8.6.x. Thanks!