-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[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 our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool initiative!
There was a problem hiding this 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-compatible stella-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 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). 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.
/** | ||
* @author Nicolas Grekas <[email protected]> | ||
*/ | ||
class MockClock implements TimeInterface |
There was a problem hiding this comment.
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.
Or to multiple specific ones when the constructor also accepts an array of DateTimeImmutables.
A possible further Implementation might then be a FreezableClock
that can be frozen multiple times using @theofidry's idea via a freeze
method.
There was a problem hiding this comment.
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 for and 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.
There was a problem hiding this comment.
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 first MockClock
version wasn't final
, 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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 :)
There was a problem hiding this comment.
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
Hi Andreas, Why would we want to remove the |
9d304cf
to
b8694fe
Compare
🤷 hurry up PHP-FIG :)
I'd better not as I explained in #46715 (comment) |
Thanks for the review btw @heiglandreas ! |
The So the component would either require an external |
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.
Replying in the main thread to help readers follow the discussion: see #46715 (comment) about this topic. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool
0d67d50
to
8a65085
Compare
Thanks for this proposal! In the description we see this:
I guess it's a typo that If the naming of the class/methods is up for debate, here's a proposal for your consideration: // Before
interface TimeInterface
{
public function now(): \DateTimeImmutable;
public function sleep(float|int $seconds): void;
public function timeInt(): int;
public function timeFloat(): float;
public function timeArray(): array;
}
// After
interface ClockInterface
{
public function now(): \DateTimeImmutable;
public function sleep(float|int $seconds): void;
public function timestamp(): float;
} I'd remove Thanks. |
605f167
to
ff18540
Compare
Brain activity at night made me realize that we could remove the Then I did my homework and tried to verify my own claim about performance, which is the justification I've provided for these Comparing This difference is not significant enough to warrant my claim. I'm thus removing all Since this means the interface now contains only I've also implemented the offset logic @derrabus suggested for 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 a |
e35de12
to
2876ada
Compare
I'm looking forward to the newest PSR-20 and this component! At Sylius, we've recently introduced the Calendar component, but we will surely try to migrate to the use yours. Our use case mainly decouples implementation from |
Sure we are if this proves needed :) |
There was a problem hiding this 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.
After reading php-fig/fig-standards#1257 I'm wondering if it wouldn't make sense to add 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 add 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. |
001bb63
to
34b6c13
Compare
PR updated with the approach proposed in my previous comment: I also registered a 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I loved it!
Thank you @nicolas-grekas. |
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 a
ClockInterface
in PSR-20.This PR provides a
ClockInterface
, 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()
.The
sleep()
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.