Хабрахабр

[Перевод] Перестаньте использовать DateTime

Специально для студентов курса «Backend разработчик на PHP» подготовили перевод интересной статьи о сайд-эффекте популярного инструмента.

Работа с датами и временем в PHP порой раздражает, поскольку приводит к неожиданным багам в коде:

$startedAt = new DateTime('2019-06-30 10:00:00'); $finishedAt = $startedAt->add(new DateInterval('PT3M')); var_dump($startedAt->format('Y-m-d H:i:s')); //2019-06-30 10:03:00 var_dump($finishedAt->format('Y-m-d H:i:s')); //2019-06-30 10:03:00

В приведенном выше примере, конечно же, показано нежелательное поведение. Обе функции $startdate и $finishdate спешат на три минуты, потому как такие методы, как add (), sub() или modify() также изменяют объект DateTime, для которого они вызываются, прежде чем вернуть его.

Мы можем исправить эту ошибку, скопировав объект, на который происходит ссылка, прежде чем взаимодействовать с ним, например:

$startedAt = new DateTime('2019-06-30 10:00:00'); $finishedAt = clone $startedAt;
$finishedAt->add(new DateInterval('PT3M'));

Каждый раз, когда я сталкиваюсь с клоном в коде на PHP, это пахнет, как хак чьей-то неудачной архитектуры кода. В данном случае мы использовали клонирование, чтобы избежать изменения поведения, но в то же время код стал некрасивым и приобрел много ненужного шума.

В качестве альтернативы, проблему можно решить преобразованием исходного DateTime экземпляра в DateTimeImmutable:

$startedAt = new DateTime('2019-06-30 10:00:00'); $finishedAt = DateTimeImmutable::createFromMutable($startedAt)->add(new DateInterval('PT3M'));

Почему же с самого начала не использовать DateTimeImmutable?

Бескомпромиссное использование DateTimeImmutable

Вместо того, чтобы вручную применять защитные методы для предотвращения неожиданных изменений при передаче объектов даты/времени, используйте DateTimeImmutable, который инкапсулирует методы, делая ваш код более надежным.

$startedAt = new DateTimeImmutable('2019-06-30 10:00:00'); $finishedAt = $startedAt->add(new DateInterval('PT3M')); var_dump($startedAt->format('Y-m-d H:i:s')); //2019-06-30 10:00:00 var_dump($finishedAt->format('Y-m-d H:i:s')); //2019-06-30 10:03:00

В большинстве случаев понятие даты рассматривается как значение, мы сравниваем даты по их значениям, и когда мы изменяем дату, она становится уже другой датой. Все это прекрасно коррелирует с понятием Value Object, а одной из важных характеристик объектов-значений является то, что они неизменяемы.

Подробный стиль написания кода

Неизменяемость заставляет вас явно переназначать объект DateTimeImmutable каждый раз, когда вы взаимодействуете с ним, поскольку он никогда не меняет свое значение, вместо этого возвращая копию. После многих лет работы с DateTime и из-за того, что изменяемость является значением по умолчанию во многих императивных языках программирования, трудно избавиться от привычки ее использовать и соответствовать новому стилю написания кода, который продвигает переназначение:

$this->expiresAt = $this->expiresAt->modify('+1 week');

Инструменты статистического анализа, такие как PHPStan и одно из его расширений, могут предупреждать нас, в случае если мы опускаем назначение и неправильно используем DateTimeImmutable.

Само по себе это воспринимается как бессмысленное утверждение, которому явно не хватает переназначения: $a = $a + 3; или $A += 3;. Однако такой когнитивный уклон в сторону изменчивости подавляется, когда мы выполняем арифметические операции над значениями примитивов, например: $a + 3;. Было бы классно использовать что-то подобное в случае с объектами-значениями, не так ли?

Я был бы не против, если бы PHP позаимствовал этот прием из какого-нибудь другого языка программирования, и мы могли бы писать следующим образом: Некоторые языки программирования имеют синтаксический сахар, называемый перегрузкой операторов, который позволяет реализовывать операторы в пользовательских типах и классах, чтобы они вели себя также, как и примитивные типы данных.

$this->expiresAt += '1 week';

Одноразовые расчеты

Некоторые люди утверждают, что с точки зрения производительности лучше использовать DateTime, поскольку вычисления выполняются в пределах одной области выполнения. Это допустимо, однако если вам не нужно выполнять сотни операций, и вы помните о том, что ссылки на старые объекты DateTimeImmutable будут собраны сборщиком мусора, в большинстве случаев на практике потребление памяти не будет проблемой.

Библиотеки Date/time

Carbon – это крайне популярная библиотека, которая расширяет Date/Time API в PHP, добавляя богатый набор функций. Точнее говоря, она расширяет API изменяемого класса DateTime, использование которого идет вразрез с темой этой статьи.

Это standalone библиотека, которая изначально основывалась на Carbon, особенное внимание уделяя предоставлению неизменяемых объектов даты/времени по умолчанию, однако в своем составе она имеет и изменяемые варианты на случай необходимости.
Поэтому если вам нравится работать с Carbon, но вы предпочитаете неизменяемость, я предлагаю вам ознакомиться с Chronos.

Тем не менее, причина, по которой я отдаю предпочтение Chronos, заключается в том, что в отличие от Carbon, он поощряет и способствует неизменяемости по умолчанию, как в коде, так и в документации, и это является решающим факторов в контексте данной статьи. Отредактировано (05/07/2019): Оказалось, что у Carbon есть неизменяемый вариант date/time, что является большим плюсом.

Последняя мысль

DateTimeImmutable был впервые представлен еще в древнем PHP 5.5, но к моему удивлению, многие разработчики открывают его для себя только сейчас. Используйте DateTimeImmutable по умолчанию, когда это возможно, но имейте ввиду некоторые компромиссы, о которых я говорил и которые я считаю скорее делом привычки и сдвигом в образе мышления.

По устоявшейся традиции ждем ваши комментарии, друзья. На этом все.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть