Компонент Clock
Дата оновлення перекладу 2024-05-02
Компонент Clock
Компонент Clock розʼєднує додатки та системний годинник. Це дозволяє вам виправити час, щоб покращити тестованість логіки, чутливої до часу.
Компонент надає ClockInterface
з наступними реалізаціями для різних випадків
використання:
- NativeClock
-
Надає спосіб взаємодіяти з системним годинником, це те ж саме, що робити
new \DateTimeImmutable()
. - MockClock
-
Часто використовується в тестах в якості заміни
NativeClock
, щоб мати можливість заморожувати та змінювати поточний час, використовуючиsleep()
абоmodify()
. - MonotonicClock
-
Покладається на
hrtime()
та надає монотонний годинник з високою роздільною здатністю, коли вам потрібен точний секундомір.
Установка
1
$ composer require symfony/clock
Note
Якщо ви встановлюєте цей компонент поза додатком Symfony, вам потрібно підключити
файл vendor/autoload.php
у вашому коді для включення механізму автозавантаження
класів, наданих Composer. Детальніше можна прочитати у цій статті.
Використання
Клас Clock повертає поточний час та дозволяє використовувти будь-яку реалізацію, сумісну з PSR-20, в якості глобального годинника у вашому додатку:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
use Symfony\Component\Clock\Clock;
use Symfony\Component\Clock\MockClock;
// за замовчуванням, Clock використовує реалізацію NativeClock, але ви можете це змінити,
// встановивши іншу реалізацію
Clock::set(new MockClock());
// Потім ви можете отримати екземпляр годинника
$clock = Clock::get();
// Додатково ви можете встановити часовий пояс
$clock->withTimeZone('Europe/Paris');
// Звідси ви можете отримати поточний час
$now = $clock->now();
// І перейти в режим сну на будь-яку кількість секунд
$clock->sleep(2.5);
Компонент Clock також надає функцію now()
:
1 2 3 4
use function Symfony\Component\Clock\now;
// Отримати поточний час як екземпляр DateTimeImmutable
$now = now();
Функція now()
приймає необов'язковий аргумент modifier
,
який буде застосовано до поточного часу:
1 2 3
$later = now('+3 hours');
$yesterday = now('-1 day');
Ви можете використовувати будь-який рядок прийнятний для конструктора DateTime.
Далі на цій сторінці ви зможете дізнатися, як використовувати цей годинник у ваших сервісах і тестах.
При використанні компонента Clock ви маніпулюєте екземплярами DatePoint.
Ви можете дізнатися більше про це у спеціальному розділі .
Доступні реалізації годинника
Компонент Clock надає деякі готові до використання реалізації ClockInterface, які ви можете використати як глобальні годинники у вашому додатку, залежно від ваших потреб.
Доступні реалізації Clock
Компонент Clock надає деякі готові до використання реалізації ClockInterface, які ви можете використовувати як глобальний годинник у вашому додатку залежно від ваших потреб.
NativeClock
Сервіс годинника заміняє створення нового обʼєкта DateTime
або DateTimeImmutable
для поточного часу. Натомість, ви впроваджуєте ClockInterface
та викликаєта now()
.
За замовчуванням, ваш додоаток скоріш за все використовуватиме NativeClock
, який
завжди повертає поточний час системи. У тестах він заміняється на MockClock
.
Наступний приклад представляє сервіс, що використовує компонент Clock, щоб визначити поточний час:
1 2 3 4 5 6 7 8 9 10 11 12 13
use Symfony\Component\Clock\ClockInterface;
class ExpirationChecker
{
public function __construct(
private ClockInterface $clock
) {}
public function isExpired(DateTimeInterface $validUntil): bool
{
return $this->clock->now() > $validUntil;
}
}
MockClock
MockClock
інстанціюється з часоом та не просувається вперед сам по собі. Час фіксований
до виклику sleep()
або modify()
. Це надає вам повний контроль над тим, який час ваш
код вважає поточним.
При написанні тесту для цього сервісу, ви можете переглянути обидва випадки, де в чогось завершився строк дії або ні, змінивши час годинника:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
use PHPUnit\Framework\TestCase;
use Symfony\Component\Clock\MockClock;
class ExpirationCheckerTest extends TestCase
{
public function testIsExpired(): void
{
$clock = new MockClock('2022-11-16 15:20:00');
$expirationChecker = new ExpirationChecker($clock);
$validUntil = new DateTimeImmutable('2022-11-16 15:25:00');
// $validUntil у майбутньому, тому строк дії не закінчився
static::assertFalse($expirationChecker->isExpired($validUntil));
// Годинник спить 10 хвилин, тому тепер - '2022-11-16 15:30:00'
$clock->sleep(600); // Миттєво змінює час, ніби ми зачекали 10 хвилин (600 секунд)
// змінити годинник, приймає всі формати, підтримувані DateTimeImmutable::modify()
static::assertTrue($expirationChecker->isExpired($validUntil));
$clock->modify('2022-11-16 15:00:00');
// $validUntil знову у майбутньому, тому строк дії не закінчився
static::assertFalse($expirationChecker->isExpired($validUntil));
}
}
Монотонний годинник
MonotonicClock
дозволяє вам реалізувати точний секундомір; в залежності від системи, до
наносекундної точності. Він може бути використаний, щоб виміряти час між двома викликами, не
піддаючисьь впливу неточностей, які іноді вносяться системним годинником, наприклад, його
оновленням. Замість цього, він постійно збільшує час, що робить його особливо корисним для
вимірювання продуктивності.
Використання годинника всередині сервіса
Використання компонента Clock у ваших сервісах для вилучення поточного часу робить їх
простішими у тестуванні. Наприклад, використовуючи реалізацію MockClock
за замовчуванням
під час тестування, ви матимете повний контроль, щоб встановлювати "поточний час" у будь-який
довільну дату/час.
Для того, щоб використовувати цей компонент у ваших сервісах, зробіть так, щоб їх класи
використовували ClockAwareTrait. Завдяки
автоконфігурації сервісів , метод риси setClock()
автоматично буде викликано сервіс-контейнером.
Тепер ви можете викликати метод $this->now()
, щоб отримати поточний час:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
namespace App\TimeUtils;
use Symfony\Component\Clock\ClockAwareTrait;
class MonthSensitive
{
use ClockAwareTrait;
public function isWinterMonth(): bool
{
$now = $this->now();
return match ($now->format('F')) {
'December', 'January', 'February', 'March' => true,
default => false,
};
}
}
Завдяки ClockAwareTrait
, і використовуючи реалізацію MockClock
, ви можете
встановити поточний час довільно, без необхідності змінювати ваш службовий код. Це
допоможе вам тестувати кожний випадок вашого методу, без необхідності дійсно знаходитися
в одному чи іншому місяці.
Клас DatePoint
Компонент Clock використовує спеціальний клас DatePoint.
Це невелика обгортка над PHP-класом DateTimeImmutable. Ви можете без проблем
без проблем використовувати її скрізь, де очікується DateTimeImmutable або
DateTimeInterface. Об'єкт DatePoint
отримує дату і час з класу
Clock. Це означає, що якщо ви внесли будь-які зміни
до годинника, як зазначено у секції про використання , це буде відображено
при створенні нового DatePoint
. Ви також можете створити новий екземпляр DatePoint
напряму, наприклад, при використанні його як значення за замовчуванням:
1 2 3 4 5 6 7 8 9 10
use Symfony\Component\Clock\DatePoint;
class Post
{
public function __construct(
// ...
private \DateTimeImmutable $createdAt = new DatePoint(),
) {
}
}
Конструктор також дозволяє задати часовий пояс або користувацьку дату:
1 2 3 4 5 6
// ви можете вказати часовий пояс
$withTimezone = new DatePoint(timezone: new \DateTimezone('UTC'));
// ви також можете створити DatePoint з користувацької дати
$referenceDate = new \DateTimeImmutable();
$relativeDate = new DatePoint('+1month', reference: $referenceDate);
Клас DatePoint
також надає іменований конструктор для створення дат з
міток часу:
1 2 3 4 5 6 7
$dateOfFirstCommitToSymfonyProject = DatePoint::createFromTimestamp(1129645656);
// еквівалентно:
// $dateOfFirstCommitToSymfonyProject = (new \DateTimeImmutable())->setTimestamp(1129645656);
// негативні часові мітки (для дат до 1го січня 1970 року) та плаваючі часові мітки
// (для високоточних субсекундних часових міток) також підтримуються
$dateOfFirstMoonLanding = DatePoint::createFromTimestamp(-14182940);
7.1
Метод createFromTimestamp()
було представлено в Symfony 7.1.
Note
Крім того, DatePoint
пропонує більш суворі типи повернення і забезпечує послідовну
обробку помилок у різних версіях PHP, завдяки полізаповненню поведінки PHP 8.3.
на цю тему.
DatePoint
також дозволяє встановлювати та отримувати частину мікросекунди дати та часу:
1 2 3
$datePoint = new DatePoint();
$datePoint->setMicrosecond(345);
$microseconds = $datePoint->getMicrosecond();
Note
Ця функція заповнює поведінку PHP 8.4 у цій темі, оскільки мікросекундні маніпуляції недоступні у попередніх версіях PHP.
7.1
Методи setMicrosecond() та getMicrosecond() були представлені в Symfony 7.1.
Написання тестів, чутливих до часу
Компонент Clock надає ще одну рису, яка називається ClockSensitiveTrait, яка допоможе вам писати тести, чутливі до часу. Ця риса надає методи для зупинки часу та відновлення глобального годинника після кожного тесту.
Використайте метод ClockSensitiveTrait::mockTime()
для взаємодії з імітованим
годинником у ваших тестах. Цей метод приймає різні типи як свій єдиний аргумент:
- Рядок, який може бути датою для встановлення годинника (наприклад,
1996-07-01
) або інтервал для зміни годинника (наприклад,+2 days
); DateTimeImmutable
для встановлення годинника;- булеву функцію, щоб зупинити або відновити глобальний годинник.
Припустимо, ви хочете протестувати метод MonthSensitive::isWinterMonth()
вищенаведеного
прикладу. Ось як ви можете написати цей тест:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
namespace App\Tests\TimeUtils;
use App\TimeUtils\MonthSensitive;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Clock\Test\ClockSensitiveTrait;
class MonthSensitiveTest extends TestCase
{
use ClockSensitiveTrait;
public function testIsWinterMonth(): void
{
$clock = static::mockTime(new \DateTimeImmutable('2022-03-02'));
$monthSensitive = new MonthSensitive();
$monthSensitive->setClock($clock);
$this->assertTrue($monthSensitive->isWinterMonth());
}
public function testIsNotWinterMonth(): void
{
$clock = static::mockTime(new \DateTimeImmutable('2023-06-02'));
$monthSensitive = new MonthSensitive();
$monthSensitive->setClock($clock);
$this->assertFalse($monthSensitive->isWinterMonth());
}
}
Цей тест поводитиметься однаково незалежно від того, в яку пору року ви його запускаєте. Комбінуючи ClockAwareTrait та ClockSensitiveTrait, ви маєте повний контроль над поведінкою вашого чутливого до часу коду.
Управління виключеннями
Компонент Clock використовує всі переваги деяких винятків PHP DateTime.
Якщо ви передасте годиннику невалідний рядок (наприклад, при створенні годинника або
модифікації MockClock
), ви отримаєте виключення DateMalformedStringException
. Якщо ви
передасте невалідний часовий пояс, ви отримаєте виключення DateInvalidTimeZoneException
:
1 2 3 4 5 6 7
$userInput = 'invalid timezone';
try {
$clock = Clock::get()->withTimeZone($userInput);
} catch (\DateInvalidTimeZoneException $exception) {
// ...
}
Ці виключення доступні починаючи з версії PHP 8.3. Однак, завдяки залежності symfony/polyfill-php83, необхідної для компонента Clock, ви можете використовувати їх, навіть якщо ваш проект ще не використовує PHP 8.3.