Рефакторинг legacy кода на PHP: от А до Я — полное руководство

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Рефакторинг 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): На

Блоги

Рекомендаций по книгам