Skip to content

Commit e60870f

Browse files
author
Denis Brumann
committed
[Clock] Documentation for new Clock component
1 parent fd3f21f commit e60870f

File tree

1 file changed

+97
-0
lines changed

1 file changed

+97
-0
lines changed

components/clock.rst

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
.. index::
2+
single: Clock
3+
single: Components; Clock
4+
5+
.. _`clock-component`:
6+
7+
The Clock Component
8+
===================
9+
10+
.. versionadded:: 6.2
11+
12+
The Clock component was introduced in Symfony 6.2
13+
14+
The Clock component decouples applications from the system clock. This improves
15+
testability of time-sensitive logic.
16+
17+
The component provides a ``ClockInterface`` with the following implementations
18+
for different use cases.
19+
20+
* ``NativeClock`` provides a way to interact with the system clock.
21+
* ``MockClock`` is commonly used in tests as a replacement for the
22+
``NativeClock`` to be able to freeze and change the current time using either
23+
``sleep()`` or ``modify()``.
24+
* ``MonotonicClock`` relies on ``hrtime()`` and provides a high resolution,
25+
monotonic clock, when you need a precise stopwatch.
26+
27+
Installation
28+
------------
29+
30+
.. code-block:: terminal
31+
32+
$ composer require symfony/clock
33+
34+
.. include:: /components/require_autoload.rst.inc
35+
36+
Usage
37+
-----
38+
39+
A clock service replaces creating a new ``DateTime`` or
40+
``DateTimeImmutable`` object for the current time. Instead, you inject the
41+
``ClockInterface`` and call ``now()``. By default, your application will likely
42+
use a ``NativeClock``, which always returns the current system time. In tests it is replaced by a ``MockClock``. The
43+
``MockClock`` is instantiated with a time and does not move forward on its own, meaning when you create an
44+
instance with a time, it is always kept, unless you change it using
45+
either ``sleep()`` or ``modify()``. This gives you full control over what your
46+
code assumes is the current time.
47+
48+
The following example introduces a service utilizing the clock component to
49+
determine the current time::
50+
51+
use Symfony\Component\Clock\ClockInterface;
52+
53+
class ExpirationChecker
54+
{
55+
public function __construct(
56+
private readonly ClockInterface $clock
57+
) {}
58+
59+
public function isExpired(DateTimeInterface $time): bool
60+
{
61+
return $this->clock->now() > $time;
62+
}
63+
}
64+
65+
66+
Testing
67+
-------
68+
69+
When writing a test for this service, you can check both cases where something
70+
is expired or not, by modifying the clock's time::
71+
72+
use Symfony\Component\Clock\MockClock;
73+
use PHPUnit\Framework\TestCase;
74+
75+
class ExpirationCheckerTest extends TestCase
76+
{
77+
public function testIsExpired(): void
78+
{
79+
$clock = new MockClock('2022-11-16 15:20:00');
80+
$expirationChecker = new ExpirationChecker($clock);
81+
$time = new DateTimeImmutable('2022-11-16 15:25:00');
82+
83+
// $time is in the future, so it is not expired
84+
static::assertFalse($expirationChecker->isExpired($time));
85+
86+
// Clock sleeps for 10 minutes, so now is '2022-11-16 15:30:00'
87+
$clock->sleep(600); // Instantly changes time as if we waited for 10 minutes (600secs)
88+
89+
// $time is in the past after advancing the clock, so it is expired
90+
static::assertTrue($expirationChecker->isExpired($time));
91+
92+
$clock->modify('2022-11-16 15:00:00');
93+
94+
// $time is in the future again, so it is no longer expired
95+
static::assertFalse($expirationChecker->isExpired($time));
96+
}
97+
}

0 commit comments

Comments
 (0)