Definindo autoloader para múltiplos diretórios - Entendendo o padrão MVC na prática – Parte 03 Artigo

Conheça os cursos gratuitos do WebDevBr! - Inscreva-se!


Este artigo foi publicado a 3 anos, 9 meses, 3 semanas, 5 dias atrás.

Depois da vitória esmagadora deste artigo sobre os demais na pesquisa que fiz aqui sobre o que você gostaria de ver aqui no blog (foi 42% dos votos) estou aproveitando para adiantar este artigo que só ia aparecer por aqui lá para março.

Lembra no primeiro artigo quando criamos uma classe para carregar automaticamente os arquivos sem que precisemos usar o require ou include? Então, agora vou me aprofundar um pouco mais no assunto e montar uma classe que além de carregar os arquivos eu possa também informar a extensão, um sufixo, um prefixo e até mesmo vários diretórios separados, uma vantagem para quem quer separar os arquivos do "core" e a aplicação em sí.

Eu não vou usar todos os recursos no nosso framework, mas fica disponível pra vocês usarem em outras aplicações se quiserem.

Gostou deste artigo?

Receba atualizações semanais com novos artigos do WebDevBr e outras dicas!

Como o splautoloadregister() funciona

Para carregar o nosso autoloader vamos precisar usar a função splautoloadregister() do PHP que irá registra uma classe e uma função na pilha de carregamento de arquivos, a documentação do PHP chama isso de "pilha de __autoload da SPL" para efeitos de nome vamos usar "pilha de arquivos". A pilha de arquivos precisa ser ativada para ser usada e a função splautoloadregister() já faz isso por padrão, além disso se você estiver usando o __autoload(), splautoload() ou splautoloadcall() precisará registrar esta função com o splautoloadregister(), ele vai substituir todas as 3 que eu citei, ou seja, ao chamar o splautoload_register() elas param de funcionar.

Caso o arquivo carregado não exista ela vai retornar false, caso exista retornará true.

Vou colocar aqui na integra o texto da documentação em português brasileiro que está no site do PHP.

Registra a função na pilha de __autoload da SPL. Se a pilha ainda não estiver ativa, ela será ativada. Se nenhum parâmetro for passado, a implementação padrão spl_autoload será registrada. Quando o registro for bem sucedido, o retono é true; em caso de falha, será retornado false.

Se o seu código já possuir uma função __autoload, essa função deve ser registrada na pilha de __autoload explicitamente. Isso é necessário porque a splautoloadregister() irá de fato substituir internamente a função de __autoload por splautoload() ou splautoload_call().

Eu sempre leio a documentação em português de tudo, sempre que posso, inglês não é meu forte, mas é sempre uma boa prática abrir o texto em sua língua nativa, neste caso descobrimos um paragrafo que explica a principal vantagem de se usar este método ao invés do __autoload(), e quem já testou sabe o que estou dizendo: O __autoload() não pode mais ser chamado mais de uma vez, ao contrário de seu amigo mais "parrudo" (splautoloadregister()).

Parâmetros do splautoloadregister()

A função deve ser chamada da seguinte forma: splautoloadregister($autoload_function,$throw=true,$prepend=false), os valores informados (ex.: $throw=true ) são os padrões da função caso não sejam enviados

$autoload_function - Define qual função será chamada para executar o "include/require".
$throw - Diz o que deve ser feito quando a função definida anteriormente (autoload_function) não puder ser registrada.
$prepend - Se for verdadeiro (true) informa que esta função deve ser registrada no início da pilha de arquivos e não no final como é o padrão (false).

Obs.: Embora para usar esta função você precise da versão 5.1.2 ou superior o suporte a namespaces e o parâmetro prepend só foram adicionados na versão 5.3.0, a versão em pt-br não diz isso até o momento em que eu escrevi este artigo (FAIL).

Nosso autoloader

Com base no que vimos acima vamos criar uma classe que vai controlar a inclusão dos arquivos no framework.

Crie um arquivo na pasta base-do-frameworkLibraryErikCoreAutoload.php

<?php
/**
 * Arquivo de carregamento de classes
 *
 * PHP version 5
 *
 * @category Erik
 * @package  Core
 * @author   Erik Figueiredo <alecom@erikfigueiredo.com.br>
 * @license  http://opensource.org/licenses/gpl-license.php GNU Public License
 * @link     http://blog.erikfigueiredo.com.br/
 *
 */
 /**
 * Carregamento automático de Classes
 *
 * @category Erik
 * @package  Core
 * @author   Erik Figueiredo <falecom@erikfigueiredo.com.br>
 * @license  http://opensource.org/licenses/gpl-license.php GNU Public License
 * @link     http://blog.erikfigueiredo.com.br/
 *
 */
class AutoLoad
{

    protected $ext;
    protected $prefix;
    protected $sufix;

    /**
     * Define o caminho local até a raiz do script
     *
     * @param string $path caminho completo até o script
     *
     * @return  Não retorna nada
     *
     */
    public function setPath($path)
    {
        set_include_path($path);
    }

    /**
     * Define a extensão do arquivo a ser exportado
     *
     * @param string $ext a extensão sem o ponto
     *
     * @return  Não retorna nada
     *
     */
    public function setExt($ext)
    {
        $this->ext='.'.$ext;
    }

    /**
     * Define se havera algo a se colocar antes do nome do arquivo
     *
     * @param string $prefix o que vai antes do nome do arquivo
     *
     * @return  Não retorna nada
     *
     */
    public function setPrefix($prefix)
    {
        $this->prefix=$prefix;
    }

    /**
     * Define se havera algo a se colocar após o nome do arquivo
     *
     * @param string $sufix o que vai após o nome do arquivo
     *
     * @return  Não retorna nada
     *
     */
    public function setSufix($sufix)
    {
        $this->sufix=$sufix;
    }

    /**
     * Transforma a classe em caminho até o arquivo correspondente
     *
     * @param string $className caminho completo até o script
     *
     * @return  $fileName: o caminho até o arquivo da classe
     *
     */
    protected function setFilename($className)
    {
        $className = ltrim($className, "\");
        $fileName  = '';
        $namespace = '';
        if ($lastNsPos = strrpos($className, "\")) {
            $namespace = substr($className, 0, $lastNsPos);
            $className = substr($className, $lastNsPos + 1);
            $className = $this->prefix.$className.$this->sufix;
            $fileName  = str_replace("\", DS, $namespace) . DS;
        }
        $fileName .= str_replace('_', DS, $className) . $this->ext;
        return $fileName;
    }

    /**
     * Carrega arquivos da library
     *
     * @param string $className a classe a se carregar
     *
     * @return  Não retorna nada
     *
     */
    public function loadCore($className)
    {
        $fileName=$this->setFilename($className);
        $fileName=get_include_path().DS.'Library'.DS.$fileName;

        if (is_readable($fileName)) {
            include $fileName;
        }
    }

    /**
     * Carrega arquivos da aplicação
     *
     * @param string $className a classe a se carregar
     *
     * @return  Não retorna nada
     *
     */
    public function loadApp($className)
    {
        $fileName=$this->setFilename($className);
        $fileName=get_include_path().DS.'App'.DS.$fileName;

        if (is_readable($fileName)) {
            include $fileName;
        }

    }

    /**
     * Carrega os módulos da aplicação
     *
     * @param string $className a classe a se carregar
     *
     * @return  Não retorna nada
     *
     */
    public function loadModulos($className)
    {
        $fileName=$this->setFilename($className);
        $fileName=get_include_path().DS.'App'.DS.'Modulos'.DS.$fileName;

        if (is_readable($fileName)) {
            include $fileName;
        }
    }

    /**
     * Carrega outros arquivos
     *
     * @param string $className a classe a se carregar
     *
     * @return  retorna um erro caso o arquivo não seja encontrado
     *
     */
    public function load($className)
    {
        $fileName=$this->setFilename($className);
        $fileName=get_include_path().DS.$fileName;

        if (is_readable($fileName)) {
            include $fileName;
        } else {
            echo $fileName.' não encontrado!
';
            echo '<pre>';
            var_dump(debug_backtrace());
            echo '</pre>';
            exit;
        }
    }
}

O código está bem comentado, vamos dar uma repassada rápida, as funções estão divididas em dois tipos de visibilidade, protected e public, as funções com visibilidade protected estão disponíveis apenas internamente no objeto (class) e as que a herdarem, já os métodos public estarão visíveis em qualquer que precisarmos e que a classe esteja instanciada.

Os métodos com prefixo set (setPath, setExt, setPrefix, setSufix e setFilename) configuram algo, os com prefixo load (loadCore, loadApp, loadModulos e o próprio load) carregam arquivos em um diretório específico.

Como usar nosso autoloader

Agora que você já tem sua classe de carregamento automático de arquivos no php só vai precisar chama-lá e criar os arquivos com os namespaces corretos e o PHP vai se encarregar e fazer o resto (trabalho feito uma vez só).

Crie um arquivo chamado index.php na raiz do framework com o seguinte código.

<?php

header('Content-Type: text/html; charset=utf-8');

define('DS',DIRECTORY_SEPARATOR);
define('ROOT',dirname(__FILE__));

require 'Library'.DS.'Erik'.DS.'Core'.DS.'AutoLoad.php';
$autoLoad = new AutoLoad();
$autoLoad->setPath(ROOT);
$autoLoad->setExt('php');

spl_autoload_register(array($autoLoad, 'loadApp'));
spl_autoload_register(array($autoLoad, 'loadModulos'));
spl_autoload_register(array($autoLoad, 'loadCore'));
spl_autoload_register(array($autoLoad, 'load'));

use ErikCoreRouter;

$router = new Router();

Aqui eu segui também uma das regras PSR que cita que não se deve ter efeitos secundários e declarar símbolos no mesmo arquivo, declaramos símbolos no Autoload.php e efeitos secundários no index.php.

Uma coisa que não comentei aqui é como informar a classe que será usada e a função no splautoloadregister, eu disse acima que bastaria informar a função, mas no exemplo eu declarei isso com um array(), dizendo no primeiro item a classe a se usar ($autoLoad) e a função no segundo item.

Existem outras formas de se fazer isso? Sim! Melhores? Sim! Aqui eu ressaltei uma forma de o fazer, ainda temos que evitar ter que criar uma classe para cada diretório, mas dar o código é fácil, quero que vocês entendam o conceito. O importante é que você ganhou uma nova ferramenta para seu arsenal PHP, note que não usei o splautoloadextensions() e outras ferramentas do spl para não estender o artigo, mas se você quer mesmo se aprofundar no assunto participe da palestra no Eventials, você vai aprender muito, vale o investimento, garanto.

Aqui os arquivos desta aula e o estudo completo.


Cursos relacionados


* Parcelamento apenas cartão de crédito! Pode haver uma pequena variação no parcelamento em relação a simulações apresentadas!