Рефакторинг legacy кода на PHP: от А до Я
Работа с устаревшим (legacy) кодом — неизбежная часть жизни любого PHP-разработчика. Рано или поздно вы столкнетесь с проектом, написанным на PHP 5.6 (или даже 4), где нет автозагрузки, используются глобальные переменные, а бизнес-логика перемешана с HTML. Рефакторинг такого кода — это не просто "причесывание", а стратегический процесс, который может спасти проект от технического долга и сделать его поддерживаемым.
В этой статье мы разберем, как подойти к рефакторингу legacy PHP-кода системно, не сломав работающую функциональность, и превратить "болото" в современный, тестируемый код.
1. Диагностика и оценка: с чего начать?
Прежде чем менять код, нужно понять, с чем вы имеете дело. Начните с аудита проекта.
Шаг 1. Определите "болевые точки"
- Зависимости: Используются ли устаревшие библиотеки? Например,
mysql_*функции вместо PDO или MySQLi. - Архитектура: Есть ли хоть какая-то структура (MVC)? Или весь код в одном файле
index.php? - Тесты: Существуют ли автоматические тесты? Если нет, это ваш первый приоритет.
Шаг 2. Инструменты для анализа
Используйте статические анализаторы, чтобы быстро выявить проблемные места:
- PHPStan / Psalm: Найдут ошибки типов и несоответствия сигнатур.
- Rector: Автоматически обновит синтаксис под новую версию PHP.
- PhpMetrics: Покажет циклическую сложность и связность кода.
Пример запуска Rector для обновления с PHP 7.4 на 8.0:
vendor/bin/rector process src/ --set php80
2. Стратегия "Хирургия без боли": пошаговый план
Рефакторинг legacy кода — это не "большой взрыв". Это серия маленьких, безопасных шагов. Используйте технику "Strangler Fig" (удушающий инжир) — постепенно заменяйте старые модули новыми, не переписывая всё сразу.
Шаг 1. Покройте код тестами
Без тестов рефакторинг — это игра в русскую рулетку. Начните с характеристических тестов (characterization tests):
class LegacyCalculatorTest extends TestCase {
public function testOldCalculation(): void {
// Запускаем старый код и фиксируем результат
$result = legacy_calculation(10, 5);
$this->assertEquals(15, $result);
}
}
Это создаст "сеть безопасности" перед изменениями.
Шаг 2. Изолируйте "грязную" логику
Если код работает с глобальными переменными ($GLOBALS), вынесите их в параметры функции:
// Было (глобальная зависимость)
function processOrder() {
global $db;
$result = $db->query("SELECT * FROM orders");
}
// Стало (внедрение зависимости)
function processOrder(PDO $db) {
$result = $db->query("SELECT * FROM orders");
}
Шаг 3. Замените устаревшие функции
Например, замена mysql_* на PDO:
// Старый код
$link = mysql_connect('localhost', 'user', 'pass');
mysql_select_db('test', $link);
$result = mysql_query("SELECT * FROM users", $link);
// Новый код (PDO)
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$stmt = $pdo->query("SELECT * FROM users");
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
3. Типичные проблемы legacy PHP и их решения
Проблема 1: "Спагетти-код" (смесь логики и представления)
Решение: Разделите код на слои. Вынесите бизнес-логику в отдельные классы, а HTML — в шаблоны (например, с помощью Twig).
// Плохо: логика и HTML в одном файле
if ($user->isAdmin()) {
echo "<h1>Привет, админ!</h1>";
} else {
echo "<h1>Привет, пользователь!</h1>";
}
// Хорошо: контроллер + шаблон
// UserController.php
$viewData = ['isAdmin' => $user->isAdmin()];
echo $twig->render('greeting.html.twig', $viewData);
// greeting.html.twig
<h1>{% if isAdmin %}Привет, админ!{% else %}Привет, пользователь!{% endif %}</h1>
Проблема 2: "Божественный объект" (God Object)
Один класс делает всё. Решение: Разделите его на несколько классов по принципу единственной ответственности (SRP).
// Плохо: один класс управляет пользователями, заказами и платежами
class SuperManager {
public function createUser() { /* ... */ }
public function processOrder() { /* ... */ }
public function chargePayment() { /* ... */ }
}
// Хорошо: три отдельных класса
class UserManager { /* ... */ }
class OrderProcessor { /* ... */ }
class PaymentGateway { /* ... */ }
Проблема 3: Отсутствие типизации
Решение: Добавьте строгие типы (PHP 7+) и используйте PHPStan для проверки.
// Было
function calculate($a, $b) {
return $a + $b;
}
// Стало (со строгими типами)
function calculate(int $a, int $b): int {
return $a + $b;
}
4. Инструментарий для рефакторинга
Ускорьте процесс с помощью современных инструментов:
- Rector: Автоматически обновляет код под новые версии PHP и PSR-стандарты.
- PHP CS Fixer: Приводит код к единому стилю (PSR-12).
- PHPStan (level max): На