Composer и PSR-4 в 1С-Битрикс - vendor, автозагрузка и Loader
Composer - стандартный менеджер зависимостей PHP: ставит сторонние библиотеки и
генерирует для них автозагрузчик по PSR-4. У 1С-Битрикс при этом есть
собственный, гораздо более старый механизм автозагрузки - Bitrix\Main\Loader,
который точно так же находит и подключает класс по имени, но по своим правилам,
и настраивается отдельно в каждом модуле платформы. В реальном проекте оба
механизма работают одновременно, и от того, где лежит composer.json, как
подключён vendor/autoload.php и в каком порядке это происходит относительно
собственной автозагрузки платформы, зависит, поднимется ли сайт вообще. Тема
всплывает в трёх типичных ситуациях. Первая - в проект добавляют стороннюю
библиотеку с Packagist. Вторая - свой код оформляют модулем и выбирают между
двумя способами автозагрузки. Третья - сайт после переноса или обновления падает
с Class not found там, где вчера всё работало.
Как это работает
Оба автозагрузчика - это просто две функции в одной очереди PHP. Функция
spl_autoload_register() добавляет обработчик в общую очередь автозагрузки.
Встретив ещё не объявленный класс, PHP опрашивает эту очередь по порядку
регистрации. Опрос останавливается, как только очередной обработчик объявил
класс. Если ни один обработчик не справился, PHP выбрасывает Error с текстом о
том, что класс не найден. Bitrix\Main\Loader и автозагрузчик Composer из
vendor/autoload.php - это ровно два таких обработчика в общей очереди, и они
ничего не знают друг о друге, кроме порядка, в котором их зарегистрировали.
Loader ищет класс в два шага, и оба реестра заполняет модуль сам. Сначала
проверяется явная карта «класс - файл», которую заполняет
Loader::registerAutoLoadClasses(). Если класса там нет, идёт поиск по
пространствам имён из Loader::registerNamespace(). Работает PSR-4-подобное
правило, где каждый \ в namespace превращается в папку. Не нашлось ни там, ни
там - ошибка. Оба реестра модуль заполняет в своём include.php, а этот файл
подключается автоматически при Loader::includeModule('имя_модуля') - то есть
автозагрузка классов конкретного модуля появляется не раньше, чем модуль явно
подключили.
PSR-4 у Loader - тот же принцип, что у Composer, но не побайтово тот же
стандарт. Оба транслируют пространство имён в путь одинаково: \ - в папку,
имя класса - в имя файла. Разница - в регистре букв. Спецификация PSR-4 требует,
чтобы регистр имени файла совпадал с регистром имени класса буква в букву. А
правила оформления кода 1С-Битрикс требуют обратного. Файлы классов в /lib
модуля называют строчными буквами независимо от регистра самого класса. Поэтому
класс Loader из пространства Bitrix\Main лежит в файле lib/loader.php, а
не lib/Loader.php. Для своего пространства, уже зарегистрированного через
registerNamespace(), это работает без сюрпризов. Но тот же код когда-нибудь
подключит строгий сторонний PSR-4-автозагрузчик, например после выноса в
отдельный composer-пакет. Тогда регистр файла уже будет иметь значение. На
файловой системе, чувствительной к регистру, несовпадение даст ту же ошибку про
не найденный класс. Обычный Linux-сервер именно такой, в отличие от Windows и
macOS по умолчанию.
Composer подключается через .settings.php, а не руками в коде. Путь к
своему composer.json прописывают в /bitrix/.settings.php, в секции
composer -> value -> config_path - и дальше платформа сама подключает
/vendor/autoload.php, требовать его вручную не нужно. Среди шагов пролога это
происходит на самом первом шаге - «подключение загрузчика, автозагрузки и
утилит», то есть раньше соединения с базой и заведомо раньше init.php. Полный
разбор шагов пролога - в статье «Архитектура платформы».
Порядок важен именно поэтому. Если подключить vendor/autoload.php вручную
внутри init.php вместо .settings.php, обработчик встанет в очередь
автозагрузки только на четвёртом шаге пролога - после соединения с базой и
определения сайта. Любой класс из composer-зависимостей, нужный раньше
(например, свой класс подключения к БД, указанный прямо в .settings.php), к
этому моменту ещё не резолвится, и сайт падает раньше, чем init.php вообще
подключается. А подключение vendor/autoload.php из init.php вдобавок к
автоматическому даёт два независимых обработчика в очереди. Они могут указывать
на две разные копии одних и тех же библиотек. Какая копия победит, решает не
разработчик, а порядок регистрации.
Примеры
1. Куда класть composer.json, чтобы не мешать обновлениям
/home/bitrix/composer.json - вне DOCUMENT_ROOT/home/bitrix/vendor/ - сюда composer install ставит зависимости/home/bitrix/www/ - это DOCUMENT_ROOT/home/bitrix/www/bitrix/ - системные файлы платформы, не трогать/home/bitrix/www/local/ - пользовательский код проекта// /bitrix/.settings.php - добавить в существующий массив настроек'composer' => [ 'value' => [ 'config_path' => '/home/bitrix/composer.json', ],],composer -V# Composer version 2.8.5 2025-01-21 15:23:40По умолчанию система ищет composer.json внутри /bitrix/ - но это системный
каталог, который обновление платформы перезаписывает целиком, поэтому там файл
не место. Официальная рекомендация - вынести его за пределы DOCUMENT_ROOT
совсем, как в примере выше. Тогда composer.json не попадает под обновление и
не скачивается напрямую по URL. В незащищённом виде это просто текстовый файл со
списком зависимостей и версий. Для разведки перед атакой такой список - готовая
подсказка. Если сервер не даёт доступа выше DOCUMENT_ROOT (типичная ситуация
на части хостингов), официальный запасной вариант - папка /local/composer/,
закрытая от внешнего доступа средствами веб-сервера.
2. Стандартные зависимости платформы через composer-merge-plugin
Файл /home/bitrix/composer.json:
{ "require": { "wikimedia/composer-merge-plugin": "^2.0" }, "extra": { "merge-plugin": { "include": [ "/home/bitrix/www/bitrix/composer-bx.json" ] } }}composer installЯдро само поставляет заготовку - /bitrix/composer.json.example, её удобно
взять за основу своего файла. В composer-bx.json перечислены стандартные
зависимости самой платформы: они нужны, например, CLI-командам вроде
orm:annotate из статьи «D7 ORM» - без предварительно
поставленных через composer зависимостей эта команда не запустится вовсе. Плагин
composer-merge-plugin на лету подмешивает требования из composer-bx.json к
требованиям проекта, и одна команда composer install ставит и то, и другое
разом - результат появляется в /home/bitrix/vendor/.
3. Свои классы: Loader::registerNamespace против PSR-4 у Composer
Модуль регистрирует своё пространство имён сам, в include.php:
// подключается автоматически при Loader::includeModule('mycompany.helpers')\Bitrix\Main\Loader::registerNamespace( 'MyCompany\Helpers', __DIR__ . '/lib');namespace MyCompany\Helpers;
class PriceCalculator{ public static function withVat(float $price): float { return round($price * 1.2, 2); }}\Bitrix\Main\Loader::includeModule('mycompany.helpers');
var_dump(\MyCompany\Helpers\PriceCalculator::withVat(1000));// float(1200)Класс нашёлся только после includeModule() - до этого вызова его пространство
имён вообще не зарегистрировано. Для кода без привязки к жизненному циклу модуля
тот же результат даёт обычный PSR-4 в /home/bitrix/composer.json:
{ "autoload": { "psr-4": { "MyCompany\\Pricing\\": "libs/pricing/src/" } }}composer dump-autoloadПосле пересборки класс из libs/pricing/src/Calculator.php доступен везде, где
подключён vendor/autoload.php, - без единого includeModule(), с самого
первого шага пролога. Отсюда и граница уместности. registerNamespace() в
модуле берут, когда код должен ставиться и удаляться штатным установщиком,
получать версию и попадать в маркетплейс. Этот жизненный цикл разобран в статье
«Свой
модуль». Composer PSR-4 берут для кода без такого цикла:
общих библиотек и обёрток над чужими SDK. Туда же идёт всё, что хочется
тестировать обычным PHP-тулингом отдельно от платформы, как в статье «Модульное
тестирование».
4. Поздняя автозагрузка ломает раннюю инициализацию
// /bitrix/.settings.php - кастомный класс соединения из собственного composer-пакета'connections' => [ 'value' => [ 'default' => [ 'className' => '\Acme\Db\LoggingConnection', 'host' => 'localhost', 'database' => 'dbname', 'login' => 'user', 'password' => 'pass', ], ],],// без строки ниже автозагрузчик Composer вообще не подключится сам'composer' => [ 'value' => [ 'config_path' => '/home/bitrix/composer.json', ],],Если убрать секцию composer из .settings.php и вместо неё понадеяться на
require_once($_SERVER['DOCUMENT_ROOT'] . '/local/vendor/autoload.php'); в
начале init.php, результат - падение ещё до того, как init.php вообще
подключится:
PHP Fatal error: Uncaught Error: Class "Acme\Db\LoggingConnection" not foundКласс соединения нужен на шаге установки связи с базой - это второй шаг пролога.
init.php - только четвёртый, после определения сайта. Автозагрузчик,
подключённый через .settings.php, встаёт в очередь на первом шаге и успевает к
моменту, когда класс соединения понадобится; автозагрузчик из init.php - нет.
5. Одна и та же библиотека грузится дважды - и побеждает не та версия
// в проекте: composer require guzzlehttp/guzzle:^7.8, ставится в /home/bitrix/vendor/// в стороннем модуле с маркетплейса: своя копия ^6.5 в// /home/bitrix/www/bitrix/modules/partner.module/vendor/, подключена// модулем напрямую собственным require(), в обход общего composer.json
$client = new \GuzzleHttp\Client();echo (new \ReflectionClass($client))->getFileName();/home/bitrix/www/bitrix/modules/partner.module/vendor/guzzlehttp/guzzle/src/Client.phpВывод показывает не ту версию, что объявлена в composer.json проекта (^7.8).
Отвечает версия, которая физически лежит в модуле (^6.5). Автозагрузчик модуля
зарегистрировался в очереди spl_autoload_register() раньше и ответил первым.
Многие решения с маркетплейса носят собственный vendor/ и подключают его сами
именно потому, что не могут полагаться на то, что у проекта вообще настроен
Composer. Официального способа заставить оба автозагрузчика использовать одну
версию нет - остаётся проверять реальный путь класса через
ReflectionClass::getFileName(), когда возникает подозрение на конфликт.
Справочник API
Файлы и точки подключения
| Место | Назначение | Особенности |
|---|---|---|
composer.json вне DOCUMENT_ROOT (например /home/bitrix/) | конфигурация зависимостей проекта | официальная рекомендация; запасной вариант - /local/composer/, закрытая от веба |
/bitrix/.settings.php, ключ composer -> value -> config_path | путь к своему composer.json | без него автоматического подключения vendor/autoload.php не будет |
/vendor/ рядом с composer.json | сами зависимости | создаётся composer install; в репозиторий обычно не попадает |
/vendor/autoload.php | автозагрузчик Composer | платформа подключает сама, на первом шаге пролога, если задан config_path |
/bitrix/composer.json.example | образец с зависимостью от merge-plugin | шаблон для своего composer.json |
composer-bx.json | стандартные зависимости самой платформы | подключается через wikimedia/composer-merge-plugin (не ниже ^2.0) |
composer.lock | точные зафиксированные версии | коммитить в приложении обязательно - иначе на другой машине встанут другие версии |
Методы Bitrix\Main\Loader
| Метод | Назначение | Особенности |
|---|---|---|
Loader::includeModule($moduleName) | подключить модуль, его include.php и /lib/autoload.php | bool; автозагрузка классов модуля доступна только после успешного вызова |
Loader::registerNamespace($namespace, $path) | зарегистрировать пространство имён по PSR-4-подобному правилу | несколько путей на одно пространство - с main 20.600.0 |
Loader::registerAutoLoadClasses($moduleName, $arClasses) | явная карта «класс -> файл» | проверяется раньше, чем реестр пространств имён; $moduleName может быть null для классов вне модулей |
Loader::getDocumentRoot() | корень сайта | с версии 14.0.0; удобен внутри include.php для построения путей |
Команды Composer
| Команда | Назначение | Особенности |
|---|---|---|
composer install | поставить зависимости по composer.lock | без lock-файла ведёт себя как update и создаёт его заново |
composer update | пересчитать версии по ограничениям composer.json | переписывает composer.lock |
composer dump-autoload | пересобрать vendor/autoload.php | обязателен после правки секции autoload, иначе новые классы не найдутся |
composer check-platform-reqs | сверить PHP и расширения сервера с требованиями пакетов | стоит добавить в пайплайн деплоя |
Частые ошибки
Class not found сразу после переноса на боевой сервер, хотя локально всё
работало. vendor/ обычно не из репозитория (типичная запись в .gitignore),
и на сервере либо забыли выполнить composer install, либо выполнили без
закоммиченного composer.lock - тогда встали другие версии зависимостей, чем
те, на которых разработчик проверял код локально.
После планового обновления платформы пропали composer.json и vendor, сайт
лежит. Их положили внутрь /bitrix/ - а это системный каталог, который
обновление перезаписывает целиком. Правильное место - вне DOCUMENT_ROOT или,
если это невозможно, /local/composer/, закрытая от веб-доступа.
Свой класс из /local/modules/.../lib не находится, хотя файл на месте.
Возможных причин три. В include.php модуля не вызван registerNamespace() или
registerAutoLoadClasses(). Сам модуль не подключён через
Loader::includeModule() до обращения к классу. Либо имя файла не в нижнем
регистре: файлы /lib платформа ожидает строчными буквами независимо от
регистра имени класса.
После добавления ручного require vendor/autoload.php в init.php
посыпались ошибки в неожиданных местах. Автозагрузчик мог уже подключиться
платформой через config_path в .settings.php. Тогда повторное подключение из
init.php в лучшем случае избыточно. В худшем оно регистрирует второй
обработчик на другой каталог vendor/. В очереди spl_autoload_register()
побеждает не та копия одноимённого класса, которую ожидал разработчик.
Класс из composer-зависимости нужен раньше, чем что-либо в init.php, и сайт
падает ещё до него. Пример - кастомный класс подключения к базе, указанный
прямо в .settings.php через connections -> default -> className. Он
нужен на шаге соединения с базой, а это на два шага раньше init.php. Если
автозагрузчик Composer подключён только в init.php, до этого момента дело
просто не доходит.
Пакет ставится и работает у разработчика, а на проде - ошибка платформы или
PHP Parse error. Библиотека негласно требует более новый PHP, чем стоит на
боевом сервере. Локально версия новее, и Composer резолвит зависимости именно
под неё. Официальный минимум для 1С-Битрикс на сегодня - PHP 8.2, но реальный
боевой сервер может отставать даже от этого минимума, либо конкретный пакет
требовать версию ещё свежее. Несоответствие ловится заранее командой composer check-platform-reqs или явно заданной секцией platform (ключ php) в
конфигурации Composer, эмулирующей версию боевого сервера.
Частые вопросы
Обязательно ли ставить Composer в проект на 1С-Битрикс?
Нет. Собственные классы прекрасно грузятся штатным Bitrix\Main\Loader через registerNamespace() или registerAutoLoadClasses(), без единого пакета с Packagist. Composer становится нужен, когда в проект добавляют стороннюю библиотеку либо используют CLI-инструменты самой платформы - например, команда orm:annotate прямо требует предварительно поставленных через composer зависимостей.
Можно ли просто подключить vendor/autoload.php внутри init.php?
Работать будет, но это не тот способ, который предусмотрен платформой, и у него есть цена. Официальный путь - зарегистрировать composer.json через ключ config_path в .settings.php: тогда автозагрузчик подключается платформой на первом шаге пролога, раньше соединения с базой. init.php подключается только на четвёртом шаге, и если composer-классы нужны раньше - например, кастомный класс соединения с базой из .settings.php - до init.php дело просто не доходит.
Куда правильно класть composer.json и vendor в проекте на Битрикс?
За пределами DOCUMENT_ROOT, например в /home/bitrix/ - так рекомендует официальная документация: файл не попадает под обновление платформы и не открывается напрямую по ссылке. Если такой возможности нет, официальный запасной вариант - папка /local/composer/, закрытая от внешнего доступа веб-сервером. Внутрь /bitrix/ класть нельзя ни в каком случае - это системный каталог, и обновление платформы перезапишет его целиком.
Чем автозагрузка Bitrix\Main\Loader отличается от PSR-4 у Composer?
Механика похожая, но не идентичная. Loader ищет класс в два шага: сначала по явной карте registerAutoLoadClasses(), затем по пространствам имён registerNamespace(), зарегистрированным PSR-4-подобным правилом - namespace через обратный слеш превращается в папку. Отличие в регистре: строгий PSR-4 требует, чтобы регистр имени файла совпадал с регистром имени класса буква в букву, а правила платформы требуют называть файлы в /lib модуля строго строчными буквами независимо от регистра класса. Для собственного пространства, зарегистрированного через registerNamespace(), это не проблема, а вот при переносе того же кода под чужой строгий PSR-4-автозагрузчик несовпадение регистра уже может дать ошибку про не найденный класс.
Что делать, если сторонний модуль конфликтует по версии библиотеки с той, что уже стоит в проекте через Composer?
Сначала проверить, не носит ли модуль собственную копию vendor и не подключает ли её сам - многие решения с маркетплейса делают именно так, потому что не могут полагаться на настроенный Composer в проекте. Если копии две, побеждает не версия из composer.json проекта, а та, чей автозагрузчик раньше встал в очередь spl_autoload_register - это проверяется вызовом (new ReflectionClass($object))->getFileName() на реальном объекте библиотеки. Надёжное решение - обновить или заменить конфликтующий модуль; временное - явно понимать порядок подключения и держать версии под контролем через composer.lock.
Связанные темы
- Архитектура платформы - шаги пролога целиком: где среди них подключается автозагрузка и когда - init.php
- Стандарты кода - как платформа выборочно опирается на PSR и что это значит для оформления своего кода
- Свой модуль - структура
/libиinclude.php, куда попадаетregisterNamespace()собственного модуля - D7 ORM - команда
orm:annotate, один из практических поводов ставить Composer в проект - Автодополнение и IDE - почему декларативный PSR-4 у
Composer виден редактору сразу, а
registerNamespace()платформы - только после подключения модуля - Модульное тестирование -
autoload-devи PSR-4 для тестируемого кода, отдельно от автозагрузки ядра - Окружение и деплой - требования к серверу и версии PHP, с которыми должны совпадать зависимости из composer.json
- Раздел Основы
Первоисточники
- Composer в 1С-Битрикс
- Автозагрузка классов
- Жизненный цикл запроса
- Loader::registerNamespace()
- Loader::registerAutoLoadClasses()
- Loader::includeModule()
- Курс «Разработчик Bitrix Framework»: автозагрузка классов модуля
- ORM-аннотации: требование composer install
- Технические требования: PHP 8.2+
- PSR-4: спецификация автозагрузки
- Composer: базовое использование и автозагрузка
- Composer: схема composer.json, секция autoload
- Composer: platform packages и check-platform-reqs
- spl_autoload_register()