Улучшаем процессы разработки в Modx Revo: работа с git, деплой с gulp, тестовые окружения

май 2 , 2016

Улучшаем разработку в Modx RevoДавно не писал статьи про мою любимую CMS Modx Revo, а зря. Яндекс.метрика убедительно говорит, что статьи на эту тему пользуются большой популярностью у посетителей блога. Сегодня сей пробел частично исправлю и хочу поговорить вот о чем. К сожалению или к счастью, одной из главных фишек Modx является тот факт, что практически все содержимое сайта, включая, ресурсы, шаблоны, сниппеты, чанки и прочее-прочее хранится в mysql-базе. Из этого вытекают и плюсы, и минусы, но мне минусов видится больше.
Основные претензии: работа с git в Modx, настройка рабочего окружения, конфиги для боевых и тестовых серверов, деплой и встроенные редакторы кода
Итак, в этой статье я покажу, как я разбирался с этими пунктами, что удалось сделать успешно и какие моменты не получилось реализовать до конца. Добро пожаловать в статью и комменты!


Подробнее о том, что нас не устраивает в Modx

Главный минус - это непонятность, а кто-то утверждает, что и невозможность подружить эту cms с любой системой контроля версий. В cms тупо нет "девелоперских" файлов - все в таблицах БД! Не знаю, как остальным modx-коллегам, но мне совершенно не хочется вести разработку без привычного git-а. Сколько я не яндексил по интернетам, так и не нашел адекватного описания, как работать с git в Modx.
Вторая проблема - это неудобство деплоя на тестовый или боевой сайт. Вы можете сказать, что можно каждый раз делать дамп всей базы и заливать ее через условный phpMyAdmin, а после копировать все папки-файлы целиком по ftp, но удовольствие довольно сомнительное. Первый раз это все равно придется делать, но для послерелизных мелких правок не хочется возиться с дампами базы, а хочется сделать что-то вроде gulp deploy из командной строки и перезалить сайт без обновления базы.
Третья проблема, это уже мои личные тараканы, но Modx-ские редакторы кода далеки от идеала. Относительно неплохой редактор кода Ace, не говоря уже о предустановленном редакторе кода, не позволяет делать кучу вещей, к которым быстро привыкаешь в любимом sublime или phpStorm.


Основная идея сего повествования

Делаем каждый элемент Modx статичным!
Да, именно так. Если git не идет к Modx, то пусть сам Modx идет к git.
Для тех, кто не в курсе: в Modx есть возможность каждый элемент (шаблон, чанк, сниппет) сделать статичным. Это означает, что содержимое этого элемента хранится во внешнем файле, а не в таблицах БД. В настройках есть скромная галочка Статичный, включение которой позволяет выбрать файл для хранения кода этого элемента.
Я не знаю, зачем разработчики Modx ввели эту штуку, но именно ей мы воспользуемся для организации дружбы с git (хм, а может, для этого и ввели создатели Modx статичные элементы?..)

Итак, идея озвучена, теперь подробнее, как мы ее будем реализовывать и что из этого получится.


Структура проекта Modx Revo.

Допустим, мы создаем сайт webdevkin.ru.

Первое: после установки Modx в корне проекта помимо стандартных папок assets, core, connectors и manager создаем папку modx. Это незатейливое название говорит нам, что именно в ней мы будем хранить код для шаблонов, чанков и сниппетов. А в папке modx создадим еще 3 папочки: templates, chunks и snippets. Дабы не сваливать файлы в одну кучу. Также в корне заведем папку scripts, где будем хранить внешние php-файлы. Думаю, практически на каждом сайте парочка таких php-скриптов да есть. Они потихоньку дергаются с клиента аяксом и делают что-то полезное, например, отправляют сообщение владельцу сайта с формы обратной связи.

Второе: создаем в каталоге assets папку templates, а в ней папку webdevkin. webdevkin - это название шаблона для нашего сайта. В webdevkin лежат такие папки: css, js, fonts, img. Назначение их, думаю, понятно. Таким образом, вся статика для сайта лежит в одном месте - в папке assets/templates/webdevkin


Вопрос: а как создавать чанки, шаблоны и сниппеты

Ответ: создаем как обычно, в админке Modx. После создания отмечаем галочку Статичный и выбираем файл, в котором будет храниться содержимое элемента. Не забываем, для склада этих файлов мы и создали папку Modx. Я предпочитаю для чанков и шаблонов создавать файлы .tpl, для сниппетов .php. Как удобно, так и делайте, хоть храните в .txt все подряд. После создания файла мы можем работать с ним не в админке Modx, а в привычном редакторе кода.

По сути, основная идея уже расписана, базовая структура проекта у нас создана, как создаются элементы, разобрали. Дальше пойдут детали. Но прежде чем перейти к настройке git, рассмотрим такой небольшой момент, как кеширование.


Отключение кэширования в Modx

Про проблемы кэширования рассказано уже много и всем, кому не лень. Даже на нашем сайте есть статья про кэширование в Modx Revo.

Как задевает кэширование нас именно сейчас? Когда мы пишем код какого-то элемента в админке Modx и нажимаем Сохранить, то, как правило, кэш очищается и мы сразу можем обновить страницу и полюбоваться результатом. Конечно, если мы не сняли специально галочку Очистить кэш при сохранении. Если же мы создаем код по нашей схеме, то Modx не понимает, в какой момент мы сохраняем сниппет в условном sublime и соответственно, не очищает кэш. Вы обновляете страницу и не видите никаких изменений: все взято из кэша, сохраненного ранее. Как быть? Не лезть же в админку Modx вручную очищать кэш после каждой правки какого-то чанка? Нет, не для такого мы все это замутили.
Нужно просто отключить кэширование ресурсов в Modx на время разработки.
Важно: только на время разработки!
Оставить сайт на Modx работать без кэширования - это страх и ужас, modx-товарищи, полагаю, хорошо понимают, о чем речь :-)
Самый простой и надежный способ временно отключить кэширования с помощью гугла отыскался такой:

  • 1. Создаем плагин DisableCache
  • 2. Добавляем в него такой кодик
  •             global $modx;
                $modx->documentObject["cacheable"] = 0;
            
  • 3. Вешаем этот плагин на событие OnLoadWebDocument
  • 4. Profit!
Теперь каждый раз документ будет рендериться заново, не залезая в кэш - то, что нам и нужно.


Git и Modx

Я не буду рассказывать, как установить git, как провести инициализацию и прочее - это тема для отдельной статьи (впрочем, вот она). Думаю, соратники по сайтостроительству, привычные к гиту, хорошо представляют, что нужно делать, но одну интересную вещь стоит показать: файлик .gitignore именно для Modx.

    /.idea
    /webdevkin.sublime-project
    /webdevkin.sublime-workspace

    /node_modules
    
    components
    /connectors
    /core
    /manager
    /setup
    
    custom.log
    error.log

Разбираем: .idea и .sublime-* - служебные файлы редакторов phpStorm и sublime (работаю и с тем, и другим) node_modules - папка для npm-модулей, как минимум, модуль для деплоя, разберем чуть позже. Дальше идут 5 служебных папок Modx. components указываем без /, потому как подразумеваем папку /assets/components/ И 2 файла с логами, хотя можно и закинуть их в папку logs.

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

Я привел самый простой вариант, когда стандартной поставки Modx вполне хватает для разработки, нам не требуется создавать никакие дополнительные файлы и папки. В реальной жизни структура проекта чаще всего разветвленнее, но Вы наверняка по этому примеру сможете настроить .gitignore под свои нужды.


Деплой сайта по ftp с помощью gulp

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

UPDATED: если Вы интересуетесь, что можно хорошего сделать с gulp еще, предлагаю ознакомиться со статьей Сборка приложения Backbone + Require.js с помощью gulp

Для настройки задачи деплоя нам понадобятся следующие модули: сам gulp, gulp-util (где он только не нужен) и, главное, vinyl-ftp. Содержимое gulpfile.js таково (мы рассматриваем только одну задачу деплоя нашего сайта на боевой сервер)

    'use strict';
    
    var gulp = require('gulp'),
        gutil = require('gulp-util'),
        ftp = require('vinyl-ftp');
    
    // Файлы для копирования по ftp
    var globs = [
        'assets/templates/webdevkin/**/*.*',
        'scripts/**/*.php',
        'modx/**/*.*'
    ];
    
    // Соединение с ftp
    function getConn() {
        return ftp.create({
            host: '111.222.333.444',
            user: 'ftp_user',
            pass: 'ftp_pass'
        });
    }
    
    gulp.task('deploy', function () {
        var conn = getConn();
        return gulp.src(globs, {base: '.', buffer: false})
            .pipe(conn.dest('/path_to_your_files'))
            .pipe(gutil.noop());
    });

Подробнее.
В начале мы подключаем 3 нужных для работы модуля. Дальше указываем в массиве, что мы хотим заливать на боевой сайт.

  • 1. Статика (css, js, img, fonts)
  • 2. Скрипты php, если они у вас есть, конечно
  • 3. Чанки, шаблоны, сниппеты из папки modx
Функция getConn() создает соединение с ftp-сервером, указываете в параметрах настройки Вашего ftp. Задача "deploy" выполняет само копирование файлов.
Если все настроено правильно, начиная с установки самого gulp, то по запуску gulp deploy из командной строки в корне проекта на Ваш боевой сайт скопируются все нужные файлы. И больше не нужно будет создавать дамп локальной базы, заходить в phpMyAdmin боевого сайта и восстанавливать всю базу ради правки трех шаблонов. Единственно, нужно помнить о кэше и сбрасывать его каждый раз после процедуры деплоя. Можно делать это в админке Modx боевого сайта, лично меня не напрягает, или покопаться в плагинах того же gulp - наверняка есть плагин, позволяющий очищать кэш Modx, а именно содержимое папки core/cache. У Вас есть какие-то идеи по этому поводу? С удовольствием почитаю в комментариях.


Настройка разных окружений в Modx.

Что я имею в виду под этой громкой фразой?
Чаще всего в разработке на Modx мы имеем дело как минимум с двумя средами - локальная машина и хостинг клиента, то есть боевой сайт. Каждый раз при заливке файлов на сайт и копированию обратно на свой компьютер нужно не забывать менять в конфигах настройки доступа к mysql, а также пути к корневой папке проекта. На хостинге это выглядит примерно как /home/user1234/site.ru/www/, на своем компьютере - c:/projects/www/site.ru/ И соответственно, настройки БД тоже разные. И это в простом случае, если мы деплоим с локальной среды сразу на рабочий сайт. Также приходится предварительно проверять функционал на тестовом сервере, настройки которого опять-таки отличаются от вышеуказанных.
Конечно, можно один раз создать 3 варианта файлов настроек для всех окружений, разместить их на каждом из испльзуемых серверов и копировать файлы сайта только нашим способом, который мы разобрали выше. Но иногда все-таки приходится перезаливать сайт целиком и нужно каждый раз не забывать менять все 4 файла конфига:

  • /config.core.php
  • /manager/config.core.php
  • /connectors/config.core.php
  • /core/config/config.inc.php

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

Посмотрим на первые 3 файла. Практически у всех они совпадают до единого пробела - в них указывается путь к ядру Modx, примерно так

    define('MODX_CORE_PATH', 'c:/www/site.ru/core/');
    define('MODX_CONFIG_KEY', 'config');

Кстати, буду весьма благодарен читателям, ежели подскажете неразумному, по каким причинам был вообще создан этот файл да еще и скопипащен в трех экземплярах. Если Вам нужно в константе MODX_CORE_PATH указать путь к папке core, то можно это сделать и так

    $rootPath = $_SERVER['DOCUMENT_ROOT'];
    if ($rootPath[strlen($rootPath) - 1] != '/') {
        $rootPath = $rootPath . '/';
    }
    define('MODX_CORE_PATH', $rootPath . 'core/');
    define('MODX_CONFIG_KEY', 'config');

Что поменялось? Да ничего, кроме того, что мы не прописываем путь к корню проекта руками, а вытаскиваем его из переменной $_SERVER['DOCUMENT_ROOT']. Условие с добавлением слэша в конец пути добавлено на всякий случай. Некоторые веб-серверы добавляют этот слэш, некоторые нет (сталкивался с таким пару раз). А нам этот слэш категорически нужен для правильного формирования полного пути к core/

Таким образом, заменив содержимое всех трех файлов config.core.php на вышеприведенный код, нам становится совершенно по барабану, на каком хостинге или машине располагается наш проект. Путь к папке core всегда будет определяться точно.

А теперь давайте посмотрим на более интересный файл core/config/config.inc.php Там кроме кучи путей к разным папкам и хостам содержатся настройки к базе данных. Выглядит это примерно так:

    /**
     *  MODX Configuration file
     */
    $database_type = 'mysql';
    $database_server = 'localhost';
    $database_user = 'user_siteru';
    $database_password = 'password_siteru';
    $database_connection_charset = 'utf8';
    $dbase = 'db_siteru';
    $table_prefix = 'modx_';
    $database_dsn = 'mysql:host=localhost;dbname=db_siteru;charset=utf8';

    ...

    if (!defined('MODX_CORE_PATH')) {
        $modx_core_path= '/home/site.ru/public_html/core/';
        define('MODX_CORE_PATH', $modx_core_path);
    }
    
    ...
    // Здесь еще десяток аналогичных условий

И каждый раз при деплое всех файлов сайта приходится шерстить этот конфиг и менять в нем все настройки под текущее окружение. Конечно, вопрос внимательности, но не все программисты роботы, могут и ошибиться в одной букве и долго разбираться, почему сайт отказывается работать. Особенно весело бывает в случаях разворачивания проекта на site.ru и test.site.ru. Часто доступ к базе один и тот же, а вот название самой базы, естественно, различается. При частых деплоях забыть об этом совсем немудрено. Как же это исправить?

Нам нужен способ определять текущее окружение разработки: локальный компьютер, тестовый сервер и боевой сайт. Самое простое, что приходит в голову - это определять окружение через тот же $_SERVER['DOCUMENT_ROOT']. Например, на локальном компьютере путь включает в себя projects/www/site, на боевом - siteru/public_html, а на тестовом - siteru/test. Посмотрим на код:

    /**
     *  MODX Configuration file
     */
    
    // Если константы не определены, то задаем их
    // Проверка на наличие константы важна, так как файл конфига может подключаться несколько раз
    if (!defined('CONFIG_DBSERVER')) {
    
        // Определяем окружение
        $env = 0; // по умолчанию локальное окружение site.lc
        if (strpos($_SERVER['DOCUMENT_ROOT'], 'siteru/public_html') !== false) {
            $env = 1; // боевой сайт site.ru
        }
        if (strpos($_SERVER['DOCUMENT_ROOT'], 'siteru/test') !== false) {
            $env = 2; // тестовый сервер test.site.ru
        }
    
        // Настройки для локального окружения
        if ($env === 0) {
            DEFINE('CONFIG_DBSERVER', 'localhost');
            DEFINE('CONFIG_DBUSER', 'root');
            DEFINE('CONFIG_DBPASSWORD', 'root');
            DEFINE('CONFIG_DBNAME', 'siteru');
            DEFINE('CONFIG_PATH', 'C:/projects/www/site/');
            DEFINE('CONFIG_HOST', 'site.lc');
        }
        // Настройки для боевого сайта
        if ($env === 1) {
            DEFINE('CONFIG_DBSERVER', '111.222.333.444');
            DEFINE('CONFIG_DBUSER', 'user_siteru');
            DEFINE('CONFIG_DBPASSWORD', 'password_siteru');
            DEFINE('CONFIG_DBNAME', 'db_siteru');
            DEFINE('CONFIG_PATH', '/home/siteru/public_html/');
            DEFINE('CONFIG_HOST', 'site.ru');
        }
        // Настройки для тестового сайта test.site.ru
        if ($env === 2) {
            DEFINE('CONFIG_DBSERVER', '111.222.333.444');
            DEFINE('CONFIG_DBUSER', 'user_test_siteru');
            DEFINE('CONFIG_DBPASSWORD', 'password_test_siteru');
            DEFINE('CONFIG_DBNAME', 'db_test_siteru');
            DEFINE('CONFIG_PATH', '/home/www/siteru/test/');
            DEFINE('CONFIG_HOST', 'test.site.ru');
        }
    }
    
    $database_type = 'mysql';
    $database_server = CONFIG_DBSERVER;
    $database_user = CONFIG_DBUSER;
    $database_password = CONFIG_DBPASSWORD;
    $database_connection_charset = 'utf8';
    $dbase = CONFIG_DBNAME;
    $table_prefix = 'modx_';
    $database_dsn = 'mysql:host=' . CONFIG_DBSERVER . ';dbname=' . CONFIG_DBNAME . ';charset=utf8';
    
    // ...
    
    if (!defined('MODX_CORE_PATH')) {
        $modx_core_path = CONFIG_PATH . 'core/';
        define('MODX_CORE_PATH', $modx_core_path);
    }
    
    // ...

Как видно из кода, сначала мы определяем настройки базы, хост и путь к корневой директории, а затем используем полученные данные для задания констант Modx. Приведен только один пример для MODX_CORE_PATH, остальные задаются аналогично. Специально не привожу код всего файла, так как настройки в разных версиях cms могут отличаться. Используя такой файл конфига, мы легко можем добавлять новые окружения, сколько угодно перезаливать сайты на боевые и тестовые сервера с локальной машины и в обратную сторону, не боясь, что по невнимательности забудем поправить какую-то настройку.


Подводим итоги

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

  • 1. Как упростить работу с кэшем? (очистка кэша после деплоя)
  • 2. Главный вопрос: как создавать чанки, шаблоны и сниппеты на всех окружениях не вручную? Ведь все равно эти элементы нужно заводить в базе и создавать для каждого окружения руками. Неудобство такое есть, оно сказывается при частом создании новых элементов.
  • 3. Можно ли улучшить определение окружения, делать это не через $_SERVER['DOCUMENT_ROOT'], а каким-то более элегантным способом?
У Вас есть предложения по этому поводу или можете поделиться своим опытом обустройства работы с Modx?
Напишите все, что думаете, в комментариях!

Заходите в группу в контакте - https://vk.com/webdevkin
Анонсы статей, обсуждения интернет-магазинов, vue, фронтенда, php, гита.
Истории из жизни айти и обсуждение кода.
Как Вам статья? Оцените!