PHP - Using the PSR-20 Clock

Daniel Opitz
Daniel Opitz
16 Nov 2023

Handling time can be a tricky task, especially when it comes to testing and ensuring predictable results. PHP provides various functions like time(), microtime() and the \DateTimeImmutable class to fetch the current time, but the problem is when \DateTimeImmutable is hard-coded into logic, unit tests will not be reliable. This is where the ClockInterface comes into play.

What is the ClockInterface?

The ClockInterface is a concept that aims to provide a standardized way to obtain time in PHP applications. It offers a solution not only for accessing the real system time but also for mocking time in scenarios where predictable results are required, such as during testing. By using the ClockInterface, developers can avoid the need to resort to PHP extensions or complex workarounds, like redeclaring the time() function within a local namespace.

The clock interface defines the most basic operations to read the current time and date from the clock. It returns the current time as a \DateTimeImmutable object.

<?php

namespace Psr\Clock;

use DateTimeImmutable;

interface ClockInterface
{
    public function now(): DateTimeImmutable;

}

The following implementation show how the ClockInterface can be used to fetch the current time from the system.

<?php

namespace Example;

use DateTimeImmutable;
use Psr\Clock\ClockInterface;

final class SystemClock implements ClockInterface
{
    public function now(): DateTimeImmutable
    {
        return new DateTimeImmutable();
    }
}

The FrozenClock can be used to fetch create a “frozen” time instance for testing purposes.

<?php

namespace Example;

use DateTimeImmutable;
use Psr\Clock\ClockInterface;

final class FrozenClock implements ClockInterface
{
    private DateTimeImmutable $now;

    public function __construct(DateTimeImmutable $now)
    {
        $this->now = $now;
    }

    public function now(): DateTimeImmutable
    {
        return clone $this->now;
    }
}

Of course, this was just an example, and in reality you could just install a package that already provides such functionality, for example the symfony/clock package.

Why Bother with ClockInterface?

You might be wondering why we need yet another method for dealing with time in PHP applications. The key reason is interoperability. While there are existing libraries that tackle this issue, they often come with their own clock interfaces, creating a lack of standardization.

Popular libraries, such as Carbon and its fork Chronos, offer mocking capabilities through a static setTestNow() method. While this can be useful, it lacks isolation and requires calling the method again to stop mocking.

Usage

It’s easier to explain the usage of the usage with a real example. So let’s try out the symfony/clock package.

Open your console and navigate to your project’s root directory, then run the following command:

composer require symfony/clock

The retrieve the current system time, you can use the Symfony\Component\Clock\Clock class as follows:

use Symfony\Component\Clock\Clock;

$clock = new Clock();
$now = $clock->now();

// The current date and time
echo $now->format('Y-m-d H:i:s');

For testing purposes, you can use the Symfony\Component\Clock\MockClock class, which will return always the same time.

use Symfony\Component\Clock\MockClock;

$clock = new MockClock('2024-01-31 15:45:59');
$now = $clock->now();

// A fix value: 2024-01-31 15:45:59
echo $now->format('Y-m-d H:i:s');

Using Dependency Injection

You can now use dependency injection to provide a ClockInterface implementation to objects requiring access to the current time.

Example

<?php

namespace MyNamespace;

use Psr\Clock\ClockInterface;

final class MyService
{
    private ClockInterface $clock;

    public function __construct(ClockInterface $clock)
    {
        $this->clock = $clock;
    }

    public function doSomething(): void
    {
        $now = $this->clock->now();
        $formattedTime = $now->format('Y-m-d H:i:s');

        echo "[$formattedTime] $message\n";
    }
}

In this class, the ClockInterface is injected through the constructor, allowing you to use it to get the current time.

Defining Services in a DI Container

To use the ClockInterface within your class, you need to tell the DI container what implementation should be used and injected.

Here’s an example of how you can define the ClockInterface::class using PHP-DI:

<?php

use Symfony\Component\Clock\Clock;
use Psr\Clock\ClockInterface;

return [
    ClockInterface::class => function () {
        return new Clock();
    },
];

Now, the DI container will inject the Symfony Clock instance into your application classes.

By defined different ClockInterface implementations within the DI container, you can control whether your application uses the system clock or a mocked / frozen clock, which is useful for testing and other scenarios.

To replace the default implementation, you can use the set method of the DI container as follows:

use Psr\Clock\ClockInterface;
use Symfony\Component\Clock\MockClock;

$now = '2024-01-31 00:00:00';
$timezone = 'UTC';

$container->set(ClockInterface::class, new MockClock($now, $timezone));

In practice, you would better put that code into a test trait for better reusability.

<?php

namespace App\Test\Traits;

use DateTimeImmutable;
use DateTimeZone;
use Psr\Clock\ClockInterface;
use Symfony\Component\Clock\MockClock;

/**
 * PSR-20 Test Clock.
 */
trait ClockTestTrait
{
    private function setTestNow(DateTimeImmutable|string $now = 'now', DateTimeZone|string $timezone = null): void
    {
        $this->container->set(ClockInterface::class, new MockClock($now, $timezone));
    }
}

Note: This example assumes that you have a $this->container member variable in your PHPUnit test class with the applications DI container instance.

Usage

use ClockTestTrait;
// ...

// Within the phpunit test method
$this->setTestNow('2014-02-01 14:45:30');

This approach allows you to decouple your code from specific clock implementations, making it more flexible, maintainable, and suitable for different use cases.

Conclusion

In conclusion, the ClockInterface is a valuable addition, simplifying time handling, enabling easier testing, and promoting interoperability between packages.

It’s a step towards more reliable and maintainable PHP applications that can accurately manage time-related tasks.