declare(strict_types=1)

В прошлом выпуске Пятиминутки PHP я рассказал, как обновлял кодовую базу с помощью утилиты Rector. Одно из направлений – простановка типов. Типы в свойствах классов, типы в параметрах функций, типы возвращаемых значений.

Но на этом я не остановился. Следующий шаг – проставить declare(strict_types=1)! Весь новый код, который я пишу по умолчанию идёт с declare(strict_types=1) в начале файла, это легко настроить в шаблонах PhpStorm. Но так ли просто взять и обновить старые исходники?

Чтобы напомнить, как работает declare(strict_types=1), процитирую фрагмент из официальной документации, там всё достаточно просто и кратко сформулировано.

По умолчанию PHP пытается привести значения несоответствующих типов к скалярному типу, если это возможно. Например, если в функцию передается целое число (int), а тип аргумента объявлен как строка (string), в итоге функция получит преобразованное в строку (string) значение.

Для отдельных файлов можно включать режим строгой типизации. В этом режиме в функцию можно передавать значения только тех типов, которые объявлены для аргументов. В противном случае будет выбрасываться исключение TypeError. Есть лишь одно исключение — целое число (int) можно передать в функцию, которая ожидает значение типа float.

Два важных замечания.

Во-первых, режим строгой типизации распространяется на вызовы функций, совершенные из файла, в котором этот режим включен. Я считаю это было очень грамотным решением. Например, я использую какую-то современную библиотеку и внутри всё описано с типами и все файлы этой библиотеки содержат декларацию strict_types. Однако, это не обязывает меня, пользователя библиотеки, внимательно следить за типами. В своём коде, т.е. в коде, вызывающем библиотеку, я пишу по старинке, без declare(strict_types=1), передаю аргументы не совсем точно совпадающие по типу и всё ок.

Почему это хорошо? Это позволяет постепенно мигрировать на declare(strict_types=1). Сначала обновляем какие-то глубокие файлы ядра приложения или фреймворка, внедряем strict_types в замкнутой экосистеме, так сказать, при этом вызывающий код, код с бизнес логикой, код которого много продолжает работать без проблем и модификаций. Постепенно declare(strict_types=1) с очередным рефакторингом будет проникать на уровень приложения и бизнес логики.

Во-вторых, строгая типизация применима только к скалярным типам и работает только в PHP 7.0 и выше. Равно как и сами объявления скалярных типов добавлены в этой версии.

Именно таким путём я и пошел, из глубины. С одним отличием — не стал растягивать удовольствие на месяцы, я плотно засел на весь день.

И это оказалось тяжело, тяжелее чем с обновлениями Rector. Очень много мелочей повыскакивало. Например, такие функции как trim (обрезает строки от пробельных символов по краям), htmlspecialchars, strpos, explode и другие — принимают на вход исключительно строки! Оказалось, что в коде проекта частенько туда передавалось число или даже null. Или вот пример: вызов ini_set('default_socket_timeout', 100); — второй параметр в ini_set это строка, поэтому число 100 нужно заключить в кавычки. Хотя, можно спросить, что вообще делает ini_set в коде и почему эта настройка не вынесена в файл конфигурации php.ini?

Короче, ручное приведение к string во всех этих примерах, по сути, это ничего не поменяло, программа и раньше работала верно, но потрудиться пришлось изрядно.

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

Резюмируя: в прошлом выпуске, я однозначно рекомендовал попробовать Rector. В этом выпуске я однозначно рекомендую использовать declare(strict_types=1) в новом коде, но не факт, что есть смысл трогать старый код, особенно код уровня приложения.