Aller au contenu
AFUP AFUP Day 2025 Baromètre Planète PHP PUFA
 

Watch the clock

Description

Creating current time information in code is rather easy by calling time() or new DateTimeImmutable() directly when the information is necessary. But that then makes the code a nightmare to test. In this session we will see how PSR-20 allows us to change that by still making it easy to generate current time information but also allowing one to test the code. And we will get some background info about why it was designed in such a way, what obstacles are in the way and how to still be able to use the feature.

Conférence donnée lors du Forum PHP 2022, ayant eu lieu les 13 et 14 octobre 2022.

Informations complémentaires

Vidéo

Le speaker

Andreas HEIGL

Andreas helps solving problems in various software-teams. He sometimes even uses code for that. Besides his family, his work as tribe leader and contributions to different open-source-projects he still finds the time to run PHPUGFFM, the PHP-Usergroup in Frankfurt/Main and maintain PHP.ug.

Commentaires

So cool and interesting
Stéphan, le 13/10/2022
Very interesting
Anouk Moutounet, le 13/10/2022

Verbatim

_ Thank you very much. [FRENCH] And that is about my friend. It is amazing to see people in France. I want that want to have someone speak English. Time seems to be a pain. This is what we will talk about today. Clocks and face. Kind of. I think I can skip the slides because that was already mentioned kind of. I would appreciate feedback that you can give, constructive feedback about what I'm doing wrong and what I can improve on. You can use a QR code, whatever, that's about it. With those technicalities aside, let's dive into the whole thing. This is an email that started a bunch of stuff for me a few years ago, I was in the hospital, and had nothing to do then check my emails, and this email appeared. On a fake mailing list. How many notes the framework operability group, there could be a few more, but I will talk about that, so perhaps afterwards, more on that. That's a mailing list, and he proposed I would like to actually have a clock interface as a standard. Most of you know PR standards, PSR for zero and four for autoloading and stuff like that will get to that in a seconds In this started my curiosity because that is about time, I may time nerd, sorry about that so I could not help. But, before we start with that perhaps let's get back to what is a clock interface. And to describe what a clock interface is.

Who of you knows what a clock interface is? Some of you, but most of you, 50-50 I would say. Let's dive into that. Why is it important, why is it interesting, what is it solve your issue with your appearing box and disappearing box? We have best practices into object-oriented programming, test and development, and stuff like that. Sort of principles. To make that easier, let's think about a problem, a common problem that perhaps everyone has heard about. Let's filter all the items that are in the future, so you have a list of a bunch of items with a date involved. And you want to have all the stuff that is in the future. And to do that you can write code. This is already a bit abstracted. That could be even easier. I will show you in a second why that could be better to do it this way. Raising a filter. With a GIT method with an array of things that have a time key that shows a value. We as a function for that, and that function calls a instant or a method on an instance of a class, whatever. Let's call that class filter future items class. Perhaps, I should have added a L there. The solution for this class that we are calling, the easy solution is, we have a function validate that gets an item, as I said, this item has a key for an integer . As a value. And we just returned where the time, so the response for that time function is smaller than the value in this key. Problem solved. Who has done such an approach, already? Some. Yes. Most of you don't use time at all. Okay. So that was easy. But that gives us a problem. Because time is what we call and in pure function.

What are pure functions? Has anybody heard the term pure function? Okay, so for those who have not heard it, a peer function is a function that always returns the same value when you pass in the same parameters. In the function has no side effects. We can leave the side effects out. But the main issue here is whenever we call time with the same parameters in this case none. We get a different value out of that. And that makes the whole thing hard because it is hard to test. It is hard to manage. And it is kind of always unpredictable what happens in there. And the main thing with a clock interface is, to get rid of this unpredictability. So, how do we do that? Well, one thing is we can pass the time into the function. And now, our function is pure because when ever we call valid with our item, with the same item, and the same current time, we get the same result out of that. Problem solved. What am I here for? Well, that's also easy to test now, because it is predictable. So, it's write a test for that, we have three items, they all have different times, and we do some assertions, and yes! Everything works, doesn't it!? Who spots the problem? Yes. Exactly, that time can change, or the response from time, from the time function can change between two calls to it. So it will work most of the time. And suddenly it fails because yes, it did not work. So yes, we have the same problem. We just moved it from within our function out of our function. So, we made it somebody else's problem, cool. Most of the time somebody else is still me. So that is one way to fix that. We just call time once. Yes! Now it works. This is predictable. And this will either work or it won't. It should always work, because once it is in time, that is it. And by the way, please, please, please use data immutable and the appropriate functions. But, that is a different story. But we will get to that later on. But still we have the problem. That we are calling and in peer function within our whole environment. That we want to get rid of. One possibility, yes cool, let's inject this daytime immutable that we are using now instead of time, let's inject that into our filter. So, instead of instantiating that in the function, we injected into it. Cool. It works. It will still work without any issue. Yes, we need to do that because of some other thing, I don't know if you notice that, let's go back for a bit. Because here in this case, the valid function requires two parameters. But, in this method, we are only passing one. So, that is why we actually have to inject that thing. Here we go. So, now we inject that time . Now recall the valid function with our one parameter. Perfect. Everything solved. Yes, kind of. Who spots the problem here? Anyone spot an issue with that? Yes. That time might change, yes, and the main thing is here, we have our filter and new filter future items, we have that followed immediately Mark are actually filtering algorithm. In real life most of the time there is some time in between. Yes, we are only talking about second so hey, who cares, we have filtering future items. But depending on your use case, the seconds may actually make a difference. So that is also somehow not working. What are the possibilities that we have, well, how do we solve that with other issues. If we need something at a certain point in time and we can't inject it right away. Well, we use factories. So, let's create a factory for that. Let's create a current daytime factory. It has a current daytime object function.

That it returns and you are like awesome. And now we actually have our problem kind of solved. Instead of injecting the daytime object, we now inject the factory and in the valid method. Yes, that looks a bit like awesome long code. But at least it is verbose, and everyone knows what is going on there. So we call in the current daytime factory, requesting our current daytime object, and get a timestamp method on that, and check to see if it's bigger or smaller than whatever we have. Cool. Problem solved. And the awesome thing is we only need to change one part here in this filter function, instead of injecting actually the daytime object, the filter method itself, stays the same. Amazing. Actually that is it. Because now you are able to inject something into your function that returns to the current daytime object. How about testing? Well, when you are testing, well, you can mock the current daytime factory. And let that current daytime factory always return a predictable daytime object instead of the actual current time and date. So for testing, you now even have the possibility to get predictable results. While the actual factory method is not pure because it always return something different, even though you are calling it with the same parameters. But, you can make that predictable, you can make it a peer function. Okay now something else kicks in, we should not use mock objects, we should use the implementation or whatever, but there's a way around that, one thing is you can of course extend your factory. Yes. We are working with object oriented programming, so you can do that, should you do that, perhaps not. Instead of that, because you will overwrite the only existing function in the factory, so it is kind of pointless. So instead of that, it's create an interface for that. So it's create daytime factory interface. Whether you name that interface at the end were not is a different question, I'm not talking about that here. But that interface defines a current daytime data object. Which returns and in mutable objects, the same of what we know. And our factory now just implements this interface. That's all we need to change, which is at the implements daytime factory interface. Everything else stays the same.

And now, we call that exactly the same. The difference though is in our constructor, the type we are requesting here, is no longer a daytime factory, but a daytime factory interface. And now, we can write a separate daytime factory for actually returning protectable values. Implementing the same interface, awesome, great! And we can use this filter here and the cool thing is, you don't need to write your own class, you can write your class within that test. Because this is nothing else, the new class daytime interface, and the functions, and that is it. You don't need a separate class for that. You can use that here. But, of course you can write a separate class for that, it is up to you. As soon as you want to use it twice in two different places, make sense to have a separate class. That is it. I am done. Thank you very much. Any of you having questions regarding that? That did not understand what I was talking about here. Because this is in essence Emma the just. It's what I want to get across. How easy it is to use an interface to making in peer function kind of predictable. I see no one actually having issues with that. If you have, and you don't want to say that right now, hit me up afterwards and we will talk about that. So, to get that to the next level, let's do some renaming for that, instead of doing that renaming, let's call it clock interface, instead of current daytime factory, call that system clock. And instead of daytime factory, yes, this class extends daytime factory interface thing, let's call that a fixed clock or a frozen clock. Whatever you want in the end. In the current daytime object function, let's call that now, it's a bit shorter. Only one or two characters, but hey. And that is the clock interface. So, that is easy. So, you might ask yourself, why do we need a fake for that. Because in essence, it is easy to do, it's not really much code, you can put that into your own project, you can build your own clock interface, you don't need any fake or whatever, and that is fine. If you want to do that, do it! The issue you have, is that you are not interoperable. You will have to maintain this system clock, this interface, this fixed clock, frozen clock, whatever you call it, if you are using an interoperable interface, that somebody else provides, and in this case, that the fake provides, you can use already existing code out there.

As clock, you don't need to worry about that. You don't need to worry about, did I implement that right or wrong or what about time zones and whatnot, we will get to that in a second. You don't need to worry about that. Implement the interface, you just request and call it that now method, and I get my date. And that's why it might make sense to actually use something that is interoperable. The fake is the framework into operability group. Short history, I think in 2010,, every thing started with the piece on the zero for autoloading which is now deprecated. But this thing kind of revolutionized the way we are currently coding PHP. Because without the zeros, there would have been no possibility to actually build composer. And without composer, we were not code the way we are. And that was pretty soon deprecated in favor of PSR four, 2013. That was the time when PHP 53 hit the road with name spaces. Because name spaces are required for PSR four. Another when you might know is PSR three for logging, what else do we have PSR six for caching for example, to name a few. So that is it. Oh look, okay, that was, yes, how did I get involved in that whole thing? Because now were talking about PSR 20. PSR 20 is proposed clock interface. I already told you, I was in the hospital, nothing else to do, and this email came in that I showed you at the beginning, and this email kind of proposed an interface, that's what Chris actually thought the interface should look like. So, he wanted to have five different methods, one for timestamp, one for in mutable object, one for daytime, one for micro time, and one for time zone. As I am a time nerd, I knew a few of these things, and my immediate thought was, at least regarding this immutable and daytime difference direct reference, the one we have already seen this morning, the time guy in PHP that built the time library, he once said, daytime immutable is what daytime should have been in the first place. He actually regrets to have ever introduced daytime as a mutable object. Let's get rid of that in that interface because it does not make sense. It actually gives more headaches than it should. So just use daytime immutable.

Time zone is actually part of the daytime immutable object, so we don't need a separate function for that. And we can get a timestamp from a daytime immutable object by calling the GIT times that method for that so we don't need a different function for that. Do you see something happening? Because we actually don't need micro time because you can get that from you guessed it, the daytime immutable object. What we are left with is just an interface with a function, immutable. Let's just rename that into now, and what do we have that, exactly, what we had before. Yes, that was I think a matter of a few days. Or something like that back and forth on the email list. And then within the very short reformed working group of which I was a member, or which I am a member. Some other topics came up. One of those were, yes but, won't it be cool to have a sleep method in there. No. Yes, you are right, it would be cool to have an object that you can use and call a sleep method on. But, that is nothing for the clock interface that were talking about, so we always have to differentiate between the interface and the actual implementation which are two different things. In the implementation can implement a lot of interfaces, and one could for example be, sleep interface, whatever you want to call it. But, sleeping or other such things behavioral things that we want to separate from our clock interface. From getting predictable time. That took a bit of back-and-forth, but we at one point said no it doesn't make sense. Then my major topic came up, time zones, yes, no. Time zones are amazing, they are cool, I can talk to you for hours about time zones. I won't do that here right now, don't worry. But, after give you a short introduction in them, at least a bit of that. Time sums, you can always set the time zone for the daytime immutable object. You get a new daytime immutable object if you call it set time zone. Then you have an object that has your specific time zone. The problem I see with that is we always have to think about what do we want, we want now.

So what is now? Now, I like to call it a slice of time. And it does not matter where this slice of time is actually observed. Which is what the time zone tells you. Now is just a slice of time, and this slice of time is the same whether you are in Paris, or in Kathmandu, or in New York, or wherever, or even on Mars, the slice of time is exactly the same. And so, if you create a daytime object, a daytime immutable object, with a specific time zone, and create a separate one with a different time zone, you can actually compare those two, he actually use that greater than, smaller than, equals, triple equals which will always be self because it will not the same. You can see which one bigger is smaller. You don't need to do any funny stent Mathematics, is the timestamp the same, just compare the two objects, it is a value object. It doesn't matter where the value was observed. Yes, it gets interesting as soon as you want to format that for printing or whatever, but that is a different topic. If you want to format that for printing, that is completely separate from getting now. So in essence, it does not matter whether this now function returns you an object with a time zone of UTC, or your Berlin, or Europe Paris, Europe, Asia, Kathmandu or America New York. It does not matter, if you want to compare them and work with them within your domain. Happy to talk about that later, but let's keep it at that point, any questions regarding that? No. Good. As I said, hit me up afterwards, if you have one.

As soon as you have to know the time zone of your daytime object within your business domain, for me that is a.[INDISERNIBLE] Something fishy is going on here. You should not need to know the time zone of your daytime object for doing comparisons or whatever. Some people might disagree, happy to discuss that. As I said, later on. So, yes, you can set your, no one thought in the actual implementation, if you are implementing your system clock, no one actually restricts you to not set a specific time zone, but, that was always, shouldn't the now method always return the daytime object in UTC? The interface, for the interface that is deftly completely irrelevant. If you want to have a daytime object returned with a specific time zone, no one is hindering you from implement the system clock that actually does that. But, that is the difference between the interface and the implementation. For the interface is completely irrelevant. So, what are the next steps? This working group is working, working and big quotes, now for 1.5 years, or something like that, for such a small interface that is quite some time.

That has nothing to do with the figure itself, that was due to different reasons, two people of the working group, are swamped on other things, so they resurfaced recently, the working group now has actually decided yes, this is ready for the final build and now it needs to be cast in the PHP fig which still has not happened, I was eager to tell you yes, it is available, but this vote has not happened. If that starts to happen, it's two or four weeks time until we can say yes, this piece is ready to be used. And have no clue when that will happen, so I'm so so sorry to say, it might take a while. Until then, I took the liberty to actually implement the interface into a package that you can readily use. Because all the other things that we talked about before that the fig is famous for like autoloading like caching, whatever, I was always like we have an issue here, no one else has tackled that. Let's think about an awesome solution for that. The awesomeness is discussable, but there was nothing. In case of the clock interface, actually, we have four different implementations, all implementing the same method. In the same interface. But, we have one interface for each of these implementations. So it is not interoperable. Which is what we want. What the fig actually does or wanted to do is create one interface that is interoperable with at least those for implementations. And as they just did not get their butts moving, I took the liberty, let's create a package that does exactly the same, that will as soon as PSR 20 is finalized, you implement PSR 20 so you're actually using PSR 20, but until then you can use this package which is exactly the same. Yeah. And that is more or less what this is going to look like. We have a public function now. That is a clock interface, and we have our class full which requires that, which does something with that. So, and, I was a bit faster than I thought originally, we might actually be able to answer some questions. As soon as we run out of time. We move that out into the hallway. Are there any questions? Come on! I can't be that good. So, I answered all your questions? I don't believe you.

[LAUGHTER]

Yes. There were at least two. Hello, hybrid.

_ Any notion of calendar?

_ How do you mean calendar?

_ I mean Gregorian?

_ I can dive into that as well, but that is not something for the clock interface, that is the same with the time zones, it's a slice it now, with the observed that in America, in Kathmandu, or Berlin, and Gregorian time is irrelevant. It is definitely interesting if you're into that kind of stuff, give me a call. But it is not relevant for the clock interface.

_ At the beginning you said time with daytime immutable. What is the actual benefit from this?

_ The benefit is it is actually easier to replace the daytime immutable object to mock the daytime immutable object then actually replace that time. And, time always returns a timestamp. Say don't have any information about yes, this whole topic of where that was observed. It will always be UTC, and you might lose information with that. So, that is why I recommend use daytime immutable, it is most of the time not to much lower than time, yes of course, time is much more instant, but were talking about a magnitude of microseconds. So that is actually your concern in timing. You might have a different problem. And the other thing is, you have more possibilities with the daytime immutable object then you will have calls to time. So, I always recommend using daytime immutable and if you need a date timestamp, you can call it. Does that answer your question? Cool, thanks.

_ When you say slice of now, do you mean UNIX time?

_ No. The slice of time is kind of, okay, I have to dive into that a bit. Time is actually 1/4 dimension. We are constantly moving on a timeline. And whether your markings on the timeline are in timestamp. Or in microseconds. Time, in Melbourne, I do not care, that is just a matter of how you measure the stream, but what you want what you are talking about here, just the slice. And then you can decide as soon you need to, to store that, or display that somewhere, then you declare or decide upon how do I want to display that? How do I want to store that? And that is the point where you need to then think about what kind of thing do I want to use your, do I want to use some other esoteric measure. Do I want to use Mars time for that? So in essence, timestamp, is just one way of telling everyone where on that stream you are. But, it is irrelevant if you just want to say now. Do you understand what I mean? Yes? Let's talk about that later if you're interested in that. I'm going to do a whole talk on that in about three weeks time or something like that. Just this topic of now . So, that is kind of, yes. Any other questions? Now they are coming.

_ Hi, thank you for your presentation. I was wondering about the PHP architecture of the code you presented. Have you considered a low diameter in the architecture you implemented, especially in the food class you introduced into the interface, at that time first. And yes, feasibly if I want to retrieve or do whatever needs to be done, if I want to retrieve a timestamp, I think it would break the.[INDISERNIBLE], Have you heard about it or considered it? The load diameter says you have to take to your direct dependencies, and if I want to manipulate a timestamp, will have to call the clock interface, that interface.[INDISERNIBLE] And then manipulate the timestamp into that.

_ You don't actually usually need to modify the time zone at the time you are requesting the now object.

_ I'm not modifying, just requesting.

_ You may have an issue with that, but still that is the easier way because you only have one object that you are passing around. The alternative would be to pass around an integer timestamp. The problem we have with an integer is that it is an integer. No one actually knows, it is just by definition, that it is a timestamp. It can also be, like I do not know, the number of people in this room. But, you can actually tell that . That's why always recommend use a dedicated value object for whatever you are doing. And if you want to get that timestamp, yes, you have to call the object first. And then you have to call the timestamp method. To get a timestamp. If you're that deep into the whole topic, then you might find other issues as well with other things. I'm not sure whether it makes sense to apply that to everything. Is there someone else?

_ We don't have any time.

_ We don't have any time? I am sorry for that, I will hang out, and feel free to hit me up. Let me say thank you very much. And, please provide some feedback. And I will upload, the slides are already on the Internet. I have links in here for those interested in the details. But, that is it, thank you very much. [APPLAUSE]