Русский
Русский
English
Статистика
Реклама

D7

Ещё один велосипед пишем свой автозагрузчик классов для Битрикс

03.07.2020 20:16:17 | Автор: admin
Кто бы что ни говорил, но я считаю, что изобретение велосипедов штука полезная. Использование готовых библиотек и фреймворков, конечно, хорошо, но порой стоит их отложить и создать что-то своё. Так мы поддерживаем мозг в тонусе и реализуем свой творческий потенциал.

Статья обещает быть длинной, поэтому устраивайтесь поудобнее, я начинаю.

Итак, Битрикс, а точнее, 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:

image

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

Итак, поехали.

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, содержащий следующий класс:

<?phpnamespace 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:

<?phpuse 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;}

Вместо заключения


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

Спасибо за внимание.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru