Clock component in Symfony 7

Jean-Michel LIEVIN
5 min readMar 15, 2024

--

Control time in your Symfony applications with the Clock component.

Picture by Stockgiu on freepik.com

Symfony’s Clock component is a service for managing time in your application. This component offers a simple and flexible interface for obtaining the current time, as well as for creating custom clocks.

Historically, the component appeared in Symfony 2.8 in the form of Clock Mocking to resolve all time-related test errors.

In fact, certain tests linked to time variables can fail during continuous integration phases or during server load peaks.

Another disadvantage arises when using, for example, the sleep(20) method in tests, which requires you to wait for a period of 20 real seconds during script execution.

The Clock component is designed to solve the following problems:

  • Lack of a standardized solution:

Before Symfony 6.2, there was no standardized solution for managing time in Symfony applications. Developers had to use third-party libraries or create their own code, which could lead to compatibility and maintenance issues.

  • Difficulty obtaining the current time:

Getting the current time reliably and accurately can be difficult, especially in distributed applications. The component makes it easier to obtain the current time in different time zones and contexts.

  • Lack of flexibility

Existing time management solutions were often rigid and didn’t allow for easy customization of applications’ time behavior. This component offers great flexibility for creating customized clocks and manipulating time.

  • Difficulty testing time code:

Testing time-dependent code can be difficult, as it’s hard to simulate the current time in unit tests. The Clock component facilitates testing of time-dependent code by providing a simulated clock implementation.

This component has evolved in Symfony 6.3 by introducing a new Clock class and a now() function. This class provides a simpler, more intuitive interface for obtaining the current time. The now() function is a convenient shortcut for obtaining the current time from the default clock. The global clock can now be injected as a service into classes that need it. This decouples the code from the concrete implementation of the clock. It also makes it easier to test time-dependent code.

Case study:

Let’s consider the case of a website offering courses accessible from a specific date or time. For this study, we’ll use the Symfony framework version 7.0.

The Clock component provides several different clock implementations:

  • NativeClock: This implementation uses the default system clock.
  • MockClock: This implementation is useful for testing purposes. It allows you to simulate the current time.
  • MonotonicClock: This implementation is useful for measuring performance. It provides a clock whose time can never go backwards.

NativeClock:

Benefits:

  • Easy to use
  • No additional configuration required
  • Accurate and reliable

Disadvantages:

  • Cannot be simulated for testing
  • Cannot be used to measure performance

Sample code:

<?php

namespace App\Controller;

use App\Entity\Course;
use App\Repository\CourseRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Clock\NativeClock;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class CourseController extends AbstractController
{
#[Route('/courses', name: 'courses.index')]
public function index(CourseRepository $courseRepository): Response
{
// NativeClock Instance
$clock = new NativeClock();
$now = $clock->now();
$courses = $courseRepository->findAll();

$availableCourses = array_filter($courses, function (Course $course) use ($now) {
return $course->getAvailableAt() <= $now ?? [];
});

return $this->render('course/index.html.twig', [
'availableCourses' => $availableCourses,
'unavailableCourses' => array_diff_assoc($courses, $availableCourses)
]);
}
}

This clock implementation is ideal for most applications. It’s easy to use, accurate and reliable.

MockClock

MockClock is a simulated clock implementation.

Benefits:

  • Simulates the current time
  • Useful for unit and integration testing
  • Allows you to test your application’s behavior in different situations.

Disadvantages:

  • More complex to use than NativeClock
  • Requires additional configuration

Sample code:

$delay = 3600;
$client = static::createClient();

$mockClock = new MockClock(self::EUROPE_TIMEZONE);
$startTime = $mockClock->now()->getTimestamp();
// Simulates a 1-hour delay
$mockClock->sleep($delay);
// Check 1-hour simulate time
$this->assertEquals($delay, $mockClock->now()->getTimestamp() - $startTime);

As previously stated, it is no longer necessary to wait for the return of the sleep() method, as the MockClock component does not block script execution.

Time: 00:00.520, Memory: 52.50 MB

OK (11 tests, 22 assertions)

This clock implementation is ideal for testing. It lets you simulate the current time and test your application’s behavior in different situations.

MonotonicClock

Benefits:

  • Provides a time that can never go backwards
  • Useful for measuring performance

Disadvantages:

  • More complex to use than NativeClock
  • Requires additional configuration

Sample code:

$numbers = 50;
$availableForeach = [];
$unavailableForeach = [];
$courses = [];

for ($i = 0; $i < $numbers; $i++) {
if ($i % 2 === 0) {
// Available course
$course = $this->createMockCourse(self::EUROPE_TIMEZONE, 1);
} else {
// Unavailable course
$course = $this->createMockCourse(self::EUROPE_TIMEZONE, 1, true);
}
$courses[] = $course;
}

$clockForeach = new MonotonicClock();
$startForeach = $clockForeach->now();

foreach ($courses as $course) {
if ($course->getAvailableAt() <= $startForeach) {
$availableForeach[] = $course;
} else {
$unavailableForeach[] = $course;
}
}

$endForeach = $clockForeach->now();
$executionTimeForeach = $endForeach->diff($startForeach);

$clockFilter = new MonotonicClock();
$startFilter = $clockFilter->now();

$availableFilter = array_filter($courses, function (Course $course) use ($startFilter) {
return $course->getAvailableAt() <= $startFilter ?? [];
});

$unavailableFilter = array_diff_assoc($courses, $availableFilter);

$endFilter = $clockFilter->now();
$executionTimeFilter = $endFilter->diff($startFilter);

echo "Foreach method:" . PHP_EOL;
echo "Even values count : " . count($availableForeach) . PHP_EOL;
echo "Odd values count : " . count($unavailableForeach) . PHP_EOL;
echo "Execution time : " . $executionTimeForeach->format('%s,%f') . " seconds" . PHP_EOL;

echo "Array_filter method" . PHP_EOL;
echo "Even values count : " . count($availableFilter) . PHP_EOL;
echo "Odd values count : " . count($unavailableFilter) . PHP_EOL;
echo "Execution time : " . $executionTimeFilter->format('%s,%f') . " seconds" . PHP_EOL;
Foreach method:
Even values count : 25
Odd values count : 25
Execution time : 0,8 seconds

Array_filter method
Even values count : 25
Odd values count : 25
Execution time : 0,10 seconds

MonotonicClock is an ideal clock implementation for measuring performance. It provides a time that can never go backwards, enabling precise measurements.

Conclusion

NativeClock, MockClock and MonotonicClock are the three clock implementations available in Symfony 7’s Clock component. Each implementation has its own advantages and disadvantages. You should choose the implementation that best suits your needs. Clock is a powerful tool for managing time in your application. It’s easy to use, flexible, accurate, extensible and offers a number of useful features.

The source code for this article is available on Github:

Sources:

--

--