Pull to refresh

MODx Revo workflow. Организация рабочего процесса, контроль версий и деплой

Reading time10 min
Views9.9K
Все основные элементы системы MODX, такие как чанки, шаблоны, сниппеты и т.д, хранятся в БД, из этого появляется проблема осуществления контроля версий за этими элементами, а также сложности с разделением на development и production версии сайта.

Приведу основные требования, чего я хочу от своего рабочего процесса на MODX Revo:
  • контроль версий везде, где пишу какой-либо код (html, css, js, php),
  • иметь отдельную dev-версию сайта, на которой ведётся текущая разработка, а после — деплоить все изменения в продакшн, причём, желательно, автоматизировать этот процесс,
  • минимум копипаста при разработке и деплое.



Содержание


  1. Введение
  2. Development и Production версии сайта
  3. Контроль версий
  4. Схема работы
  5. Деплой
  6. Заключение
  7. Ссылки



Введение


На момент написания этой статьи я имел опыт веб-разработки чуть более года. Мне повезло, я начал свой путь в этой сфере именно с MODX Revo, но несмотря на все плюсы этого фреймворка, со временем я начал сталкиваться и с минусами. Пока что я не работал в команде над большими сложными проектами и не знаю как обычно люди справляются с указанными выше сложностями. В интернетах я не нашёл какого-либо конкретного решения, и это поспособствовало созданию своего workflow, который я хочу представить в этой статье. Сразу оговорюсь, я не придумал ничего принципиально нового, а просто собрал разбросанные по интернету куски информации в одно руководство как устроить свой рабочий процесс при работе с MODX Revolution.

Что нам понадобится.
— Git
— npm + Gulp
Для работы я использую IDE PHPStorm.


Development и Production версии сайта


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

Не буду долго рассуждать и скажу сразу, я предлагаю использовать один экземпляр сайта, в котором мы воспользуемся механизмом контекстов MODX Revo. Стандартный контекст web будет содержать релизную версию сайта. Плюс мы создадим ещё один контекст dev, работу с которым вынесем в поддомен. Таким образом, нам нужно:
— домен example.com, работающий в контексте web
— поддомен dev.example.com, работающий в контексте dev
При деплое мне нужно, чтобы изменения сделанные в контексте dev, каким бы то ни было образом сливались в web.

Создаём контекст dev


Эта часть работы начинается с того момента, когда у вас уже установлен движок и необходимые компоненты и настроены доменные имена, т.е. при обращении по обоим адресам example.com и dev.example.com открывается одна и та же стартовая страница MODX (корневая директория обоих доменов общая).

Теперь заходите в админ-панель → системные настройки → контексты и создайте новый контекст. Ключ укажите dev, имя — как угодно, я назвал Development. В контекстном меню слева у вас появится новый контекст. Первым делом создайте для него ресурс, а потом зайдите в настройки контекста (правой кнопкой по контексту → редактировать → настройки контекста) и задайте настройки
site_start
error_page
unauthorized_page,
указав в них ID созданного ресурса. Это нужно, чтобы система не падала с ошибкой, если при работе в контексте dev не будет найдена какая-либо страница.


прим. Я создал контекст dev (Development) и переименовал стандартный контекст Website на Production, ключ стандартного контекста остался прежним — web.

Теперь в папке core/elements/common/plugins/ (создайте необходимые папки) создайте файл switchContext.php со следующим содержимым
<?php
/**
 * @var modX $modx
 */

/* don't execute if in the Manager */
if ($modx->context->get('key') == 'mgr') {
    return;
}

switch ($_SERVER['HTTP_HOST']) {
    case 'dev.example.com':
        $modx->switchContext('dev');
        break;
    case 'example.com':
        break;
    default:
        $modx->log(modX::LOG_LEVEL_ERROR, 'Check this plugin! May be your headache coming from here.');
        break;
}
return;

Поясню, что делает плагин. При работе с сайтом через админку, т.е. в контексте mgr, ничего не делает. При работе с фронтальной частью сайта на домене dev.example.com переключает контекст на dev. При работе на фронт-енде на основном домене тоже ничего не делает. Но в случае, если исполнение скрипта каким-то образом попадает в default, то выводит ошибку в лог. Такое может случиться, например, при переносе сайта на новое доменное имя, и это сообщение призвано облегчить жизнь тому разработчику, который будет разбираться, почему после переноса ничего не работает. Когда я проверял описанную здесь схему работы на удалённом сервере, я как раз забыл исправить доменные имена в этом плагине и по ошибке в логе сразу понял что не так. В очередной раз сам себе сказал «Спасибо».

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

Через админ-панель создайте новый плагин, который будет подтягивать созданный файл
<?php
$filepath = 'plugins/switchContext.php';
$context = 'common';
$plugin = $modx->getOption('pdotools_elements_path') . "$context/$filepath";
if (file_exists($plugin)) {
    return include $plugin;
}

повесьте запуск плагина на событие OnHandleRequest.

Теперь у нас есть два контекста, как их использовать в дальнейшей разработке будет показано ниже.


Контроль версий


Создаём репозиторий проекта, например на GitHub, при этом в рабочем каталоге будут только файлы и папки, не относящиеся к движку. Примерно так
assets/
|-- dev/
    |-- css/
    |-- js/
    |-- img/
    \-- scss/
|-- web/
    |-- css/
    |-- js/
    \-- img/
core/
\-- elements/
    |-- common/
        |-- plugins/
    |-- dev/
        |-- chunks/
        |-- plugins/
        |-- snippets/
        \-- templates/
    |-- web/
        |-- chunks/
        |-- plugins/
        |-- snippets/
        \-- templates/
    \-- plugins/
.gitignore
gulpfile.js
package.json

При этом, мы будем также контролировать файлы, получаемые в результате сборки (assets/web/ и core/elements/web/), чтобы иметь возможность откатиться после неудачного деплоя. Такая структура папок будет объяснена ниже.

Разработчики, как обычно, работают в своих локальных репозиториях и пушат коммиты в удалённый репозиторий. Кроме того, должен существовать репозиторий в корне сайта.
Пример файла .gitignore
    # IntelliJ project files
    .idea/
    *.iml
    out
    gen

    # ignore MODX files
    /assets/*
    /connectors/*
    /core/*
    /manager/*
    /setup/*
    /config.core.php
    /index.php
    /.htaccess

    # ignore node modules
    /node_modules/

    # do not ignore
    !/assets/dev/
    !/assets/web/
    !/core/elements/




Схема работы


Когда я выше говорил о Develpoment и Production версиях, я предложил использовать один экземпляр сайта. Это также предполагает отсутствие его локальной копии (не путать с локальным git-репозиторием проекта). Мы будем править файлы на локальной машине, но при этом в нашем локальном рабочем каталоге будут только файлы и папки, не относящиеся к движку. Мы будем вносить правки в имеющиеся файлы, затем делать upload этих файлов на сайт. После выполнения какой-либо задачи делаем коммит в локальном репозитории, после чего, если нужно, пушим изменения на GitHub, или где вы там собираетесь держать проект.

Теперь я расскажу, как я предлагаю устроить версионирование основных элементов системы — шаблонов, чанков, сниппетов и плагинов, и описать рабочий процесс через IDE PHPStorm в связке с панелью администрирования MODX. В этом нам поможет легендарный pdoTools и используемый им шаблонизатор Fenom.

Как известно, парсер pdoTools позволяет подключать внешние файлы прямо в теле чанка, и именно эта фича лежит в основе всего устройства контроля версий.
Необходимо выставить настройке pdotools_fenom_parser (Использовать Fenom на страницах) значение Да

Компонент pdoTools имеет системную настройку pdotools_elements_path со значением по умолчанию {core_path}elements/. Что ж, соответственно, создаём папку elements/ (если ещё не создана) в папке core/ движка и внутри создаём следующую структуру папок:
elements/
|-- common/
    |-- plugins/
|-- dev/
    |-- chunks/
    |-- plugins/
    |-- snippets/
    \-- templates/
|-- web/
    |-- chunks/
    |-- plugins/
    |-- snippets/
    \-- templates/
\-- plugins/

Собственно разработка ведётся в папке dev/ при этом действует важное правило для всей команды: «Никто и никогда не должен проводить никакие правки в папке web/». При деплое всё содержимое папки dev/ копируется в web/ с помощью Gulp. Папка common/ содержит общие для обоих контекстов файлы, например, плагин переключающий текущий контекст, описанный выше.

Такая структура папок позволяет при подключении внешних файлов при помощи шаблонизатора Fenom использовать значение плейсхолдера [[*context_key]] примерно так
{set $ctx = $_modx->resource.context_key}
{include "file:$ctx/chunks/common/head.tpl"}

В первой строке мы складываем в переменную $ctx ключ текущего контекста (dev или web), а во второй строке используем это значение в пути к файлу. Это и позволяет иметь две версии сайта на одном движке.


прим. Подключения чанков в файле шаблона

Помимо файлов, содержащих основные элементы системы, нам также нужно вести контроль версий файлов вёрстки и js. Как правило, эти файлы расположены в папке assets/ следующим образом.
assets/
|-- css/
|-- js/
\-- img/

По старой схеме предлагаю создать следующую структуру
assets/
|-- dev/
    |-- css/
    |-- js/
    |-- img/
    \-- scss/
|-- web/
    |-- css/
    |-- js/
    \-- img/

Работая с файлами вёрстки и скриптов следует учесть следующий момент. Например, в папке dev/js/ может лежать несколько файлов .js (к примеру, calendar.module.js и search-form.module.js), но при деплое в продакшн эти файлы следует объединить в один и минифицировать (например, main.bundle.min.js), так что в чанках нельзя просто взять и вписать в путь файла необходимое звено [[*context_key]], следует поступить примерно следующим образом и развернуть такую конструкцию
[[*context_key:is=`dev`:then=`
  <script src="[[++assets_url]][[*context_key]]/js/calendar.module.js"></script>
  <script src="[[++assets_url]][[*context_key]]/js/search-form.module.js"></script>
  <!-- остальные скрипты -->
`]]
[[*context_key:is=`web`:then=`
  <script src="[[++assets_url]][[*context_key]]/js/main.bundle.min.js"></script>
`]]



Связываем ресурсы с шаблонами


Шаблоны создаём в папке core/elements/dev/templates/, в которых подключаем чанки из папки core/elements/dev/chunks, используя синтаксис шаблонизатора Fenom. При этом, создавая файл шаблона, мы также должны создать соответствующий ему шаблон в админ-панели. Только мы не будем создавать статический шаблон, так как не можем указать конкретный путь к файлу, потому что он зависит от контекста, в котором шаблон используется. Вместо этого в теле шаблона мы пропишем одну единственную строку
{include 'file:[[*context_key]]/templates/base.tpl'}

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


прим. Подключение файла шаблона

Создаём плагины


При таком подходе разработки следует отдельно оговорить внедрение в систему новых плагинов.
Аналогично ситуации с шаблонами, плагины создаём в папке core/elements/dev/plugins/, в которых пишем логику работы плагина. Далее, заходим в админ-панель и создаём соответствующий ему плагин (НЕ статический!), в котором подключаем файл плагина через include следующим образом
<?php
$filepath = 'plugins/somePlugin.php';
$context = $modx->context->get('key');
$plugin = $modx->getOption('pdotools_elements_path') . "$context/$filepath";
if (file_exists($plugin)) {
    return include $plugin;
}

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


Деплой


Вот мы и подошли к самому ответственному моменту. Выкатываем тестовую версию сайта на продакшн. Предварительно необходимо выполнить кое-какие действия с репозиторием на сервере, а именно создать SSH ключ, зарегистрировать его и прописать в настройках репозитория на GitHub. Описывать здесь этот процесс я не буду. Ссылка на иснтуркцию по генерации и регистрации ключа я оставлю внизу.
Итак, город засыпает, разработчики прекращают свою работу, все файлы закоммичены и запушены в удалённый репозиторий. Просыпается тимлид, его задача — максимально быстро выкатить всю произведённую работу в релиз. Предлагаю такой порядок действий.
  1. На своей локальной машине нужно сделать синхронизацию файлов с удалённым репозиторием, чтобы убедиться, что в локальном репозитории все файлы в актуальном состоянии.
  2. Создаём метку, например «v1.0.666-pre», обозначая таким образом коммит, предшествующий сборке версии v1.0.666, для быстрого и удобного отката в случае неудачного деплоя.
    $ git tag v1.0.666-pre

  3. С помощью Gulp собираем новую версию проекта.
    $ gulp modx:build

    Приводу пример gulp файла. Здесь я описал лишь одну задачу для копирования файлов из core/elements/dev в core/elements/web/, остальные таски, наверняка, сможете написать и сами.
        'use strict';
    
        var gulp = require('gulp');
    
        var paths = {
            dist: {
                //  output
                js: 'assets/web/js/',
                css: 'assets/web/css/',
                img: 'assets/web/img/',
                fonts: 'assets/web/fonts/',
                modx: 'core/elements/web/'
            },
            src: {
                //  sources
                js: 'assets/dev/js/**/*.js',
                style: 'assets/dev/sass/style.scss',
                img: 'assets/dev/img/**/*.*',
                fonts: 'assets/dev/fonts/**/*.*',
                modx: 'core/elements/dev/**/*.*'
            },
            watch: {
                //  files watch to
                js: 'assets/dev/js/**/*.js',
                style: 'assets/dev/sass/**/*.scss',
                img: 'assets/dev/img/**/*.*',
                fonts: 'assets/dev/fonts/**/*.*',
                modx: 'core/elements/dev/**/*.*'
            }
        };
    
        gulp.task('modx:build', function () {
            return gulp.src(paths.src.modx)
                .pipe(gulp.dest(paths.dist.modx))
        });
        

  4. В результате сборки будут добавлены новые или изменены старые файлы в папках /assets/web/ и /core/elements/web/. Делаем коммит, назначем ему метку и делаем push с флагом --tags, чтобы отправить в удалённый репозиторий созданные метки.
    
        $ git add . 
        $ git commit -m "Build v1.0.666" 
        $ git tag v1.0.666
        $ git push --tags
        

  5. Через SSH подключаемся к серверу и переходим в корневую директорию сайта, подтягиваем данные из удалённого репозитория и делаем hard reset, чтобы привести все файлы сайта к тому виду, в котором они хранятся в репозитории.
    
        $ git fetch --all
        $ git reset --hard origin/master
        

  6. Смотрим результат в продакшене. Если я правильно представляю как устроен мир разработки и программирования, то примерно в 10 из десяти случаев что-то пойдёт не так. Поэтому переходим обратно в консоль, выполняем команду
    $ git checkout v1.0.666-pre

    т.е. переводим продакшн-файлы в состояние до сборки и начинаем разбираться что не так.



прим. Пример дерева коммитов.

Выше описанное относится к деплою шаблонов, чанков и сниппетов, а также файлов вёрстки, но что же у нас с ресурсами?

А что с ресурсами?


Рассмотрим такую задачу. На сайте есть раздел Новости, со временем количество публикаций стало очень большим и было принято решение создать новый раздел сайта Архив новостей.

В dev контексте вы создаёте соответствующий контейнер, добавляете пару экземпляров архивных новостей, создаёте чанки, если нужно шаблон и т.п. В результате на домене dev.example.com появляется новый раздел. После одобрения заказчиком, вы производите деплой, описанный выше, но нового раздела на продакшене, конечено, не появится, хотя все файлы будут приведены к необходимому виду. Конечно, это произойдёт, потому что созданный раздел (имеется в виду совокупность созданных ресурсов) будет находиться в контексте dev и не будет доступна в контексте web.
— И что делать?
— Копипаст, товарищи, увы.
А точнее перенос. Мы просто переносим созданный раздел в контекст web, а после повторно создаём его копию в dev, чтобы структура dev-версии сайта соответствовала продакшену.
Да, полностью избавиться от копипаста у меня не получилось.


Заключение


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

Шаблон для разработки, созданный по ходу написания статьи, доступен на Github https://github.com/vanyaraspopov/start-code-modx.


Ссылки


pdoTools. Парсер
pdoTools. Файловые элементы
Синтаксис Fenom
Переключение контекстов в зависимости от URL
Contexts | MODX Revolution
Using One Gateway Plugin to Manage Multiple Domains
Настройка IDE PHPStorm
Приятная сборка frontend проекта
8 двухколёсных советов по MODX Revolution
Generating a new SSH key and adding it to the ssh-agent
GitHub. vanyaraspopov/start-code-modx
Tags:
Hubs:
Total votes 9: ↑9 and ↓0+9
Comments10

Articles