В опенсорс-проектах часто можно увидеть использование инструментов для проверки кода: проверяется кодстайл, выполняется статический анализ. Эти инструменты широко распространены, но в проектах на Битриксе они встречаются редко. В этой статье я покажу, как начать использовать такие инструменты в своих проектах на Битриксе.
Предупреждение
Прежде чем внедрять инструменты из статьи, стоит подумать, какую проблему вы хотите решить.
Допустим, вы заметили, что слишком часто возвращаете мерж-реквесты на доработку с просьбой добавить пробел между (int) и переменной, чтобы соответствовать PER Coding Style. Если вы в своем проекте придерживаетесь какого-то кодстайла, то проверку соответствия этому кодстайлу вполне можно поручить компьютеру, а не делать ее вручную.
Либо бывают ситуации, когда в одной ветке разработчик переименовал метод, а в другой ветке в то же время другой разработчик добавил новый вызов этого метода. В таком случае после слияния на площадке у нас возникнет ошибка обращения к несуществующему методу. Чтобы не ловить такие ошибки в рантайме, проверку существования вызываемого метода также можно доверить компьютеру!
Когда вы поняли проблему, нужно определиться с необходимостью ее решения. Если у вас на проекте 1 бэкендер и 1 фронтендер, то добавление проверок может и не пригодиться. Каждый разработчик сам будет следить за своим кодстайлом и не будет переименовывать метод одновременно с добавлением нового его вызова. Не стоит бросаться добавлять новые штуки просто потому, что кто-то другой так сделал — сначала определите, какую проблему вы решаете.
С чего начать
Допустим, вы определились с проблемой и какой-то из инструментов может ее решить. С чего начать? Рекомендую начать с минимального CI. Чтобы добавленными инструментами пользовались, вам нужно сделать использование этих инструментов удобным. Если проверки запускаются в CI, то вся команда видит статус каждого коммита. И если разработчик у себя проверки не запустил, они все равно запустятся в CI. Таким образом, вам становится неважно, в какой среде разработки или операционной системе запускаются ваши проверки — источник истины в любом случае будет в CI.
Показывать буду на примере гитлаба, потому что по моим наблюдениям в студиях обычно разворачивают именно его. Создаем файл .gitlab-ci.yml. Для начала он будет содержать всего одну стадию: check.
check:
  image: quay.io/bitrix24/php:8.3.22-fpm-v1-alpine
  script:
    - composer validate --strict
    - composer install
Для запуска проверок используем официальный образ php от Битрикса. Для начала в теле скрипта укажем всего две команды — composer validate и composer install. После пуша видим в нашем репозитории зеленую лампочку. Теперь мы в любой момент времени можем быть уверены, что композер сконфигурирован корректно и успешно устанавливает все пакеты.
Добавляем PHP CS Fixer
Далее добавим PHP CS Fixer:
composer require --dev friendsofphp/php-cs-fixer
Также добавим правила кодстайла PHPyh:
composer require --dev phpyh/coding-standard
Создадим файл .php-cs-fixer.dist.php со следующим содержимым:
<?php
use PhpCsFixer\Config;
use PhpCsFixer\Finder;
use PHPyh\CodingStandard\PhpCsFixerCodingStandard;
$config = (new Config())
    ->setFinder(
        (new Finder())
            ->in([
                __DIR__ . '/src/',
                __DIR__ . '/public/local/',
                __FILE__,
            ])
    );
(new PhpCsFixerCodingStandard())->applyTo($config);
return $config;
Для удобства добавим элемент scripts в composer.json:
"scripts": {
    "cs-check": "php-cs-fixer check",
    "cs-fix": "php-cs-fixer fix"
}
В целом можно этого не делать и запускать всегда команду php-cs-fixer check, но так удобнее будет смотреть какие команды используются на проекте.
Также добавим запуск cs-check в CI:
check:
  image: quay.io/bitrix24/php:8.3.22-fpm-v1-alpine
  script:
    - composer validate --strict
    - composer install
    - composer cs-check
Готово! Теперь у нас есть проверки кодстайла и вам больше не нужно проверять вручную глазами форматирование кода, за этим будет следить компьютер.
Добавляем Rector
Далее добавим Rector:
composer require --dev rector/rector
В конфигурацию пропишем те же пути, что и для PHP CS Fixer. Rector нужно обязательно использовать вместе с каким-нибудь кодстайл-фиксером, потому что форматированием кода Rector не занимается.
<?php
declare(strict_types=1);
use Rector\Config\RectorConfig;
return RectorConfig::configure()
    ->withCache(__DIR__ . '/cache/rector')
    ->withPaths([
        __DIR__ . '/src',
        __DIR__ . '/public/local',
    ])
    ->withParallel()
    ->withPhpSets();
Явно укажем путь кэша в rector.php.
И в .php-cs-fixer.dist.php тоже:
<?php
declare(strict_types=1);
use PhpCsFixer\Config;
use PhpCsFixer\Finder;
use PHPyh\CodingStandard\PhpCsFixerCodingStandard;
$config = (new Config())
    ->setCacheFile(__DIR__ . '/cache/.php-cs-fixer.cache')
    ->setFinder(
        (new Finder())
            ->in([
                __DIR__ . '/src/',
                __DIR__ . '/public/local/',
            ])
        ->append([
            __FILE__,
            __DIR__ . '/rector.php',
        ]),
    );
(new PhpCsFixerCodingStandard())->applyTo($config);
return $config;
В файле .gitlab-ci.yml добавим кэширование, чтобы проверки выполнялись быстрее:
check:
  image: quay.io/bitrix24/php:8.3.22-fpm-v1-alpine
  script:
    - composer validate --strict
    - composer install
    - composer cs-check
    - composer rector-check
  cache:
    paths:
      - vendor
      - cache
Также добавим команду для запуска Rector в composer.json:
"scripts": {
    "cs-check": "php-cs-fixer check",
    "cs-fix": "php-cs-fixer fix",
    "rector-check": "rector process --dry-run",
    "rector-fix": "rector process"
},
"scripts-descriptions": {
    "cs-check": "Check coding standards",
    "cs-fix": "Apply coding standards",
    "rector-check": "Check rector rules",
    "rector-fix": "Apply rector rules"
}
Добавляем PHPStan
Далее добавим PHPStan:
composer require --dev phpstan/phpstan
Чтобы PHPStan не выдавал ошибки class.notFound на все классы Битрикса, в конфигурацию добавляем пути до используемых модулей Битрикса в scanDirectories:
parameters:
    level: 10
    tmpDir: cache/phpstan
    paths:
        - src
        - public/local
    scanFiles:
        - stubs/orm.stub
        - public/bitrix/modules/orm_annotations.php
        - stubs/bitrix.stub
    scanDirectories:
        - public/bitrix/modules/main
        - public/bitrix/modules/iblock
        - public/bitrix/modules/highloadblock
level подбирайте под проект, скорее всего максимальный уровень в начале вам не подойдет. На легаси-проект у меня удалось затащить только первый уровень (есть еще нулевой), на проект поновее и поменьше подошел четвертый. Даже на низком уровне вы все равно получите бонусы статического анализа, как минимум не поймаете на проде ошибку Call to undefined method... В примере я поставлю максимальный 10 уровень, чтобы поймать все возможные предупреждения анализатора.
Также укажем в tmpDir путь до директории с кэшем, чтобы результат анализа кэшировался между запусками CI.
Далее — какие у вас могут возникнуть проблемы?
Например, вы где-то в коде используете функцию Битрикса LocalRedirect чтобы средиректить пользователя. Возвращаемый тип у нее void, хотя на самом деле там never! Из-за этого PHPStan будет неправильно анализировать ваш код, думая что после LocalRedirect исполнение продолжается, хотя в рантайме это, конечно, будет не так. Чтобы исправить эту проблему, создадим файл bitrix.stub со следующим содержимым:
<?php
/**
 * @param string $url
 * @param bool $skip_security_check
 * @param string $status
 * @return never
 */
function LocalRedirect($url, $skip_security_check = false, $status = "302 Found") {}
И укажем путь до файла в scanFiles. PHPStan будет читать этот стаб и понимать, что после вызова LocalRedirect исполнение завершается.
Если вы в проекте используете ORM Битрикса, то вы скорее всего уже генерируете подсказки для IDE (orm_annotations.php). Этот файл с подсказками также нужно указать в scanFiles, PHPStan будет его понимать.
При этом, в сгенерированном файле также могут быть ошибки. Например, по какой-то причине PHPStan считает, что string и \string, это два разных типа, и пишет в ошибке Parameter of class constructor expects string, string given. Это явно ошибка, не понятно только ошибка PHPStan или проблема Битрикса, что он для некоторых типов добавляет в аннотации символ \. Но в любом случае проблему надо решать, и поэтому также добавим файл orm.stub:
<?php
namespace Bitrix\Iblock\Elements {
    /**
     * @method string getName()
     * @method string getPreviewText()
     * @method string getDetailText()
     */
    class EO_ElementNews {}
}
Укажем в нем используемые нами методы с типом без \.  Также добавим путь к файлу в scanFiles. Важно — orm.stub нужно добавить до orm_annotations.php. Теперь PHPStan понимает, что же вернет Битрикс в getPreviewText, например вот в таком вызове ORM:
/** @var EO_ElementNews_Collection $collection */
$collection = ElementNewsTable::query()
    ->addSelect('NAME')
    ->addSelect('PREVIEW_TEXT')
    ->addSelect('DETAIL_TEXT')
    ->fetchCollection();
$news = [];
foreach ($collection as $element) {
    $news[] = new NewsDTO(
        $element->getName(),
        $element->getPreviewText(),
        $element->getDetailText(),
    );
}
и не будет ругаться так:
Parameter #1 $name of class Example\News\NewsDTO constructor expects string, string given.
Далее самое интересное — как все это запустить в CI? Ведь в CI нет файлов ядра Битрикса и сгенерированного orm_annotations.php. Добавлять ядро в систему контроля версий?
Можно конечно и так поступить. Видел проект, где ядро было закоммичено в отдельный репозиторий, там вероятно можно было бы его и подтягивать. Но в целом иметь ядро под контролем версий обычно не нужно.
Мне больше нравится добавлять ядро в образ для запуска проверок в CI.
Через админку создаем бэкап ядра без базы и без файлов публичной части.
Далее создаем Dockerfile с таким содержимым:
FROM quay.io/bitrix24/php:8.3.22-fpm-v1-alpine
RUN curl -SL https://ilimurzin.ru/bitrix/backup/20250625_215733_core_c3qfdf0u7s7z98q5.tar.gz | tar -xzC /tmp
quay.io/bitrix24/php:8.3.22-fpm-v1-alpine это тот же образ PHP от Битрикса, который мы использовали для запуска проверок.
Курлом качаем и распаковываем созданный бэкап.
В полученном образе ядро Битрикса будет лежать по пути /tmp/bitrix.
Сборку и деплой образа в гитлабовский Container registry можно автоматизировать, пример вот тут: https://gitlab.com/ilimurzin/php-image-example
check:
  image: registry.gitlab.com/ilimurzin/php-image-example:1
  script:
    - composer validate --strict
    - composer install
    - composer cs-check
    - composer rector-check
    - ln -s /tmp/bitrix public/bitrix
    - composer phpstan
  cache:
    paths:
      - vendor
      - cache
Полученный образ указываем в image. Перед запуском PHPStan линкуем ядро Битрикса в public/bitrix. Готово! Лампочка зеленая — PHPStan в CI видит файлы Битрикса.
Полный пример можно посмотреть тут
Вывод
Технически вполне возможно использовать все современные инструменты вместе с Битриксом, но важно подумать, прежде чем что-то применять. У вас должна быть причина для изменений — иначе это просто бесполезно проделанная работа.
Инструменты могут отличаться от проекта к проекту: вы можете использовать их в разных сочетаниях. При этом на одном проекте инструмент может принести заметную пользу, а на другом — никогда не окупить время, потраченное на его внедрение и поддержку.
Об этом не стоит забывать, и всегда думать прежде чем что-то делать :-)
 
           
 
SieMax
Спасибо за статью, как раз давно хотел что-то подобное настроить, а тут целая инструкция!