Новая жизнь legacy проекта

в 19:19, , рубрики: legacy, php, symfony

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

Хорошо, когда у вас есть годик или два для подготовки нового проекта с нуля (был у меня и такой опыт). Но если владелец не готов вкладываться в то, что вы будете что-то делать параллельно с нуля, пока старый движок во всю загибается. Попытки даже заговорить, о том, чтобы написать все по новой, встречают моментальный отказ. Но можно попробовать маленькими шажками, делать новый движок, который постепенно бы начал забирать на себя всю большую и большую роль в работе проекта. Собственно об опыте осуществить такую замену, я бы и хотел вам рассказать.

Дано

Сайт по продаже билетов на концерты, пхп плюс мускуль, код старый, имеет небольшое логическое разделение (на мой вкус, чуть лучше битрикса), шаблоны, бизнес логика и прямые запросы в бд прямо в одном методе. Наиболее используемые классы на 2.000+ строк. Вообщем все как мы любим.

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

Создание моделей

Данные это самое главное, что у вас есть, особенно если вы хотите “выкинуть” движок. Поэтому начинать разработку нового движка логично с того, чтобы подцепить данные из текущей базы. В случае с symfony мы будем создавать модели. На самом деле здесь все довольно просто, базовые поля вы можете создать с помощью команды doctrine:mapping:import (подробнее можно почитать здесь). Для того, чтобы не обрабатывать сразу все таблицы, команду запускали на урезаной базе, где остались только основные таблицы (заказы, билеты, мероприятия и пользователи, плюс пара таблиц связей и расширенных данных)

Здесь вы можете встретить первые проблемы. В нашей базе оказались enum, которые доктрина никак не хочет переваривать, но за пару часов работы получилось составить миграцию (здесь имеется ввиду просто sql файл, а не миграция доктрины) на наш небольшой десяток этих полей, которая заменила enum на строки/числа.

Немного поработав с колонками мы без проблем получи и связи в наших объектах. Если конечно старые разработчики заботились о вас и использовали primary key в качестве связи или какие-то колонки, с помощью которых вы бы могли описать связи one/many. Так что здесь нам немного повезло.

Почти повезло. Вторая проблема оказалась с заказами, слово order является зарезервированными, в одной из таблиц, есть колонка которая содержит в этом поле id заказа (спасибо автору проекта, что в других таблицах он неожиданно стал использовать ord_id а не слово order). Здесь из быстрых решений оказался только хак с загрузкой.

public function postLoad(LifecycleEventArgs $event)
{
    $entity = $event->getEntity();

    if ($entity instanceof Ticket) {
        // getOrderNumber выдает номер который содержится в колонке order
        $id = $entity->getOrderNumber();

        if ($id) {
            $order = $event->getEntityManager()->getRepository('AppBundle:Orders')->find($id);

            if (!$order) {
                throw new Exception(sprintf("Order %d not found in base", $id));
            }

            // setOrder/getOrder на самом деле работают со скрытым полем orderObject который содержит в себе объект заказа
            $entity->setOrder($order);
        }
    }
}

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

/**
 * @ORMColumn(type="integer", options={"default" = 0})
 */
public $type = 0;

Замена крона

С чего начать внедрять новый движок? Что не страшно сломать без потери клиентов, если ваш проект не покрыт тестами? Где происходит “грязная” работа, которая особо не интересна держателям бизнеса? Где можно реализовать логику, но при этом не потребуется интегрировать шаблоны старого сайта? Так что у нас выбор на боевое крещение нового движка выпал на крон задачи. Как минимум еще одна причина по который его хотелось заменить, это то, что в старом движке вызовы шли через wget.

Да, на этом этапе мы словили первые проблемы, но лучше у нас билеты повисят в резерве больше положенного, чем человек, не сможет их вообще купить.

Первые шаги (отдельный виджет)

Второй блок который был реализован на новом движке, это неожиданно подвернувшийся виджет для партнеров. Из себя он представлял небольшую страницу, которая через iframe встраивается на партнерский сайт. А также эта страница требовала отдельного управления в админке. Честно говоря, когда я читал требования и осознавал, что новый движок только что прошел боевое крещение, я думал, что идеальнее просто быть и не может.

С самим iframe проблем нет, верстка отдельно, шаблон отдельно, даже отдельная таблица, для управления тем, что нужно показывать. Но это все требовалось редактировать через админку, и самое главное, чтобы пользователь не почувствовал разницы (или почувствовал только в лучшую сторону). Поэтому надо было решить две проблемы: авторизация и внешний вид форм.

Первое решилось через механизм guard’ов. Понадобился только перенести логику из старого кода в методы getCredentials и getUser. Для нашего проекта вся логика заняла меньше 10 строк. За мои 5 лет на симфонии, у меня никогда не было более быстрой настройки авторизации и всего лишь одним классом. Вопрос с внешним видом легко решился с помощью стилизации форм.

Настройка nginx

Теперь надо запустить все это чудо. На старом проекте использовалась связка nginx+apache, и здесь есть некоторые проблемы, что конфиги апача мало кто толком сейчас умеет настраивать с разделениям по путям, поэтому был организован второй бэкэнд с php-fpm. Новые пути отправлялись на app.php а он уже в свою очередь отправлялся на fpm с корнем в папке нового проекта. Собственно в конфиг был добавлен примерно такой код.

location ~ ^/admin/parnter {
    root  /path-to-new-engine/web;
    try_files $uri /app.php$is_args$args;
}

location ~ ^/app.php(/|$) {
    root  /path-to-new-engine/web;
    Internal;
    ...fast_cgi config...
}

Что в итоге?

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

Автор: UnDelete

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js