Abr@X@bra.ru
Правильный старт bitrix проекта
Правильный старт bitrix проекта

Правильный старт bitrix проекта

27.07.2016
2043
Считаем, что принято решение делать сайт на CMS и в качестве CMS был выбран битрикс. Дизайнер отрисовал картинку, верстальщик сделал html+css+js код и дальше начинает работу серверный программист. С чего начать проект? Как лучше организовать код? Об этом я хочу поговорить в данной статье. Здесь я не ставлю задачу объяснить каждую деталь подробно, не ставлю задачу показать прямо конкретные шаги, вроде выполните первое, выполните второе и т.д. Здесь я постараюсь обрисовать общую картину. Я хочу показать как должен быть устроен проект на битриксе с моей точки зрения. Если у вас есть какие то замечания, дополнения или уточнения, то пишите в комментарии. Было бы любопытно сравнить разные подходы к формированию структуры проекта.

Создание проекта

Начну с банальностей. Любой проект начинается с папки, так что создайте папку в любом удобном для вас месте. Эта папка будет корнем сайта, то есть если вы редактируете файл index.php в этой новой папке, то это все равно что вы редактируете файл /index.php на сайте (после синхронизации естественно). Ну это я думаю и так понятно. Если вы пользуетесь IDE, то можете создать новый проект через меню редактора, но суть та же останется — будет создана новая папка.

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


Далее, сразу после создания папки, пока папка пуста делайте из этой папки репозиторий для системы контроля версий. Зачем нужен контроль версий и что это такое вы можете прочитать тут. Я пользуюсь гитом (git), так что для старта репозитория просто выполняю git init в нужной папке. Вы можете создать репозиторий любым удобным для вас способом. Например можно поставить «tortoseGit» под винду, это визуальный интерфейс, он добавляет новые пункты в контекстное меню по правой кнопке мыши в папке. Мне он кажется довольно простым и удобным, просто кликая мышкой без консоли можно управлять репозиторием.

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

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

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

Автодополнение

В битриксе довольно много всякого функционала и соответственно много всяких API функций. Вызов API можно конечно делать и вручную, прямо писать каждый раз название функции, либо открывать документацию и копировать, но гораздо удобнее настроить автодополнение в IDE. Лично я пользуюсь phpstorm от jetbrains, поэтому покажу настройку автодополнения именно тут. Для настройки автодополнения нам понадобится папка /bitrix/modules/ в исходных кодах, то есть ставите где-то битрикс, активируете его, обновляете до последней доступной версии и скачиваете из поставленного битрикса папку /bitrix/modules/. Битрикс не обязательно должен быть активированным, от шифрования кода они уже давно отказались, так что я думаю пойдет /bitrix/modules/ даже от пробной версии, но честно говоря не проверял.

После того как скачаете папку /bitrix/modules/, откройте phpstorm. У вас будет примерно такая картинка перед глазами




Сделайте двойной щелчок мыши слева по строке «External Libraries». Откроется окошко добавления внешней библиотеки. Нажмите в этом окошке плюсик и укажите путь до скачанной папки /bitrix/modules/. После индексации папки, везде во всех файлах будет работать автодополнение по основным функциям. Будет работать переход по ctrl+клик на название функции, можно быстро посмотреть что именно происходит внутри конкретных функций ядра.

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

Верстка

Верстальщик как правило присылает результат в виде папки с html файлами и дополнительными подпапками типа /css/, /images/, /fonts/, /js/ и т.д. То есть собственно страницы сайта которые уже можно открывать в браузере и всякие стили и скрипты для этих страниц. Сразу же закиньте все присланное верстальщиком в корень проекта. Если в верстке используется gulp, grunt, sass, postcss и т.д. и вы верстку не планируете трогать, то достаточно закинуть скомпилированный проект, если планируете править верстку с использованием сборщиков проекта, то вы я думаю и так знаете что делать. Итого без сборщика проекта в корне у вас будет лежать папка /project_name или /verstka/ или еще что в которой лежат html файлы + все что в этих файлах используется. Это очень удобно, можно будет сразу присылать заказчику ссылку на верстку и ссылку на интегрированную в битрикс страницу, типа было и стало, ну либо самому сравнивать если вы собственный проект разрабатываете. Кроме того можно будет сразу брать нужные html куски из верстки не закрывая и не переключаясь из IDE. В дальнейшем, по завершении интеграции эту папку естественно стоит удалить.

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

О папке local

Вообще вся работа будет вестись в папке local. По большей части это «/local/templates», «/local/php_interface/», «/local/modules/» и «/local/components/». Да, иногда требуется править и страницы публичной части, то есть например /index.php, или /about/index.php, но там практически всегда будут только вызовы компонентов, бывают и особо сложные разделы где много чего прямо на публичных страницах приходится делать, но не часто.

Преимущество папки local в том, что вы точно знаете, что любой код который там лежит был написан именно под этот проект. В папке /bitrix/ сильно дофига служебных и просто не нужных файлов, всякие старые или демонстрационные шаблоны, dbconn.php который отвлекает от init.php и т.д.. В общем чтобы найти что-то в папке /bitrix/ придется приложить больше усилий, потратить больше внимания на каждую операцию. В системе контроля версий не нужно писать огромный .gitignore с перечислением всех служебных папок (попробуйте например добавить в контроль версий только /bitrix/php_interface/init.php и проигнорировать dbconn.php из этой же папки), мы просто весь /bitrix/ игнорируем, а весь /local/ храним в репозитории. Проще говоря папка local гораздо удобнее.

То есть для нового проекта обязательно создаем папку /local/. В ней вам гарантированно потребуется папка templates с шаблоном сайта.

Шаблон сайта

Для начала создайте папку с шаблоном /local/templates/template_name/, либо /local/templates/.default/ если она вам больше нравится. Далее смотрите папку с версткой и копируете оттуда вообще все за исключением html кода в папку с шаблоном. То есть из верстки нужно взять все стили, все скрипты, все картинки, все шрифты и т.д. и положить их в папку с шаблоном. Причем все относительные пути надо сохранять. То есть копируем с сохранением структуры. Если верстальщик грамотный, то ни одного абсолютного пути он не напишет, все картинки будут браться из папки «../images/», а не «/images/», то есть из рядом лежащей папки, вне зависимости от того на какой глубине они лежат.

Весь глобальный шаблон сайта на битриксе это по сути два файла header.php и footer.php в папке с шаблоном. Что должно быть в файле header.php на мой взгляд? Вот примерно такое

  1. <?if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED !== true) die();
  2. /**
  3. * @global CMain $APPLICATION
  4. * @global CUser $USER
  5. */
  6. $dir = $APPLICATION->GetCurDir();
  7. $page = $APPLICATION->GetCurPage();
  8. $show_left_sidebar = $APPLICATION->GetDirProperty("show_left_sidebar");
  9. $assets = \Bitrix\Main\Page\Asset::getInstance();
  10. ?><!DOCTYPE html>
  11. <html>
  12. <head>
  13. <meta charset="UTF-8">
  14. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  15. <?
  16. /**
  17. * CSS
  18. */
  19. $assets->addCss(SITE_TEMPLATE_PATH . '/css/reset.css');
  20. $assets->addCss(SITE_TEMPLATE_PATH . '/css/owl.carousel.css');
  21. $assets->addCss(SITE_TEMPLATE_PATH . '/css/owl.theme.css');
  22. $assets->addCss(SITE_TEMPLATE_PATH . '/css/owl.transitions.css');
  23. $assets->addCss(SITE_TEMPLATE_PATH . '/css/animate.min.css');
  24. $assets->addCss(SITE_TEMPLATE_PATH . '/css/jquery.formstyler.css');
  25. $assets->addCss(SITE_TEMPLATE_PATH . '/css/fancybox/jquery.fancybox.css');
  26. $assets->addCss(SITE_TEMPLATE_PATH . '/css/main.css');
  27. /**
  28. * JS
  29. */
  30. //VENDOR
  31. \CJSCore::Init(array('jquery'));
  32. $assets->addJs(SITE_TEMPLATE_PATH . '/vendor/owl.carousel.min.js');
  33. $assets->addJs(SITE_TEMPLATE_PATH . '/vendor/jquery.maskedinput.min.js');
  34. $assets->addJs(SITE_TEMPLATE_PATH . '/vendor/jquery.fancybox.pack.js');
  35. //LOCAL
  36. $assets->addJs(SITE_TEMPLATE_PATH . '/js/helpers.js');
  37. $assets->addJs(SITE_TEMPLATE_PATH . '/js/main.js');
  38. $assets->addJs(SITE_TEMPLATE_PATH . '/js/order.js');
  39. $assets->addJs(SITE_TEMPLATE_PATH . '/js/ajax.js');
  40. $assets->addJs(SITE_TEMPLATE_PATH . '/js/catalog.js');
  41. $assets->addJs(SITE_TEMPLATE_PATH . '/js/blog.js');
  42. /**
  43. * BITRIX ->ShowHead()
  44. */
  45. $APPLICATION->ShowMeta("robots", false);
  46. $APPLICATION->ShowMeta("keywords", false);
  47. $APPLICATION->ShowMeta("description", false);
  48. $APPLICATION->ShowLink("canonical", null);
  49. $APPLICATION->ShowCSS(true);
  50. $APPLICATION->ShowHeadStrings();
  51. $APPLICATION->ShowHeadScripts();
  52. ?>
  53. <title><? $APPLICATION->ShowTitle() ?></title>
  54. </head>

Практически на всех сайтах, которые меня просят доработать, в шаблоне я вижу либо старые варианты подключения скриптов и стилей $APPLICATION->AddHeadScript(‘src’), либо же вообще оставшееся от верстки подключение тэгами <link> и <script>. Между тем уже давно в битриксе есть штатное удобное подключение, получаем экземпляр класса \Bitrix\Main\Page\Asset и дальше функциями addJs() и addCss() подключаем скрипты и стили. Если скрипты и стили подключены в шаблоне сайта, то они объединятся в один файл template_random.css и template_random.js (если это включено в настройках конечно же). Если скрипты и стили тем же классом Asset подключаются в любом другом месте, то они будут добавлены в отдельный файл page_random.css и page_random.js. То есть общие для всех страниц файлы кешируются отдельно от скриптов и стилей нужных для конкретной страницы.

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

Пользоваться файлами style.css и script.js в папке с шаблоном компонента вообще не стоит. Да в целом это может быть удобно когда подключаешь компонент на странице, и автоматом подтягиваются все его стили, ни о чем больше думать не нужно. Однако для правок лазить по папкам всех шаблонов и искать эти кусочные стили дикий геморрой. Поэтому все стили для сайта должны лежать в папке с шаблоном, в подпапке /css/ например. Точно так же и со скриптами, все скрипты должны быть в папке /js/ шаблона. Название у папки может быть любое, главное чтобы не в корне сайта и не в папках с шаблонами компонентов.

Скрипты стоит подключать именно в файле header.php и именно функцией addJs(), даже если в верстке скрипты по модному вынесены в конец страницы. В битриксе, в настройках есть галочка которая переносит все скрипты в конец страницы, вне зависимости от того где они были подключены. Поэтому как то специально извращаться со скриптами не нужно. Гораздо удобнее помнить что вообще все внешние файлы, и css и js подключаются в файле header.php в тэге head.

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

Еще момент по функции ShowHead(). В последнее время все стали верстать в манере html5, и соответственно мета тэг кодировки отличается от битриксовского. Чтобы полностью соответствовать верстке я заменил функцию ShowHead() на то что она собственно содержит. Только убрал в этой функции вывод одного мета тэга. В принципе можно и полный meta charset вызывать, такой же как в функции $APPLICATION->ShowHead(), и тогда можно собственно саму эту функцию и вызывать.

Зависимые области

Если на каких то страницах есть боковая панель слева или справа от основного контента, а на каких то страницах этой панели нет, то на мой взгляд панель надо включать в основной шаблон сайта. Не нужно писать html код панели прямо на самой странице. Например, если боковая панель должна быть в разделе «О компании», папка /about/, то не нужно писать html код в файле /about/index.php, в этом файле должно быть только основное содержимое страницы. Это касается не только боковых панелей, но и вообще любых областей сайта которые отображаются и пропадают в зависимости от раздела. Все такие области условно можно назвать зависимыми областями.

Как именно отображать зависимые области? Если речь идет только о главной странице, то достаточно просто хардкодом прописать в глобальном шаблоне условие $APPLICATION->GetCurDir() == ‘/’. Если область должна как то более гибко отображаться, то можно использовать свойство раздела битрикса. Свойство страницы в данном случае не подходят, так как на момент выполнения файла header.php свойство страницы еще не определено, а вот свойство раздела уже есть. Достаточно в настройках модуля управления структурой прописать отдельное свойство под каждую область и в дальнейшем просто включать и отключать область в нужных местах. Если область по умолчанию должна отображаться везде кроме главной страницы, плюс отключена на нескольких выбранных страницах, то определяем свойство раздела show_area_name = Y в корне сайта, и во всех подразделах область будет отображаться, чтобы значение по умолчанию не влияло на главную страницу прописываем в глобальном шаблоне проверку и на свойство и на папку одновременно.

В качестве примера можете посмотреть код шаблона выше, там еще до объявления DOCTYPE у меня собрано свойство show_left_sidebar и текущая директория. Использовать эти собранные переменные можно так:

  1. <?if($show_left_sidebar == 'Y' && $dir != '/'):?>
  2. <div class="row">
  3. <div class="col-md-4 col-sm-6">
  4. <?
  5. $APPLICATION->ShowViewContent('leftSidebar');
  6. ?>
  7. </div>
  8. <div class="col-md-8 col-sm-6" id="article">
  9. <?endif;?>

Вместо вызова ShowViewContent() можно поставить какую нибудь включаемую область или сразу вызывать нужные компоненты.

Константы

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

Создайте файл /local/php_interface/init.php. Создайте файл /local/php_interface/include/constants.php. В файле /local/php_interface/init.php подключите файл /local/php_interface/include/constants.php через require или include. Таким образом файл констант у нас будет выполняться вообще всегда, при каждом открытии страницы. Что должно быть в файле с константами? Собственно сами константы. Объявляем константы так:

  1. <?php
  2. /**
  3. * IBLOCK IDs
  4. */
  5. define("IBLOCK_ID__CATALOG", 4);
  6. define("IBLOCK_ID__OFFERS", 11);
  7. define("IBLOCK_ID__VIDEO", 9);
  8. ...
  9. остальные используемые инфоблоки
  10. ...
  11. /**
  12. * PATHS
  13. */
  14. define('PATH_TO_BASKET', '/personal/order/make/');
  15. define('PATH_TO_ORDER', '/personal/order/make/');

И дальше подставляем константы например в вызов компонента:
  1. <?$APPLICATION->IncludeComponent(
  2. "bitrix:catalog",
  3. "catalog",
  4. array(
  5. "IBLOCK_TYPE" => "catalog",
  6. "IBLOCK_ID" => IBLOCK_ID__CATALOG,
  7. [...]
  8. ),
  9. false
  10. );?>

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

В том случае когда над проектом работают несколько программистов на разных серверах, либо даже если только один программист собирается перенести функционал с тестового сервера на боевой могут возникнуть проблемы, так как ID инфоблоков могут различаться. Например инфоблоки были созданы в разном порядке, или достаточно всего один раз удалить инфоблок на одном из сайтов, чтобы все последующие инфоблоки даже созданные в верном порядке были под разными ID. При простом копировании вызова компонента будут вызываться разные инфоблоки, то есть например на тестовом выводятся новости из компонента с ID=3, а на боевом инфоблок новостей имеет ID=2. И соответственно при копировании вызова компонента будут не новости отображаться, а какая нибудь фотогалерея.

Чтобы решить проблему с разными ID инфоблоков на разных сайтах константы тоже пригодятся. Достаточно при старте страницы получить инфоблок по его символьному коду и засунуть в константу. Специально для этой цели я написал класс — Класс для получения инфоблока по коду. Здесь я полный листинг файла приводить не буду, там ничего особенного. Вкратце, что там происходит вообще. Если вы пользуетесь моим классом, то объявляем константу вот так:
define('IBLOCK_ID__CATALOG', IBlockData::getByCode('CATALOG'));
Далее функция проверяет статическую переменную класса $byCode, если эта переменная пуста, то получает полный список инфоблоков, и записывает в эту переменную массив ID инфоблоков, где ключами массива являются коды инфоблоков. Далее функция смотрит элемент массива с ключом переданным в качестве аргумента функции. Если элемент найден по ключу, то возвращает его значение. Итого функция по коду инфоблока возвращает его ID. Полная выборка инфоблоков кешируется стандартным битриксовским кешем на сутки, то есть запрос к БД будет только при первом обращении, а весь следующий день значение будет браться из кеша. В кеш ложатся только пара символьный код — ID, поэтому функция достаточно легкая. Инфоблоки берутся с привязкой к сайту, то есть класс применим при работе с многосайтовостью, по коду вернется ID инфоблока на конкретном сайте.

Наименование констант

Выше я привел довольно простой файл констант, в котором только несколько инфоблоков и пара путей. Однако же бывают проекты в котором констант на пару экранов. И если называть константы не задумываясь, то будет хаос. Лично я стараюсь обозначать константы по виду. Например если в константе хранится ID инфоблока, то делаю префикс IBLOCK_ID, если там путь, то префикс PATH. В общем тут строгих правил никаких нет, но все же стоит как-то хотя бы минимально обозначать в названии константы ее значение. Константа «TEST_BLOCK» какая-нибудь вообще непонятно что содержит, особенно если там просто цифра записана.

Модуль проекта

Со временем у любого программиста накапливается довольно много готового кода под разные ситуации, который заметно облегчает работу. Кроме того во многих проектах есть сложный функционал, который нужно где-то хранить. Я встречал подключение классов в init.php (как довольно грамотно сделанное с автолоадом, так и простые инклуды), всякие вычисления в result_modifier.php, сам я раньше создавал под проект свой специфический модуль. Однако же сейчас я остановился на создании универсального локального модуля, который уже содержит все наработки и готов к написанию специфичного функционала. На всех проектах я использую модуль «local.lib». Название через точку чтобы он отобразился в списке модулей marketplace. Что именно можно и нужно делать в модуле?

Автолоад классов

На текущий момент битрикс поддерживает автоматический автолоад классов. Тавтология небольшая, но тем не менее. Автолоад классов — подключение класса по требованию. Автоматический автолоад — не нужно прописывать классы в автолоад заранее, путь до файла автоматически определяется при вызове. Что это означает на практике? В папке с модулем создаете папку /lib/, в папке /lib/ создаете файл testecho.php, название обязательно в нижнем регистре. В файле пишете:

  1. <?php
  2. namespace Local\Lib;
  3. class TestEcho
  4. {
  5. public function test()
  6. {
  7. echo 'test';
  8. }
  9. public static function testTwo()
  10. {
  11. echo 'test two';
  12. }
  13. }

Теперь в любом месте проекта, на любой странице, в любом компоненте и т.д. можете этот свой класс использовать:
  1. <?php
  2. if(\Bitrix\Main\Loader::includeModule('local.lib')) {
  3. \Local\Lib\TestEcho::testTwo();
  4. $test = new \Local\Lib\TestEcho();
  5. $test->test();
  6. }

И в месте вызова будет отображаться строка «test», либо «test two» из класса.

Теперь после примера можно чуть подробнее остановиться на том, как работает автолоад классов. Обратите внимание на первую строку в файле с классом «namespace Local\Lib;» именно на основе пространств имен все и работает. Модуль у нас называется local.lib, это означает что все классы модуля должны быть в пространстве имен Local\Lib. Если бы модуль назывался test.module, то мы бы во всех классах прописывали пространство имен Test\Module. При публикации на marketplace битрикса первое слово до наклонной черты — это id партнера, второе слово это название модуля (просто на всякий случай).

Можно использовать подпространства имен. Например Local\Lib\Helpers. Создаете папку /lib/helpers/ и в ней файл increment.php

  1. <?php
  2. namespace Local\Lib\Helpers;
  3. class Increment
  4. {
  5. public static function get($number)
  6. {
  7. return $number++;
  8. }
  9. }

Обратите на пространство имен, я туда добавил Helpers. И точно так же как и в предыдущем примере используем класс в любом месте
  1. <?php
  2. if(\Bitrix\Main\Loader::includeModule('local.lib')) {
  3. echo \Local\Lib\Helpers\Increment::get(1);
  4. }

После выполнения кода в месте вызова отобразится циферка «2».

Подробнее для тех кто не понял в чем суть. В модуле битрикса можно создать сколько угодно классов, классы можно красиво разложить по папкам. Класс вообще никак не влияет на производительность, до тех пор, пока к нему не обратились. То есть только в момент обращения к классу срабатывает автолоад, по вызванному имени класса битрикс пытается найти файл, и подключает конкретно этот файл. Например имя класса \Local\Lib\Helpers\Increment означает что битрикс будет смотреть в папку модулей, будет искать в этой папке подпапку local.lib, далее внутри папки local.lib он будет искать lib/helpers/ и внутри этой папки уже будет искать файл increment.php. Если такой файл найден, то он подключается инклудом и вызывается класс из этого файла.

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

Обработчики

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

  1. <?php
  2. if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
  3. if (file_exists($_SERVER["DOCUMENT_ROOT"]."/local/php_interface/include/constants.php"))
  4. require_once($_SERVER["DOCUMENT_ROOT"]."/local/php_interface/include/constants.php");
  5. if (file_exists($_SERVER["DOCUMENT_ROOT"]."/local/php_interface/include/handlers.php"))
  6. require_once($_SERVER["DOCUMENT_ROOT"]."/local/php_interface/include/handlers.php");

То есть всего две операции — подключение вышеупомянутого файла констант, и подключение файла обработчиков. Примерный листинг файла констант я уже приводил, а вот листинг файла обработчиков
  1. <?php
  2. use \Bitrix\Main\Loader;
  3. $eventManager = \Bitrix\Main\EventManager::getInstance();
  4. //page start
  5. AddEventHandler("main", "OnPageStart", "loadLocalLib", 1);
  6. function loadLocalLib()
  7. {
  8. Loader::includeModule('local.lib');
  9. }
  10. //BASKET
  11. //basket add
  12. AddEventHandler("sale", "OnBeforeBasketAdd", array('Local\Lib\Handlers\Basket', 'beforeAdd'));
  13. AddEventHandler("sale", "OnBasketAdd", array('Local\Lib\Handlers\Basket', 'afterAdd'));
  14. //basket update
  15. AddEventHandler("sale", "OnBeforeBasketUpdate", array('Local\Lib\Handlers\Basket', 'beforeUpdate'));
  16. AddEventHandler("sale", "OnBasketUpdate", array('Local\Lib\Handlers\Basket', 'afterUpdate'));
  17. // basket delete
  18. AddEventHandler("sale", "OnBeforeBasketDelete", array('Local\Lib\Handlers\Basket', 'beforeDelete'));
  19. AddEventHandler("sale", "OnBasketDelete", array('Local\Lib\Handlers\Basket', 'afterDelete'));
  20. //order
  21. AddEventHandler("sale", "OnOrderAdd", array('Local\Lib\Handlers\Order', 'afterAdd'));
  22. AddEventHandler("sale", "OnOrderUpdate", array('Local\Lib\Handlers\Order', 'afterUpdate'));
  23. //property types
  24. AddEventHandler("main", "OnUserTypeBuildList", array('Local\Lib\Properties\Complect', 'GetUserTypeDescription'));
  25. AddEventHandler("iblock", "OnIBlockPropertyBuildList", array('Local\Lib\Properties\Complect', 'GetUserTypeDescription'));
  26. //user
  27. AddEventHandler("main", "OnBeforeUserRegister", array('\Local\Lib\Handlers\User', 'beforeUpdate'));
  28. AddEventHandler("main", "OnBeforeUserUpdate", array('\Local\Lib\Handlers\User', 'beforeUpdate'));
  29. //highload blocks
  30. $eventManager->addEventHandler('', 'UserDataOnUpdate', array('\Local\Lib\Handlers\UserData', 'afterUpdate'));
  31. $eventManager->addEventHandler('', 'UserDataOnAdd', array('\Local\Lib\Handlers\UserData', 'afterAdd'));

Никакого кода здесь нет и не должно быть. Единственное исключение, это функция подключения локального модуля. Чтобы не надо было везде его прописывать, просто сразу пользуемся функциями модуля. Если модуль гарантированно подключен при старте страницы, то писать \Bitrix\Main\Loader::includeModule(‘local.lib’) как в предыдущем примере уже не нужно, сразу вызываем классы модуля и все.

Как вы уже наверное поняли на основе предыдущего блока про автолоад — все обработчики лежат в локальном модуле. Вызов array(‘Local\Lib\Handlers\Basket’, ‘afterDelete’) по событию OnBasketDelete означает, что при необходимости битрикс автоматически подключит файл /local/modules/local.lib/lib/handlers/basket.php, и вызовет статическую функцию afterDelete, класса Basket.

Битрикс позволяет регистрировать обработчики при установке модуля. То есть в файле /install/index.php просто один раз регистрируем обработчик и он начинает работать. При этом в файле handlers.php будет пусто. Для модулей в маркетплейс это безусловно очень удобно, и по сути единственный возможный для них вариант работы. Однако я считаю, что для модуля проекта такого делать не стоит. Во первых, обработчики часто добавляются и удаляются, а каждый раз удалять и устанавливать обратно модуль это вообще не круто, как и постоянно лезть в консоль php. Во вторых, зарегистрированные при установке модуля обработчики фиг найдешь. Про них нужно заранее знать, в то время как обработчики из init.php все программисты лезут проверять в первую очередь. Понять что и где делается гораздо проще.

Класс обработчика

Подробнее о том что должно быть в файле /local/modules/local.lib/lib/handlers/basket.php, и о том как должен строиться класс Local\Lib\Handlers\Basket. В битриксе названия событий достаточно запутанные. Всякие before, onBefore и т.д. Впринципе вызываемую функцию можно назвать просто по названию события. Если событие называется OnBeforeBasketDelete, то в классе Basket так и пишем:

  1. <?php
  2. namespace Local\Lib\Handlers;
  3. use Bitrix\Main\Localization\Loc;
  4. use Bitrix\Main\Loader;
  5. Loc::loadMessages(__FILE__);
  6. class Basket
  7. {
  8. public static function OnBeforeBasketDelete($ID)
  9. {
  10. //do something
  11. }
  12. }

Однако логичнее как мне кажется называть функции по шаблону «когдаДействие», например «beforeDelete», «afterDelete», «beforeAdd», «afterChange» и т.д. Класс обработчика предназначен для работы с конкретным объектом (условным), например класс для событий корзины, класс для событий элемента инфоблока, класс для работы с событиями заказа и т.д. Смысла дублировать название класса в названии функции на мой взгляд нет. В этом примере мы работаем с событиями корзины, и соответственно функция которая будет вызываться перед удалением товара из корзины должна называться beforeDelete.
  1. <?php
  2. namespace Local\Lib\Handlers;
  3. use Bitrix\Main\Localization\Loc;
  4. use Bitrix\Main\Loader;
  5. Loc::loadMessages(__FILE__);
  6. class Basket
  7. {
  8. public static function beforeDelete($ID)
  9. {
  10. //do something
  11. }
  12. }

Все функции событий должны быть статическими. Битрикс не создает объект класса, он просто вызывает одну конкретную функцию. Но использовать класс просто как хранилище функций довольно глупо. Поэтому этот класс мы сделаем паттерном singleton и во всех функциях событий будем получать объект класса. То есть код будет выглядеть вот так:
  1. <?php
  2. namespace Local\Lib\Handlers;
  3. use Bitrix\Main\Localization\Loc;
  4. use Bitrix\Main\Loader;
  5. Loc::loadMessages(__FILE__);
  6. class Basket
  7. {
  8. /**
  9. * @var array
  10. */
  11. protected $arFields;
  12. /**
  13. * @var array
  14. */
  15. protected $errors = array();
  16. /**
  17. * @var self
  18. */
  19. private static $instance;
  20. private function __construct()
  21. {
  22. }
  23. private function __clone()
  24. {
  25. }
  26. private function __wakeup()
  27. {
  28. }
  29. public static function getInstance()
  30. {
  31. if (empty(self::$instance)) {
  32. self::$instance = new self();
  33. }
  34. return self::$instance;
  35. }
  36. public static function beforeDelete($basketItemId)
  37. {
  38. $handler = self::getInstance();
  39. $handler->doSomething();
  40. $handler->doSomethingElse();
  41. return $basketItemId;
  42. }
  43. protected function doSomething()
  44. {
  45. //do something
  46. }
  47. protected function doSomethingElse()
  48. {
  49. //do something else
  50. }
  51. }

Кроме того обратите внимание на функции doSomething() и doSomethingElse(), по факту обработчик события вызвался всего один раз, однако в обработчике мы выполнили два логически разделенных действия. В файле handlers.php всего один обработчик на событие OnBeforeBasketDelete, но внутри функции beforeDelete мы вызываем несколько логически не связанных функций. Это тоже значительно увеличивает читабельность файла handlers.php с обработчиками. Внутри функций класса мы уже можем использовать объект $this, как в нормальном полноценном классе, вызывать другие функции класса, использовать переменные класса, получить какие то общие данные (типа состояние элемента до изменения) и использовать их в нескольких разных функциях и т.д. То есть писать какой то уже более менее сложный код.

Следующий пример использования класса чуть посложнее. Если в функции по событию OnBeforeIBlockElementUpdate вернуть false и в $APPLICATION бросить ошибку, то элемент не сохранится. Если в этой же функции изменить какое либо поле, то сохранится уже измененное значение. Допустим мы хотим вообще всем элементам в инфоблоке каталог, в названии добавлять префикс test при сохранении. Кроме того представим, что в инфоблоке каталог у нас есть свойство типа список, с одним значением (галочка да/нет) и кодом READ_ONLY. Если галочка в свойстве READ_ONLY стоит, то элемент не должен сохраниться. Вот так это можно реализовать в классе обработчика:

  1. <?php
  2. namespace Local\Lib\Handlers;
  3. use Bitrix\Main\Localization\Loc;
  4. use Bitrix\Main\Loader;
  5. Loc::loadMessages(__FILE__);
  6. class Catalog
  7. {
  8. /**
  9. * @var array
  10. */
  11. protected $arFields;
  12. /**
  13. * @var array
  14. */
  15. protected $arElement;
  16. /**
  17. * @var array
  18. */
  19. protected $errors = array();
  20. /**
  21. * @var self
  22. */
  23. private static $instance;
  24. private function __construct()
  25. {
  26. }
  27. private function __clone()
  28. {
  29. }
  30. private function __wakeup()
  31. {
  32. }
  33. public static function getInstance()
  34. {
  35. if (empty(self::$instance)) {
  36. self::$instance = new self();
  37. }
  38. return self::$instance;
  39. }
  40. /**
  41. * Выполняется по событиям
  42. * OnBeforeIBlockElementUpdate
  43. *
  44. * @param $arFields
  45. *
  46. * @return array|bool
  47. */
  48. public static function beforeChange(&$arFields)
  49. {
  50. if ($arFields['IBLOCK_ID'] == IBLOCK_ID__CATALOG) {
  51. $handler = self::getInstance();
  52. $handler->setFields($arFields);
  53. $handler->getElement();
  54. $handler->setName();
  55. $handler->checkReadOnly();
  56. if (!empty($handler->errors)) {
  57. $errors = implode("\r\n", $handler->errors);
  58. global $APPLICATION;
  59. $APPLICATION->throwException($errors);
  60. return false;
  61. } else {
  62. $arFields = $handler->arFields;
  63. }
  64. }
  65. return $arFields;
  66. }
  67. protected function setName()
  68. {
  69. $this->arFields['NAME'] = 'Test '.$this->arFields['NAME'];
  70. }
  71. protected function checkReadOnly()
  72. {
  73. if(!empty($this->arElement['PROPERTY_READ_ONLY_VALUE'])){
  74. $this->errors[] = 'Снимите галочку "Только для чтения", чтобы сохранить элемент';
  75. }
  76. }
  77. protected function setFields($arFields)
  78. {
  79. $this->errors = array();
  80. $this->arFields = $arFields;
  81. }
  82. protected function getElement()
  83. {
  84. if (is_array($this->arFields)) {
  85. $this->arElement = \CIBlockElement::GetList(
  86. array(),
  87. array(
  88. 'ID' => $this->arFields['ID'],
  89. 'IBLOCK_ID' => $this->arFields['IBLOCK_ID'],
  90. ),
  91. false,
  92. false,
  93. array(
  94. 'ID',
  95. 'IBLOCK_ID',
  96. 'PROPERTY_READ_ONLY',
  97. )
  98. )
  99. ->Fetch();
  100. }
  101. }
  102. }

Вместо функции setName можно поставить любые преобразования данных.

То есть в переменной класса $errors у нас сохраняются ошибки. Если в любой функции класса объявить ошибку вот так «$this->errors[] = ‘text’;», то при сохранении в админке пользователю отобразится ошибка. Мы можем вызывать сколько угодно функций, вместо свойства READ_ONLY проверить любые другие зависимые данные, может быть даже из нескольких источников и в случае необходимости объявить ошибку с отменой сохранения элемента.

Это два простеньких примера использования класса обработчика. На самом деле возможности тут гораздо шире. Еще например перед удалением элемента можно получить все его поля в переменную класса (напомню, класс singleton, статический класс), а потом уже после удаления просто проверить эту же переменную класса и совершить какие нибудь действия после гарантированного удаления элемента, но с использованием уже удаленных по сути данных.

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

Хэлперы и части страниц

Помимо обработчиков в модуле проекта у меня лежат классы хелперы и часто используемый фукнционал по формированию html (условно части страниц).

Хелперы, это простенькие классы, можно сказать набор функций объединенных в класс. Я часто использую хелпер Files для ресайза и получения изображений (который в отличие от битриксовского всегда возвращает и src, и SRC, плюс еще несколько удобных вещей делает), класс Validation для валидации различных данных типа email, телефонов, ИНН, КПП и т.д., класс Mail для отправки почтовых событий с прикрепленными файлами. Кроме того под проекты довольно часто пишутся свои хелперы, если какой то функционал вызывается из нескольких разных мест.

Части страниц это по сути те же хелперы, но не просто набор функций, а уже что-то более менее взаимосвязанное. Например очень частая задача — отобразить любую картинку из анонса, либо из детальной, либо из свойства инфоблока. Для этого я написал класс Image и вынес его в модуль проекта, пример использования:

  1. <?php
  2. //make detail image
  3. $image = new \Local\Lib\Parts\Image(array(
  4. 'width' => 400,
  5. 'height' => 400
  6. ));
  7. $image->addImage($this->arProduct['DETAIL_PICTURE']);
  8. $image->addImage($this->arProduct['PREVIEW_PICTURE']);
  9. $image->addImagesList($this->arProduct['PROPERTIES']['MORE_IMAGES']['VALUE']);
  10. $this->arProduct['DETAIL_PICTURE'] = $image->getFirstResized();

В классе последовательно проверяются все переданные данные, и берется первая найденная картинка. По итогу в $this->arProduct[‘DETAIL_PICTURE’] будут храниться данные по файлу картинки, либо false если ни в одном из указанных мест картинка не была найдена. Если картинка найдена, то она сразу пережмется до указанных размеров.

Другой пример для части страниц — слайдеры. Практически во всех проектах есть слайдеры картинок и как правило в слайдер нужно засунуть не одну, а несколько картинок из разных мест, плюс пережать эти картинки под несколько разных размеров, для превью и для отображения на странице как минимум. Класс слайдера работает так:

  1. <?php
  2. //main slider
  3. $arSizes = array(
  4. 'FULL' => array(
  5. 'width' => 2000,
  6. 'height' => 650
  7. ),
  8. 'PREVIEW' => array(
  9. 'width' => 200,
  10. 'height' => 100
  11. ),
  12. );
  13. $slider = new \Local\Lib\Parts\Slider($arSizes);
  14. $slider->addSlide($arResult['DETAIL_PICTURE']);
  15. $slider->addSlidesList($arResult['PROPERTIES']['MORE_PHOTO_SITE']['VALUE']);
  16. $slider->addSlidesList($arResult['PROPERTIES']['MORE_PHOTO']['VALUE']);
  17. $arResult['SLIDER'] = $slider->getSlider();

То есть примерно как с картинкой, указали желаемый массив размеров и на выходе в $arResult[‘SLIDER’] мы получим
  1. $arResult[‘SLIDER][‘ORIGINAL’] массив картинок в полном оригинальном размере
  2. $arResult[‘SLIDER][‘FULL’] массив картинок ужатых до какого-то промежуточного размера
  3. $arResult[‘SLIDER][‘PREVIEW’] массив картинок ужатых до самого мелкого размера

Каждый из массивов содержит все картинки совпадающие по ключу. Картинка $arResult[‘SLIDER][‘ORIGINAL’][0] это та же самая картинка, что и $arResult[‘SLIDER][‘PREVIEW’][0], только с другими размерами естественно. При необходимости можно добавить еще какую то градацию размеров в массив $arSizes

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

Итого по модулю проекта

Помимо хранилища классов модуль проекта можно использовать для хранения глобальных настроек, достаточно закинуть файлик options.php в корень модуля и с помощью класса Options (сделал свой на основе этого) задать массив желаемых параметров. В модуле сразу можно делать классы моделей для работы с ORM битрикса. Можно сделать класс для работы c Highload блоком, то есть вызываем функцию типа так Local\Lib\Map::add($arData); в которую передаем чистые данные, или делаем выборку примерно так же, только вместо add что нибудь типа getByUserId, а все взаимодействие с HL блоком прописано внутри класса. В общем модуль проекта это круто, и я крайне советую вам его использовать. Но использовать разумно, не пихать туда все подряд, всякие мелочи, и наоборот большой функционал с использованием всяких сторонних библиотек можно (нужно?) выносить в отдельные модули.

Компоненты

По устройству типового компонента мне нечего добавить. Как использовать result_modifier.php, component_epilog.php, шаблоны компонентов и т.д. все это можно прочитать в официальных обучающих курсах. Единственное что хочу добавить по компонентам касается файла class.php. В каких случаях писать свой компонент, а в каких использовать стандартный я не буду объяснять, большая часть задач решается стандартными компонентами. Остановлюсь чуть подробнее на написании собственных компонентов.

При написании собственных компонентов стоит полностью отказаться от файла component.php. Если там прям совсем какой то микрофункционал, то может быть и стоит его оставить, и то непонятно зачем. Однако на чуть более серьезных задачах стоит пользоваться классом. Перекрываем родительский метод executeComponent() и все содержимое component.php пишем прямо в этой функции. Плюс вместо разделения смысловых блоков комментариями выносим их в отдельные функции. И читабельность кода заметно повышается. Функция executeComponent() если все из нее вынесено в отдельные логические блоки становится довольно короткой, на экран или в крайнем случае на несколько и ее гораздо легче понять, чем десятки экранов сплошного кода разделенного только комментариями.

В классе можно использовать переменную $this->request для доступа к переменным запроса, в эту переменную пишется объект класса Bitrix\Main\HttpRequest и можно прямо сразу в любой функции класса обращаться к его методам, например $this->request->getQuery(«some_name») или $this->request[«some_name»].

В классе доступна функция $this->setTemplateName(‘name’) которая позволяет прямо на лету изменить шаблон, и шаблон переданный при инициализации компонента перестает учитываться. Это может пригодиться например для сложного пошагового мастера, который в зависимости от предыдущих данных определяет текущий шаг и подключает шаблон с этим шагом. Или например если один компонент обслуживает какой-то front-end функционал состоящий из нескольких связанных окошек, можно в зависимости от запроса отображать нужное окошко из определенного шаблона компонента.

В заранее определенной функции onPrepareComponentParams($arParams) можно сразу провести валидацию переданнных в компонент параметров (она получает на вход то что передано в компонент при вызове) и объявить при необходимости ошибку. Там же можно заполнить массив параметров данными из запроса, опять таки провалидировать их и в дальнейшем уже не к запросу обращаться, а просто смотреть гарантированно верные параметры. Функция onPrepareComponentParams() выполняется до executeComponent(), и если в ней объявить ошибку например в переменную класса $this->errors[], то уже в executeComponent() наличие ошибок проверяется просто по заполненности переменной $this->errors

В общем класс компонента может содержать довольно большой объем кода без вреда для читабельности. Это позволяет шире использовать компоненты при решении различных задач. И встает вопрос о том, в каких случаях использовать модуль проекта, а в каких случаях класс компонента.

Компонент vs Модуль

Все это достаточно субъективно, но на мой взгляд выносить какой либо функционал в модуль стоит только в двух случаях:

  1. Функционал используется в нескольких местах сразу
  2. Тяжелый функционал

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

Тяжелый это функционал или нет судить вам, на мой взгляд грань — это внешние библиотеки. То есть в компоненте не должно быть подключения внешних библиотек вообще. Если вы используете какую-то внешнюю библиотеку (тот же phpexcel например), то ее практически наверняка стоит вынести либо в модуль проекта, либо вообще в отдельный модуль по работе с этой библиотекой. Если никакие внешние библиотеки не используются, то никаких четких критериев предложить не могу, разве что количество строк. Если число строк кода в компоненте больше 500-1 000, то возможно стоит задуматься о выносе части кода в модуль.

Общий взгляд

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




PHP, Bitrix
Читайте также:
Как в редакторе Coda включить отображение скрытых файлов

Как в редакторе Coda включить отображение скрытых файлов

Привет, старик), рад тебя видеть. Хочу тебе рассказать, как может показаться на первый взгляд, давольно простую и баналь...
Читать
Как вести бюджет: советы практикующего фрилансера

Как вести бюджет: советы практикующего фрилансера

Для многих слово «фриланс» звучит ангельским пением, но у этого способа работы есть свои неоднозначные особенности. Одна...
Читать
Как вывести кнопки вставки кода и цитаты в редакторе Битрикс

Как вывести кнопки вставки кода и цитаты в редакторе Битрикс

Добавим свои кнопки в новом визуальном редакторе Битрикс
Читать
Источник: verstaem.com