PHP Autoload e PSR-4
Num projeto com 50 classes, cada arquivo que usa outras classes precisaria de uma pilha de require no topo. Esqueceu um? Erro. Incluiu um a mais? Desperdício. O autoload ("carregamento automático") inverte o jogo: você ensina ao PHP onde as classes moram, e ele carrega cada arquivo sozinho, só quando a classe for usada pela primeira vez.
- Por que muitos
requireviram um problema. - Como o
spl_autoload_registerfunciona: o PHP "pede socorro" quando não conhece uma classe. - A escrever um autoloader de verdade, de dez linhas.
- A regra PSR-4: o namespace vira o caminho da pasta.
- O autoload do Composer:
vendor/autoload.phpecomposer dump-autoload.
Antes, veja Namespaces: o autoload depende da regra "uma classe por arquivo, pastas iguais ao namespace" que aparece lá.
O problema: a pilha de require
A lojinha da Ana já tem três classes, cada uma no seu arquivo:
auto/
├── antes.php
└── src/
├── Carrinho.php « Loja\Carrinho
├── Produto.php « Loja\Produto
└── Pagamento/
└── Pix.php « Loja\Pagamento\Pix
<?php
namespace Loja;
class Produto
{
public function __construct(
public string $nome,
public float $preco,
) {}
}
<?php
namespace Loja;
class Carrinho
{
/** @var Produto[] */
private array $itens = [];
public function adicionar(Produto $p): void
{
$this->itens[] = $p;
}
public function total(): float
{
return array_sum(array_map(fn(Produto $p) => $p->preco, $this->itens));
}
}
<?php
namespace Loja\Pagamento;
class Pix
{
public function pagar(float $valor): string
{
return "Pix de R$ " . number_format($valor, 2, ",", ".") . " gerado";
}
}
O programa principal precisa de um require para cada uma:
<?php
require __DIR__ . '/src/Produto.php';
require __DIR__ . '/src/Carrinho.php';
require __DIR__ . '/src/Pagamento/Pix.php';
use Loja\Carrinho;
use Loja\Produto;
use Loja\Pagamento\Pix;
$carrinho = new Carrinho();
$carrinho->adicionar(new Produto("Caneca", 19.9));
$carrinho->adicionar(new Produto("Camiseta", 49.9));
echo (new Pix())->pagar($carrinho->total()), "\n";
$ php antes.php Pix de R$ 69,80 gerado
Funciona. Mas imagine o projeto crescendo:
- Cada página nova copia a mesma pilha de
require, e cada classe nova obriga a mexer em todas as páginas. - A ordem importa em alguns casos: uma classe que herda de outra (
class Pix extends Pagamento) só pode ser carregada depois da classe-mãe. - Carrega-se tudo, mesmo o que a página não usa: a página do blog carregaria a classe do Pix.
- Bibliotecas de terceiros têm centenas de arquivos. Ninguém escreveria esses
requireà mão.
A ideia: o PHP pede ajuda
Pense numa biblioteca: você não carrega todos os livros para a mesa "por precaução". Quando precisa de um, pede ao bibliotecário, que sabe em que estante ele está. O autoload é esse bibliotecário.
Funciona assim: quando o PHP encontra uma classe que ainda não conhece (por exemplo, em new Loja\Produto), em vez de dar erro na hora, ele chama uma função sua e diz "preciso da classe Loja\Produto, você sabe onde ela está?". A sua função faz o require do arquivo certo, e o PHP continua como se nada tivesse acontecido. Se ninguém resolver, aí sim vem o erro Class "…" not found.
Você registra essa função com spl_autoload_register (SPL é a "Standard PHP Library", a biblioteca padrão; register = "registrar").
Espiando os pedidos do PHP
Primeiro, um autoloader que não carrega nada: ele só mostra o que o PHP pediu.
<?php
spl_autoload_register(function (string $classe) {
echo "[autoload] o PHP pediu: $classe\n";
});
echo "Começo\n";
$existe = class_exists('Loja\Produto');
var_dump($existe);
echo Loja\Produto::class, "\n";
new Loja\Produto("Caneca", 19.9);
- spl_autoload_register(…)
- Registra a função que o PHP vai chamar quando não conhecer uma classe.
- function (string $classe)
- Uma função anônima. O PHP passa a ela o nome completo da classe, com namespace e sem a barra da frente.
- class_exists('Loja\Produto')
- "Essa classe existe?". Para responder, o PHP tenta o autoload.
- Loja\Produto::class
- Só monta o texto com o nome. Não precisa da classe, então não chama o autoload.
- new Loja\Produto(…)
- Precisa da classe de verdade: chama o autoload.
$ php espiao.php
Começo
[autoload] o PHP pediu: Loja\Produto « pedido do class_exists
bool(false) « ninguém carregou: "não existe"
Loja\Produto « ::class não chamou o autoload
[autoload] o PHP pediu: Loja\Produto « pedido do new
Fatal error: Uncaught Error: Class "Loja\Produto" not found in /home/ana/auto/espiao.php:10
Stack trace:
#0 {main}
thrown in /home/ana/auto/espiao.php on line 10
Repare: o autoloader foi chamado, não fez require nenhum, e o PHP, sem a classe, deu o erro de sempre. Agora vamos fazer a função trabalhar.
Um autoloader que carrega de verdade
A regra que a Ana segue é simples: a classe Loja\Produto está em src/Produto.php; a classe Loja\Pagamento\Pix, em src/Pagamento/Pix.php. Ou seja: tire o Loja\ do começo, troque as barras invertidas \ por barras normais / e acrescente .php.
<?php
spl_autoload_register(function (string $classe): void {
echo " [autoload] carregando $classe\n";
require __DIR__ . '/src/' . str_replace(['Loja\\', '\\'], ['', '/'], $classe) . '.php';
});
echo "1. criando o carrinho\n";
$carrinho = new Loja\Carrinho();
echo "2. criando dois produtos\n";
$carrinho->adicionar(new Loja\Produto("Caneca", 19.9));
$carrinho->adicionar(new Loja\Produto("Camiseta", 49.9));
echo "3. total: ", $carrinho->total(), "\n";
- str_replace(['Loja\\', '\\'], ['', '/'], $classe)
- Duas trocas de uma vez:
Loja\vira nada, e cada\restante vira/. Dentro das aspas simples,'\\'é uma barra invertida (a primeira "escapa" a segunda). - __DIR__ . '/src/' . … . '.php'
- Monta o caminho completo:
/home/ana/auto/src/Produto.php. O__DIR__é a pasta do arquivo atual (veja include e require).
$ php falante.php 1. criando o carrinho [autoload] carregando Loja\Carrinho 2. criando dois produtos [autoload] carregando Loja\Produto « só na primeira vez 3. total: 69.8
Três coisas importantes nessa saída:
- Os arquivos foram carregados no momento do uso, não no começo.
- O segundo
new Loja\Produtonão chamou o autoload: depois de carregada, a classe fica conhecida até o fim do programa. - A classe
Pixnem foi carregada, porque este programa não a usa.
Experimente: rode e veja quando cada classe é carregada. Depois mude a ordem das linhas (crie um Produto antes do Carrinho) e rode de novo. Neste editor, os arquivos ficam todos na mesma pasta; por isso o autoloader só tira o Loja\ do nome.
<?php
spl_autoload_register(function (string $classe): void {
echo " [autoload] carregando $classe\n";
require __DIR__ . '/' . str_replace('Loja\\', '', $classe) . '.php';
});
echo "1. criando o carrinho\n";
$carrinho = new Loja\Carrinho();
echo "2. criando dois produtos\n";
$carrinho->adicionar(new Loja\Produto("Caneca", 19.9));
$carrinho->adicionar(new Loja\Produto("Camiseta", 49.9));
echo "3. total: ", $carrinho->total(), "\n";
<?php
namespace Loja;
class Produto
{
public function __construct(
public string $nome,
public float $preco,
) {}
}
<?php
namespace Loja;
class Carrinho
{
private array $itens = [];
public function adicionar(Produto $p): void
{
$this->itens[] = $p;
}
public function total(): float
{
return array_sum(array_map(fn(Produto $p) => $p->preco, $this->itens));
}
}
Deixando o autoloader educado
O autoloader acima tem um defeito: ele tenta carregar qualquer classe, até as que não são da loja e as que não existem. Veja o que ele faz com uma classe que ainda não foi criada:
<?php
spl_autoload_register(function (string $classe): void {
require __DIR__ . '/src/' . str_replace('Loja\\', '', $classe) . '.php';
});
$c = new Loja\Cupom("ANA10");
$ php sem-if.php
Warning: require(/home/ana/auto/src/Cupom.php): Failed to open stream: No such file or directory in /home/ana/auto/sem-if.php on line 3
Fatal error: Uncaught Error: Failed opening required '/home/ana/auto/src/Cupom.php' (include_path='.:') in /home/ana/auto/sem-if.php:3
Stack trace:
#0 /home/ana/auto/sem-if.php(6): {closure:/home/ana/auto/sem-if.php:2}('Loja\\Cupom')
#1 {main}
thrown in /home/ana/auto/sem-if.php on line 3
O erro fala de arquivo, não de classe, o que confunde. Pior: num projeto com vários autoloaders (o seu e os das bibliotecas), o seu derrubaria o programa antes que o autoloader certo tivesse a chance de tentar. A regra de boa educação é: se a classe não é sua, ou o arquivo não existe, apenas volte (return) sem fazer nada. O PHP então pergunta ao próximo autoloader registrado, e, se ninguém achar, mostra o erro claro Class "…" not found.
Este é o autoloader completo da Ana, num arquivo próprio:
<?php
spl_autoload_register(function (string $classe): void {
$prefixo = 'Loja\\';
$pasta = __DIR__ . '/src/';
// A classe não é da loja? Não é comigo.
if (!str_starts_with($classe, $prefixo)) {
return;
}
// "Loja\Pagamento\Pix" -> "Pagamento\Pix" -> "Pagamento/Pix.php"
$relativo = substr($classe, strlen($prefixo));
$arquivo = $pasta . str_replace('\\', '/', $relativo) . '.php';
if (is_file($arquivo)) {
require $arquivo;
}
});
| Linha | O que faz |
|---|---|
$prefixo = 'Loja\\'; | O começo do namespace que este autoloader atende: Loja\. |
$pasta = __DIR__ . '/src/'; | A pasta onde esse namespace começa. |
str_starts_with($classe, $prefixo) | "O nome começa com Loja\?" (str_starts_with existe desde o PHP 8.0). Se não, return. |
substr($classe, strlen($prefixo)) | Corta o prefixo: Loja\Pagamento\Pix → Pagamento\Pix. |
str_replace('\\', '/', $relativo) | Troca as barras: Pagamento/Pix. |
is_file($arquivo) | Só faz o require se o arquivo existir. Se não existir, a função termina sem fazer nada. |
Com ele, o programa principal troca a pilha de require por um só:
<?php
require __DIR__ . '/autoload.php';
use Loja\Carrinho;
use Loja\Produto;
use Loja\Pagamento\Pix;
$carrinho = new Carrinho();
$carrinho->adicionar(new Produto("Caneca", 19.9));
$carrinho->adicionar(new Produto("Camiseta", 49.9));
echo (new Pix())->pagar($carrinho->total()), "\n";
print_r(array_map(fn($f) => basename($f), get_included_files()));
$ php depois.php
Pix de R$ 69,80 gerado
Array
(
[0] => depois.php
[1] => autoload.php
[2] => Carrinho.php « carregados pelo autoload,
[3] => Produto.php « na ordem em que foram usados
[4] => Pix.php
)
A função get_included_files() lista todos os arquivos que o PHP abriu; o basename deixa só o nome de cada um. E agora a classe que não existe dá o erro certo:
$ php cupom.php
Fatal error: Uncaught Error: Class "Loja\Cupom" not found in /home/ana/auto/cupom.php:4
Stack trace:
#0 {main}
thrown in /home/ana/auto/cupom.php on line 4
Para criar a classe Loja\Cupom, basta criar o arquivo src/Cupom.php. Nenhum outro arquivo precisa mudar.
PSR-4: a regra que todo mundo segue
A regra que a Ana inventou não é dela: é a PSR-4, uma recomendação da comunidade PHP adotada por praticamente todas as bibliotecas e frameworks (PSR quer dizer PHP Standards Recommendation, "recomendação de padrão do PHP"; veja Padrões PSR). Em palavras simples:
| Regra da PSR-4 | No exemplo da Ana |
|---|---|
| Um prefixo de namespace corresponde a uma pasta base | Loja\ → src/ |
| O resto do namespace vira subpastas | Loja\Pagamento\ → src/Pagamento/ |
O nome da classe vira o nome do arquivo, com .php | Pix → Pix.php |
| Maiúsculas e minúsculas têm que bater exatamente | Produto.php, e não produto.php |
| Um autoloader nunca dá erro: se não achar, só não carrega | O return e o is_file |
Exemplos de nome completo e arquivo, com o prefixo AnaSilva\Blog\ apontando para a pasta src/:
| Classe | Arquivo |
|---|---|
AnaSilva\Blog\Post | src/Post.php |
AnaSilva\Blog\Comentario\Moderador | src/Comentario/Moderador.php |
AnaSilva\Blog\Http\Controller\PostController | src/Http/Controller/PostController.php |
Repare que o prefixo não vira pasta (não existe src/AnaSilva/Blog/). Isso deixa as pastas curtas: só a parte "interna" do namespace aparece.
Para o PHP, nomes de classe não diferenciam maiúsculas: new loja\produto e new Loja\Produto são a mesma classe, se ela já estiver carregada. Mas o autoload transforma o nome em caminho de arquivo, e no Linux produto.php e Produto.php são arquivos diferentes:
$ php minuscula.php
Fatal error: Uncaught Error: Class "Loja\produto" not found in /home/ana/auto/minuscula.php:4
Stack trace:
#0 {main}
thrown in /home/ana/auto/minuscula.php on line 4
No Windows e no macOS (que, por padrão, não diferenciam maiúsculas nos nomes de arquivo), o mesmo código funciona, e o erro só aparece quando o site vai para o servidor Linux. Escreva sempre o nome exatamente como no arquivo.
O autoload do Composer
Na prática, quase ninguém escreve o próprio autoloader: o Composer, o gerenciador de pacotes do PHP, gera um pronto. Você só descreve a regra num arquivo composer.json:
{
"autoload": {
"psr-4": {
"Loja\\": "src/"
}
}
}
- "autoload"
- A seção que configura o carregamento automático.
- "psr-4"
- "Use a regra PSR-4."
- "Loja\\": "src/"
- O prefixo
Loja\fica na pastasrc/. Em JSON, a barra invertida também precisa ser dobrada; o prefixo termina sempre com\\.
O index.php agora carrega o autoloader do Composer, que fica na pasta vendor/:
<?php
require __DIR__ . '/vendor/autoload.php';
use Loja\Carrinho;
use Loja\Produto;
use Loja\Pagamento\Pix;
$carrinho = new Carrinho();
$carrinho->adicionar(new Produto("Caneca", 19.9));
$carrinho->adicionar(new Produto("Camiseta", 49.9));
echo (new Pix())->pagar($carrinho->total()), "\n";
Rodando antes de gerar o autoloader:
$ php index.php
Warning: require(/home/ana/auto-composer/vendor/autoload.php): Failed to open stream: No such file or directory in /home/ana/auto-composer/index.php on line 2
Fatal error: Uncaught Error: Failed opening required '/home/ana/auto-composer/vendor/autoload.php' (include_path='.:') in /home/ana/auto-composer/index.php:2
Stack trace:
#0 {main}
thrown in /home/ana/auto-composer/index.php on line 2
A pasta vendor/ ainda não existe. Quem a cria é o comando composer dump-autoload ("despejar o autoload", ou seja, gerar os arquivos):
$ composer dump-autoload Generating autoload files Generated autoload files $ find vendor | sort vendor vendor/autoload.php « o único arquivo que você inclui vendor/composer vendor/composer/autoload_classmap.php vendor/composer/autoload_namespaces.php vendor/composer/autoload_psr4.php « a sua regra Loja\ → src/ vendor/composer/autoload_real.php vendor/composer/autoload_static.php vendor/composer/ClassLoader.php « o autoloader de verdade vendor/composer/LICENSE $ php index.php Pix de R$ 69,80 gerado
Dá para ver a sua regra dentro de um dos arquivos gerados:
$ cat vendor/composer/autoload_psr4.php
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Loja\\' => array($baseDir . '/src'),
);
Por dentro, o ClassLoader.php faz o mesmo que o autoloader da Ana: registra uma função com spl_autoload_register e transforma nomes em caminhos. A vantagem é que ele também carrega todas as bibliotecas que você instalar com o Composer, cada uma com a sua regra, com um único require.
Quando rodar dump-autoload de novo
| Você fez | Precisa de composer dump-autoload? |
|---|---|
Criou uma classe nova numa pasta já mapeada (ex.: src/Cupom.php) | Não. A regra PSR-4 acha o arquivo sozinha |
Mudou a seção autoload do composer.json (novo prefixo, outra pasta) | Sim |
Apagou a pasta vendor/ | Sim (ou composer install, que também gera) |
| Vai publicar o site e quer o máximo de velocidade | composer dump-autoload --optimize: gera uma lista pronta classe → arquivo (classmap) |
Conferindo a primeira linha da tabela, com a classe Cupom criada depois do dump-autoload:
$ php cupom.php ANA10
Formas de carregar classes: qual usar
| Forma | Quando usar |
|---|---|
require de cada arquivo | Scripts pequenos, com 1 ou 2 arquivos. Aulas e testes rápidos. |
spl_autoload_register próprio | Para entender como funciona; projetos sem Composer (hospedagem muito limitada). |
Composer, "psr-4" + vendor/autoload.php | O padrão em qualquer projeto de verdade. |
Composer, "files" | Arquivos só com funções (sem classes), que precisam ser carregados sempre: funções não têm autoload. |
__autoload() | Nunca. Forma antiga, removida no PHP 8.0. Se você encontrar em tutorial velho, troque por spl_autoload_register. |
O autoload só existe para classes, interfaces, traits e enums. Um arquivo só com funções (como o funcoes.php da aula de Namespaces) ainda precisa de require, ou da seção "files" do Composer. É um dos motivos pelos quais o PHP moderno usa tanto classes com métodos estáticos.
Quando dá errado
O que significa: o autoloader rodou e não achou o arquivo. Verifique, nesta ordem: (1) o arquivo existe no caminho que a regra PSR-4 calcula? (2) As maiúsculas batem (Cupom.php, não cupom.php)? (3) O namespace dentro do arquivo está certo? (4) O nome da classe dentro do arquivo é o mesmo do arquivo?
Dica: coloque temporariamente um echo no seu autoloader, como no falante.php, para ver o nome e o caminho calculado.
O que significa: o autoloader fez require de um arquivo que não existe. O erro aponta para a linha do autoloader, não para a linha que usou a classe; o stack trace mostra a linha de verdade (#0 … sem-if.php(6)).
Como consertar: verifique o arquivo com is_file antes do require e devolva sem fazer nada quando ele não existir.
O que significa: a pasta vendor/ não existe. Acontece ao baixar um projeto do GitHub: a pasta vendor/ não vai junto (ela é gerada).
Como consertar: rode composer install (se o projeto tiver dependências) ou composer dump-autoload na pasta onde está o composer.json.
O que significa: quase sempre é maiúscula/minúscula. No Windows/macOS, produto.php e Produto.php são o mesmo arquivo; no servidor Linux, não.
Como consertar: renomeie o arquivo para ter exatamente o nome da classe.
Referência
| Item | O que faz |
|---|---|
spl_autoload_register(callable $f) | Registra um autoloader; pode haver vários, chamados em ordem |
spl_autoload_functions() | Lista os autoloaders registrados |
spl_autoload_unregister($f) | Remove um autoloader |
class_exists('Nome') | "A classe existe?" (tenta o autoload; class_exists('Nome', false) não tenta) |
get_included_files() | Lista os arquivos já carregados |
"autoload": {"psr-4": {"Loja\\": "src/"}} | Regra PSR-4 no composer.json |
"autoload": {"files": ["src/funcoes.php"]} | Arquivos que o Composer carrega sempre |
require __DIR__ . '/vendor/autoload.php'; | Liga o autoload do Composer |
composer dump-autoload | Gera de novo os arquivos de autoload |
composer dump-autoload --optimize | Gera um mapa classe → arquivo (mais rápido em produção) |
Exercícios
1. O arquivo da classe
Com a regra "Loja\\": "src/", em qual arquivo fica a classe Loja\Pagamento\Boleto?
2. Registrar o autoloader
Complete o nome da função que registra um autoloader:
3. O arquivo que liga o Composer
Complete a linha que liga o autoload do Composer:
4. Você criou src/Cupom.php com a classe Loja\Cupom, num projeto que já tem a regra PSR-4 Loja\ → src/. O que precisa fazer?
5. Quando o PHP chama o autoloader?
6. Um autoloader não encontrou o arquivo da classe. O que ele deve fazer?
Missão final: o index.php não tem nenhum require e por isso não acha as classes. Escreva um autoloader com spl_autoload_register que transforme Loja\Produto em Produto.php (neste editor, todos os arquivos ficam na mesma pasta). Se a classe não começar com Loja\ ou o arquivo não existir, ele não deve fazer nada. A saída deve ser Total: R$ 69,80.
<?php
// escreva o autoloader aqui
use Loja\Carrinho;
use Loja\Produto;
$carrinho = new Carrinho();
$carrinho->adicionar(new Produto("Caneca", 19.9));
$carrinho->adicionar(new Produto("Camiseta", 49.9));
echo "Total: R$ ", number_format($carrinho->total(), 2, ",", "."), "\n";
<?php
namespace Loja;
class Produto
{
public function __construct(
public string $nome,
public float $preco,
) {}
}
<?php
namespace Loja;
class Carrinho
{
private array $itens = [];
public function adicionar(Produto $p): void
{
$this->itens[] = $p;
}
public function total(): float
{
return array_sum(array_map(fn(Produto $p) => $p->preco, $this->itens));
}
}
<?php
spl_autoload_register(function (string $classe): void {
$prefixo = 'Loja\\';
if (!str_starts_with($classe, $prefixo)) {
return;
}
$arquivo = __DIR__ . '/' . substr($classe, strlen($prefixo)) . '.php';
if (is_file($arquivo)) {
require $arquivo;
}
});
use Loja\Carrinho;
use Loja\Produto;
$carrinho = new Carrinho();
$carrinho->adicionar(new Produto("Caneca", 19.9));
$carrinho->adicionar(new Produto("Camiseta", 49.9));
echo "Total: R$ ", number_format($carrinho->total(), 2, ",", "."), "\n";
Resumo
- Autoload = o PHP chama uma função sua quando encontra uma classe desconhecida; ela faz o
requiredo arquivo certo. spl_autoload_register(function (string $classe) { … })registra essa função. Ela recebe o nome completo, com namespace.- Cada classe é carregada só quando é usada, e só uma vez.
- Um bom autoloader não dá erro: se a classe não é dele ou o arquivo não existe, só volta.
- PSR-4: prefixo de namespace → pasta base; o resto do nome vira subpastas e arquivo
.php; maiúsculas contam. - Composer: regra no
composer.json,composer dump-autoload, e um únicorequire __DIR__ . '/vendor/autoload.php';.