[1С-Битрикс, API, PHP, Разработка веб-сайтов] Ещё один велосипед: пишем свой автозагрузчик классов для Битрикс
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Кто бы что ни говорил, но я считаю, что изобретение велосипедов — штука полезная. Использование готовых библиотек и фреймворков, конечно, хорошо, но порой стоит их отложить и создать что-то своё. Так мы поддерживаем мозг в тонусе и реализуем свой творческий потенциал.
Статья обещает быть длинной, поэтому устраивайтесь поудобнее, я начинаю.
Итак, Битрикс, а точнее, Bitrix Framework. Несмотря на наличие богатого API, периодически возникает необходимость в создании своих классов/библиотек, а также подключении сторонних. Поэтому для начала рассмотрим уже имеющиеся способы автозагрузки.
Старый добрый include/require. Я его добавил сугубо для исторической справки. Хотя на заре своего программерского пути я складывал нужные классы и библиотеки в отдельную папку, создавал отдельный файл, куда инклудил все эти классы и уже потом инклудил файл с инклудами (прошу прощения за тавтологию).
Composer. Позволяет подключать как собственные классы, так и сторонние библиотеки. Однако при добавлении новых классов требует ручного обновления. Кроме того, сами классы, файлы и неймспейсы также нужно писать вручную. О том, как подружить Битрикс с композером, можно почитать тут
Битриксовый лоадер. Используется как для подключения модулей, так и для автозагрузки классов. Однако прежде, чем подключить нужные классы, придётся сформировать массив, где ключами будут выступать имена классов, а значениями пути к ним. И выглядеть это всё будет примерно так:
$classes = [
'Namespace\\Package\\ClassName' => '/path/to/class.php'
];
Loader::registerAutloadClasses(null, $classes);
Свои модули. Говорят, что это самый рекомендуемый способ — создаёшь модуль, устанавливаешь его в админке, затем подключаешь в любом месте и пользуешься в своё удовольствие. С виду всё просто, а в реальности мы имеем следующее:
- Помимо написания классов нужно также прописать процедуру установки и удаления модуля. Там есть ряд обязательных параметров и методов, без которых модуль, возможно, работать не будет (хотя не знаю, не проверял)
- Без подключения модуля классы работать не будут
- Не всегда имеет смысл выносить класс в отдельный модуль
Тем не менее, если вы написали свой локальный модуль и потом решили добавить в него ещё пару классов, то для их использования вам уже не потребуется переустановка модуля — просто вызываете нужные методы в нужном месте, и всё!
Ну а теперь, собственно, сам велосипед...
Проанализировав все вышеописанные методы, я задумался о том, что бы такое придумать, чтобы достаточно было просто складывать новые классы в определённое место, а они бы подгружались потом автоматически, без добавления новых элементов в массив неймспейсов и путей к файлам.
В итоге было решено написать специальный модуль — как бы странно это ни звучало, но мне эта идея показалась более удачной, чем добавлять несколько функций в init.php — который будет автоматически подгружать все классы из необходимой директории.
Опущу процесс написания установки/удаления модуля — кому надо, посмотрят в исходниках, а сразу перейду к основному функционалу.
Т.к. изначально неизвестно количество уровней вложенности папок, то нужно, чтобы методы были рекурсивными. Также мы будем использовать класс Bitrix\Main\Loader, который и будет грузить классы.
Представим, что мы решили складировать все наши классы в директорию /local/php_interface/lib:
Также у нас могут быть файлы, которые не содержат классов и, соответственно, не должны быть включены в автозагрузчик, поэтому нужно также учесть и этот момент.
Итак, поехали.
namespace Ramapriya\LoadManager;
use Bitrix\Main\Loader;
class Autoload
{
}
Первым делом нам нужно получить всё содержимое нашей папки. Для этого напишем метод scanDirectory:
public static function scanDirectory(string $dir) : array
{
$result = [];
$scanner = scandir($dir); // сканируем содержимое директории
foreach ($scanner as $scan) {
switch ($scan) {
// пропускаем
case '.':
case '..':
break;
default:
// получаем путь вложенной папки или файла
$item = $dir . '/' . $scan;
$SplFileInfo = new \SplFileInfo($item);
if($SplFileInfo->isFile()) {
// если элемент является файлом, кладём в возвращаемый массив
$result[] = $scan;
} elseif ($SplFileInfo->isDir()) {
// если элемент является директорией, вызываем текущую функцию и результаты кладём в возвращаемый массив
$result[$scan] = self::scanDirectory($item, $result[$scan]);
}
}
}
return $result;
}
На выходе должно получиться следующее:
Как мы видим, структура файлов соблюдена, поэтому можно приступать к формированию массива для автозагрузки:
/* Тут уже добавляется переменная $defaultNamespace, которая и будет являться основой для имени класса.
Также добавим массив php-файлов, которые не должны попасть в автозагрузчик
*/
public static function prepareAutoloadClassesArray(string $directory, string $defaultNamespace, array $excludeFiles) : array
{
$result = [];
// вызываем предыдущий метод
$scanner = self::scanDirectory($directory);
foreach ($scanner as $key => $value) {
$sep = '\\';
switch(gettype($key)) {
case 'string':
// если тип ключа является строкой, скорее всего это директория
$SplFileInfo = new \SplFileInfo($directory . '/' . $key);
$classNamespace = $defaultNamespace . $sep . $key;
if($SplFileInfo->isDir()) {
// ещё раз проверяем, является ли ключ директорией, и если является, то вызываем текущую функцию, передавая в качестве аргументов полученные значения
$tempResult = self::prepareAutoloadClassesArray($directory . '/' . $key, $classNamespace, $excludeFiles);
foreach($tempResult as $class => $file) {
// делаем прогон массива и записываем полученные данные в результат
$result[$class] = $file;
}
}
break;
case 'integer':
// если тип ключа - число, то с большой долей вероятности значением является файл
$SplFileInfo = new \SplFileInfo($directory . '/' . $value);
// получаем название класса из файла (поэтому я рекомендую именовать файлы и папки с соблюдением того же регистра, что и в классах)
$classNamespace = $defaultNamespace . $sep . str_ireplace('.php', '', $SplFileInfo->getBasename());
// далее проверяем является ли значение php-файлом
if(
$SplFileInfo->isFile() &&
$SplFileInfo->getExtension() === 'php'
) {
// прогоняем массив исключаемых файлов и проверяем, нет ли их среди наших значений
foreach($excludeFiles as $excludeFile) {
if($SplFileInfo->getBasename() !== $excludeFile) {
// записываем в массив относительный путь файла с классом
$result[$classNamespace] = str_ireplace($_SERVER['DOCUMENT_ROOT'], '', $directory . '/' . $value);
}
}
}
break;
}
}
return $result;
}
Если всё сделано правильно, то в итоге мы получим сформированный массив для автозагрузки с помощью битриксового лоадера:
Для проверки работоспособности добавим в папку с исключениями файл MainException.php, содержащий следующий класс:
<?php
namespace Ramapriya\Exceptions;
class MainException extends \Exception
{
public function __construct($message = null, $code = 0, Exception $previous = null)
{
parent::__construct($message, $code, $previous);
}
}
Как мы видим, наш файл подгрузился в массив классов:
Забегая вперёд, попробуем вызвать наше новое исключение:
throw new Ramapriya\Exceptions\MainException('test exception');
В результате увидим:
[Ramapriya\Exceptions\MainException]
test exception (0)
Итак, нам осталось реализовать метод автозагрузки полученного массива. Для этой цели напишем метод с самым что ни на есть банальным названием loadClasses, куда передадим полученный массив:
public static function loadClasses(array $classes, $moduleId = null)
{
Loader::registerAutoloadClasses($moduleId, $classes);
}
Данный метод использует битриксовый лоадер, который и регистрирует массив с нашими классами.
Теперь осталось совсем немного — сформировать массив с классами и загрузить их с помощью написанного нами класса. Для этого в нашей папке lib создадим файл include.php:
<?php
use Bitrix\Main\Loader;
use Bitrix\Main\Application;
use Ramapriya\LoadManager\Autoload;
// загружаем наш модуль - обязательно нужно перед этим его установить, иначе ничего не будет работать
Loader::includeModule('ramapriya.loadmanager');
$defaultNamespace = 'Ramapriya';
$excludeFiles = ['include.php'];
$libDir = Application::getDocumentRoot() . '/local/php_interface/lib';
$autoloadClasses = Autoload::prepareAutoloadClassesArray($libDir, $defaultNamespace, $excludeFiles);
Autoload::loadClasses($autoloadClasses);
Далее подключим данный файл в init.php:
// init.php
$includeFile = $_SERVER['DOCUMENT_ROOT'] . '/local/php_interface/lib/include.php';
if(file_exists($includeFile)) {
require_once $includeFile;
}
Вместо заключения
Ну что же, поздравляю, наш велосипед готов и со своей функцией прекрасно справляется.
Исходники, как всегда, на гитхабе.
Спасибо за внимание.
===========
Источник:
habr.com
===========
Похожие новости:
- [ВКонтакте API, Программирование] Как реализовать свою идею и не сойти с ума на самоизоляции
- [JavaScript, ReactJS, VueJS, Разработка веб-сайтов] Устройство ленивой загрузки в популярных фронтенд-фреймворках (перевод)
- [DIY или Сделай сам, Natural Language Processing, Будущее здесь, Голосовые интерфейсы, Информационная безопасность] Голосовой помощник для совершения операций на бирже
- [Финансы в IT] Диверсификация и снижение рисков: как работают модельные портфели для инвестиций на бирже
- [Open source, Python] Ищем фильмы, книги и подкасты с помощью Python
- Выпуск Psalm 3.12, статистического анализитора для языка PHP. Альфа выпуск PHP 8.0
- [JavaScript, jQuery, Программирование, Разработка веб-сайтов] Как я в 15 лет написал свой первый jQuery плагин и как их создавать
- [DevOps, Системное администрирование] Новый HAProxy Data Plane API: два примера программной конфигурации
- [IT-стандарты, Образование за рубежом, Учебный процесс в IT] xAPI и IMS Caliper. Или ADL против IMS?
- [IT-стандарты, Интерфейсы, Спортивное программирование, Функциональное программирование] Минимизация кликов и горячие клавиши для жизни разработчика + Темнее Тёмной Темноты
Теги для поиска: #_1sbitriks (1С-Битрикс), #_api, #_php, #_razrabotka_vebsajtov (Разработка веб-сайтов), #_bitriks (Битрикс), #_d7, #_php, #_1sbitriks (
1С-Битрикс
), #_api, #_php, #_razrabotka_vebsajtov (
Разработка веб-сайтов
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 03:48
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Кто бы что ни говорил, но я считаю, что изобретение велосипедов — штука полезная. Использование готовых библиотек и фреймворков, конечно, хорошо, но порой стоит их отложить и создать что-то своё. Так мы поддерживаем мозг в тонусе и реализуем свой творческий потенциал. Статья обещает быть длинной, поэтому устраивайтесь поудобнее, я начинаю. Итак, Битрикс, а точнее, Bitrix Framework. Несмотря на наличие богатого API, периодически возникает необходимость в создании своих классов/библиотек, а также подключении сторонних. Поэтому для начала рассмотрим уже имеющиеся способы автозагрузки. Старый добрый include/require. Я его добавил сугубо для исторической справки. Хотя на заре своего программерского пути я складывал нужные классы и библиотеки в отдельную папку, создавал отдельный файл, куда инклудил все эти классы и уже потом инклудил файл с инклудами (прошу прощения за тавтологию). Composer. Позволяет подключать как собственные классы, так и сторонние библиотеки. Однако при добавлении новых классов требует ручного обновления. Кроме того, сами классы, файлы и неймспейсы также нужно писать вручную. О том, как подружить Битрикс с композером, можно почитать тут Битриксовый лоадер. Используется как для подключения модулей, так и для автозагрузки классов. Однако прежде, чем подключить нужные классы, придётся сформировать массив, где ключами будут выступать имена классов, а значениями пути к ним. И выглядеть это всё будет примерно так: $classes = [
'Namespace\\Package\\ClassName' => '/path/to/class.php' ]; Loader::registerAutloadClasses(null, $classes); Свои модули. Говорят, что это самый рекомендуемый способ — создаёшь модуль, устанавливаешь его в админке, затем подключаешь в любом месте и пользуешься в своё удовольствие. С виду всё просто, а в реальности мы имеем следующее:
Тем не менее, если вы написали свой локальный модуль и потом решили добавить в него ещё пару классов, то для их использования вам уже не потребуется переустановка модуля — просто вызываете нужные методы в нужном месте, и всё! Ну а теперь, собственно, сам велосипед... Проанализировав все вышеописанные методы, я задумался о том, что бы такое придумать, чтобы достаточно было просто складывать новые классы в определённое место, а они бы подгружались потом автоматически, без добавления новых элементов в массив неймспейсов и путей к файлам. В итоге было решено написать специальный модуль — как бы странно это ни звучало, но мне эта идея показалась более удачной, чем добавлять несколько функций в init.php — который будет автоматически подгружать все классы из необходимой директории. Опущу процесс написания установки/удаления модуля — кому надо, посмотрят в исходниках, а сразу перейду к основному функционалу. Т.к. изначально неизвестно количество уровней вложенности папок, то нужно, чтобы методы были рекурсивными. Также мы будем использовать класс Bitrix\Main\Loader, который и будет грузить классы. Представим, что мы решили складировать все наши классы в директорию /local/php_interface/lib: Также у нас могут быть файлы, которые не содержат классов и, соответственно, не должны быть включены в автозагрузчик, поэтому нужно также учесть и этот момент. Итак, поехали. namespace Ramapriya\LoadManager;
use Bitrix\Main\Loader; class Autoload { } Первым делом нам нужно получить всё содержимое нашей папки. Для этого напишем метод scanDirectory: public static function scanDirectory(string $dir) : array
{ $result = []; $scanner = scandir($dir); // сканируем содержимое директории foreach ($scanner as $scan) { switch ($scan) { // пропускаем case '.': case '..': break; default: // получаем путь вложенной папки или файла $item = $dir . '/' . $scan; $SplFileInfo = new \SplFileInfo($item); if($SplFileInfo->isFile()) { // если элемент является файлом, кладём в возвращаемый массив $result[] = $scan; } elseif ($SplFileInfo->isDir()) { // если элемент является директорией, вызываем текущую функцию и результаты кладём в возвращаемый массив $result[$scan] = self::scanDirectory($item, $result[$scan]); } } } return $result; } На выходе должно получиться следующее: Как мы видим, структура файлов соблюдена, поэтому можно приступать к формированию массива для автозагрузки: /* Тут уже добавляется переменная $defaultNamespace, которая и будет являться основой для имени класса.
Также добавим массив php-файлов, которые не должны попасть в автозагрузчик */ public static function prepareAutoloadClassesArray(string $directory, string $defaultNamespace, array $excludeFiles) : array { $result = []; // вызываем предыдущий метод $scanner = self::scanDirectory($directory); foreach ($scanner as $key => $value) { $sep = '\\'; switch(gettype($key)) { case 'string': // если тип ключа является строкой, скорее всего это директория $SplFileInfo = new \SplFileInfo($directory . '/' . $key); $classNamespace = $defaultNamespace . $sep . $key; if($SplFileInfo->isDir()) { // ещё раз проверяем, является ли ключ директорией, и если является, то вызываем текущую функцию, передавая в качестве аргументов полученные значения $tempResult = self::prepareAutoloadClassesArray($directory . '/' . $key, $classNamespace, $excludeFiles); foreach($tempResult as $class => $file) { // делаем прогон массива и записываем полученные данные в результат $result[$class] = $file; } } break; case 'integer': // если тип ключа - число, то с большой долей вероятности значением является файл $SplFileInfo = new \SplFileInfo($directory . '/' . $value); // получаем название класса из файла (поэтому я рекомендую именовать файлы и папки с соблюдением того же регистра, что и в классах) $classNamespace = $defaultNamespace . $sep . str_ireplace('.php', '', $SplFileInfo->getBasename()); // далее проверяем является ли значение php-файлом if( $SplFileInfo->isFile() && $SplFileInfo->getExtension() === 'php' ) { // прогоняем массив исключаемых файлов и проверяем, нет ли их среди наших значений foreach($excludeFiles as $excludeFile) { if($SplFileInfo->getBasename() !== $excludeFile) { // записываем в массив относительный путь файла с классом $result[$classNamespace] = str_ireplace($_SERVER['DOCUMENT_ROOT'], '', $directory . '/' . $value); } } } break; } } return $result; } Если всё сделано правильно, то в итоге мы получим сформированный массив для автозагрузки с помощью битриксового лоадера: Для проверки работоспособности добавим в папку с исключениями файл MainException.php, содержащий следующий класс: <?php
namespace Ramapriya\Exceptions; class MainException extends \Exception { public function __construct($message = null, $code = 0, Exception $previous = null) { parent::__construct($message, $code, $previous); } } Как мы видим, наш файл подгрузился в массив классов: Забегая вперёд, попробуем вызвать наше новое исключение: throw new Ramapriya\Exceptions\MainException('test exception');
В результате увидим: [Ramapriya\Exceptions\MainException] test exception (0) Итак, нам осталось реализовать метод автозагрузки полученного массива. Для этой цели напишем метод с самым что ни на есть банальным названием loadClasses, куда передадим полученный массив: public static function loadClasses(array $classes, $moduleId = null)
{ Loader::registerAutoloadClasses($moduleId, $classes); } Данный метод использует битриксовый лоадер, который и регистрирует массив с нашими классами. Теперь осталось совсем немного — сформировать массив с классами и загрузить их с помощью написанного нами класса. Для этого в нашей папке lib создадим файл include.php: <?php
use Bitrix\Main\Loader; use Bitrix\Main\Application; use Ramapriya\LoadManager\Autoload; // загружаем наш модуль - обязательно нужно перед этим его установить, иначе ничего не будет работать Loader::includeModule('ramapriya.loadmanager'); $defaultNamespace = 'Ramapriya'; $excludeFiles = ['include.php']; $libDir = Application::getDocumentRoot() . '/local/php_interface/lib'; $autoloadClasses = Autoload::prepareAutoloadClassesArray($libDir, $defaultNamespace, $excludeFiles); Autoload::loadClasses($autoloadClasses); Далее подключим данный файл в init.php: // init.php
$includeFile = $_SERVER['DOCUMENT_ROOT'] . '/local/php_interface/lib/include.php'; if(file_exists($includeFile)) { require_once $includeFile; } Вместо заключения Ну что же, поздравляю, наш велосипед готов и со своей функцией прекрасно справляется. Исходники, как всегда, на гитхабе. Спасибо за внимание. =========== Источник: habr.com =========== Похожие новости:
1С-Битрикс ), #_api, #_php, #_razrabotka_vebsajtov ( Разработка веб-сайтов ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 03:48
Часовой пояс: UTC + 5