Статический анализ кода PHP — держите свой код под контролем

Статический анализ кода PHP — держите свой код под контролем

Автор: MaxRokatansky

Сколько раз вы замечали код, который, казалось, был написан наспех? Думаю, много. Иногда он написан кем-то другим, но довольно часто ваши собственные творения, по прошествии достаточного времени, заставляют вас задуматься: «Кто, черт возьми, написал эту чушь?» Поддерживать чистоту кода нелегко, поэтому я решил представить вам несколько инструментов, которые смогут вам в этом помочь. Пришло время освежить свои знания по статическому анализу кода в PHP!

Держать код под контролем — это одно, при этом самое ужасное то, что не существует универсальных стандартов кодирования. Огромные проекты с открытым исходным кодом, фреймворки и библиотеки применяют свои собственные стандарты программирования. Эта проблема была замечена некоторое время назад, и она привела к возникновению PHP Standards Recommendations (PSR. Рекомендации по стандартам PHP). Это документ, составленный совместно авторами самых популярных проектов PHP. 

Хороший код в сравнении с плохим 

Несмотря на то, что основные рекомендации (PSR-0 - PSR-4) были признаны большинством сообщества PHP в качестве стандарта де-факто, последующие попытки унифицировать более распространенные элементы приложений часто оказывались неудачными, и с тех пор многие люди покинули проект, включая авторов таких известных разработок, как Symfony, Laravel, Doctrine или Guzzle.

Конкурирующие стандарты

Разработчики также отличаются друг от друга своими стандартами качества и чистоты кода. Отсутствие санации стандартов кодирования на уровне проекта может привести к ненужным спорам во время код-ревью.

Два типа людей

Поддерживать довольно хорошее качество кода — задача не из легких. Ниже я постараюсь показать вам несколько инструментов статического анализа кода, которые могут помочь вам в этом.

Но что такое статический анализ кода?

Статический анализ кода PHP — это действие, выполняемое инструментом статического анализа кода, предназначенным для чтения исходного кода PHP без его фактического запуска. Выполнение такого анализа обычно является частью процесса непрерывной интеграции (CI). Статические анализаторы позволяют программистам ограничивать и предсказывать поведение программного обеспечения не запуская его.

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

Разнообразие инструментов статического анализа кода в сообществе PHP довольно велико. Вот некоторые из них:

  • Easy Coding Standard (ECS), 
  • PHPStan, 
  • Psalm.

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


Вы можете исправить свой код в соответствии с выбранным стандартом, в том числе разработанным с определенными сообществами, такими как Symfony.


Исправляет нарушения стандартов кодирования и токенизирует файлы PHP, JS и CSS.


Анализатор кода, в задачу которого входит подтверждение некорректности, предотвращая ложные срабатывания. 


Работает со многими фреймворками, предоставляет симпатичные отчеты.


Поможет обновить ваш PHP.


Имеет большое сообщество и поддерживает множество языков, помимо PHP.


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


В следующей части статьи я представлю вам три моих любимых анализатора кода, которые я использую в большинстве своих проектов: вышеупомянутый ECS, PHPStan и Psalm.

Почему конкретно ECS, PHPStan и Psalm? Среди множества вариантов именно они являются, на мой взгляд, самыми простыми в использовании и дают больше всего преимуществ. Почему три, а не один? Потому что эти инструменты дополняют друг друга.

Как я уже упоминал ранее, проекты часто содержат различные, "устаревшие" стили написания кода. У программистов, участвующих в проекте, также есть свои привычки. Вот почему конфигурация анализаторов для каждого проекта должна быть индивидуальной и сохранена в репозитории этого проекта. Чтобы облегчить процесс настройки, я покажу вам несколько элементарных конфигурационных файлов, которые мы используем в The Software House.

Исправьте мой код!

Прежде чем начать исправление своего кода, стоит усвоить несколько правил, которые могут помочь вам в этом.


  1. Создайте коммит кода перед выполнением любой команды, изменяющей его, а также перед внесением любых правок в конфигурацию инструмента. Ее запуск может привести к тому, что скрипт повредит ваш код, а произошедшие перемены будет трудно восстановить.
  2. Выполняйте тесты после каждого изменения кода, чтобы убедиться, что все по-прежнему работает так, как ожидалось.
  3. Настраивая анализатор, лучше всего начать со всех доступных чекеров и фиксеров. Затем следует исключить те, которые не нужны, или которые вы не хотите выполнять. В конце концов, вы получите наиболее подходящее количество улучшений.
  4. Бессмысленно использовать для этой задачи плагины IDE или даже добавлять эти скрипты в качестве прекоммит-хуков. Их использование отвлекает программиста. Вместо этого лучше запускать проверки непосредственно перед отправкой кода в репозиторий и, что очень важно, в режиме непрерывной интеграции.
  5. Если ваш код очень старый, могут возникнуть конфликты в зависимостях Composer’а. В таком случае правильным решением будет инсталлировать эти инструменты в отдельный каталог, например, в ./ecs/composer.json.

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


EasyCodingStandard — один инструмент для управления ими всеми

ECS — это статический анализатор кода, созданный Томашем Вотрубой (Tomas Votruba) и его соавторами. Он способен автоматически исправить большинство изъянов в вашем коде. Он содержит правила из PHP Code Fixer, Code Sniffer, Slevomat и Simplify. Кроме того, он значительно упрощает настройку. Звучит слишком хорошо, чтобы быть правдой? Проверьте это сами.


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

imports:
    - { resource: 'vendor/symplify/easy-coding-standard/config/set/clean-code.yaml' }
    - { resource: 'vendor/symplify/easy-coding-standard/config/set/common.yaml' }
    - { resource: 'vendor/symplify/easy-coding-standard/config/set/php70.yaml' }
    - { resource: 'vendor/symplify/easy-coding-standard/config/set/php71.yaml' }
    - { resource: 'vendor/symplify/easy-coding-standard/config/set/psr2.yaml' }
    - { resource: 'vendor/symplify/easy-coding-standard/config/set/psr12.yaml' }
    - { resource: 'vendor/symplify/easy-coding-standard/config/set/symfony.yaml' }
    - { resource: 'vendor/symplify/easy-coding-standard/config/set/symfony-risky.yaml' }

services:
    # most of these services are taken from symplify.yaml
    # see https://github.com/Symplify/Symplify/blob/master/packages/CodingStandard/config/symplify.yaml

    # PHP 5.5
    Symplify\CodingStandard\Fixer\Php\ClassStringToClassConstantFixer: ~

    # Control Structures
    Symplify\CodingStandard\Fixer\Property\ArrayPropertyDefaultValueFixer: ~
    Symplify\CodingStandard\Fixer\ArrayNotation\StandaloneLineInMultilineArrayFixer: ~
    Symplify\CodingStandard\Fixer\ControlStructure\RequireFollowedByAbsolutePathFixer: ~

    # Spaces
    Symplify\CodingStandard\Fixer\Strict\BlankLineAfterStrictTypesFixer: ~

    # Comments
    Symplify\CodingStandard\Fixer\Commenting\RemoveSuperfluousDocBlockWhitespaceFixer: ~

    # Naming
    PhpCsFixer\Fixer\PhpUnit\PhpUnitMethodCasingFixer: ~

    # Debug
    Symplify\CodingStandard\Sniffs\Debug\DebugFunctionCallSniff: ~
    Symplify\CodingStandard\Sniffs\Debug\CommentedOutCodeSniff: ~

    # final classes
    PhpCsFixer\Fixer\ClassNotation\FinalInternalClassFixer: ~

    # multibyte
    PhpCsFixer\Fixer\Alias\MbStrFunctionsFixer: ~

    # psr
    PhpCsFixer\Fixer\Basic\Psr0Fixer: ~
    PhpCsFixer\Fixer\Basic\Psr4Fixer: ~

    # psr-1
    PHP_CodeSniffer\Standards\PSR1\Sniffs\Classes\ClassDeclarationSniff: ~
    PHP_CodeSniffer\Standards\PSR1\Sniffs\Files\SideEffectsSniff: ~
    PHP_CodeSniffer\Standards\PSR1\Sniffs\Methods\CamelCapsMethodNameSniff: ~

    PhpCsFixer\Fixer\CastNotation\LowercaseCastFixer: ~
    PhpCsFixer\Fixer\CastNotation\ShortScalarCastFixer: ~
    PhpCsFixer\Fixer\PhpTag\BlankLineAfterOpeningTagFixer: ~
    PhpCsFixer\Fixer\Import\NoLeadingImportSlashFixer: ~
    PhpCsFixer\Fixer\Import\OrderedImportsFixer:
        importsOrder:
            - 'class'
            - 'const'
            - 'function'
    PhpCsFixer\Fixer\LanguageConstruct\DeclareEqualNormalizeFixer:
        space: 'none'
    PhpCsFixer\Fixer\Operator\NewWithBracesFixer: ~
    PhpCsFixer\Fixer\Basic\BracesFixer:
        'allow_single_line_closure': false
        'position_after_functions_and_oop_constructs': 'next'
        'position_after_control_structures': 'same'
        'position_after_anonymous_constructs': 'same'

    PhpCsFixer\Fixer\ClassNotation\NoBlankLinesAfterClassOpeningFixer: ~
    PhpCsFixer\Fixer\ClassNotation\VisibilityRequiredFixer:
        elements:
            - 'const'
            - 'method'
            - 'property'
    PhpCsFixer\Fixer\Operator\TernaryOperatorSpacesFixer: ~
    PhpCsFixer\Fixer\FunctionNotation\ReturnTypeDeclarationFixer: ~
    PhpCsFixer\Fixer\Whitespace\NoTrailingWhitespaceFixer: ~

    PhpCsFixer\Fixer\Semicolon\NoSinglelineWhitespaceBeforeSemicolonsFixer: ~
    PhpCsFixer\Fixer\ArrayNotation\NoWhitespaceBeforeCommaInArrayFixer: ~
    PhpCsFixer\Fixer\ArrayNotation\WhitespaceAfterCommaInArrayFixer: ~

    # merge issets
    PhpCsFixer\Fixer\LanguageConstruct\CombineConsecutiveIssetsFixer: ~
    PhpCsFixer\Fixer\LanguageConstruct\CombineConsecutiveUnsetsFixer: ~

    # remove useless phpdoc
    PhpCsFixer\Fixer\FunctionNotation\PhpdocToReturnTypeFixer: ~
    PhpCsFixer\Fixer\Import\FullyQualifiedStrictTypesFixer: ~
    PhpCsFixer\Fixer\Phpdoc\NoSuperfluousPhpdocTagsFixer: ~

    # arguable checkers, feel free to remove them
    Symplify\CodingStandard\Sniffs\ControlStructure\SprintfOverContactSniff: ~
    PhpCsFixer\Fixer\ClassNotation\OrderedClassElementsFixer:
        order:
            - 'use_trait'
    PhpCsFixer\Fixer\Operator\BinaryOperatorSpacesFixer: ~
    PhpCsFixer\Fixer\Operator\UnaryOperatorSpacesFixer: ~
    PhpCsFixer\Fixer\Operator\ConcatSpaceFixer:
        spacing: 'one'
    PhpCsFixer\Fixer\Whitespace\BlankLineBeforeStatementFixer:
        statements:
            - 'return'

    # cognitive complexity - adjust level to your needs, starting from 100
    Symplify\CodingStandard\Sniffs\CleanCode\CognitiveComplexitySniff:
        maxCognitiveComplexity: 30

    # this one is RISKY, but if you are sure your phpdoc is right then go on
    SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff: ~

parameters:
    cache_directory: var/cache/ecs
    skip:
        PHP_CodeSniffer\Standards\Generic\Sniffs\CodeAnalysis\AssignmentInConditionSniff: ~
        PhpCsFixer\Fixer\ClassNotation\ClassAttributesSeparationFixer: ~
        PhpCsFixer\Fixer\ClassNotation\OrderedClassElementsFixer: ~
        PhpCsFixer\Fixer\Operator\ConcatSpaceFixer: ~
        PhpCsFixer\Fixer\Operator\IncrementStyleFixer: ~
        PhpCsFixer\Fixer\Operator\UnaryOperatorSpacesFixer: ~
        PhpCsFixer\Fixer\Phpdoc\PhpdocAnnotationWithoutDotFixer: ~
        PhpCsFixer\Fixer\Phpdoc\PhpdocSummaryFixer: ~
        SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingTraversableParameterTypeHintSpecification: ~
        SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingTraversableReturnTypeHintSpecification: ~

Хорошо, но вы можете спросить: "Как его использовать?" Ниже вы ознакомитесь с несколькими простыми действиями:

  1. Сохраните приведенный выше код в основной папке проекта (в файле ecs.yaml).
  2. Инсталлируйте ECS: composer require --dev symplify/easy-coding-standard
  3. Запустите чекер: vendor/bin/ecs check src (по умолчанию команда выполняет пробный запуск (dry-run) — это означает, что она проверяет код, но не изменяет его).
The checker (программа проверки. чекер)
  1. Проверьте предложенные изменения. Если какие-то из них не отвечают вашим требованиям, удалите соответствующие правила из конфигурационного файла или добавьте их в раздел "skip" (пропустить). Здесь же можно добавить исключения для конкретных файлов и вернуться к шагу 3. 
  2. После достижения удовлетворительного результата позвольте программе исправить ваш код. Для этого закоммитите изменения, а затем выполните команду из шага 3. На этот раз вы должны сделать это с помощью аргумента "fix" (исправить): vendor/bin/ecs check src --fix
Ошибки, которые должны быть исправлены вручную
  1. Не все можно исправить автоматически. Если что-то осталось в выводе, измените код вручную, пока не появится зеленый.
Ошибок не обнаружено
  1. Выполните тест и/или перейдите в приложение, чтобы проверить, работает ли оно по-прежнему правильно.
  2. Если во время выполнения процесса что-то пошло не так, вы можете вернуть состояние кода к коммиту, который вы сделали до команды fix (исправить).
  3. Готово.

Выполнение этого может значительно улучшить качество вашего кода. Но мы еще не закончили! Давайте посмотрим, что еще можно усовершенствовать с помощью других инструментов.


PHPStan

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


parameters:
    paths:
        - src
        - tests
    tmpDir: var/cache/ecs
    level: 5
    inferPrivatePropertyTypeFromConstructor: true

Вновь ответим на вопрос: как его использовать?

  1. Сохраните приведенный выше код в главной директории проекта, в файле phpstan.neon.
  2. Инсталлируйте PHPStan: composer require --dev phpstan/phpstan
  3. Запустите его: vendor/bin/phpstan analyse src
Найдена ошибка
  1. Исправьте ошибки, предъявленные в выводе.
Ошибок не обнаружено
  1. Выполните тесты и/или перейдите в приложение, чтобы проверить, работает ли оно по-прежнему правильно.
  2. Теперь вы можете повысить уровень в конфигурации, а затем вернуться к шагу 3, пока не достигнете удовлетворяющего вас эффекта. По моему опыту, большинство проектов останавливаются на уровнях от 3 до 5.
  3. Готово.

Psalm

Последний из инструментов, которые я использую ежедневно, называется Psalm. Он был создан компанией Vimeo. К сожалению, как и в случае с PHPStan, ваш код не будет исправлен автоматически. Конфигурирование осуществляется через XML-файл и предлагает множество возможностей. Моя рекомендация — начать со всех правил на минимальном уровне "предупреждения" (warning), постепенно исключая их. Окончательный конфиг будет выглядеть следующим образом.


<?xml version="1.0"?>
<psalm
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        totallyTyped="false"
        xmlns="https://getpsalm.org/schema/config"
        xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
    <projectFiles>
        <directory name="src" />
        <directory name="tests" />
    </projectFiles>

    <issueHandlers>
        <MissingPropertyType errorLevel="suppress" />
        <PossiblyInvalidMethodCall errorLevel="suppress" />
        <ArgumentTypeCoercion errorLevel="suppress" />
        <LessSpecificReturnStatement errorLevel="suppress" />
        <MoreSpecificReturnType errorLevel="suppress" />
        <InvalidStringClass errorLevel="suppress" />
        <MoreSpecificImplementedParamType errorLevel="suppress" />
        <InvalidNullableReturnType errorLevel="suppress" />
        <NullableReturnStatement errorLevel="suppress" />
        <PossiblyInvalidArgument errorLevel="suppress" />
        <MissingClosureReturnType errorLevel="suppress" />
        <DeprecatedMethod errorLevel="info" />
        <MissingParamType errorLevel="info" />
        <MissingClosureParamType errorLevel="info" />
        <MissingReturnType errorLevel="info" />
        <DeprecatedClass errorLevel="info" />
        <PossiblyNullReference errorLevel="info" />
        <PossiblyNullArgument errorLevel="info" />
        <PossiblyNullOperand errorLevel="info" />
        <UndefinedClass>
            <errorLevel type="suppress">
                <directory name="tests"/>
            </errorLevel>
        </UndefinedClass>
        <PossiblyUndefinedMethod>
            <errorLevel type="suppress">
                <directory name="tests"/>
            </errorLevel>
        </PossiblyUndefinedMethod>
        <PropertyNotSetInConstructor>
            <errorLevel type="suppress">
                <directory name="tests"/>
            </errorLevel>
        </PropertyNotSetInConstructor>
        <InternalMethod>
            <errorLevel type="suppress">
                <directory name="tests"/>
            </errorLevel>
        </InternalMethod>
    </issueHandlers>
</psalm>

Как его использовать?

  1. Сохраните приведенный выше код в главном каталоге проекта, в файле psalm.xml.
  2. Инсталлируйте Psalm: composer require --dev vimeo/psalm 
  3. Запустите его: vendor/bin/psalm --show-info=false
Найденные ошибки
  1. Если ошибок слишком много или вы не согласны с какой-либо из них, вам следует исключить правила и файлы, которые вы не собираетесь корректировать.
  2. Исправьте свой код в соответствии с оставшимися правилами.
Ошибок не обнаружено
  1. Выполните тест и/или перейдите в приложение, чтобы проверить, работает ли оно по-прежнему правильно.
  2. Готово.

Если вам интересно увидеть реальные примеры вышеупомянутых инструментов, я рекомендую вам посетить наш рабочий образец этих анализаторов в репозитории GitHub


Что дальше для статического анализа кода PHP?

Теперь код выглядит намного чище! Используя инструмент статического анализа PHP, вы можете:


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

Но подождите, разве ваш мозг не пресытился этими знаниями? Задаете ли вы себе вопросы: «Почему существует так много этих инструментов статического анализа кода PHP? Почему не достаточно использовать только один из них? Чем они отличаются друг от друга?»


Если да, то я хотел бы порекомендовать вам несколько более подробных технических статей в отличном блоге Томаша Вотрубы (Tomas Votruba), создателя вышеупомянутого ECS, которые, надеюсь, ответят на эти вопросы:




Report Page