1 заметка с тегом

phpcsfixer

Как настроить php-cs-fixer и phpstan на локалке

Проблема 1

При совместной разработке (от 2х человек) code style становится критичным. Если каждый будет писать в своём стиле, вскоре проект превратится в бардак, где по разному стилю можно будет оценивать возраст кода будто по кольцам на деревьях.

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

Проблема 2

Чем больше кодовая база проекта, тем проще допустить ошибку или опечатку. Вручную проверка кода перед каждым коммитом возможна, но требует много времени и большой внимательности. Результат опять непредсказуем и ненадёжен.

Решение

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

Такие инструменты уже есть — code style и статические анализаторы кода.

Единый стиль кода

Поскольку я специализируюсь на Symfony, то для код-стайла выбираю PHP-CS-Fixer. Он написан при участии создателя Symfony и имеет из коробки поддержку рекомендованного сообществом Symfony Code Standards. Есть ещё PHPCodeSniffer, но он умеет только обнаруживать ошибки, но не исправляет их автоматически, поэтому я рекомендую именно PHP-CS-Fixer.

Совет: обязательно изучите и следуйте code style и рекомендациям вашего фреймворка. С большой вероятностью в будущих проектах вам встретится именно общепринятый в сообществе фреймворка code style, поэтому будет проще вписаться в новую команду, если вы придёте к этому стилю как можно раньше. К тому же, уже принятый в сообществе стиль поможет избежать ненужных холиваров внутри команды.

Устанавливаем через composer и настраиваем PHPCSFixer в Symfony-проекте:

cd /ваш/проект
composer req --dev friendsofphp/php-cs-fixer
# Если используете docker-compose, то устанавливайте в нём
docker-compose exec web composer req --dev friendsofphp/php-cs-fixer

Создаём в корне проекта файл .php_cs:

<?php

$finder = PhpCsFixer\Finder::create()
    ->in([
        __DIR__ . '/src',
        __DIR__ . '/tests'
    ])
;

return PhpCsFixer\Config::create()
    ->setRules([
        '@Symfony' => true,
        'array_syntax' => ['syntax' => 'short'],
        'concat_space' => ['spacing' => 'one'],
        'increment_style' => ['style' => 'post'],
        'no_extra_blank_lines' => ['tokens' => [
            'extra',
            'parenthesis_brace_block',
            'square_brace_block',
            'throw',
            'use',
        ]],
        'no_superfluous_phpdoc_tags' => false,
        'phpdoc_align' => false,
        'phpdoc_annotation_without_dot' => false,
        'trailing_comma_in_multiline_array' => false,
        'yoda_style' => false
    ])
    ->setFinder($finder)
;

Теперь вы можете запустить проверку всего проекта (или конкретного пути) с помощью команды

# только поиск ошибок
./vendor/bin/php-cs-fixer  fix --dry-run --diff ./
# поиск ошибок и их автоматический фикс
./vendor/bin/php-cs-fixer fix ./

Ошибки и качество кода

Для отлова опечаток и даже поиска более сложных ошибок мы воспользуемся статическим анализатором кода. Прелесть анализаторов в том, что они не выполняют код, а просто читают и анализируют — это очень быстро. Кроме того, каждый из них имеет свои уровни строгости, поэтому можно внедрять их в существующий проект постепенно: фиксим ошибки с минимальным уровнем, коммитим, повышаем уровень, фиксим, коммитим, снова повышаем.

Самые популярные статичиские анализаторы кода:

Я покажу, как настроить и внедрить PHPStan, а вы можете добавить в проект все три по аналогии. Устанавливаем через composer сам PHPStan и несколько полезных плагинов — PHPUnit, Symfony, Doctrine.

cd /ваш/проект
composer req --dev phpstan/phpstan phpstan/phpstan-doctrine phpstan/phpstan-phpunit phpstan/phpstan-symfony

Должен создаться в корне проекта файл .phpstan.neon.dist, редактируем его:

includes:
    - vendor/phpstan/phpstan-doctrine/extension.neon
    - vendor/phpstan/phpstan-phpunit/extension.neon
    - vendor/phpstan/phpstan-phpunit/rules.neon
    - vendor/phpstan/phpstan-symfony/extension.neon
parameters:
    reportUnmatchedIgnoredErrors: false     #to avoid throwing of errors ... if no errors matching the ignored one is raised
    scanFiles:  #we need this for some twig based functions
        - '%rootDir%/../../../vendor/twig/twig/src/Extension/CoreExtension.php'
    bootstrapFiles:
        - '%rootDir%/../../../vendor/bin/.phpunit/phpunit-X.X.X/vendor/autoload.php'
    ignoreErrors:
        - message: '/^Service "[^"]+" is private.$/'
          path: '%rootDir%/../../../tests/'
    level: 0 
    symfony:
        container_xml_path: '%rootDir%/../../../var/cache/development/App_KernelDevelopmentDebugContainer.xml'

Внимательно пройдитесь по этим настройкам и укажите корректные пути. Особое внимание:

  1. bootstrapFiles — включает путь до текущего phpunit. Если его не используете, то можете закоментить.
  2. level — уровень строгости проверок от 0 до 8. 0 — самый слабый уровень, подходит для первого внедрения.

Теперь вы можете запустить проверку всего проекта (или конкретного пути) с помощью команды

./vendor/bin/phpstan analyse src tests

Советы по внедрению

Несколько советов по внедреню PHP-CS-Fixer в существующий проект с большой командой.

  1. Выберите одного ответственного, кто запустит скрипты и пофиксит весь проект у себя на локалке, а потом создаст ПР / смерджит в develop или master.
  2. Лучшее время — первое утро нового спринта. Обычно после спринта все ветки смёрджены, и это позволит залить все найденные правки в основную ветку проекта без конфликтов.
  3. В статических анализаторах используйте для начала минимальный уровень строгости проверок. У каждого анализатора для этого своя школа, читайте документацию.
  4. Первый фикс — самый критичный момент, так как правок может быть очень много. Все последующие фиксы обычно не создают ощутимых конфликтов и гит их смёрдживает самостоятельно.

Чтобы не запоминать длинные пути до скриптов, мы добавим несколько задач (tasks) в composer.json:

{
    "require": { ... },
    "require-dev": { ... },
    ...
    "scripts": {
        ...
        "csfix": "./vendor/bin/php-cs-fixer fix",
        "csfix-validate" : "./vendor/bin/php-cs-fixer fix --dry-run --diff",
        "phpstan": "./vendor/bin/phpstan analyse src tests",
        "code-quality": [
            "@phpcsfixer-validate",
            "@phpstan"
        ]

Теперь мы можем запускать проверки в упрощённом формате:

composer csfix
composer phpstan
composer code-quality

Обязательный запуск проверок перед коммитом

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

Воспользуемся Git-хуками (git hooks) — это скрипты, которые мы можем попросить git выполнить при возникновении какого-то события: момент перед созданием коммита, момент после создания коммита, момент добавления сообщения в коммит и т. п.

Полный список git-хуков вы можете увидеть у себя в репозитории:

$ ls -al .git/hooks
-rwxr-xr-x   1 mk  staff   478 Jul 13 13:04 applypatch-msg.sample
-rwxr-xr-x   1 mk  staff   896 Jul 13 13:04 commit-msg.sample
-rwxr-xr-x   1 mk  staff  3327 Jul 13 13:04 fsmonitor-watchman.sample
-rwxr-xr-x   1 mk  staff   189 Jul 13 13:04 post-update.sample
-rwxr-xr-x   1 mk  staff   424 Jul 13 13:04 pre-applypatch.sample
-rwxr-xr-x   1 mk  staff   382 Dec  1 14:46 pre-commit
-rwxr-xr-x   1 mk  staff  1638 Jul 13 13:04 pre-commit.sample
-rwxr-xr-x   1 mk  staff   416 Jul 13 13:04 pre-merge-commit.sample
-rwxr-xr-x   1 mk  staff  1348 Jul 13 13:04 pre-push.sample
-rwxr-xr-x   1 mk  staff  4898 Jul 13 13:04 pre-rebase.sample
-rwxr-xr-x   1 mk  staff   544 Jul 13 13:04 pre-receive.sample
-rwxr-xr-x   1 mk  staff  1492 Jul 13 13:04 prepare-commit-msg.sample
-rwxr-xr-x   1 mk  staff  3610 Jul 13 13:04 update.sample

Создадим скрипт, который будет запускать проверки каждый раз перед попыткой создать коммит.

vim .git/hooks/pre-commit

Добавляем внутрь файла:

#!/bin/sh
export PATH=/usr/local/bin:$PATH
# Если вы используете docker-compose (рекомендуется), то добавьте
export COMPOSE_INTERACTIVE_NO_CLI=1

if [ -t 1 ]; then
    # If we're in a terminal, redirect stdout and stderr to /dev/tty and
    # read stdin from /dev/tty. Allow interactive mode for CaptainHook.
    exec >/dev/tty 2>/dev/tty </dev/tty
fi

errors=0

composer code-quality
# или, если вы используете docker-compose (рекомендуется), то
docker-compose exec -T web php bin/composer code-quality

if [ "$?" -ne 0 ]; then
    errors=1
fi

if [ "$errors" -eq 1 ]; then
    echo "Errors detected!"
    exit 1
fi

Теперь можете попробовать сделать ошибку в .php файле и создать коммит. Git должен ругнуться:

Устанавливаем git-hook автоматически

Финальный шаг — добавить git-hook в .git/hooks/ папку при запуске composer. Тогда ни один разработчик не сможет пропустить запуск проверок по невнимательности. Для этого добавим в composer.json такую строку:

{
    ...,
    "scripts": {
        "post-autoload-dump": "mkdir -p .git/hooks && cp config/pre-commit .git/hooks/pre-commit",
        ...
    }
}

Итог

  1. Мы добавили в проект PHPCsFixer для код-стайла и PHPStan для поиска ошибок и опечаток.
  2. Запускаем их при каждой попытке создать коммит.
  3. И всё это начинает работать автоматически после первого же composer install.