Outra bicicleta: escrevendo seu próprio carregador automático de classe para Bitrix

Não importa o que alguém diga, mas acho que a invenção da bicicleta é uma coisa útil. Usar bibliotecas e estruturas prontas, é claro, é bom, mas às vezes você deve adiá-las e criar algo próprio. É assim que mantemos o cérebro em boa forma e realizamos nosso potencial criativo.



O artigo será longo, então sente-se quando eu começar.





UPD: Como se viu, o método descrito neste artigo não ajuda em todos os casos - quando se trata de ORM, onde a nomeação de classes e arquivos é diferente (por exemplo, a classe ConfigTable, que está no arquivo config.php), problemas e erros começam. Portanto, ainda é melhor usar o Composer.




Então, Bitrix, ou melhor, Bitrix Framework. Apesar da presença de uma API avançada, de tempos em tempos é necessário criar suas próprias classes / bibliotecas, além de conectar outras de terceiros. Portanto, para começar, vejamos os métodos de carregamento automático existentes.



Os bons e velhos incluem / exigem. Eu o adicionei puramente para referência histórica. Embora no início do meu caminho de programação, coloquei as classes e bibliotecas necessárias em uma pasta separada, criei um arquivo separado onde incluí todas essas classes e só depois incluí o arquivo com as inclusões (peço desculpas pela tautologia).



Compositor.Permite conectar suas próprias classes e bibliotecas de terceiros. No entanto, ao adicionar novas classes, é necessária uma atualização manual. Além disso, as próprias classes, arquivos e espaços para nome também precisam ser gravados manualmente. Você pode ler sobre como fazer o Bitrix fazer amizade com um compositor aqui, no



Bitrix loader . É usado para conectar módulos e para carregar automaticamente classes. No entanto, antes de conectar as classes necessárias, você precisará formar uma matriz, onde as chaves serão os nomes das classes e os valores do caminho para elas. E tudo será algo como isto:



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

Loader::registerAutloadClasses(null, $classes);


Módulos personalizados. Eles dizem que esta é a maneira mais recomendada - você cria um módulo, instala-o na área de administração, conecta-o em qualquer lugar e usa-o para seu prazer. Parece simples, mas, na realidade, temos o seguinte:



  • Além de escrever aulas, você também precisa registrar o procedimento para instalar e remover o módulo. Existem vários parâmetros e métodos necessários, sem os quais o módulo pode não funcionar (embora eu não saiba, ainda não o testei)
  • As aulas não funcionarão sem conectar o módulo
  • Nem sempre faz sentido mover uma classe para um módulo separado


No entanto, se você escreveu seu módulo local e decidiu adicionar mais algumas classes a ele, para usá-lo, não é mais necessário reinstalar o módulo - basta chamar os métodos necessários no lugar certo, e é isso!



Bem, agora, de fato, a bicicleta em si ...



Depois de analisar todos os métodos acima, pensei sobre o que criar para que fosse suficiente adicionar novas classes a um determinado local e elas fossem carregadas automaticamente, sem adicionar novos elementos à matriz de espaços para nome e caminhos de arquivo.



Como resultado, decidiu-se escrever um módulo especial - por mais estranho que possa parecer, mas essa idéia me pareceu mais bem-sucedida do que adicionar algumas funções ao init.php - que carregará automaticamente todas as classes do diretório necessário.



Eu omitirei o processo de escrever a instalação / remoção de um módulo - quem precisar, ele procurará a fonte e passará diretamente para a funcionalidade principal.



Porque inicialmente, o número de níveis de aninhamento de pastas é desconhecido; os métodos precisam ser recursivos. Também usaremos a classe Bitrix \ Main \ Loader, que carregará as classes.



Vamos imaginar que decidimos colocar todas as nossas classes no diretório / local / php_interface / lib:



imagem



Também podemos ter arquivos que não contêm classes e, portanto, não devem ser incluídos no carregador automático, portanto, esse ponto também deve ser levado em consideração.



Então vamos.



namespace Ramapriya\LoadManager;

use Bitrix\Main\Loader;

class Autoload
{
}


Primeiro de tudo, precisamos obter todo o conteúdo da nossa pasta. Para fazer isso, vamos escrever o 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;
    }


A saída deve ser a seguinte:







Como podemos ver, a estrutura do arquivo é respeitada, para que você possa começar a formar uma matriz para carregamento automático:



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


Se tudo for feito corretamente, no final obteremos uma matriz gerada para carregamento automático usando um carregador bitrix:







Para verificar a funcionalidade, adicione o arquivo MainException.php à pasta com exceções que contêm a seguinte classe:



<?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, nosso arquivo foi carregado em uma matriz de classes:







Olhando para o futuro, vamos tentar chamar nossa nova exceção:



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


Como resultado, veremos:



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


Portanto, resta implementar o método de carregamento automático para a matriz resultante. Para esse fim, escreveremos um método com o nome mais banal loadClasses, onde passaremos a matriz resultante:




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


Este método usa um carregador bitrix, que registra uma matriz com nossas classes.



Agora, resta muito pouco - formar uma matriz com classes e carregá-las usando a classe que escrevemos. Para fazer isso, em nossa pasta lib, crie um arquivo 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);


Em seguida, vamos incluir este arquivo no init.php:



// init.php

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

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


Em vez de uma conclusão



Bem, parabéns, nossa bicicleta está pronta e faz um excelente trabalho com sua função.

As fontes estão, como sempre, no github .



Agradecimentos para sua atenção.



All Articles