В прошлом выпуске Пятиминутки 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)
в новом коде, но не факт, что есть смысл трогать старый код, особенно код уровня приложения.