Как улучшить Legacy PHP приложения

Перевёл для вас отличную статью с набором анти-паттернов в старых PHP-проектах и методами их решений. Под катом — 8 наиболее частых ошибок и способы их обработать.

Недавно мне довелось поработать с несколькими старыми PHP проектами. На всех проектах были общие ошибки, и я решил выделить несколько анти-паттернов, которые приходилось исправлять. Это статья не о том как переписать старые PHP приложения с <вставь тут любой старый PHP-фреймворк>, а о том как сделать код более удобным в поддержке.

Анти-паттерн #1: приватные данные в коде

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

Чтобы исправить это — я обычно использую пакет, который работает с любым PHP-приложением — vlucas, который позволяет создать .env-файл и использовать переменные среды через суперглобальные переменные PHP.

Мы создаём 2 файла: .env.example, который служит шаблоном для файла .env, и файл .env, который содержит приватные данные. .env.example добавляется в git, .env же нужно добавить в .gitignore.

Например, .env.example:

DB_HOST=
DB_DATABASE=
DB_USERNAME=
DB_PASSWORD=

.env:

DB_HOST=localhost
DB_DATABASE=mydb
DB_USERNAME=root
DB_PASSWORD=root

В исполняемом коде загрузка переменных осуществляется так:

$dotenv = Dotenv\Dotenv::createImmutable(DIR);
$dotenv->load();

После этого мы сможем получить переменную среды через $_ENV['DB_HOST'].
Стоит заметить, что на бою лучше не использовать этот пакет, а вместо этого:

  • Вставить переменные среды во время выполнения контейнера, если вы используете Docker, или в конфигурацию HTTP-сервера, если это возможно.
    Тогда мы можем
  • Кэшируйте переменные среды, чтобы избежать их загрузки при каждом исполнении кода. Laravel, например, делает это так.

Анти-паттерн #2: работа без composer

Я обнаружил, что раньше очень распространён был такой подход: заводилась папка lib, куда сохранялись все зависимости. Управлять версиями было невозможно. Чтобы этого избежать — используем composer.

Анти-паттерн #3: разработка на боевой среде

Если вы исправили анти-паттерн #1 — настроить среду для локальной разработки будет легче, так как мы отвязались от переменных среды, привязанных к боевым сервисам.
Старые проекты, как правило, работают на старых версиях PHP и обвязаны старыми зависимостями, которые вы, вероятно, не хотите себе устанавливать. Самым простым решением будет использование docker и docker-compose, где можно быстро установить все зависимости и работать локально.

Анти-паттерн #4: не использовать дирректорию public

Я обнаружил, что большинство старых проектов доступны их корня. Другими словами, любой файл в корне проекта доступен для чтения всем. Это может сыграть злую шутку, если злоумышленники попытаются получить доступ к файлам напрямую. Учитывая, что мы используем .env и composer, создающий папку vendor, для нас это совсем недопустимо, потому что такие данные открывать нельзя.
В таком случае создаём в корне папку public, ставим ей права на чтение и переносим все общие данные в неё, после чего настраиваем веб-сервер таким образом, чтобы сделать её корнем нашего приложения.
Моё типичное приложение выглядит так:

  • папка docker с docker-файлами, файлом docker-compose и настройками для контейнеров
  • папка app для бизнес-логики приложения
  • папка public, где располагаются клиентские PHP-скрипты, jss, css, статика. С точки зрения клиента — это корень приложения.
  • Файлы .env и .env.example

Анти-паттерн #5: очевидные проблемы с безопасностью

PHP приложения без использования фреймворков часто имеют проблемы с безопасностью, такие как:

  • Нет защиты от SQL-инъекций, так как параметры в запросе не экранируются. Чтобы этого избежать — используйте PDO.
  • XSS-инъекции, так как пользовательские данные могут не экранироваться при отображении. htmlspecialchars вам в помощь.
  • Загрузка файлов… отдельная тема. Если разработчик реализовал собственную загрузку — одна из описанных тут (https://www.acunetix.com/websitesecurity/upload-forms-threat/) проблем вполне может у него быть.
  • CSRF-атака — если адрес происхождения запроса не проверяется. Я рекомендую пакет Anti-CSRF (https://github.com/paragonie/anti-csrf), который можно легко интегрировать почти с любым приложением.
  • Плохое шифрование пароля. Я видел много проектов, где до сих пор используют SHA-1 или даже MD5. PHP предлагает хорошую поддержку BCrypt из коробки начиная с версии 5.5, поэтому если есть возможность — используйте его. Чтобы перенести пароли без проблем, я обычно обновляю хэш в базе при входе пользователя. Главное убедиться, что столбец в базе достаточно длинный, чтобы сохранить хэш BCrypt (varchar(255) — достаточно). Вот небольшой псевдокод, чтобы понять, как это работает:
<?php
// Password still in old hash: does not start with $
// Password is correct: convert it and log the user
if (strpos($oldPasswordHash, '$') !== 0 &&
    hash_equals($oldPasswordHash, sha1($clearPasswordInput))) {
    $newPasswordHash = password_hash($clearPasswordInput, PASSWORD_DEFAULT);

    // Update the password column

    // User logged in: return success
}

// Password already converted
if (password_verify($clearPasswordInput, $currentPasswordHash)) {
    // User logged in: return success
}

// User not logged in: return failure

Анти-паттерн #6: отсутствие тестов

Наиболее частая проблема. Я считаю, что для старых приложений очень сложно сразу начать писать модульные тесты, но можно начать с функциональных. Эти тесты помогут в дальнейшем убедиться, что последующий рефакторинг не сломает главные функции приложения. А после рефакторинга можно перейти к модульному тестированию. Для тестирования можно использовать PHPUnit и Codeception.

Анти-паттерн #7: плохая обработка ошибок

Если (а точнее когда) что-то сломается — вы захотите узнать об этом как можно быстрее. Большинство старых приложений плохо обрабатывают ошибки или не обрабатывают их вовсе, ссылаясь на то, что PHP раньше многое им прощал.

На эту тему есть хорошая статья. Прочитайте её, и снабдите код достаточным количеством try..catch, с логированием ошибок.

Анти-паттерн #8: глобальные переменные

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

Используйте инъекции зависимостей, это обеспечит вам больший контроль и безопасность кода. Такой пакет, как Pimple поможет вам с этой задачей.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *