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

в 14:34, , рубрики: datetime, php, Блог компании OTUS. Онлайн-образование, Программирование

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

Перестаньте использовать DateTime - 1


Работа с датами и временем в 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 + 3;. Само по себе это воспринимается как бессмысленное утверждение, которому явно не хватает переназначения: $a = $a + 3; или $A += 3;. Было бы классно использовать что-то подобное в случае с объектами-значениями, не так ли?

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

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

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

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

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

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

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

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

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

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

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

Автор: MaxRokatansky

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js