Otra bicicleta: escribir su propio autocargador de clase para Bitrix

No importa lo que digan, pero creo que la invención de la bicicleta es algo útil. El uso de bibliotecas y marcos listos para usar, por supuesto, es bueno, pero a veces vale la pena posponerlos y crear algo propio. Así es como mantenemos el cerebro en buena forma y realizamos nuestro potencial creativo.



El artículo va a ser largo, así que siéntate cuando empiece.





UPD: Como resultó, el método descrito en este artículo no ayuda en todos los casos: cuando se trata de ORM, donde el nombre de las clases y los archivos es diferente (por ejemplo, la clase ConfigTable, que está en el archivo config.php), comienzan los problemas y errores. Por lo tanto, aún es mejor usar Composer.




Entonces, Bitrix, o más bien, Bitrix Framework. A pesar de la presencia de una API rica, de vez en cuando es necesario crear sus propias clases / bibliotecas, así como conectar las de terceros. Por lo tanto, para comenzar, veamos los métodos de carga automática existentes.



Los buenos viejos incluyen / requieren. Lo agregué solo para referencia histórica. Aunque al comienzo de mi ruta de programación, puse las clases y bibliotecas necesarias en una carpeta separada, creé un archivo separado donde incluí todas estas clases y solo luego incluí el archivo con las inclusiones (pido disculpas por la tautología).



Compositor.Le permite conectar sus propias clases y bibliotecas de terceros. Sin embargo, al agregar nuevas clases, requiere una actualización manual. Además, las clases mismas, los archivos y los espacios de nombres también deben escribirse manualmente. Puede leer sobre cómo hacer amigos de Bitrix con un compositor aquí



cargador de Bitrix . Se utiliza tanto para conectar módulos como para clases de carga automática. Sin embargo, antes de conectar las clases necesarias, deberá formar una matriz, donde las claves serán los nombres de las clases y los valores de la ruta a ellas. Y todo se verá así:



$classes = [
    'Namespace\\Package\\ClassName' => '/path/to/class.php'
];

Loader::registerAutloadClasses(null, $classes);


Módulos a medida. Dicen que esta es la forma más recomendada: crea un módulo, lo instala en el área de administración, luego lo conecta en cualquier lugar y lo usa para su placer. Parece simple, pero en realidad tenemos lo siguiente:



  • Además de escribir clases, también debe registrar el procedimiento para instalar y quitar el módulo. Existen varios parámetros y métodos necesarios, sin los cuales el módulo puede no funcionar (aunque no lo sé, no lo he probado)
  • Las clases no funcionarán sin conectar el módulo
  • No siempre tiene sentido mover una clase a un módulo separado


Sin embargo, si escribió su módulo local y luego decidió agregarle un par de clases más, para usarlos ya no necesita reinstalar el módulo, simplemente llame a los métodos necesarios en el lugar correcto, ¡y listo!



Bueno, ahora, de hecho, la bicicleta en sí ...



Después de analizar todos los métodos anteriores, pensé en qué pensar para que sería suficiente simplemente agregar nuevas clases a un lugar determinado, y luego se cargarían automáticamente, sin agregar nuevos elementos a la matriz de espacios de nombres y rutas de archivos.



Como resultado, se decidió escribir un módulo especial , por extraño que parezca, pero esta idea me pareció más exitosa que agregar algunas funciones a init.php, que cargará automáticamente todas las clases desde el directorio requerido.



Omitiré el proceso de escribir la instalación / eliminación del módulo; quien lo necesite, buscará en la fuente e irá directamente a la funcionalidad principal.



Porque inicialmente, se desconoce el número de niveles de anidamiento de carpetas, luego los métodos deben ser recursivos. También usaremos la clase Bitrix \ Main \ Loader, que cargará las clases.



Imaginemos que decidimos poner todas nuestras clases en el directorio / local / php_interface / lib:



imagen



Además, es posible que tengamos archivos que no contengan clases y, en consecuencia, no deberían incluirse en el cargador automático, por lo que este punto también debe tenerse en cuenta.



Entonces vamos.



namespace Ramapriya\LoadManager;

use Bitrix\Main\Loader;

class Autoload
{
}


En primer lugar, necesitamos obtener todo el contenido de nuestra carpeta. Para hacer esto, escribamos el método 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;
    }


El resultado debe ser el siguiente:







Como podemos ver, se respeta la estructura del archivo, por lo que puede comenzar a formar una matriz para la carga automática:



/*     $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;
    }


Si todo se hace correctamente, al final obtendremos una matriz generada para la carga automática usando un cargador de bitrix:







Para verificar la funcionalidad, agregue el archivo MainException.php a la carpeta con excepciones que contienen la siguiente clase:



<?php

namespace Ramapriya\Exceptions;

class MainException extends \Exception
{
    public function __construct($message = null, $code = 0, Exception $previous = null)
    {
        parent::__construct($message, $code, $previous);
    }
}


Como podemos ver, nuestro archivo se ha cargado en una serie de clases:







mirando hacia adelante, intentemos llamar a nuestra nueva excepción:



throw new Ramapriya\Exceptions\MainException('test exception');


Como resultado, veremos:



[Ramapriya\Exceptions\MainException] 
test exception (0)


Por lo tanto, nos queda por implementar el método de carga automática para la matriz resultante. Para este propósito, escribiremos un método con el nombre más banal loadClasses, donde pasaremos la matriz resultante:




    public static function loadClasses(array $classes, $moduleId = null)
    {
        Loader::registerAutoloadClasses($moduleId, $classes);
    }


Este método utiliza un cargador de bitrix, que registra una matriz con nuestras clases.



Ahora queda muy poco: formar una matriz con clases y cargarlas usando la clase que hemos escrito. Para hacer esto, en nuestra carpeta lib, cree un archivo 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);


A continuación, incluyamos este archivo en init.php:



// init.php

$includeFile = $_SERVER['DOCUMENT_ROOT'] . '/local/php_interface/lib/include.php';

if(file_exists($includeFile)) {
    require_once $includeFile;
}


En lugar de una conclusión



Bueno, felicidades, nuestra bicicleta está lista y hace un excelente trabajo con su función.

Las fuentes están, como siempre, en el github .



Gracias por tu atención.



All Articles