Community

Time Travelling in PHP

For the August PHPNW User Group, I decided to take to the stage to present a lightning talk. The idea behind lightning talks is a simple one; present a talk in less than ten minutes. My talk covered an age-old problem of building web systems, ensuring that the system will work in the future by tricking a system into believing it is a different date in the future.

As soon as you start to write something more than a simple website, you may quickly find that you need date-triggered events. Here are a few examples of when you might use them:

  • If a user hasn’t completed their registration 3 days after starting it, send them a nudge via email
  • Send a “hope you’re enjoying our service/product” 3 days after a completed purchase
  • Every month, evaluate a users eligibility to remain on a “freemium” service

Date based triggers keep an application alive and minimise manual intervention. They’re a very simple, essential concept in web development.

PHP to the Future

Detecting the passage of time is not difficult. There are a multitude of ways to compare two dates to see if one occurs after the other and then triggering our behaviour is trivial. That is not the problem that the talk set out to identify. The problem is making sure you (the developer) can observe what will happen in a week/month/year’s time.

Speaking from experience, the way most developers would approach this issue is to bring forward the threshold or deadline, rather than sit there waiting for that deadline to pass. Continuing an example from above, let’s say that we want to send an email to someone one week after registration to ask how they’re getting on. So our test case registers some one, then diligently waits for a week? Probably not. The registration date will be brought forward by a week, circumventing the wait and thus triggering the desired behaviour. Let’s explore why we need to do that.

Simplistically, if we want some PHP code to see if we’ve passed a specific place, you couldn’t be blamed for imagining the following:

if (time() > $deadline) {

// start the ball rolling

}

The problem is nothing to do with procedural code, lack of event systems, object orientation: it’s to do with the use of time(). One does not simply mock out when “now” is and wherever you’re comparing against actual “now”, you’re going to have a problem. One route to working around this would be through indirection. If you ensure that your developers, rather than use time(), DateTime() or strtotime(), always go via your own date/time provider, you have the opportunity to mock. Carbon is a DateTime wrapper which I’ve been watching for a long time. I recently suggested a change which would allow users to mock out “now”. While the initial pull request was rejected, I think the approach has been refined and implemented. Take a look here (https://github.com/briannesbitt/Carbon/pull/17) to see the discussion. The above code changes to:

if (Carbon::now() > Carbon($deadline)) {

// start the ball rolling

}

Using the features introduced in pull request 17, it’s easy to modify what Carbon::now() returns, so we can jump in the future and test our system. However, that solution assumes you have the luxury to be able to say “let’s change all of our code”. If you have an existing system, there is another solution.

Flux Capacitor

Flux CapacitorFlux Capacitor is a neat little (linux-specific) process wrapper which circumvents some core OS behaviour. In a nutshell, it does two things:

1.  Intercepts any system calls which ask for the current time

2. Interrupts any moments of idle in your application (either because the application is sleeping, or it’s polling).

So, my shell script which is running inside flux capacitor will have any calls to sleep() or time_sleep_until() cut short – instantly. However, as far as the calling code is concerned, the sleep finished successfully and time has passed. The best way to demonstrate this is to run a very simple php script:

<?php

echo date('r');

sleep(86400*7);

echo date('r');

If you run this on the command line through flux capacitor, it returns instantly. If you run it without flux capacitor … well, you’ll be waiting a while. So, how do we push our application into the future? Just do a time_sleep_until() during bootstrap and suddenly, any user code, library or core C extension which tries to ask the OS what time it is will be hoodwinked into thinking they’re in the future. You can also demonstrate this using PHP 5.4’s built in web server.

./fluxcapacitor -- php -S localhost:8080

However, because the built in webserver polls every second (waiting for a request), flux capacitor interrupts the second-long sleep and returns instantly. This has the side effect of PHP thinking that time is racing ahead quicker than it really is. For every second I would refresh the page, the time had jumped forward 30 seconds. To prevent this behaviour, use the idleness switch on flux capacitor and set the value to something over a second (like 1300000000). So now, fluxcapacitor will only interrupt sleeps and polls which have been waiting for longer than 1.3 seconds.

I’m really excited by the idea we can temporarily fool our environment into thinking it’s next week, next month or next year. It’s so simple, but it’s about as close as you’ll ever get to manually changing the clock on your PC without the hassle.