Rector

Rector – это утилита для обновления кодовой базы PHP проекта под современные стандарты. И речь идёт не о PSR-12, а о более интересных преобразованиях, сейчас расскажу!

Представьте, что мы хотим добавить больше типизации в код, например, проставить типы возвращаемых из функций значений. Если код написан более-менее прямолинейно и без лишней магии, то, вообще говоря, статический анализ может вывести тип возвращаемого значения, не смотря на весь динамизм PHP. Почему бы не автоматизировать эту задачу?

Вместо ручного изучения и правки тысяч или десятков или сотен тысяч строк кода, запускаем утилиту Rector и она автоматически проставляет типы возвращаемых из функций значений!

А что насчёт простановки типов свой классов, которые появились в PHP 7.4? Вполне посильная задача для статического анализа и автоматизации.

Или, например, заменить использование Laravel фасадов на Dependency Injection.

Пару недель назад я опробовал Rector на одном не самом старом, но и не самом новом проекте, подтянул кодовую базу до приятного современного вида и в целом остался доволен.

Для начала нужно установить саму утилиту Rector как composer пакет в dev секцию. Под капотом используется php-parser от Никиты Попова, строится абстрактное синтаксическое дерево, затем применяются модификации.

Установить с помощью composer require мне не удалось – попал на конфликты версий с уже установленными пакетами. Что хорошо, в README репозитория Rector есть упоминание как раз такого случая – при конфликтах рекомендуется использовать заранее скомпилированную phar версию, чем я и воспользовался. Также есть Docker образ, на случай если локальная версия PHP не подходит для запуска Rector.

В комплекте идёт более 500 правил или преобразований – их можно включать или выключать по отдельности с помощью файла конфигурации. Есть также готовые наборы из правил, sets, например: symfony40 или code-qualiy – что конкретно скрывается за этими наборами нужно смотреть в документации.

Можно сформировать свои собственные наборы или даже написать собственное преобразование.

Также настройками задаются пути к директориям с кодом для обновления. Отдельный флаг --match-git-diff запустит Rector только на изменённых в последнем коммите файлах, это удобно для ускорения процесса, особенно если кодовая база большая.

Так называемый dry-run режим показывает будущие изменения в консоли, но не применяет их к файлам.

Я подошел к процессу следующим образом: сначала взял парочку наборов (sets): code quality и простановку типов. С типами вроде всё понятно, мне нравятся явно указанные типы, пусть будут! Что такое code-quality? Видимо, что-то связанное с качеством кода, а мы за всё хорошее.

Запустил не на всём проекте, а на одной из директорий, но без флага dry-run – мне удобнее смотреть diff не в консоли, а в PhpStorm.

Оказалось, что всё не так-то и хорошо, особенно с простановкой типов параметров функций – при внимательном просмотре нашел много косяков.

Откатил все изменения из решил подойди к вопросу более вдумчиво. Открыл в документации список всех правил (которых, напомню, более 500) и стал внимательно читать, что каждое из них делает. Удобно, что под каждым правилом есть фрагмент кода до и после применения, очень наглядно.

Все правила разделены на категории, есть оглавление — это позволило сначала сосредоточиться на наиболее интересных лично для меня.

Есть категории относящиеся к конкретным фреймворкам и библиотекам – Symfony, Laravel, CakePHP Doctrine, Guzzle, PHPOffice и так далее. Есть правила, обновляющие код с использованием новых фишек самого языка, соответственно они разделены на группы по версиям PHP. Например, в PHP 7.3 появились функции array_key_first и array_key_last – преобразование находит фрагменты кода, где последовательно применяется reset и key и заменяет их на array_key_first, аналогично end и key заменяется на array_key_last.

А в PHP 7.4 появилась короткая запись анонимных функций – если в коде есть анонимки, состоящие из одного return, то они будут преобразованы к короткому синтаксису. И так далее, много мелких, достаточно безопасных и освежающих код преобразований!

Насчёт типов параметров: есть преобразование, которое подставляет тип на основе DocBlock комментариев, а есть и обратное, генерирует DocBlock комментарии на основе кода. Также в некоторых случаях типы могут быть выведены по способу использования переменной.

Чтобы прочувствовать, что именно работает хорошо, а что нет, я постепенно добавлял в конфиг по одному правилу, запускал, изучал diff.

Как я уже говорил, с простановкой типов параметров функции было достаточное число ошибок, которые пришлось поправить вручную.

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

После каждого успешного применения и проверки я делал отдельный коммит.

Что интересно, Rector ведь можно запускать на регулярной основе, как автоматическое форматирование кода. Главное выделить тот набор правил, в которых уверен и которые подходят для данного проекта. Например, всё с теми же типами параметров, после ручной правки косяков, я отключил это правило и на регулярной основе запускать его не планирую.

В целом у меня ушло 2 дня на работу плотную с кодовой базой в 100 тыс строк кода и постепенным пошаговым применением различных правил, просмотром дифов и обязательным прогоном тестов. Сами понимаете, без тестов в таком рефакторинге никуда.

Но даже если тестов нет или их мало, я считаю, что стоит попробовать Rector, хотя бы пройтись по категориям связанным с обновлением версий PHP, чтобы наглядно вспомнить, что было нового в последних версиях и применить эти преобразования – там всё достаточно надёжно с точки зрения статического анализа, изменения простые и понятные.

Пишите на современном PHP, а Rector вам поможет обновить уже написанный код!