Avatar ·

Как выполнить статический анализ кода PHP: инструменты и примеры использования

Всем привет! Я пишу на PHP уже около года, и мой проект (интернет-магазин на самописном движке) разросся до 50+ файлов. Я заметил, что в коде появляются странные ошибки: то переменная не объявлена, то метод вызывается у null, то типы не совпадают. Вручную искать такие баги — просто ад. Я слышал, что в других языках (например, TypeScript или Java) есть статический анализ кода — программы, которые проверяют код до его запуска. Но как это работает в PHP? Есть ли что-то подобное? Я пробовал включать строгие типы (declare(strict_types=1)) и error_reporting(E_ALL), но это не ловит логические ошибки. Например, у меня есть такой код: ```php function calculateDiscount($price, $percent) { return $price - ($price * $percent / 100); } $total = calculateDiscount(1000, '10%'); // Опечатка: передал строку echo $total; // Выдает 1000, потому что строка преобразовалась в 0 ``` Я ожидал, что PHP хотя бы предупредит, что я передаю строку вместо числа. Но ничего не произошло — просто тихое преобразование типов. Как мне автоматически находить такие проблемы? Какие инструменты существуют для статического анализа PHP? И как их настроить? Заранее спасибо за помощь! <<>>

Что такое статический анализ и зачем он нужен в PHP?

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

Основные инструменты для статического анализа PHP

Вот три самых популярных и эффективных инструмента:

  • PHPStan — мощный анализатор с уровнями строгости от 0 до 9. Позволяет находить ошибки типов, несовместимость сигнатур методов и многое другое.
  • Psalm — альтернатива PHPStan от Vimeo. Поддерживает аннотации типов, проверку на утечки памяти и имеет встроенный плагин для Symfony/Laravel.
  • PhpStorm (встроенный инспектор) — если вы используете эту IDE, она уже включает базовый статический анализ, но уступает специализированным инструментам.

Пример настройки PHPStan для вашей задачи

Установите PHPStan через Composer:

composer require --dev phpstan/phpstan

Создайте файл конфигурации phpstan.neon:

parameters:
    level: 5
    paths:
        - src/
    checkMissingIterableValueType: true
    checkGenericClassInNonGenericObjectType: true

Теперь запустите анализ:

vendor/bin/phpstan analyse

Для вашего примера с функцией calculateDiscount PHPStan выдаст предупреждение, если вы добавите строгую типизацию:

function calculateDiscount(int $price, int $percent): int {
    return $price - ($price * $percent / 100);
}

$total = calculateDiscount(1000, '10%'); // PHPStan: Parameter #2 $percent expects int, string given.

Как улучшить ваш код с помощью статического анализа

Вот несколько конкретных шагов:

  1. Используйте строгие типы — добавьте declare(strict_types=1); в начало каждого файла. Это заставит PHP выбрасывать TypeError при несовпадении типов, а не преобразовывать их.
  2. Добавьте аннотации типов — для функций и методов укажите типы параметров и возвращаемых значений. Например: function calculateDiscount(float $price, float $percent): float.
  3. Настройте уровень строгости — начните с уровня 1 (базовые ошибки) и постепенно повышайте до 5-6, когда код станет чище.
  4. Интегрируйте с CI/CD — добавьте запуск PHPStan в GitHub Actions или GitLab CI. Пример для GitHub Actions:
name: Static Analysis
on: [push]
jobs:
  phpstan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: composer install
      - run: vendor/bin/phpstan analyse --error-format=github

Что делать с вашим примером?

Перепишите функцию с учётом строгой типизации и статического анализа:

declare(strict_types=1);

function calculateDiscount(float $price, float $percent): float {
    if ($percent < 0 || $percent > 100) {
        throw new InvalidArgumentException('Percent must be between 0 and 100');
    }
    return $price - ($price * $percent / 100);
}

// Теперь вызов с ошибкой будет пойман:
$total = calculateDiscount(1000, '10%'); // TypeError: Argument 2 must be of type float, string given

После настройки PHPStan вы увидите в терминале чёткое сообщение об ошибке, а не тихое преобразование типов.

Дополнительные советы

  • Используйте baseline-файл — если проект уже содержит много ошибок, сгенерируйте baseline, чтобы игнорировать старые проблемы и сосредоточиться на новых.
  • Подключите плагины — для Laravel есть phpstan/phpstan-laravel, для Symfony — phpstan/phpstan-symfony.
  • Регулярно обновляйте инструменты — новые версии PHPStan и Psalm добавляют проверки для современных конструкций PHP 8.x.

Статический анализ не решит всех проблем (например, логические ошибки в бизнес-логике), но он отлавливает до 80% типичных багов, связанных с типами и несоответствием сигнатур. Это must-have для любого серьёзного PHP-проекта.

<<>>

Альтернативный подход: использование Psalm с автоматическим исправлением

Если вы хотите не только находить ошибки, но и автоматически исправлять некоторые из них, попробуйте Psalm. Он имеет встроенную функцию --alter, которая может добавлять недостающие аннотации типов.

Установка:

composer require --dev vimeo/psalm
vendor/bin/psalm --init

Запуск с автоматическим добавлением типов:

vendor/bin/psalm --alter --issues=MissingReturnType,MissingParamType --dry-run

Уберите --dry-run, чтобы применить изменения. Psalm проанализирует ваш код и добавит аннотации @param и @return туда, где они отсутствуют.

Сравнение с PHPStan

Psalm лучше подходит для проектов, где уже используется @psalm-* аннотации (например, @psalm-param). Он также умеет проверять шаблоны (generics) и имеет более гибкую систему плагинов. Однако PHPStan проще в настройке для начинающих и имеет более понятные сообщения об ошибках.

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