|  | 
|  | 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 with a ``MockClock``. The | 
|  | 43 | +The ``MockClock`` is instantiated with a time and does not move forward on its own. The time is | 
|  | 44 | +fixed until ``sleep()`` or ``modify()`` are called. This gives you full control over what your code | 
|  | 45 | +assumes is the current time. | 
|  | 46 | + | 
|  | 47 | +The following example introduces a service utilizing the Clock component to | 
|  | 48 | +determine the current time:: | 
|  | 49 | + | 
|  | 50 | +    use Symfony\Component\Clock\ClockInterface; | 
|  | 51 | + | 
|  | 52 | +    class ExpirationChecker | 
|  | 53 | +    { | 
|  | 54 | +        public function __construct( | 
|  | 55 | +            private readonly ClockInterface $clock | 
|  | 56 | +        ) {} | 
|  | 57 | + | 
|  | 58 | +        public function isExpired(DateTimeInterface $validUntil): bool | 
|  | 59 | +        { | 
|  | 60 | +            return $this->clock->now() > $validUntil; | 
|  | 61 | +        } | 
|  | 62 | +    } | 
|  | 63 | + | 
|  | 64 | +Testing | 
|  | 65 | +------- | 
|  | 66 | + | 
|  | 67 | +When writing a test for this service, you can check both cases where something | 
|  | 68 | +is expired or not, by modifying the clock's time:: | 
|  | 69 | + | 
|  | 70 | +    use PHPUnit\Framework\TestCase; | 
|  | 71 | +    use Symfony\Component\Clock\MockClock; | 
|  | 72 | + | 
|  | 73 | +    class ExpirationCheckerTest extends TestCase | 
|  | 74 | +    { | 
|  | 75 | +        public function testIsExpired(): void | 
|  | 76 | +        { | 
|  | 77 | +            $clock = new MockClock('2022-11-16 15:20:00'); | 
|  | 78 | +            $expirationChecker = new ExpirationChecker($clock); | 
|  | 79 | +            $validUntil = new DateTimeImmutable('2022-11-16 15:25:00'); | 
|  | 80 | + | 
|  | 81 | +            // $validUntil is in the future, so it is not expired | 
|  | 82 | +            static::assertFalse($expirationChecker->isExpired($validUntil)); | 
|  | 83 | + | 
|  | 84 | +            // Clock sleeps for 10 minutes, so now is '2022-11-16 15:30:00' | 
|  | 85 | +            $clock->sleep(600); // Instantly changes time as if we waited for 10 minutes (600secs) | 
|  | 86 | + | 
|  | 87 | +            // $validUntil is in the past after advancing the clock, so it is expired | 
|  | 88 | +            static::assertTrue($expirationChecker->isExpired($validUntil)); | 
|  | 89 | + | 
|  | 90 | +            $clock->modify('2022-11-16 15:00:00'); | 
|  | 91 | + | 
|  | 92 | +            // $validUntil is in the future again, so it is no longer expired | 
|  | 93 | +            static::assertFalse($expirationChecker->isExpired($validUntil)); | 
|  | 94 | +        } | 
|  | 95 | +    } | 
0 commit comments