Рефакторинг legacy кода на PHP: как превратить хаос в порядок
Каждый PHP-разработчик рано или поздно сталкивается с legacy кодом — старым, плохо документированным и часто неработающим проектом. Вместо того чтобы переписывать всё с нуля (что рискованно и дорого), грамотный рефакторинг позволяет улучшить код без остановки бизнес-процессов. В этой статье мы разберём, как безопасно рефакторить legacy PHP-проекты, сохранив их работоспособность.
1. Оценка и подготовка: с чего начать рефакторинг legacy кода
Прежде чем трогать код, необходимо понять его текущее состояние. Пропустите этот этап — и рискуете сломать всё.
- Создайте резервную копию — полный дамп базы данных и файлов проекта.
- Проведите аудит — найдите все точки входа, зависимости и "чёрные ящики". Используйте
grepдля поискаinclude,require,evalиmysql_*функций. - Настройте окружение — поднимите локальную копию проекта (Docker, Vagrant или XAMPP).
- Добавьте логирование —
error_logи мониторинг помогут отследить ошибки после изменений.
Пример простого аудита через консоль:
# Поиск устаревших mysql функций
grep -rn "mysql_query\|mysql_connect\|mysql_fetch" /path/to/legacy/project/
Важно: если в проекте нет тестов — рефакторинг превращается в игру в русскую рулетку. Первым делом покройте критическую логику тестами (даже если это будут ручные сценарии, записанные в документ).
2. Стратегия рефакторинга: маленькие шаги больших изменений
Лучшая стратегия для legacy — постепенное улучшение. Не пытайтесь переписать весь проект за выходные. Используйте подход "бойскаутского правила": оставляй код чище, чем он был до тебя.
2.1. Изолируйте изменения
Каждое изменение должно быть минимальным и атомарным. Пример: переименование переменной в одном файле, затем — извлечение дублирующегося кода в функцию.
<?php
// До рефакторинга (legacy)
$sql = "SELECT * FROM users WHERE id = " . $_GET['id'];
$result = mysql_query($sql);
// После: хотя бы экранируем
$id = (int)$_GET['id'];
$sql = "SELECT * FROM users WHERE id = $id";
$result = mysqli_query($link, $sql);
?>
2.2. Используйте инструменты автоматизации
- PHPStan — статический анализ, находит ошибки без запуска кода.
- Rector — автоматический рефакторинг (PSR-12, замена устаревших функций).
- PHP CodeSniffer — проверка стиля кода.
- PHPUnit — модульное тестирование.
Пример запуска Rector для замены mysql_* на mysqli_*:
vendor/bin/rector process src/ --set mysql-to-mysqli
3. Конкретные приёмы рефакторинга PHP-кода
3.1. Замена "спагетти-кода" на ООП
Legacy часто пишется как один огромный файл с HTML и PHP вперемешку. Разделите логику на классы и функции.
<?php
// Плохо: код вперемешку
$user = getUser($_GET['id']);
echo "<h1>Привет, " . $user['name'] . "</h1>";
if ($_POST['action'] === 'save') {
saveUser($_POST);
}
// Хорошо: разделение ответственности
class UserController {
public function show(int $id): void {
$user = $this->userRepository->find($id);
$this->render('profile', ['user' => $user]);
}
}
?>
3.2. Удаление "мёртвого" кода
Неиспользуемые функции, комментарии, закомментированные блоки — всё это создаёт шум. Удаляйте без жалости, но проверьте через git log, не используется ли код где-то ещё.
// Поиск неиспользуемых функций (требуется PHPStan)
vendor/bin/phpstan analyse src/ --level 8
3.3. Переход на современные PSR-стандарты
Автозагрузка PSR-4, использование Composer, отказ от global и require в пользу DI-контейнеров.
// До: ручной require
require_once 'lib/User.php';
$user = new User();
// После: автозагрузка Composer
use App\Entity\User;
$user = new User();
4. Безопасность при рефакторинге: как не сломать бизнес
Даже маленькое изменение может привести к падению продаж или ошибкам в расчётах. Соблюдайте правила:
- Feature toggle — включайте новый код через конфиг, чтобы быстро откатиться.
- Параллельный запуск — старый и новый код работают одновременно, результаты сравниваются.
- Code review — каждое изменение должен проверить другой разработчик.
- CI/CD pipeline — автоматически прогоняйте тесты перед деплоем.
Пример простого feature toggle:
<?php
if (getenv('USE_NEW_PAYMENT') === 'true') {
$payment = new NewPaymentProcessor();
} else {
$payment = new LegacyPaymentProcessor();
}
?>
Заключение
Рефакторинг legacy кода на PHP — это не героический подвиг, а рутинная работа, которую можно и нужно автоматизировать. Начните с малого: поставьте PHPStan, напишите хотя бы один тест, замените одну устаревшую функцию. Постепенно проект станет чище, а вы — увереннее в своих действиях.
Помните: идеальный код — это ми