Optimizar função para incluir classes procurando também nas sub-directorias – php arquivo classes

Pergunta:


Tenho a seguinte função para incluir classes quando estamos a tentar utilizar uma classe que ainda não foi definida:

/**
 * Attempt to load undefined class
 * @param object $class_name object class name
 */
function __autoload($class_name) {

    $incAutoload = dirname(__FILE__);
    $filename = $class_name.'.class.php';

    /* App Classes
     */
    $appPath = $incAutoload.'/classes/'.$filename;

    if (is_file($appPath)) {
        require_once($appPath);
        return true;
    }

    /* Website Classes
     */
    $sitePath = $incAutoload.'/classes/site/'.$filename;

    if (is_file($sitePath)) {
        require_once($sitePath);
        return true;
    }

    // ...
}

Problema

Sempre que é criada uma sub-directoria para organizar as classes do projeto, tenho que editar este ficheiro e incluir uma verificação para essa sub-directoria:

/* Google Classes
 */
$path = $incAutoload.'/classes/google/'.$filename;

if (is_file($path)) {
    require_once($path);
    return true;
}

Pergunta

Como posso optimizar esta função de forma a que a mesma procure pela classe na directoria base classes mas também em qualquer uma das sub-directorias existentes?

Autor da pergunta Zuul

Faça assim:

Arquivo na raiz:

new RootClasse();
new RootfooTree();

spl_autoload_register(function ($className) {
    $className = str_replace('Root\', '', $className);
    $className = strtr($className, '\', DIRECTORY_SEPARATOR);
    require $className.'.php';
});

Arquivo “Classe.php”, também na raiz:

namespace Root;

class Classe {
    public function __construct() {
        echo 'Raiz';
    }
}

Arquivo “Tree.php”, localizado em “raiz/foo”:

namespace Rootfoo;

class Tree {
    public function __construct() {
        echo 'Tree';
    }
}

Saída ao executar o primeiro código:

Raiz Tree

Assim, você utiliza o namespace para fazer o autoload, combinando o namespace com o caminho físico, conforme eu tinha comentado. É rápido, simples, elegante e sem iteração, e você só carrega os arquivos que realmente interessam. Acessar o disco é um processo lento, se você tiver inúmeras classes, por um processo de iteração sobre pastas e arquivos, prejudicaria muito seu desempenho.

Você pode usar a spl_autoload, veja um exemplo:

spl_autoload_register(NULL, FALSE);
spl_autoload_extensions('.php');
spl_autoload_register();

set_include_path(get_include_path() . PATH_SEPARATOR . '../');

OU

set_include_path(get_include_path() . PATH_SEPARATOR . __DIR__ . '/');

Existem várias maneiras de solucionar isso. Algumas são iterativas, mas isso tem um preço em termos de performance. Eu faria algo semelhante ao que propõe o Calebe Oliveira, porém sem utilizar a função __autoload, porque, segundo o manual:

Dica
spl_autoload_register() fornece uma alternativa mais flexível para ‘autoloading’ de classes. Por esta razão, o uso da função __autoload() é desencorajado e pode estar [ficar] obsoleto ou ser removido no futuro.

Segundo um comentário presente no manual, do PHP 5.3 em diante basta criar uma estrutura de diretórios que corresponda à estrutura dos seus namespaces, e incluir o seguinte no início do seu código, uma única vez:

spl_autoload_extensions(".php"); // comma-separated list
spl_autoload_register();

Cache em um Mapa

Uma das formas de fazer isso é armazenar um cachê de classes pelo nome num array. Fiz uma implementação básica:

function list_classes($dir, $array = array()){
    $files = scandir($dir);
    foreach($files as $f){
        if($f != '.' && $f != '..'){
            if (strcmp(pathinfo($f)['extension'], 'php') == 0) {
                $array[basename($f, '.php')] = $f;
            } else if(is_dir($dir.'/'.$f)) {
                list_classes($dir.'/'.$f, $array);
            }
      }
    }
    return $array;
}


$classes_cache = list_classes(dirname(__FILE__));
var_dump($classes_cache);

O código acima lista recursivamente os arquivos .php do diretório atual, incluindo subdiretórios e armazena num array (ou mapa) cujo índice (ou chave) é o nome do arquivo sem a extensão.

Exemplo, dada uma chamada list_classes('classes') a partir de main.php:

/
  main.php
  /classes
      Class1.php
      Class2.php
      /other_classes
          Class3.php

O resultado do array seria:

{
  'Class1' => 'Class1.php',
  'Class2' => 'Class2.php',
  'Class3' => 'other_classes/Class3.php'
}

Enfim, criando este cache global, basta usá-lo no seu método de autoload.

Entretanto, haverá problema se houver arquivos com o mesmo nome em diretórios diferentes. Neste caso, seria interessante adicionar uma verificação se o item já existe no array e lançar um erro ou aviso.

Além disso, se houverem muitas pastas e diretórios, isso pode afetar um pouco o desempenho do script, mas será feito apenas uma vez. Então, se vale ou não a pena essa técnica, depende de quantas vezes o método de autoload será chamado.

Lista de diretórios

Uma segunda abordagem é criar um array de diretórios e pesquisar se a classe existe em cada um deles. Note que a ordem do array ditará a prioridade da busca.

Segue um exemplo (baseado no SO):

function __autoload($class_name) {

    $array_paths = array(
        'classes/', 
        'classes/site'
    );

    foreach($array_paths as $path) {
        $file = sprintf('%s%s/class_%s.php', dirname(__FILE__), $path, $class_name);
        if(is_file($file)) {
            include_once $file;
        } 

    }
}

O array de diretórios poderia ser carregado automaticamente com um algoritmo similar ao anterior:

$array_paths = glob(dirname(__FILE__).'/../*', GLOB_ONLYDIR);

Dessa forma, não é necessário percorrer todos os subdiretórios, mas a cada carregamento de classe será necessário olhar o sistema de arquivos.

Padrão de Nomenclatura

Outra técnica usada por alguns frameworks, como o Zend Framework é colocar um underline no nome das classes para representar o caminho a partir de um diretório base. Por exemplo, a classe Animal_Cachorro fricaria dentro no diretório /Animal.

Segue um código de exemplo (baseado no SO):

function __autoload($class_name) {
    $filename = str_replace('_', DIRECTORY_SEPARATOR, strtolower($class_name)).'.php';
    $file = dirname(__FILE__).'/'.$filename;
    if (!file_exists($file)) return FALSE;
    include $file;
}

Este é o método mais direto e com melhor desempenho, pois é feito apenas uma cesso ao sistema de arquivos.

Porém, do meu ponto de vista, ele “suja” o nome das classes. Não me parece boa prática misturar a estrutura de diretórios com os nomes das suas classes apenas para facilitar a construção de frameworks e método utilitários.

A SPL tem classe DirectoryIterator para listar diretorios e arquivos. A ideia é pegar dos os diretorios abaixo de classes e retornar como um array e depois listar todos os arquivos de cada pasta.

function listarDiretorios($dirInicial){
        $dir = new  DirectoryIterator($dirInicial);

        $dirs = array();
        foreach ($dir as $item){
            if(!$item->isDot() && $item->isDir()){
                $dirs[] = $item->getFileName();
            }
        }
        return $dirs;
}

function listarArquivos($raiz, $dirs){
    $files = array();
    foreach ($dirs as $item){
        $dir = new  DirectoryIterator($raiz.$item);
        foreach ($dir as $subitem){
            if(!$subitem->isDot() && $subitem->isFile()){
                $files[$item][] = $subitem->getFileName();
            }
        }

    }
    return $files;
}

uso:

//essas constantes podem ser definados em um arquivo config.
define('PATH', dirname(dirname(__FILE__)));
define('CLASSES_PATH', dirname(__FILE__). DIRECTORY_SEPARATOR. 'classes'.DIRECTORY_SEPARATOR);

$dir = listarDiretorios(CLASSES_PATH);
$files = listarArquivos(CLASSES_PATH, $dir);

Fonte

Related Posts:

Qual a diferença entre AppCompatActivity e Activity? – android android-activity
Pergunta: Qual a diferença da AppCompatActivity para Activity ? A partir de qual versão a AppCompatActivity foi adicionada ao Android? Autor da pergunta Luhhh A diferença reside ...
Como abreviar palavras em PHP? – php string
Pergunta: Possuo informações comuns como nome de pessoas e endereços, e preciso que elas contenham no máximo 30 caracteres sem cortar palavras. Exemplo: 'Avenida Natalino João Brescansin' ...
Qual é a finalidade de um parêntese vazio numa declaração Lambda? – c# expressões-lambda característica-linguagem
Pergunta: Criei um exemplo de uma declaração Lambda sem argumentos, entretanto, estou com duvidas referente a omissão do parêntese vazio () na declaração. Veja o exemplo: class ...
Boas práticas para URI em API RESTful – api rest restful
Pergunta: Estou com dúvida em relação às URIs de alguns recursos da api que estou desenvolvendo. Tenho os recursos projetos e atividades com relação 1-N, ...
Dúvidas sobre a integração do MySQL com Java – java mysql netbeans
Pergunta: Estou criando um sistema no NetBeans, utilizando a linguagem Java e o banco de dados MySQL. Escrevi o seguinte código para realizar a conexão ...
Qual é a finalidade da pasta Model do framework Inphinit? – php inphinit
Pergunta: No Inphinit micro-framework existe a pasta Model que fica dentro da pasta application, e nela é onde ficam as classes, mas eu estou muito ...
Uso do ‘@’ em variáveis – javascript typescript coffeescript
Pergunta: Vejo em algumas linguagens que compilam para javascript, como TypeScript e CoffeeScript, o uso do @ em variáveis, como também, casos em que o ...
Qual tamanho máximo um arquivo JSON pode ter? – json arquivo
Pergunta: Vou dar um exemplo para conseguir explicar minha duvida: Preciso recuperar informação de imagens vindas de uma API, esse banco de imagens me retorna JSON's ...
O que é Teste de Regressão? – terminologia engenharia-de-software testes
Pergunta: Na matéria de Teste de Software o professor abordou um termo chamado Teste de Regressão, isto dentro da disciplina de teste de software. Sendo ...
O que é um construtor da linguagem? – php característica-linguagem
Pergunta: Em PHP, já li e ouvi várias vezes a respeito dos Construtores da Linguagem. Os casos que sempre ouvi falar deles foi em casos ...
Função intrínseca para converter numérico para string – cobol
Pergunta: Estou a tentar saber se existe alguma função intrínseca do COBOL para converter um data numérico para string sem precisar usar a cláusula REDEFINES: ( ...
Porque usar implements? – java android
Pergunta: Qual a diferença entre usar btn.setOnClickListener(new OnClickListener() { e public class MainActivity extends Activity implements OnClickListener{ Estive fazendo um curso de Android e meu professor falou que ...
O que é XHTML e quando deve ser usado? – html xml xhtml
Pergunta: O que eu sei é que o XHTML precisa ser XML válido. Isso implica, por exemplo, que todas as tags precisam ser fechadas. Por ...
Uma placa aceleradora de vídeo pode melhorar o desempenho não-gráfico? [fechada] – desempenho
Pergunta: Para desenvolver em Ruby on Rails, eu utilizo aqui uma máquina virtual do VirtualBox com Ubuntu Server 14.04 sem interface gráfica instalada. Recentemente descobri uma ...
Concat() VS Union() – c# .net
Pergunta: Qual a diferença entre Concat() e Union() ? Quando usar Concat() e quando usar Union() ? Somente pode ser usado em list ? ...

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *