Есть вполне определенное чувство страха, возникающее, когда вы впервые открываете легаси-кодовую базу. Везде летают глобальные переменные, вызовы  mysql_query()  зарыты на глубину трех функций, файл  functions.php  имеет длину в 4000 строк, а тестов ровно ноль. И тем не менее, бизнес от этого зависит. Клиенты этим пользуются. И теперь вас попросили это модернизировать.

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

Почему инкрементальная миграция лучше переписывания «с нуля»

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

Паттерн «душитель» (strangler fig) — когда новая система оборачивается вокруг старой, а трафик перенаправляется по кусочкам — почти всегда является правильным решением. Laravel отлично для этого подходит, потому что его маршрутизация, middleware и сервис-контейнер могут сосуществовать с легаси-кодом, запущенным на том же сервере.

Шаг 1: Аудит перед тем, как вы к чему-либо прикоснетесь

Прежде чем написать хотя бы одну строку на Laravel, составьте карту существующей системы:

  •  Точки входа: какие файлы веб-сервер обслуживает на самом деле? Часто в легаси PHP-приложениях есть десятки файлов  index.php  в поддиректориях.
  •  Схема базы данных: экспортируйте полную ERD-диаграмму. Такие инструменты, как SchemaSpy или даже простой экспорт через  SHOW CREATE TABLE , станут вашими лучшими друзьями.
  •  Внешние зависимости: платежные шлюзы, настройки SMTP, сторонние API — документируйте каждое исходящее подключение.
  •  Модель сессий и аутентификации: аутентификация основана на сессиях? На куках? На кастомной таблице токенов?

Документируйте это безжалостно. Вы будете постоянно к этому возвращаться.

Шаг 2: Развертывание Laravel рядом с легаси-приложением

Установите свежее приложение Laravel в соседнюю директорию. Настройте веб-сервер (в данном случае проще всего использовать nginx) для проксирования запросов либо в легаси-приложение, либо в Laravel на основе префикса URL.

 server {
    listen 80;
    server_name example.com;

    # New Laravel routes
    location /account {
        proxy_pass http://127.0.0.1:8001;
    }

    location /api {
        proxy_pass http://127.0.0.1:8001;
    }

    # Everything else goes to the legacy app
    location / {
        root /var/www/legacy;
        index index.php;
        fastcgi_pass unix:/run/php/php7.4-fpm.sock;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}
 

Laravel работает на PHP 8.x на порту 8001 через отдельный пул FPM. Легаси-приложение продолжает обрабатывать все остальное без каких-либо изменений.

Шаг 3: Общее состояние сессий

Самая сложная проблема в этом паттерне миграции — аутентификация. Пользователи, вошедшие в легаси-систему, не должны сталкиваться с необходимостью повторного входа, когда запрос обрабатывает Laravel.

Прагматичное решение: использовать общий бэкенд сессий. Если легаси-приложение использует файловые сессии PHP, Laravel тоже может их читать, но это выглядит грязно. Лучше сначала перевести легаси-приложение на использование базы данных или хранилища сессий Redis, а затем настроить Laravel на использование того же хранилища.

 // config/session.php in Laravel
'driver' => 'redis',
'connection' => 'default',
'cookie' => 'legacy_session', // must match the legacy app's cookie name
 

Со стороны легаси используйте кастомный обработчик сессий, который записывает данные в Redis с тем же форматом ключей. PhpRedis или Predis отлично подходят для этой задачи. Это обеспечивает настоящий единый вход (SSO) между старой и новой системами без предварительной полной миграции аутентификации.

Шаг 4: Оборачивание легаси-базы данных в модели Eloquent

Не переименовывайте таблицы. Пока не приводите схему к нормальному виду. Просто укажите Eloquent на то, что уже существует.

 class LegacyUser extends Model
{
    protected $table = 'tbl_users'; // old naming convention
    protected $primaryKey = 'user_id';
    public $timestamps = false; // no created_at/updated_at columns

    protected $casts = [
        'is_active' => 'boolean',
        'created' => 'datetime', // maps the legacy column name
    ];

    public function getCreatedAtAttribute()
    {
        return $this->attributes['created'] ?? null;
    }
}
 

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

Шаг 5: Помодульная миграция маршрутов

Сначала выберите наименее рискованный и наиболее изолированный модуль. Идеально подходят панели администратора, страницы отчетности или внутренние инструменты. Избегайте платежных сценариев и основного процесса оформления заказа, пока не обретете уверенность.

Для каждого модуля:

  1. Воспроизведите функциональность в контроллере Laravel и представлении Blade (или компоненте Livewire).
  2. Напишите функциональные тесты, покрывающие позитивный сценарий и краевые случаи, обнаруженные во время аудита.
  3. Обновите конфигурацию nginx для проксирования этого префикса URL на Laravel.
  4. Мониторьте логи ошибок в течение 48–72 часов перед переходом к следующему модулю.
 // Example feature test to lock in behaviour before refactoring
public function test_order_history_displays_for_authenticated_user(): void
{
    $user = LegacyUser::factory()->create();
    $orders = LegacyOrder::factory()->count(3)->for($user)->create();

    $response = $this->actingAs($user)->get('/account/orders');

    $response->assertOk();
    $response->assertSee($orders->first()->order_ref);
}
 

Тесты, написанные на основе легаси-поведения, становятся защитной сеткой для каждого последующего рефакторинга.

Шаг 6: Миграция схемы — когда вы будете готовы

Как только модуль полностью заработал на Laravel и был протестирован, вы можете задуматься о нормализации схемы. Используйте миграции Laravel с методом  Schema::table() , чтобы добавлять колонки, переименовывать вещи и внедрять правильные индексы — но поначалу делайте это аддитивно. Сохраняйте старую колонку, выполняйте запись в обе, проверяйте целостность данных, а затем удаляйте старую колонку в одной из последующих миграций.

 Schema::table('tbl_users', function (Blueprint $table) {
    $table->string('email')->nullable()->after('user_email');
    $table->index('email');
});

// Backfill in a job, not in the migration itself
LegacyUser::whereNull('email')->eachById(function ($user) {
    $user->update(['email' => $user->user_email]);
});
 

Никогда не выполняйте бэкфилл (заполнение) больших наборов данных прямо в миграции. Это блокирует таблицу и вызывает простои.

Шаг 7: Финальный перенос

Когда все маршруты обрабатываются Laravel, а легаси-точки входа стали мертвым кодом, вы готовы к полному переключению. На этом этапе:

  • Переименуйте или заархивируйте легаси-директорию.
  • Обновите nginx, чтобы корневой путь (root) указывал напрямую на папку  public/  Laravel.
  • Удалите старый пул FPM.
  • Запустите  php artisan route:cache ,  config:cache  и  view:cache  на продакшене.

Храните заархивированную легаси-кодовую базу не менее 90 дней. Вы еще захотите к ней обратиться.

Выводы, добытые кровью и потом на продакшене

Несколько вещей, усвоенных в ходе реализации подобных проектов для реальных клиентов (включая миграции, выполненные в рамках работы на hanzweb.ae), о которых обычно не упоминают в учебниках:

  •  Не позволяйте идеальному быть врагом работающего. Уродливые модели Eloquent, отражающие запутанную схему — это нормально. Наведите в них порядок, когда система стабилизируется.
  •  В легаси-приложении будет недокументированная бизнес-логика. Прочитайте старый код перед тем, как его удалять. У каждого загадочного оператора  if  есть своя история.
  •  Логирование — ваш лучший инструмент диагностики. Добавьте структурированное логирование через Monolog с первого же дня. Вы скажете себе спасибо, когда что-нибудь сломается на продакшене в 2 часа ночи.
  •  Общайтесь со стейкхолдерами по поводу сроков применения паттерна «душитель». Миграции такого рода занимают месяцы, а не недели. Задайте эти ожидания заранее.

Заключение

Легаси-миграции проходят успешно, когда к ним относятся как к серии небольших, проверяемых шагов, а не как к единому героическому переписыванию. Паттерн «душитель», общие сессии, обертки Eloquent над существующими таблицами и функциональные тесты, написанные под легаси-поведение — всё это не блестящие гламуром техники, но именно они действительно доводят дело до релиза.

Конечный результат стоит усилий: кодовая база, в которой вы можете разобраться, которую можете уверенно тестировать и передать другому разработчику без написания 40-страничного онбординг-документа.