Введение: почему рефакторинг legacy кода — это не роскошь, а необходимость
Каждый PHP-разработчик рано или поздно сталкивается с legacy кодом. Это может быть проект, написанный 10 лет назад на PHP 5.3, или приложение, которое «просто работает», но вызывает боль при каждом изменении. Рефакторинг такого кода — это не просто «причесывание» стиля, а критически важный процесс, который повышает поддерживаемость, производительность и безопасность приложения.
В этой статье мы разберем практические подходы к рефакторингу legacy PHP-кода, используя современные возможности языка (PHP 7.4–8.3) и проверенные паттерны. Вы узнаете, как безопасно вносить изменения, не ломая работающую систему.
1. Оценка и документирование текущего состояния
Первый и самый важный шаг — понять, с чем вы имеете дело. Не пытайтесь рефакторить вслепую.
1.1. Создайте карту зависимостей
Используйте инструменты вроде PhpMetrics или Deptrac, чтобы визуализировать связи между классами. Это поможет выявить «божественные объекты» (god objects) и циклические зависимости.
// Пример типичного "божественного" класса в legacy коде
class UserManager {
public function save($data) {
// 200 строк кода: валидация, отправка email, логирование, работа с БД
}
public function sendNewsletter() {
// Еще 150 строк
}
public function generateReport() {
// И еще 100 строк
}
}
1.2. Напишите характеристические тесты
Прежде чем менять код, убедитесь, что текущее поведение задокументировано тестами. Используйте подход «Golden Master» (snapshot testing):
// Тест, который фиксирует текущее поведение
public function testLegacyCalculation(): void
{
$calculator = new OldCalculator();
$result = $calculator->compute(10, 20);
// Если нет спецификации, просто сохраняем результат
$this->assertSame(30, $result);
}
Совет: Если проект вообще без тестов, начните с интеграционных тестов на критические бизнес-операции. Используйте PHPUnit с vfsStream для изоляции файловой системы.
2. Поэтапное улучшение: от глобальных функций к ООП
Большая часть legacy PHP-кода состоит из глобальных функций и процедурного стиля. Постепенная миграция к ООП — ключевой этап рефакторинга.
2.1. Инкапсуляция глобальных функций
Замените глобальные функции на статические методы класса, затем — на полноценные сервисы.
// Было: глобальная функция
function send_email($to, $subject, $message) {
mail($to, $subject, $message);
}
// Стало: сервисный класс
class MailService {
public function __construct(
private MailerInterface $mailer
) {}
public function send(string $to, string $subject, string $message): bool {
return $this->mailer->send($to, $subject, $message);
}
}
2.2. Избавление от глобальных переменных
Глобальные переменные ($GLOBALS, $_SESSION напрямую) — главный источник багов. Используйте паттерн Registry или внедрение зависимостей (DI).
// Плохо: прямая работа с глобальными переменными
function getCurrentUser() {
global $db;
$id = $_SESSION['user_id'];
return $db->query("SELECT * FROM users WHERE id = $id");
}
// Хорошо: явное внедрение зависимостей
class UserProvider {
public function __construct(
private PDO $db,
private SessionInterface $session
) {}
public function getCurrentUser(): ?User {
$id = $this->session->get('user_id');
return $this->db->query('SELECT * FROM users WHERE id = ?', [$id]);
}
}
3. Безопасная замена устаревших конструкций
PHP развивается, и многие старые конструкции стали небезопасными или устаревшими. Вот что нужно менять в первую очередь.
3.1. Замена mysql_* на PDO или mysqli
Расширение mysql_* удалено с PHP 7.0. Миграция на PDO обязательна.
// Legacy код (уже не работает в PHP 7+)
$link = mysql_connect('localhost', 'user', 'pass');
mysql_select_db('test', $link);
$result = mysql_query("SELECT * FROM users WHERE id = " . $_GET['id']);
// Безопасная версия с PDO
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute(['id' => (int)$_GET['id']]);
3.2. Удаление eval() и create_function()
Эти функции — бомба замедленного действия. Замените их на анонимные функции (Closures).
// Устаревший подход
$func = create_function('$a, $b', 'return $a + $b;');
echo $func(2, 3); // 5
// Современная замена
$func = fn($a, $b) => $a + $b;
echo $func(2, 3); // 5
3.3. Типизация и строгие типы
Добавьте declare(strict_types=1); в начало файлов и укажите типы для аргументов и возвращаемых значений.
declare(strict_types=1);
class Calculator {
public function add(int $a, int $b): int {
return $a + $b;
}
}
4. Рефакторинг без тестов: стратегия выживания
Идеальный сценарий — писать тесты до рефакторинга. Но в реальности часто нужно менять код, который вообще не покрыт тестами. Вот безопасная тактика.
4.1. Техника «Strangler Fig» (Платановое дерево)
Вместо переписывания всей системы сразу, постепенно заменяйте старые компоненты новыми, пока старый код не останется без использования.
// Шаг 1: Добавляем прослойку (Facade)
class LegacyOrderProcessor {
public function process(array $data) {
// Вызов старого кода
return legacy_process_order($data);
}
}
// Шаг 2: Создаем