Usamos cookies para medir audiência e melhorar sua experiência. Você pode aceitar ou recusar a qualquer momento. Veja sobre o iMasters.
Estive vendo a função spl_autoload_register() e achei super interessante e útil. Mas levantou uma questão de performance quando eu implementei essa função em meus projetos.
Talvez, alguns achem que não há nenhum problema na questão que vou mencionar abaixo, porém, Eu dou grande valor à performance/desempenho de uma aplicação e não meço esforços para melhorar o código para alcançar o melhor resultado. Esse fascínio é por causa que uma aplicação de grande porte pode usar o seu código e o desempenho pode ser um fator importante para o sucesso da aplicação como um todo.
Então vamos lá...
Um exemplo simples de autoload:
Nome da aplicação: Imaster
Classes:
Arquivo: /forum/Imaster.class.php
class Imaster {}
Arquivo: /forum/Forum.class.php
class Forum {}
Arquivo: /forum/topico/Pergunta.class.php
class Pergunta {}
Diretórios dos arquivos
/forum/Imaster.class.php
/forum/Forum.class.php
/forum/topico/Pergunta.class.php
Agora nosso autoload para carregar a aplicação Imaster
Arquivo: /forum/_autoload.php
> function autoload($className){
//carrega arquivos pesquisando nos diretórios da aplicação Imaster
}
Teste
new Imaster();
new Forum();
new Pergunta();
//output
OK, classes instanciadas.
----
Agora a questão da Performance/Desempenho
----
Agora imaginemos uma outra aplicação.
Nome da aplicação: Smile
Classes:
Arquivo: /emoticon/Smile.class.php
class Smile {}
Arquivo: /emoticon/icones/Carinhas.class.php
class Carinhas {}
Diretórios dos arquivos
/emoticon/Smiles.class.php
/emoticon/icones/Carinhas.class.php
Autoload para carregar a aplicação Smile
Arquivo: /emoticon/_autoload.php
> function autoloadSmile($className){
//carrega arquivos pesquisando nos diretórios da aplicação Smile
}
-----------------------------
Usando as duas bibliotecas numa aplicação qualquer.
Arquivo: /Imaster.php
include_once('/forum/_autoload.php');include_once('/emoticon/_autoload.php');
//registra os autoloader que farão o carregamento dos arquivos requisitados
spl_autoload_register('autoload');
spl_autoload_register('autoloadSmile');
//Instanciando classes
//aplicação: Imaster
new Imaster();
new Forum();
new Pergunta();
//aplicação: Smile
new Smile();
new Carinhas();
//output
OK, todas as classes foram instanciadas.
A QUESTÃO
No arquivo Imaster.php, rodaria sem problema. Porém, quando queremos instanciar as classes da aplicação Smile, o autoload que irá ser requisitado primeiro será o da aplicação Imaster, então, será percorrido todos os diretórios desta aplicação para ver se acha os arquivos da aplicação Smile, sem sucesso, irá para o próximo autoload registrado (no caso o autoloadSmile), agora sim, irá achar os arquivos da aplicação Smile.
Exemplo da questão citada acima:
ao executar new Smile(); a aplicação irá fazer isso:
Tenta pelo /forum/_autoload.php
Checando se existe: /forum/Smile.class.php [não encontrado]
Checando se existe: /forum/topico/Smile.class.php [não encontrado]
Tenta pelo /emoticon/_autoload.php
Checando se existe: /emoticon/Smile.class.php [encontrado]
Nesta verificação sem sucesso, a aplicação pode perder muito tempo e performance se o primeiro autoload tiver muitos diretórios para checar... a minha idéia seria indicar à função spl_autoload, o autoload a ser executado primeiro, ou seja, reorganizar a ordem dos autoloads registrados.
Exemplo para demonstrar a idéia:
Arquivo: /Imaster.php
include_once('/forum/_autoload.php');include_once('/emoticon/_autoload.php');
//registra os autoloader que farão o carregamento dos arquivos requisitados
spl_autoload_register('autoload');
spl_autoload_register('autoloadSmile');
//Instanciando classes
//aplicação: Imaster
new Imaster();
new Forum();
new Pergunta();
//aplicação: Smile
spl_autoload_primary('autoloadSmile'); //ESSA FUNÇÃO NÃO EXISTE, É APENAS PARA DEMONSTRAR A IDÉIA
new Smile();
new Carinhas();
//output
OK, todas as classes foram instanciadas.
No caso acima, a aplicação não perderia tempo em pesquisar diretórios errados, iria direto no seu.
Alguém tem sugestões de como pode ser implementado essa idéia?
>
para os models prefiro não deixar no autoload
faço por include diretamente
no autoload deixo apenas as libraries nativas framework
Vejo muitos frameworks indepedentes onde o developer faz loops recursivos nos folders e subfolders desnecessariamente... a consequência é perda de performance, redundância..
exemplo,
se a url chama o módulo
http://foo.bar/?p=person/add
no controlador é feita a requisição e filtragem do parâmetro e montagem do path para a classe
$file = $_GET['p'];
$path = BASE_DIR . 'modules/' . $file;
if( file_exists($path) ){
require_once( $path );
}else{
// retorna um not found ou qq outra coisa..
}
*isso é apenas um escopo rápido, sem tratamento de dados, etc.. apenas para exemplo
sacou ?
não ha necessidade em ficar dando loop recursivo nas pastas dos módulos e outras que não são essenciais para o carregamento do sistema.
Hinom, eu também fazia o carregamento das classes direto por meio de include. Porém, numa página que use vamos supor 3 bibliotecas diferentes, já pensou quantos include seria necessário deixar explicito para rodar a aplicação? E dessa forma a tendencia do programador é dar include em todas as classes da biblioteca (a fim de não dar erro de dependência), coisa que nem sempre é necessário, pois pode não precisar de todas as classes. É justamente este o papel do autoload, incluir somente as classes necessárias para a aplicação funcionar e sem dar ao programador o trabalho de incluir todas as classes de forma manual.
Sem dúvida, eu vejo uma notável vantagem em usar autoload em todas as bibliotecas e evitar o include manual. A única questão que estou em busca de uma solução é na questão da performance em não fazer uma aplicação pesquisar diretórios que não seja do autoload especifico dele.
Se pudesse dizer ao php qual autoload deve executar primeiro seria muito bom, pois não perderia tempo pesquisando em diretórios errados.
no seu caso teria que criar um controle dos folders das classes que serão carregadas quando requisita um model
Leia sobre a PSR-0, padronize sua estrutura de arquivos e diretórios e não tenha mais problemas com autoloads complexos, lentos ou problemáticos pois até a a mais simples das implementações funcionará.
>
Leia sobre a PSR-0, padronize sua estrutura de arquivos e diretórios e não tenha mais problemas com autoloads complexos, lentos ou problemáticos pois até a a mais simples das implementações funcionará.
Eu dei uma lida rápida sobre o padrão PSR-0 e tive uma noção das especificações, vou ler mais profundamente durante a próxima semana.
Eu notei que a Zend atualmente usa em todas as suas classes namespace para facilitar ao autoload carregar as classes, é isso mesmo? (pelo que entendi do PSR-0, é essa a idéia).
No caso, para solucionar o problema de autoload que é a questão do tópico, acredito que o que você sugeriu é ter um diretório padrão para todas as aplicações/classes. E usando as especificações da PSR-0, com o uso de namespace eu consigo dizer ao autoload qual o diretorio dentro do diretório padrão estão os arquivos...
exemplo:
Arquivo: classeXYZ.php
namespace AplicacaoXYZ\;
class XYZ {}
Arquivo: _autoload.php
function autoload($className)
{
//lê o namespace, obtendo os diretórios da aplicação
//testa o arquivo se existe
//inclui o arquivo baseando-se no [b]diretório padrão[/b]
}
Seria mais ou menos isso, Bruno?
Eu notei que a Zend atualmente usa em todas as suas classes namespace para facilitar ao autoload carregar as classes, é isso mesmo? (pelo que entendi do PSR-0, é essa a idéia).
Namespaces servem para duas coisas. A mais básica delas é encurtar os nomes das classes.
Enquanto o ZF antigo usava uma classe chamada, por exemplo, Zend_Db_Table_Abstract, sendo o arquivo da classe Abstract.php estando localizado em Zend/Db/Table, com namespaces a classe em questão PASSARIA a se chamar apenas algo como TableAbstract.
Note que com naemspaces você terá uma pequena chateação com classes abstratas em interfaces quando nomeando-as em inglês, haja vista que abstract e interface são palavras reservadas.
E veja que o verbo ali em cima está bem destacado pois, pelo visto, no ZF2 essa classe não existe mais.
Enfim... A segunda e mais importante é evitar colisões com nomes de classes, para que se reduzisse, para não dizer eliminar, erros do tipo Cannot redeclare class XXX
No caso, para solucionar o problema de autoload que é a questão do tópico, acredito que o que você sugeriu é ter um diretório padrão para todas as aplicações/classes.
Sim e não.
SIM porque quanto melhor e mais bem definida for sua hierarquia de diretórios, melhor será sua organização, mais bem definidos serão seus namespaces e mais simples será seu autoloader.
E NÃO porque isso é com você. Se você acha trabalhoso criar Um/Diretório/Para/Cada/Subclassificação/Das/Suas/Classes ( :upset: ), basta você aumentar seu include_path e fazer um autoloader mais elaborado.
O problema aqui é que quanto mais extenso for seu include_path maior será o tempo de carregamento das suas classes pois antes de fazê-lo todo o include_path será varrido atrás de uma combinação.
E um autoloader muito complexo, cheio de IF's, tratamentos de strings, ordenação e sei lá mais o quê, pesará na performance geral do sistema.
E como tempo é dinheiro... :rolleyes:
E usando as especificações da PSR-0, com o uso de namespace eu consigo dizer ao autoload qual o diretorio dentro do diretório padrão estão os arquivos...
Como eu disse você consegue fazer o que quiser com seu autoloader. A PSR-0 é um consenso a que chegaram diversos programadores de peso do mundo do PHP e visa padronizar a forma como se codifica um código.
Isso garante que a estrutura do Código A será similar à estrutura do Código B, como diretórios, nomes de classes etc.
exemplo:
Arquivo: classeXYZ.php
namespace AplicacaoXYZ\;
class XYZ {}
Arquivo: _autoload.php
function autoload($className)
{
//lê o namespace, obtendo os diretórios da aplicação
//testa o arquivo se existe
//inclui o arquivo baseando-se no [b]diretório padrão[/b]
}
Você precisa ler e entender o que são namespaces.
Não somente para que você os defina corretamente (essa barra invertida antes do ponto-e-vírgula está errado) como para que você saiba o que vai acontecer com os códigos que estiverem dentro um namespace quando usados em outro e como usá-los nessa circunstância (importando-os).
Quanto ao seu autoloader, sempre que você usar uma classe que não tenha sido declarada no arquivo ainda (via include/require), ele será invocado e o parâmetro $className virá com todo o namespace definido mais o nome da classe.
Ou seja:
Quando a classe Baaz pertencente ao namespace Foo/Bar (veja abaixo) for chamada, o argumento daquela função será Foo/Bar/Baaz e a partir dessa string que você localiza a classe.
Classe Hipotética
<?php
namespace Foo\Bar;
class Baaz {}
O meu autoloader (e o de muita gente) é bem simples:
function( $classname ) {
$classname = stream_resolve_include_path(
str_replace( '\\', DIRECTORY_SEPARATOR, $classname ) . '.php'
);
if( $classname !== FALSE ) {
include $classname;
}
}
Substituo as barras invertidas pelo separador de diretórios e adiciono um .php. Pronto, já tenho path completo até o arquivo.
Então verifico se esse path pode ser resolvido em um arquivo válido e, se positivo, incluo o arquivo.
Eu sei que o propósito principal dos namespace é possibilitar utilizar classes com nomes iguais num mesmo contexto. Só dei ênfase à utilizacao com autoload por ser esse o assunto do tópico. A barra junto com o ponto e virgula eu não tinha notado que digitei, acho que foi na hora de colar aqui...
Enfim... realmente você deixou claro que os namespace são úteis tbm para o carregamento das classes. Eu tinha notado isso no código da Zend no github. É algo muito interessante para mim!
Agradeço por ter ajudado com a explicação e principalmente por expressar como você usa o seu autoload.
Uma pergunta... o autoload fica dependente de namespace? E se a classe não tiver namespace, neste caso o autoload falha?
Dependendo da implementação do autoloader pode sim falhar.
Não que ele não seja invocado, pois o autoloader é chamado sempre que uma classe não declarada for utilizada mas, a exemplo da simplicidade do meu autoloader acima, stream_resolve_include_path() não conseguirá resolver o path da classe, pois como o nome dela não terá as barras invertidas, aquele str_replace() não vai fazer nada.
Mas nada te impede de ter um sistema preparado para suportar classes com e sem namespaces ao mesmo tempo.
É aí que está a maior vantagem de spl_autoload_register() sobre __autoload(): Permitir mais de uma estratégia de carregamento sem sobrecarregar o próprio método de carregamento com N condicionais:
spl_autoload_register(
function( $classname ) {
$classname = str_replace( '\\', DIRECTORY_SEPARATOR, $classname ) . '.php';
if( stream_resolve_include_path( $classname ) !== FALSE) {
include $classname;
}
}
);
spl_autoload_register(
function( $classname ) {
$classname = str_replace( '_', DIRECTORY_SEPARATOR, $classname ) . '.php';
if( stream_resolve_include_path( $classname ) !== FALSE) {
include $classname;
}
}
);
Vai funcionar para tanto para a classe Baaz do namespace Foo\Bar, quanto para a classe Foo_Bar_Baaz, sem namespace algum.
Perfeito Bruno, entendi, muito bem explicado. Essa solução de autoload com namespace despertou meu interesse por eu ver que a Zend ta usando em toda as suas classes... com certeza essa forma é a mais indicada até o momento.
Já vou implementar um autoload dessa forma, valeu a dica dos dois autoload distintos.
Todas as classes não. Como que você aplica o autoload nele mesmo? O ZF, assim como uma boa maioria de frameworks que querem abraçar o mundo (o/) tem as diferentes estratégias de autoload também em classes.
Mas são classes sem namespaces senão ficaríamos num loop de carregamento infinito. :lol:
Mas enfim, eu não penso assim "se o ZF usa, tá certo" porque apesar de ser um framework sólido e parrudo, eu não curto todo o peso dele para coisa simples. É preferível levar em conta a adoção da PSR-0 como referencial.
Mesmo porque, xeretando no código você vê umas gafes sutis ou umas besteiras sendo feitas aqui e ali.
Aproveitando o tópico, quando uma classe possui um namespace como posso instanciar a classe sem ser pelo uso do nome do namespace? Estou com essa dificuldade...
Por exemplo:
Arquivo ferramentaA.php
namespace testeNamespace\ferramentas;
{
/**
* Representação de classe com uso de namespace
*
* @author Felipe
*/
class FerramentaA
{
public function __construct()
{
echo "FerramentaA instanciada<br />";
}
}
}
Arquivo objetoFerramenta.php
require_once('ferramentaA.php');
//chama classe pelo seu nome, sem usar o namespace.
new FerramentaA();
A chamada pelo nome sem namespace gera um fatal error. Por que?
Existem várias pequenas diferenças, vou cobrir as principais em dois exemplos.
Exemplo #1 - Dois arquivos sob mesmo namespace
Nesse exemplo dispensa-se o uso de use ou de barras invertidas para qualificação completa do(s) namespace(s):
<?php
namespace Foo/Bar;
class Baaz {}
<?php
namespace Foo\Bar;
class Baaz2 {
public function __construct() {
$baaz = new Baaz;
}
}
[b]Exemplo #2[/b] - Dois arquivos em namespaces diferentes
Você pode qualificar completamente, usando as barras invertidas ou importá-lo com [b]use[/b]:
[code]<?php
namespace Foo/Bar;
class Baaz {}
<?php
namespace FooDois\BarDois;
use \Foo\Bar\Baaz;
class Baaz2 {
public function __construct() {
$baaz = new Baaz; // Com o 'use' acima
$baaz = new \Foo\Bar\Baaz; // Sem o 'use' acima
}
}
Perceba que o use foi colocado FORA da definição da classe.
Algumas informações adicionais:
Podem haver casos em que mesmo utilizando um use o nome fique grande -OU- o nome da classe importada conflite com o nome da classe atual.
Nesses casos você utiliza um alias definido pelo uso de as após a qualificação feito com use:
<?php
namespace Foo\Bar;
use \My\Class\Inside\Too\Much\Subdirectories\AndWithAVeryLongAndUselessName as MyClass;
class Baaz {
public function __construct() {
$myClass = MyClass;
}
}
Existem ainda as classes nativas do PHP como ReflectionClass, DateTime e etc. Dentro de um namespace elas não vão funcionar pois ao fazer:
<?php
namespace Foo\Bar;
class Baaz2 {
public function __construct() {
$dateTime = new DateTime;
}
}
A classe DateTime será buscada como Foo\Bar\DateTime e como ela potencialmente não existirá, produzirá um erro.
Para esses casos, vale a mesma regra do Exemplo #2, isto é, prefixar com uma barra invertida:
<?php
namespace Foo\Bar;
class Baaz2 {
public function __construct() {
$dateTime = new \DateTime;
}
}
Você também pode importar com use, mas eu pessoalmente acho, hoje, inútil.
para os models prefiro não deixar no autoload
faço por include diretamente
no autoload deixo apenas as libraries nativas framework
Vejo muitos frameworks indepedentes onde o developer faz loops recursivos nos folders e subfolders desnecessariamente... a consequência é perda de performance, redundância..
exemplo,
se a url chama o módulo
http://foo.bar/?p=person/add
no controlador é feita a requisição e filtragem do parâmetro e montagem do path para a classe
if( file_exists($path) ){ require_once( $path ); }else{ // retorna um not found ou qq outra coisa.. }*isso é apenas um escopo rápido, sem tratamento de dados, etc.. apenas para exemplo
sacou ?
não ha necessidade em ficar dando loop recursivo nas pastas dos módulos e outras que não são essenciais para o carregamento do sistema.