Stopping time with PHP
Tags: php, datetime, symfony, laravel
Published: 2021-11-09 08:16:16

## Introduction

It is a good practice to use an interface to manage the clock in an application, as it allows having full control of time. For example, it eases testing, as it lets us define the concrete time for each test. [Frank de Jonge](https://blog.frankdejonge.nl/being-in-control-of-time-in-php/) and [Matthias Noback](https://matthiasnoback.nl/2018/02/mocking-at-architectural-boundaries-persistence-and-time/) have blog posts about it, [brick has an implementation](https://github.com/brick/date-time), and there is even a [PSR proposal to have a `ClockInterface`](https://github.com/php-fig/fig-standards/blob/master/proposed/clock.md).

However, there are more use cases for `ClockInterface`, besides testing. In our case, we had to implement endpoints to show the movies that are just released, and the ones that are about to release in the next few days or weeks.

![Filmin coming soon page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dqhh5u7i8boiwa32w7kh.png)
 

Being an API, it is easy to test using PHPUnit, as you could easily set the required date for each test. However, our frontend team had to integrate the website (a Vue app) with this endpoint. This feature is easy to develop and test locally if you have a fresh backup of the production database, as the date of your local system will be in sync with the data within your local database. However, what if you do not have your local database updated, and getting a copy from production takes too much time?

For sure, an option would be to update the local data either by querying directly the database or by using a back-office, if there is one. Nonetheless, this is a manual task that could take some time if you have to update many elements. And it is something that you have to do recurrently if you need to review the implementation after the date you set up passes.

But there is another option: we can take advantage of the `ClockInterface` to fixate the date of the whole system.

## Implementation

Having a `ClockInterface` such as:

```php
interface ClockInterface
{
    public function now(): DateTimeImmutable;
}
```

We could have this `FixedClock` implementation:

```php
use DateTimeImmutable;
use DateTimeZone;
use Exception;
use InvalidArgumentException;

final class FixedClock implements ClockInterface
{
    public const FORMAT = 'Y-m-d H:i:s';
    
    private ?DateTimeImmutable $dateTime;
    private DateTimeZone $timeZone;

    public function __construct(string $timeZone, string $dateTime = '')
    {
        $this->timeZone = $this->parseAndValidateTimeZone($timeZone);
        $this->dateTime = $this->parseAndValidateDateTime($dateTime);
    }

    public function now(): DateTimeImmutable
    {
        if ($this->dateTime !== null) {
            return $this->dateTime;
        }

        return new DateTimeImmutable('now', $this->timeZone);
    }

    private function parseAndValidateTimeZone(string $timeZone): DateTimeZone
    {
        try {
            return new DateTimeZone($timeZone);
        } catch (Exception) {
            throw new InvalidArgumentException(
                sprintf('Value "%s" for time zone is not valid.', $timeZone)
            );
        }
    }

    private function parseAndValidateDateTime(string $dateTime): ?DateTimeImmutable
    {
        $parsedDateTime = trim($dateTime);
        
        if ($parsedDateTime === '') {
            return null;
        }
        
        $dateTimeImmutable = DateTimeImmutable::createFromFormat(
            self::FORMAT,
            $parsedDateTime,
            $this->timeZone
        );
        
        if (! $dateTimeImmutable) {
            throw new InvalidArgumentException(
                sprintf(
                    'Value "%s" for date time is not valid. Expected to have the format "%s"',
                    $dateTime,
                    self::FORMAT
                )
            );
        }
        
        return $dateTimeImmutable;
    }
}

```

It is not a good practice to perform anything besides assign properties in the constructor, but it will prove useful afterwards.

## Configuration

### Symfony

We only need to set the fixed clock up for our development environment. By default, Symfony loads the `services.yaml` file and then the `services_{environment.yaml}`, so you can override any service on per-environment basis. To do so, we add to our `services_dev.yaml` the following lines:

```yaml
parameters:
    timezone: '%env(string:TIMEZONE)%'
    now: '%env(string:NOW)%'

services:
    _defaults:
        autowire: true
        autoconfigure: true

    Filmin\SharedKernel\Domain\ValueObject\Time\FixedClock:
        arguments:
            - '%timezone%'
            - '%now%'

    Filmin\SharedKernel\Domain\ValueObject\Time\ClockInterface: '@Filmin\SharedKernel\Domain\ValueObject\Time\FixedClock'

```

What we do here is:

- Lines 1-3: set up container parameters that contain the timezone and the current time from environment variables.
- Lines 9-12: setup our `FixedClock` implementation as a service. In Symfony, we can not use named constructors when defining value objects as services. Thereby, we validated the values within the constructor, as shown above.
- Line 14: alias the `ClockInterface` to our `FixedClock`, so any service that type hint a `ClockInterface` will use the `FixedClock` implementation, thus overriding any previous, generic definition.

If we do not set up any value for the environment variable `NOW`, the application will use the system time. Therefore, switching between a fixed time and the system time is as easy as to update an environment variable.

### Laravel

In a Laravel application, we would configure the `ClockInterface` within a service provider using a singleton, such as:

```php
$this->app->singleton(ClockInterface::class, function (Application $app) {
    if ($app->environment('local')) {
        return new FixedClock(getenv('TIMEZONE'), getenv('NOW'));
    }
    
    return new SystemClock();
});

```

In this case, `SystemClock` is an implementation of `ClockInterface` that uses the system time, that is what we want in production. As with the previous case, switching between the system time and a fixed time is as easy as to update an environment variable.

Note that we are not experts in Laravel, so this configuration could probably be improved.

## Conclusion

We saw that using a `ClockInterface` eases the testing process and allow fixating the time of the whole application, that can be useful for local testing purposes.

If you are implementing a new application, it is simple to start using `ClockInterface` everywhere. However, if you are working on legacy code, it could take some time until you have the `ClockInterface` in your whole application. In those cases, it could be useful to apply the boy scout rule: "Always leave the code better than you found it".