Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[Clock] A new component to decouple applications from the system clock#46715

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
fabpot merged 1 commit intosymfony:6.2fromnicolas-grekas:clock
Jul 28, 2022

Conversation

@nicolas-grekas
Copy link
Member

@nicolas-grekasnicolas-grekas commentedJun 19, 2022
edited
Loading

QA
Branch?6.2
Bug fix?no
New feature?yes
Deprecations?no
Tickets-
LicenseMIT
Doc PR-

After watching@afilina's talk at SymfonyWorld Online last week + listening to her using a "clock" service to improve testability of time-sensitive logics, I decided to propose this new "Clock" component. This also relates to the ongoing efforts to standardize aClockInterface inPSR-20.

This PR provides aClockInterface, with 3 methods:

  • now(): \DateTimeImmutable is designed to be compatible with the envisioned PSR-20;
  • sleep(float|int $seconds): void advances the clock by the provided number of seconds;
  • withTimeZone(): static changes the time zone returned bynow().

Thesleep() methods takes inspiration fromClockMock in the PhpUnitBridge, where this proved useful to improve testability. Ideally, we could use this component everywhere measuring the current time is needed and stop relying onClockMock.

This PR provides 3 clock implementations:

  • NativeClock which relies on the system clock;
  • MockClock which allows mocking the time;
  • MonotonicClock which relies onhrtime() to provide a monotonic clock.

If this gets accepted, I'll follow up with a PR to add clock services to FrameworkBundle and we'll then be able to see where we could use such clock services in other components. I hope PSR-20 will be stabilized by Symfony 6.2, but this is not required for this PR to be useful.

simivar, tarlepp, GromNaN, dmaicher, fbourigault, Guikingone, chapterjason, mtarld, tehpolicer, yceruto, and 23 more reacted with thumbs up emojifbourigault, OskarStark, hhamon, WebMamba, yceruto, mnocon, ausi, RadnoK, mcsky, zairigimad, and 8 more reacted with heart emoji
Copy link
Contributor

@drupoldrupol left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Cool initiative!

Copy link
Contributor

@heiglandreasheiglandreas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I doubt that PSR-20 is going to be ready by the release of Symfony 6.2. Though in the meantime there is the forward-compatiblestella-maris/clock interface that can be used and already is implemented by some of the most used clock-implementations.

With that in mind I'd think about - for the sake of interface segregation - removing thenow function completely from theTimeInterface (and perhaps also moving thesleep function into a separate interface - we've discussed that during the PSR-20 discussions IIRC). The different implementations can then still implement the different interfaces in one class. And should more than one interface be required in a method we can by now require an implementation via a UnionType.

GromNaN reacted with thumbs up emoji
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class MockClock implements TimeInterface
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Instead of calling the class by what it should be used for it might make more sense to call the class by what it actually does. In this case that would be aFrozenClock which freezes the point in time to a specific one.

Or to multiple specific ones when the constructor also accepts an array of DateTimeImmutables.

A possible further Implementation might then be aFreezableClock that can be frozen multiple times using@theofidry's idea via afreeze method.

jaikdean, fmata, and BoShurik reacted with thumbs up emoji
Copy link
MemberAuthor

@nicolas-grekasnicolas-grekasJun 20, 2022
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Instead of calling the class by what it should be used for it might make more sense to call the class by what it actually does. In this case that would be a FrozenClock which freezes the point in time to a specific one.

I prefer keeping MockClock. I think the name kinda says both what it might be use forand what it does, thus providing better discoverability (and is consistent with "MockHttpClient".)

Or to multiple specific ones when the constructor also accepts an array of DateTimeImmutables.

now() would then return dates from that sequence? Interesting idea :)

A possible further Implementation might then be a FreezableClock that can be frozen multiple times using@theofidry's idea via a freeze method.

Agreed. I might wait for someone that needs that to submit a PR instead of providing it in this initial PR though.

HeahDude and GromNaN reacted with thumbs up emoji
Copy link

@eerisoneerisonDec 2, 2023
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Hey@nicolas-grekas

I saw that your firstMockClock version wasn'tfinal, But I can see that all class are final, what made you add classes as final?

I ask this because I see others symfony's classes implementing an interface But those aren't final.
for example:https://github.com/symfony/serializer/blob/7.0/Encoder/JsonEncode.php#L21

is there any reason to make those clock classes as final and in others repositories not?

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

It's subtle and certainly never a black and white decision. Why are you asking?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Because I see some open source projects closing their classes for extensions and force the dev implement the interface, or maybe decarorate the classes in case they want to reuse some existing code. And as far as I know it is to help maintainers handle BC easily, because the dev won't extend classes.

But why symfony doesn't close the api and make the dev always implement the interface?

And as this clock classes are different of others, I thought it has a different reason for this.

BTW, thank you for your time in replying me, I really appreciate that.

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

In Symfony we're not dogmatic on the topic. I think we learned that from a maintenance perspective, making classes final makes it easier to evolve them without too much BC concerns. But our core desire is to empower end users, and "final" comes into the way sometimes so we're open to reconsidering. As for why some classes are not final: history for some, and openness to inheritance for others.
The open/closed principle is a balanced one.

eerison reacted with heart emoji

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

So nice♥️

But our core desire is to empower end users.

Do you mean give them more flexibility?

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Yes, I mean more flexibility to achieve what they need to achieve, and some framework to guide them in the process :)

eerison reacted with heart emoji

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This vision is so nice, because even you know that closes classes are easier for maintaining, but symfony prefer give flexibility for users, even if it costs more work from symfony side 🤯♥️

Well thank you again for your time

kbond and HeahDude reacted with thumbs up emoji
@drupol
Copy link
Contributor

With that in mind I'd think about - for the sake of interface segregation - removing thenow function completely from theTimeInterface. The different implementations can then still implement the different interfaces in one class. And should more than one interface be required in a method we can by now require an implementation via a UnionType.

Hi Andreas,

Why would we want to remove thenow method? The whole point of this component is actually to have such method.
Could you please develop?

@nicolas-grekasnicolas-grekasforce-pushed theclock branch 2 times, most recently from9d304cf tob8694feCompareJune 20, 2022 07:42
@nicolas-grekas
Copy link
MemberAuthor

I doubt that PSR-20 is going to be ready by the release of Symfony 6.2.

🤷 hurry up PHP-FIG :)

for the sake of interface segregation - removing the now function completely from the TimeInterface (and perhaps also moving the sleep function into a separate interface - we've discussed that during the PSR-20 discussions IIRC).

I'd better not as I explained in#46715 (comment)
If we end up being incompatible with PSR-20, we'll deal with it with a deprecation layer.
But looking at the state of PSR-20, it looks unlikely that we'll have to do that.

drupol reacted with thumbs up emoji

@nicolas-grekas
Copy link
MemberAuthor

Thanks for the review btw@heiglandreas !

@heiglandreas
Copy link
Contributor

heiglandreas commentedJun 20, 2022
edited
Loading

With that in mind I'd think about - for the sake of interface segregation - removing thenow function completely from theTimeInterface. The different implementations can then still implement the different interfaces in one class. And should more than one interface be required in a method we can by now require an implementation via a UnionType.

Hi Andreas,

Why would we want to remove thenow method? The whole point of this component is actually to have such method. Could you please develop?

Thenow-method would be already defined in aClockInterface - likepsr/clock orstella-maris/clock, so defining it here again in aTimeInterface is not really necessary. The actualImplementation then would implementTimeInterface, ClockInterface, SleepInterface.

So the component would either require an externalClockInterface or declare a spearateClockInterface in parallel to the currentTimeInterface and a currently not existingSleepInterface.

drupol and apfelbox reacted with thumbs up emoji

@nicolas-grekas
Copy link
MemberAuthor

The now-method would be already defined in a ClockInterface - like psr/clock or stella-maris/clock, so defining it here again in a TimeInterface is not really necessary. The actual Implementation then would implement TimeInterface, ClockInterface, SleepInterface.

I want the component to be standalone and complete on its own. When PSR-20 will be ready, we might extend it, but I wouldn't make a stable PSR-20 a requirement to this component.

So the component would either require an external ClockInterface or declare a spearate ClockInterface in parallel to the current TimeInterface and a currently not existing SleepInterface.

Replying in the main thread to help readers follow the discussion: see#46715 (comment) about this topic.

Copy link
Member

@chalasrchalasr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Cool

@nicolas-grekasnicolas-grekasforce-pushed theclock branch 2 times, most recently from0d67d50 to8a65085CompareJune 20, 2022 13:11
@javiereguiluz
Copy link
Member

Thanks for this proposal!

In the description we see this:

timeInt(): float returns the current timestamp;

I guess it's a typo thattimeInt() returns afloat instead ofint.


If the naming of the class/methods is up for debate, here's a proposal for your consideration:

// Beforeinterface TimeInterface{publicfunctionnow():\DateTimeImmutable;publicfunctionsleep(float|int$seconds):void;publicfunctiontimeInt():int;publicfunctiontimeFloat():float;publicfunctiontimeArray():array;}// Afterinterface ClockInterface{publicfunctionnow():\DateTimeImmutable;publicfunctionsleep(float|int$seconds):void;publicfunctiontimestamp():float;}

I'd removetimeInt(),timeFloat() andtimeArray() ... but you said that they are important for performance reasons. So, why not merging all of them into a single method calledtimestamp() which returns a timestamp with microseconds precision. It'd be equivalent totimeFloat() ... and thetimeInt() value can be obtained as(int) $clock->timestamp(). About thetimeArray() method, I don't understand well its need.

Thanks.

@nicolas-grekasnicolas-grekasforce-pushed theclock branch 2 times, most recently from605f167 toff18540CompareJune 24, 2022 10:00
@nicolas-grekas
Copy link
MemberAuthor

nicolas-grekas commentedJun 24, 2022
edited
Loading

Brain activity at night made me realize that we could remove thetimeInt() andtimeArray() variants and keep onlytime(), providedtime() returns a string. As a reminder, the concern this addresses is loss-less time measurements on 32-bit archs. This is the same concern that leads to havingmicrotime(false) also return a string.

Then I did my homework and tried to verify my own claim about performance, which is the justification I've provided for thesetime*() methods.

Comparingnew \DateTimeImmutable('now', $tz) tomicrotime(true), the latter is almost 5x faster.
But when comparingnew \DateTimeImmutable('now', $tz) to(string) microtime(true), the latter is only 1.1x faster!

This difference is not significant enough to warrant my claim. I'm thus removing alltime*() methods from my proposal. Performance-critical time measurements might continue to useClockMock since we can't abstract time measurements without adding overhead to it. Note that using the component for not-so-critical performance measurements is still valid, because we're talking about quite fast measurements.

Since this means the interface now contains onlynow() andsleep(), I've renamed it toSleepableClockInterface.

I've also implemented the offset logic@derrabus suggested forHrClock, which is now renamedMonotonicClock (because it's not "high-resolution" anymore, see notes about the overhead above.)

I updated the PR and the description above with all those changes.

I think this addresses most if not all concerns raised so far.

The last thing that remains is the lack of aClockInterface, and that's on the PHP-FIG 🙏

GromNaN, lchrusciel, and derrabus reacted with heart emoji

@nicolas-grekasnicolas-grekasforce-pushed theclock branch 2 times, most recently frome35de12 to2876adaCompareJune 24, 2022 10:10
@lchrusciel
Copy link
Contributor

I'm looking forward to the newest PSR-20 and this component! At Sylius, we've recently introduced theCalendar component, but we will surely try to migrate to the use yours. Our use case mainly decouples implementation fromnew in the code and provides ease-of dates testing - we need to freeze the clock and set its time for some given moment. If you are open to SettableClock implementation and interface, I would love to try it.

@nicolas-grekas
Copy link
MemberAuthor

we need to freeze the clock and set its time for some given moment. If you are open to SettableClock implementation and interface, I would love to try it.

Sure we are if this proves needed :)

Copy link
Member

@derrabusderrabus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I like the component in its current state. Nice and simple. Let's see how it works for us when we dogfood it in other components.

@nicolas-grekas
Copy link
MemberAuthor

nicolas-grekas commentedJul 21, 2022
edited
Loading

After readingphp-fig/fig-standards#1257 I'm wondering if it wouldn't make sense to addwithTimeZone()?

That'd allow registering only one clock service and give a contract to users to stick to a TZ.

Loosely related, I'm wondering if we shouldn't swap our approach to naming and addClockInterface to the component. That'd be the only interface provided by the component.

The benefit of this approach is that it would make us less dependant on the outcome and pace of the FIG.

My preference goes to doing both changes I propose here: one capable ClockInterface in the component and one more specific in PSR20 when it'll be released.

@nicolas-grekasnicolas-grekasforce-pushed theclock branch 2 times, most recently from001bb63 to34b6c13CompareJuly 27, 2022 12:36
@nicolas-grekas
Copy link
MemberAuthor

PR updated with the approach proposed in my previous comment:
The component now provides aClockInterface with 3 methods:now(),sleep() andwithTimeZone().

I also registered aclock service into FrameworkBundle and a corresponding autowiring alias for ClockInterface.
Since we now havewithTimeZone(), there is no need to provide several clock services (an UTC one and a default-TZ one.)

When PSR20 will be out (and if it remains compatible with the component), ppl will have the choice to use either the generic and narrow abstraction from the FIG - or the more capable one from the component when in need of extra features.

/cc @symfony/mergers even if you already voted, please vote again if you approve these changes.

Copy link
Member

@ycerutoyceruto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I loved it!

@fabpot
Copy link
Member

Thank you@nicolas-grekas.

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@jderussejderussejderusse left review comments

@stofstofstof left review comments

@GromNaNGromNaNGromNaN approved these changes

@lyrixxlyrixxlyrixx left review comments

@chalasrchalasrchalasr left review comments

@kbondkbondkbond approved these changes

@ycerutoycerutoyceruto approved these changes

@derrabusderrabusderrabus approved these changes

@fabpotfabpotfabpot approved these changes

+9 more reviewers

@bendaviesbendaviesbendavies left review comments

@ro0NLro0NLro0NL left review comments

@drupoldrupoldrupol approved these changes

@theofidrytheofidrytheofidry left review comments

@HeahDudeHeahDudeHeahDude left review comments

@heiglandreasheiglandreasheiglandreas left review comments

@afilinaafilinaafilina left review comments

@andersonamullerandersonamullerandersonamuller left review comments

@eerisoneerisoneerison left review comments

Reviewers whose approvals may not affect merge requirements

Assignees

No one assigned

Projects

None yet

Milestone

6.2

Development

Successfully merging this pull request may close these issues.

23 participants

@nicolas-grekas@drupol@heiglandreas@javiereguiluz@derrabus@theofidry@matthiasnoback@stof@ro0NL@lchrusciel@fabpot@kbond@afilina@GromNaN@lyrixx@jderusse@bendavies@andersonamuller@yceruto@eerison@chalasr@HeahDude@carsonbot

[8]ページ先頭

©2009-2025 Movatter.jp