Skip to content

Commit d14007e

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

File tree

1 file changed

+96
-0
lines changed

1 file changed

+96
-0
lines changed

components/clock.rst

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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+
Testing
66+
-------
67+
68+
When writing a test for this service, you can check both cases where something
69+
is expired or not, by modifying the clock's time::
70+
71+
use Symfony\Component\Clock\MockClock;
72+
use PHPUnit\Framework\TestCase;
73+
74+
class ExpirationCheckerTest extends TestCase
75+
{
76+
public function testIsExpired(): void
77+
{
78+
$clock = new MockClock('2022-11-16 15:20:00');
79+
$expirationChecker = new ExpirationChecker($clock);
80+
$time = new DateTimeImmutable('2022-11-16 15:25:00');
81+
82+
// $time is in the future, so it is not expired
83+
static::assertFalse($expirationChecker->isExpired($time));
84+
85+
// Clock sleeps for 10 minutes, so now is '2022-11-16 15:30:00'
86+
$clock->sleep(600); // Instantly changes time as if we waited for 10 minutes (600secs)
87+
88+
// $time is in the past after advancing the clock, so it is expired
89+
static::assertTrue($expirationChecker->isExpired($time));
90+
91+
$clock->modify('2022-11-16 15:00:00');
92+
93+
// $time is in the future again, so it is no longer expired
94+
static::assertFalse($expirationChecker->isExpired($time));
95+
}
96+
}

0 commit comments

Comments
 (0)