Un autre vélo: écrire votre propre chargeur automatique de classe pour Bitrix

Peu importe ce que l'on dit, mais je pense que l'invention du vélo est une chose utile. Utiliser des bibliothèques et des frameworks prêts à l'emploi, bien sûr, est une bonne chose, mais parfois vous devriez les remettre à plus tard et créer quelque chose de votre choix. C'est ainsi que nous maintenons le cerveau en bonne forme et réalisons notre potentiel créatif.



L'article va être long, alors asseyez-vous comme je commence.





UPD: Il s'est avéré que la méthode décrite dans cet article n'aide pas dans tous les cas - quand il s'agit d'ORM, où la dénomination des classes et des fichiers est différente (par exemple, la classe ConfigTable, qui se trouve dans le fichier config.php), les problèmes et les erreurs commencent. Par conséquent, il est toujours préférable d'utiliser Composer.




Donc, Bitrix, ou plutôt Bitrix Framework. Malgré la présence d'une API riche, il est parfois nécessaire de créer vos propres classes / bibliothèques, ainsi que de connecter des classes tierces. Par conséquent, pour commencer, examinons les méthodes de chargement automatique existantes.



Bon vieux inclure / exiger. Je l'ai ajouté uniquement à titre de référence historique. Bien qu'à l'aube de mon chemin de programmation, j'ai mis les classes et bibliothèques nécessaires dans un dossier séparé, créé un fichier séparé dans lequel j'ai inclus toutes ces classes et seulement ensuite inclus le fichier avec les inclusions (je m'excuse pour la tautologie).



Compositeur.Vous permet de connecter à la fois vos propres classes et des bibliothèques tierces. Cependant, lors de l'ajout de nouvelles classes, une mise à jour manuelle est nécessaire. De plus, les classes elles-mêmes, les fichiers et les espaces de noms doivent également être écrits manuellement. Vous pouvez lire comment se faire des amis Bitrix avec un compositeur ici



Bitrix loader . Il est utilisé à la fois pour la connexion de modules et pour le chargement automatique des classes. Cependant, avant de connecter les classes nécessaires, vous devrez former un tableau, où les clés seront les noms des classes et les valeurs du chemin d'accès à celles-ci. Et tout cela ressemblera à quelque chose comme ceci:



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

Loader::registerAutloadClasses(null, $classes);


Modules personnalisés. Ils disent que c'est la manière la plus recommandée - vous créez un module, installez-le dans la zone d'administration, puis connectez-le n'importe où et utilisez-le pour votre plaisir. Cela semble simple, mais en réalité, nous avons ce qui suit:



  • En plus d'écrire des classes, vous devez également enregistrer la procédure d'installation et de suppression du module. Il existe un certain nombre de paramètres et de méthodes obligatoires, sans lesquels le module peut ne pas fonctionner (même si je ne sais pas, je ne l'ai pas testé)
  • Les cours ne fonctionneront pas sans connecter le module
  • Cela n'a pas toujours de sens de déplacer une classe dans un module séparé


Néanmoins, si vous avez écrit votre module local et que vous avez ensuite décidé d'y ajouter quelques classes supplémentaires, vous n'avez plus besoin de réinstaller le module pour les utiliser - appelez simplement les méthodes nécessaires au bon endroit, et c'est tout!



Eh bien, maintenant, en fait, le vélo lui-même ...



Après avoir analysé toutes les méthodes ci-dessus, j'ai réfléchi à ce qu'il fallait proposer pour qu'il suffise d'ajouter simplement de nouvelles classes à un certain endroit, et elles seraient ensuite chargées automatiquement, sans ajouter de nouveaux éléments au tableau d'espaces de noms et de chemins de fichiers.



En conséquence, il a été décidé d'écrire un module spécial - aussi étrange que cela puisse paraître, mais cette idée m'a semblé plus réussie que d'ajouter quelques fonctions à init.php - qui chargera automatiquement toutes les classes à partir du répertoire requis.



J'omettrai le processus d'écriture de l'installation / suppression du module - celui qui en a besoin, il cherchera dans la source, et ira directement à la fonctionnalité principale.



Car au départ, le nombre de niveaux d'imbrication des dossiers est inconnu, puis les méthodes doivent être récursives. Nous utiliserons également la classe Bitrix \ Main \ Loader, qui chargera les classes.



Imaginons que nous ayons décidé de mettre toutes nos classes dans le répertoire / local / php_interface / lib:



image



De plus, nous pouvons avoir des fichiers qui ne contiennent pas de classes et, par conséquent, ne devraient pas être inclus dans l'autoloader, donc ce point doit également être pris en compte.



Alors allons-y.



namespace Ramapriya\LoadManager;

use Bitrix\Main\Loader;

class Autoload
{
}


Tout d'abord, nous devons récupérer tout le contenu de notre dossier. Pour ce faire, écrivons la méthode 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;
    }


La sortie doit être la suivante:







Comme nous pouvons le voir, la structure du fichier est respectée, vous pouvez donc commencer à former un tableau pour le chargement automatique:



/*     $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 tout est fait correctement, nous obtiendrons à la fin un tableau généré pour le chargement automatique à l'aide d'un chargeur bitrix:







Pour vérifier la fonctionnalité, ajoutez le fichier MainException.php au dossier avec des exceptions contenant la classe suivante:



<?php

namespace Ramapriya\Exceptions;

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


Comme nous pouvons le voir, notre fichier a été chargé dans un tableau de classes: pour l'







avenir, essayons d'appeler notre nouvelle exception:



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


En conséquence, nous verrons:



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


Il nous reste donc à implémenter la méthode de chargement automatique pour le tableau résultant. Pour cela, nous écrirons une méthode avec le nom le plus banal loadClasses, où nous passerons le tableau résultant:




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


Cette méthode utilise un chargeur bitrix, qui enregistre un tableau avec nos classes.



Maintenant, il ne reste que très peu de choses - pour former un tableau avec des classes et les charger en utilisant la classe que nous avons écrite. Pour ce faire, dans notre dossier lib, créez un fichier 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);


Ensuite, incluons ce fichier dans init.php:



// init.php

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

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


Au lieu d'une conclusion



Eh bien, félicitations, notre vélo est prêt et fait un excellent travail avec sa fonction.

Les sources sont, comme toujours, sur le github .



Merci de votre attention.



All Articles