Организация кода в Битрикс. Часть 3. Переделываем все к черту и делаем еще лучше!

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

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

Ранее и сейчас, весь код проекта на Битрикс хранился исключительно внутри DOCUMENT_ROOT. Это, конечно, прозрачно и понятно, однако не имеет никакого смысла хранить весь код именно там. Причин тому несколько:

  • Этот код будет доступен из веб (в случае с битриксом, это вполне вероятно, если за этим специально не следить)
  • Есть всякие другие интересные вещи, которые не имеет смысла хранить в DOCUMENT_ROOT, например логи, конфиги, сторонние библиотеки

Рассмотрим на примере. Назовем наш абстрактный сайт site.ru. Ранее он хранился бы где-нибудь в /var/www/site.ru и это был бы его DOCUMENT_ROOT. Но в реальности правильнее было бы создать еще один уровень вложенности и переместить корень туда. В нашем примере это может быть, например, /var/www/site.ru/htdocs.
Что в реальности это дает?

Во-первых, логи

Вы можете создать директорию с логами. /var/www/site.ru/log. В этой директории можно хранить логи веб-сервера, а также логи самого проекта. Благодаря этому, вы можете быстро с помощью консоли получить доступ к нужным логам. В директории с логами можно организовать любую удобную структуру. Например, для логов веб-сервера использовать такие имена директорий, как apache2 и nginx (как в /var/log). Для каждого своего сервиса создать отдельную директорию с логами. Кстати, если кто-то еще пишет свои собственные классы с логами, то примите к сведению, что есть такая мега-удобная и гибкая штука, как monolog, а также его адаптация под битрикс от BEX. Этот логгер полностью соответствует стандарту логирования PSR-3.
Удобно.

Во-вторых, зависимости.

Пропадает необходимость хранить библиотеки composer в /local директории битрикса. Само по себе хранение зависимостей внутри /local как-то противоречит самой идее данной директории, т.к. в ней должен находиться только версионируемый код разработчиков. Да и обычно все зависимости размещаются непосредственно начиная от корня проекта, но в случае с битриксом это может превращаться в неудобство.
Если же мы переносим зависимости из /local в корень проекта (а не DOCUMENT_ROOT), то мы сразу убиваем двух зайцев, удовлетворяя обычным практикам использования composer, а также более правильно следуем самой идее директории /local.
Таким образом, получается, что composer.json, composer.lock и директория /vendor будут располагаться в /var/www/site.ru. И эти файлики не будут беспокоить заказчика, если он дотошный. А чтобы поставить зависимости, достаточно зайти в корень проекта и запустить команду установки, все логично и очевидно:

Удобно!

В-третьих, статика.

Прошлый пост этой серии я закончил на том, что у меня осталась нерешенной проблема со статикой. На момент написания этих строк, проблема со статикой почти решена. Пока что я пришел к выводу, что нет пока более удобного инструмента для сборки статики, чем webpack. Мы постепенно внедряем его в наши проекты, однако его конфигурация, а также все зависимости npm хранятся также внутри DOCUMENT_ROOT, что не является целесообразным.

В данном же случае, вся конфигурация, все исходники статики, а также зависимости, необходимые для организации процесса сборки переносятся за пределы DOCUMENT_ROOT. А сам процесс сборки можно настроить таким образом, чтобы файлы сборки попадали в итоге в DOCUMENT_ROOT в какую-нибудь директорию /assets.

В-четвертых, версионный контроль.

Я надеюсь, что ни для кого из вас не секрет, что хранить системные файлы репозитория внутри DOCUMENT_ROOT не очень хорошо. Обычно, достаточно вынести репозиторий на уровень выше, и слинковать его с рабочей копией с помощью опции worktree. Однако это лишает репозиторий возможности использовать некоторые фишки, например stash, или submodules (будь они прокляты! не используйте их 🙂 ), и еще некоторых возможностей. Хорошо, если на продакшене это не нужно, но мне бы хотелось иметь полный контроль над репозиторием. В связи с этим, я в последнее время переношу репозиторий в корень рабочей копии, и закрываю директорию /.git с помощью .htaccess, например.

Если же мы используем предложенную схему, то рабочей копией репозитория можно сделать директорию /var/www/site.ru, и не придется ничего мудрить.
Крутяк!

В-пятых, да придумать то можно что угодно!

Хотите хранить бекапы — можно вынести их на новый уровень вложенности. Хотите какие-то консольные скрипты — выносите их туда же. Юнит тесты — да легко! Любые конфигурационные файлы — всегда пожалуйста: composer.json, package.json, bower.json, webpack.config.js, phpunit.xml.dist, travis.yml — к вашим услугам, не надо это хранить в DOCUMENT_ROOT. Можно и кеши какие-то свои вынести на этот уровень. Да все, что душе угодно.

Таким образом, получается нечто такое (серым помечены исключенные из версионного контроля вещи, а структура начинается с /var/www/site.ru):

file_structure

 

Немного по модификациям того, о чем я писал ранее.

Автозагрузка классов

Ранее я писал, что для автозагрузки использую свой класс. Однако это совсем не обязательно. Поскольку composer сейчас является стандартом де-факто для любого проекта, то он содержит в себе очень гибкий автозагрузчик, который в пару строк организует подключение нашей папочки classes. Достаточно написать в composer.json секцию автозагрузки:

Ну и остается следовать стандарту PSR-4, тогда автозагрузчик будет прекрасно работать в тандеме со всеми вашими зависимостями.

Ajax

К черту /local/ajax. Самым правильным решением будет организация единого API. Например, для внутреннего API использовать путь /api/internal. Создавать директорию под это дело совсем не обязательно, лучше обойтись без лишних папочек, несмотря на всю файловую природу битрикса.

Cron

К черту /local/cron :). Не надо плодить отдельные скрипты для каждой задачи. Используйте symfony/console или console jedi (который основан на symfony/console) для создания консольных команд. А на крон уже цепляйте созданные задачи, а не выполнение конкретных скриптов.

 

UPD. Нашелся, все же, небольшой косячок у всей этой структуры. В PHPStorm $_SERVER[‘DOCUMENT_ROOT’] резолвится в корень проекта. А тут корень проекта не будет соответствовать корню виртуального хоста на сервере. Из-за этого многие инклюды в битриксе не будут резолвиться корректно при просмотре их в IDE. Но это, на мой взгляд, очень несущественная и маленькая проблема, которая полностью покрывается всеми теми плюсами, которые дает предложенная структура.

  • Viktor Tretyakov

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

    • Интересный вопрос.
      Исходя из моего опыта, я вижу проблему только в шаблонизации. Современные фронтендщики собирают верстку из кусочков также, как и мы с вами. Во фронтенде рулит компонентный подход, и верстка все чаще собирается из исходников, написанных на jade например.
      Можно в принципе вкорячить jade в битрикс, но тогда придется заставлять фронтендера разрабатывать шаблоны так, чтобы они один к одному совпадали с будущими компонентами битрикс, а это может стать очень дорогим и нерентабельным. Да и может быть вообще не получится.
      Если говорить про стили и js, то это гораздо проще. Инструменты современных верстальщиков позволяют настроить сборку таким образом, чтобы тот же webpack складывал собранные файлы стилей и js туда, куда надо.
      Я не смогу дать ответ на ваш вопрос, и наверно в случае с битриксом будет очень сложно хранить код фронта и бекенда отдельно, а собирать его из разных репозиториев в один. Но если у меня появится позитивный опыт в этом вопросе, я обязательно им поделюсь в одной из следующих частей на тему организации файловой структуры проекта.

  • Viktor Tretyakov

    Здравствуйте, Михаил.
    А как Вы организовываете ajax? У Вас для каждого запроса свой файл? Или одна точка входа с маршрутизацией?

    • Мы внутри компании разработали под себя библиотеку, которая дает возможность организовать механизм единой точки входа. К ней же подключен и ajax
      Грубо говоря, запросы на адрес /ajax/component/blah:blah.blah/template/ запрашивают верстку компонента, а запросы на адрес /ajax/action/blah:blah.blah/action_name/ запрашивают метод компонента.
      Пользуемся уже с полгода — в восторге от этого подхода. Естественно можно и параметры передавать, и кроме/вместо верстки получать данные и т.д.

      • Ilya Nekrasov

        Интересная идея, но есть некоторые проблемы с безопасностью.
        Вы их как-то решаете? Вайтлист компонентов?

        • Все правильно. На текущем уровне реализации есть пока только список доверенных компонентов, к которым можно обращаться через /ajax/component. Пока есть только идеи о том, как реализовать возможность безопасной организации запросов ко всем компонентам, но до реализации пока дело не дошло. Есть еще ряд и других проблем, связанных с реализацией в ядре битрикса.
          Для метода, доступного через /ajax/action разработчик должен определить входной параметр типа AjaxRequest и вернуть в обязательном порядке AjaxResponse, чтобы метод смог корректно обработать ajax запрос. Но поскольку реализация ajax метода полностью лежит на плечах разработчика, то он уже должен думать о секьюрности запроса к этому методу

      • Михаил, а на github/packagist эту библиотеку не планируете выкладывать? А то ajax больная тема вообще.

        • Пока не планирую, сыровато еще. Можете в качестве решения использовать bbc.bitrix.expert, тоже вполне себе хороший вариант.

  • Zemledelec — profi@

    Михаил! куда пропали?
    очень нравится Вас читать .особенно про PhpStorm

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

  • Igor Bashko

    Здравствуйте, Михаил!
    Подскажите пожалуйста, какая должна быть структура, если мы хотим интегрировать Laravel, для того что бы реализовать дополнительный функционал, используя API Битрикса. Правильно ли будет если мы разместим Laravel не в корне сайта, а на одном уровне с сайтом?

    • Если вы хотите использовать API битрикса в своем laravel приложении, то вам нужно позаботиться, чтобы в DOCUMENT_ROOT вашего приложения (для laravel это директория /public) лежала директория /bitrix (или симлинк на нее) с его ядром, а также директория /upload, в которой битрикс хранит свои загружаемые файлы и всякий мусор вроде кеша отресайзенных изображений.

      В нужных местах своего приложения, где вы захотите использовать API битрикса, нужно будет подключить сам битрикс с помощью строки:
      <?php require_once ($_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php');
      Теоретически этого должно хватить. После подключения ядра вы сможете пользоваться API битрикса из веб-приложения.
      Для подключения ядра битрикса в cli потребуется немного больше танцев с бубном, но это тоже решаемо.

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

    • Maxym Klymenko

      здравствуйте, Игорь. Нашёл Ваш комментарий и у меня возник вопрос: Вам удалось связать между собой laravel и битрикс? потому что у меня сейчас такая же задача, но пока не получается 🙁

  • Ilya Nekrasov

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

    Пара мыслей:
    1) Был проект из двух сайтов на многосайтовости. Директорию htdocs назвал sites, а там уже две поддиректории с сайтами. Битрикс, локал и апплоад при этом выносятся симлинками на самый верхний уровень проекта (там где композер и т д). Удобно и понятно.

    2) Конфигурация. Одна из сотен больных тем Битрикса. Лично у меня течет кровь из глаз от if ($_SERVER[‘SERVER_NAME’] == ‘продакшн домен’) {} else {} в header.php которые я регулярно наблюдаю. Лично я эту проблему решил через dotenvphp. Сначала использовал от vlucas, потом понял что лучше написать свой облегченный. Если интересно — добро пожаловать на гитхаб.

    3) По фронтэнду точек зрения может быть много + им могут заниматься другие люди, им тоже должно быть удобно.
    У нас допустим ребята делают фронт в /html/ и он собирается через gulp в два места html и папку с шаблоном сайта (если он один). Итоговые файлы общие для всего сайта.
    Скрипты и стили в шаблонах компонентов я не юзаю (как впрочем и стандартные компоненты).
    Копаться в чудо-ядре битрикса и манкипатчить его через гоаоп я бы не подписался)

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

      Отвечая на ваши мысли:
      1. Я бы /local и /bitrix вынес в корень именно реальными директориями, а симлинки пробросил бы как раз в DOCUMENT_ROOT соответствующих сайтов. Так будет несколько удобнее, имхо. Об этом, конечно же, думал, но не так часто приходится работать с многосайтовыми конфигурациями, поэтому пока не рискнул описывать их. В следующих статьях этой серии обязательно коснусь этого вопроса.
      2. Конфигурация в битриксе — да … разделяю вашу боль. Если говорить конкретно о том, как отличить продакшен от хоста разработки, то мы тоже используем для этого переменные окружения. На хосте разработки принудительно для всех хостов выставлена переменная окружения APPLICATION_ENV=development, которая служит основанием для определения.
      Если говорить об остальных конфигах, то с появлением console-jedi стало возможным управление конфигами, которые заданы в /bitrix/.settings.php, не версионируя последним. Единственное неудобство в этом случае — нужно переинициализировать окружение каждый раз при смене конфига,
      Посмотрю на досуге ваш arrilot/dotenv-php
      3. Насчет сборки фронтенда — все фронтендеры планомерно движутся в сторону webpack. Да и backend движки тоже. Тот же Symfony отказался от Assetic в пользу вебпака. Я думаю — за ним будущее, и делаю на него ставку. Соответственно наших фронтедеров мы приучаем к вебпаку постепенно.
      В остальном да — тут все зависит от пожеланий, ведь процесс сборки все равно решается с помощью конфигов (gulpfile.js, Gruntfile.js, webpack.config.js), и если структура, имена директорий поменяются, то всегда можно внести правки в конфиг сборки.
      По поводу Go!AOP — я пока тоже не рискнул 🙂 есть лишь предположение, что можно было бы заставить ядро работать так, как нужно нам. Да — манкипатч. Но оно может быть вполне оправданно, ведь мы имеем дело с манкиCMS 🙂

      • Ilya Nekrasov

        Это тебе спасибо за статьи )
        «Я бы /local и /bitrix вынес в корень именно реальными директориями, а симлинки пробросил бы как раз в DOCUMENT_ROOT соответствующих сайтов» — я это и имел ввиду конечно. Обратное не имеет никакого смысла

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

  • Константин

    Большое спасибо за полезную статью, Михаил.
    Разбираюсь с bitrix и возникла задача реализовать возможность написания js-скриптов на es6 и стилей на sass.
    В случае использования webpack мы задаем entry-point, куда будут собираться наши скрипты. С другой стороны, рядом с каждым компонентом bitrix лежат файлы style.css и script.js. А в случае использования webpack мы должны вынести все скрипты, стили в отдельную папку frontend или что-то аналогичное, продублировать структуру bitrix компонентов и уже эти скрипты вручную подключать на страницах сайта.
    Немного смущает то, что раньше у нас скрипты/стили лежали сразу в компоненте bitrix, а сейчас лежат в другом месте. Могли бы вы рассказать, как вы решали эту задачу и вообще как дружили bitrix и webpack.

    • Если следовать идеологии webpack, то весь клиентский код нужно разрабатывать компонентным подходом. Это значит, что за каждый кусочек функциональности должен отвечать один «компонент».
      Эти самые вебпаковские компоненты вполне себе можно сравнить с компонентами битрикса. Однако … не все так просто.
      Мне пока не удалось подружить вебпак с битриксом окончательно. Чтобы сделать полностью нативную интеграцию, я решил зайти с двух сторон:
      Первая сторона — это сам вебпак. Нужно написать плагин или лоадер, который позволит резолвить имена компонентов в нормальный путь до них, чтобы написав require(‘bitrix:news.list:css’) иклюдился файл style.css нужного компонента.
      Вторая сторона — это сам битрикс. Все дело в том, что файлы style.css и script.js инклюдятся внутри ядра, и этот механизм не поддается внешней обработке. С помощью библиотечки Go!AOP можно обойти это ограничение.
      Когда мне удастся собрать все это воедино, то я поведаю об этом. Но сейчас пока webpack используем просто подключая все ресурсы из одной директории. Есть, к примеру, директория /assets/build. В этой директории находятся все собранные ресурсы. webpack-assets-plugin генерирует json файл со всеми entry-point, которые нужны для работы сайта. При разработке entry-point называем в соответствии с названиями роутов, и пишем простой класс, который при попадании на определенный роут подключает нужный файл статики + какой-то общий или основной файл. Пока так.