Миграция проекта с yii1 на yii2

После выхода yii2 довольно часто приходилось заниматься миграцией проектов с yii1 на yii2, но и сейчас нет-нет да и появиться какой-то динозавр, который решил наконец-то обновить себя любимого. В этой статье мы поделимся своим опытом. Ничего сложного в этом процессе нет и откровений не будет. Характер публикации — свой опыт + tutorial для начинающих.

Предпосылки

Если проекты, исторически сделанные на первой версии yii, продолжают развиваться, то каждому разработчику, который с ними работает рано или поздно приходит мысль: «как было бы хорошо, если бы это было на yii2«.

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

На момент когда мы были готовы заниматься миграциями для клиентов у нас были 2 проекта готовых к этому.

В первом проекте было все просто. Мы совладелец и единственный разработчик. Поэтому причина была проста «надоело писать на yii1». Писать должно быть «в кайф», иначе на выходе может получиться хрень плохое качество.

Во втором случае, мы были подрядчиками в проекте, который до нас долго писался разными разработчиками в перемешку с фрилансерами без четкой архитектуры. Поэтому на выходе имелась огромная куча legacy кода. Переписать – легче застрелиться отказаться, заказчик «рефакторинг ради рефакторинга» не признавал, что было видно по объему этой кучи.

С первым проектом все было просто и понятно: по соглашению с совладельцем работы по проекту перешли только в статус: исправление критических ошибок, а сам проект с чистого листа пишется на yii2. Соответственно все плюшки, которые скопились в очереди на реализации были перенесены в бэклог нового проекта.

Ситуация со вторым проектом была патовая. Все понимали, что с кодом проблемы, но вырваться из этого круга не могли. Тогда мы предложили постепенную модульную миграцию на yii2. По плану через 1.5 месяца часть сайта начинает работать на yii2, и появиляется место куда будет мигрировать и инфраструктура проекта, в которой можно будет осмысленно работать и не оправдываться тем, что «посмотрите, какой ужас вокруг».

Подумать, прежде чем начинать

Для себя определили несколько правил, которое должны соблюдаться или не стоит и начинать.

  1. Мотивация для обеих сторон. Она может быть любая, но она должна перевешивать все минусы.
  2. Не начинать миграцию, если у проекта нет будущего на 2-3 года вперед. Делать just for fun миграцию коммерческого проекта не слишком привлекательная идея.
  3. Весь новый функционал, всё развитие, всё новое пишем на yii2. Функционал в yii1 поддерживается ровно до момента его переноса на yii2. Формат поддержки — устранение критических ошибок. Все остальное только после переноса.
  4. Составить план миграции. Расписанный по модулям, сервисам, моделям, страницам, функционалу.
  5. Рассматривать миграцию как глубокий рефакторинг проекта, направленный на развитие. Тогда может оказаться, что часть проекта вообще не нужно переписывать, а просто красиво похоронить, переделав проект так, что она будет просто не нужна.

Идея миграции

Основная идея миграции – это одновременная совместная работа двух веток одного проекта на yii1 и yii2 в одном домене, на одном виртуальном хосте. Постепенно, пошагово портировать сервисы / страницы / модули на yii2.

Например, есть сайт, работает на yii1

site.ru/            # главная
site.ru/news        # новости
site.ru/pages       # страницы
site.ru/comments    # отзывы

Переписали новости на yii2, получили:

site.ru/            # главная 
site.ru/news        # новости (yii2)
site.ru/pages       # страницы 
site.ru/comments    # отзывы 

Переписали отзывы, получили

site.ru/            # главная 
site.ru/news        # новости (yii2)
site.ru/pages       # страницы 
site.ru/comments    # отзывы (yii2)

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

Чтобы работало одновременно

В самом просто варианте, держите обе ветки (yii1 и yii2) в одной рабочей области, например, так:

/var/www/site/htdocs/ - DOCUMENT_ROOT виртуального хоста
/var/www/site/yii1/   - проект на yii1
/var/www/site/yii2/   - проект на yii2

Неважно как назвать и разместить директории. Нужно сделать так, чтобы код на yii1 и yii2 лежал рядом и был доступен для работы в одном виртуальном хосте. А вся магия по одновременной работе будет во входных скриптах index.php и .htaccess.

В чем плюсы такого подхода:

  • В вашей среде разработки будут доступны сразу 2 ветки проекта. Это может быть удобно, т.к. долгое время придется работать с ними одновременно, переключаясь туда-сюда.
  • Оба проекта будут иметь прямой доступ к DOCUMENT_ROOT, что важно для простой работы со статикой css/js.

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

Можно создавать в IDE отдельный проект для ветки yii2, даже когда файлы веток физически лежали рядом.

Базовый пример. Исходники веток проекта yii1/yii2 в одной директории

В DOCUMENT_ROOT используются 2 входных скрипта.

index.php  - для yii1
index_yii2.php - для yii2. 
В файловой структуре

htdocs/
htdocs/index.php
htdocs/index_yii2.php
yii1/
yii2/

index.php
Если вы не меняете файловой структуры для проекта на yii1, то ваш index.php останется неизменным.

<?php

<span class="hljs-comment">/*
*   Как-то собираем конфиги и запускаем приложение.
*   Код не привожу, т.к. во всех моих проектах yii1 index.php 
*   был слишком кастомизированный и отличался от оригинала.
*/</span>

$app = Yii::createApplication(<span class="hljs-string">'WebApplication'</span>, $config);

$app-&gt;run();
<span class="hljs-meta">?&gt;</span>

index_yii2.php

<span class="hljs-meta">&lt;?php</span>
defined(<span class="hljs-string">'YII_DEBUG'</span>) <span class="hljs-keyword">or</span> define(<span class="hljs-string">'YII_DEBUG'</span>, <span class="hljs-keyword">true</span>);
defined(<span class="hljs-string">'YII_ENV'</span>) <span class="hljs-keyword">or</span> define(<span class="hljs-string">'YII_ENV'</span>, <span class="hljs-string">'dev'</span>);

<span class="hljs-comment">// Путь к директории с yii2 относительно index_yii2.php, </span>
<span class="hljs-comment">// а дальше почти как из «коробки».</span>
$path = <span class="hljs-string">'/../yii2/'</span>;

<span class="hljs-keyword">require</span>(<span class="hljs-keyword">__DIR__</span> . $path.<span class="hljs-string">'vendor/autoload.php'</span>);
<span class="hljs-keyword">require</span>(<span class="hljs-keyword">__DIR__</span> . $path.<span class="hljs-string">'vendor/yiisoft/yii2/Yii.php'</span>);
<span class="hljs-keyword">require</span>(<span class="hljs-keyword">__DIR__</span> . $path.<span class="hljs-string">'common/config/bootstrap.php'</span>);
<span class="hljs-keyword">require</span>(<span class="hljs-keyword">__DIR__</span> . $path.<span class="hljs-string">'frontend/config/bootstrap.php'</span>);

$config = yii\helpers\ArrayHelper::merge(
    <span class="hljs-keyword">require</span>(<span class="hljs-keyword">__DIR__</span> . $path.<span class="hljs-string">'common/config/main.php'</span>),
    <span class="hljs-keyword">require</span>(<span class="hljs-keyword">__DIR__</span> . $path.<span class="hljs-string">'common/config/main-local.php'</span>),
    <span class="hljs-keyword">require</span>(<span class="hljs-keyword">__DIR__</span> . $path.<span class="hljs-string">'frontend/config/main.php'</span>),
    <span class="hljs-keyword">require</span>(<span class="hljs-keyword">__DIR__</span> . $path.<span class="hljs-string">'frontend/config/main-local.php'</span>)
);

(<span class="hljs-keyword">new</span> yii\web\Application($config))-&gt;run();<span class="hljs-meta">?&gt;</span>

.htaccess
В .htaccess будем делать роутинг между yii1 и yii2

<span class="hljs-attribute"><span class="hljs-nomarkup">Options</span></span> +FollowSymlinks
<span class="hljs-attribute"><span class="hljs-nomarkup">RewriteEngine</span></span> <span class="hljs-literal">On</span>
<span class="hljs-attribute">RewriteBase</span> /

<span class="hljs-comment"># Это отправляем на yii2</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment"># Полностью контроллеры</span>
<span class="hljs-attribute"><span class="hljs-nomarkup">RewriteRule</span></span> ^test index_yii2.php<span class="hljs-meta"> [L]</span>
<span class="hljs-attribute"><span class="hljs-nomarkup">RewriteRule</span></span> ^news index_yii2.php<span class="hljs-meta"> [L]</span>
<span class="hljs-comment"># По отдельным action</span>
<span class="hljs-attribute"><span class="hljs-nomarkup">RewriteRule</span></span> ^page/one index_yii2.php<span class="hljs-meta"> [L]</span>

<span class="hljs-comment"># Проверка на существование файлов и директорий</span>
<span class="hljs-attribute"><span class="hljs-nomarkup">RewriteCond</span></span> <span class="hljs-variable">%{REQUEST_FILENAME}</span> !-f
<span class="hljs-attribute"><span class="hljs-nomarkup">RewriteCond</span></span> <span class="hljs-variable">%{REQUEST_FILENAME}</span> !-d

<span class="hljs-comment"># Всё остальное следует на yii1</span>
<span class="hljs-attribute"><span class="hljs-nomarkup">RewriteRule</span></span> . index.php

Т.е. нижеперечисленные URL-ы обрабатываются index_yii2.php и работают на yii2.

<span class="hljs-function">https://<span class="hljs-title">site</span>/<span class="hljs-title">test</span>
<span class="hljs-title">https</span>://<span class="hljs-title">site</span>/<span class="hljs-title">news</span>
<span class="hljs-title">https</span>://<span class="hljs-title">site</span>/<span class="hljs-title">page</span>/<span class="hljs-title">one</span>
</span>

За остальной сайт отвечает index.php (yii1).

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

Исходники веток yii1/yii2 разнесены по директориям

Например, если у вас свой сервер, то можно разнести хранение веток проекта по разным директориям.

/var/www/site/htdocs      - DOCUMENT_ROOT виртуального хоста для site.ru
/var/www/site/protected   - проект на yii1
/srv/site_yii2            - проект на yii2

Тогда нужно сменить путь к директории с проектом yii2 в index_yii2.php. Разумеется, так будет работать если отключен, либо настроен open_basedir. Плюс соответствующие права на сервере и отключенный / настроенный, SELinux.

index_yii2.php

<span class="hljs-meta">&lt;?php</span>
defined(<span class="hljs-string">'YII_DEBUG'</span>) <span class="hljs-keyword">or</span> define(<span class="hljs-string">'YII_DEBUG'</span>, <span class="hljs-keyword">true</span>);
defined(<span class="hljs-string">'YII_ENV'</span>) <span class="hljs-keyword">or</span> define(<span class="hljs-string">'YII_ENV'</span>, <span class="hljs-string">'dev'</span>);

$pathYii2 = <span class="hljs-string">'/srv/site_yii2/'</span>;

<span class="hljs-keyword">require</span> $pathYii2 . <span class="hljs-string">'vendor/autoload.php'</span>;
<span class="hljs-keyword">require</span> $pathYii2 . <span class="hljs-string">'vendor/yiisoft/yii2/Yii.php'</span>;
<span class="hljs-keyword">require</span> $pathYii2 . <span class="hljs-string">'common/config/bootstrap.php'</span>;
<span class="hljs-keyword">require</span> $pathYii2 . <span class="hljs-string">'frontend/config/bootstrap.php'</span>;

$config = yii\helpers\ArrayHelper::merge(
     <span class="hljs-keyword">require</span> $pathYii2 . <span class="hljs-string">'common/config/main.php'</span>, 
     <span class="hljs-keyword">require</span> $pathYii2 . <span class="hljs-string">'common/config/main-local.php'</span>, 
     <span class="hljs-keyword">require</span> $pathYii2 . <span class="hljs-string">'frontend /config/main.php'</span>, 
     <span class="hljs-keyword">require</span> $pathYii2 . <span class="hljs-string">'frontend /config/main-local.php'</span>
);
(<span class="hljs-keyword">new</span> yii\web\Application($config))-&gt;run();<span class="hljs-meta">?&gt;</span>

Что дальше

Если на сайте есть пользователи, то единая авторизация — это критичный элемент без которого одновременная работа 2 веток, по факту, невозможна. Например, сама авторизация остается в yii1, но авторизованные пользователи прозрачно видны в ветке yii2 или наоборот. Но об этом следующий раз.

%d такие блоггеры, как: