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.

Você vai aprender
  • Por que muitos require viram um problema.
  • Como o spl_autoload_register funciona: 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.php e composer 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:

  1. Os arquivos foram carregados no momento do uso, não no começo.
  2. O segundo new Loja\Produto não chamou o autoload: depois de carregada, a classe fica conhecida até o fim do programa.
  3. A classe Pix nem 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;
    }
});
LinhaO 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-4No exemplo da Ana
Um prefixo de namespace corresponde a uma pasta baseLoja\ → src/
O resto do namespace vira subpastasLoja\Pagamento\ → src/Pagamento/
O nome da classe vira o nome do arquivo, com .phpPix → Pix.php
Maiúsculas e minúsculas têm que bater exatamenteProduto.php, e não produto.php
Um autoloader nunca dá erro: se não achar, só não carregaO return e o is_file

Exemplos de nome completo e arquivo, com o prefixo AnaSilva\Blog\ apontando para a pasta src/:

ClasseArquivo
AnaSilva\Blog\Postsrc/Post.php
AnaSilva\Blog\Comentario\Moderadorsrc/Comentario/Moderador.php
AnaSilva\Blog\Http\Controller\PostControllersrc/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.

Maiúsculas no Linux

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 pasta src/. 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ê fezPrecisa 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 velocidadecomposer 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

FormaQuando usar
require de cada arquivoScripts pequenos, com 1 ou 2 arquivos. Aulas e testes rápidos.
spl_autoload_register próprioPara entender como funciona; projetos sem Composer (hospedagem muito limitada).
Composer, "psr-4" + vendor/autoload.phpO 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.
Funções e constantes não têm autoload

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

Class "Loja\Cupom" not found, mesmo com autoload

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.

Failed opening required '…/src/Cupom.php'

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.

Failed opening required '…/vendor/autoload.php'

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.

Funciona no meu computador, mas não no servidor

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

ItemO 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-autoloadGera de novo os arquivos de autoload
composer dump-autoload --optimizeGera 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:

(function (string $classe) { … });

3. O arquivo que liga o Composer

Complete a linha que liga o autoload do Composer:

require __DIR__ . '';

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 require do 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 único require __DIR__ . '/vendor/autoload.php';.