sábado, 28 de março de 2026

POR QUE SEU __DIR__ FALHOU OU O "INFERNO" DOS CAMINHOS NO PHP

POR QUE SEU __DIR__ FALHOU?

O que ganharei lendo este artigo?

Domínio de Escopo: Aprenderás que diretórios (pastas) e subdiretórios mudam a percepção do PHP.
Segurança com Constantes: Aprenderás a "travar" o caminho real do projeto para não depender tanto de onde o script está rodando.

Warning: fopen(leitura.txt): Failed to open stream: No such file or directory in/caminho/da/mente/do/ledor/inteligencia.php on line 0
Caro ledor, caso não quiser ler, na Conclusão tem um resumo (receitinha de bolo)!

No desenvolvimento web, existem três "raízes" diferentes acontecendo ao mesmo tempo no teu projeto|site|aplicativo, etc:

1- A Raiz do servidor web (DocumentRoot):
Depende de como você instala, geralmente, no Apache, é o /var/www/html no Linux e C:\Apache24\htdocs no Windows.
No Nginx geralmente é C:\nginx\html e /var/www/html ou /usr/share/nginx/html.
São o popular e famoso "localhost".
O Apache, Nginx, etc, olha para lá para saber onde o projeto|site|aplicativo começa.

2- A Raiz do Navegador (URL):
Quando você coloca barra (/) no HTML (ex.: <img src="/foto.jpg">), o navegador busca na raiz do domínio(localhost/foto.jpg), ignorando as pastas do site.

3- A "Raiz" do Script PHP (__DIR__):
É onde está o arquivo físico no SD|HD naquele momento, ou seja, para o PHP, a raiz é o diretório onde está o arquivo|script e é a partir deste diretório que o PHP vai procurar os arquivos chamados.

Parece complicado, mas confunde-se pela simplicidade.

Em vez de se fazer sempre essa pergunta programando em PHP: "onde estou para chamar os arquivos que quero?", veremos a maneira correta, bem mais simples.

Se estou no arquivo.php no caminho /pasta1/pasta2/arquivo.php então a raiz (__DIR__) para o PHP é a pasta2.

Se mudar o arquivo.php para a pasta1, então a raiz agora para o PHP é a pasta1.
É aquele negócio de ir e voltar diretórios|pastas (../../).

A bússola que te dava o norte era: onde estou? ...quem sou eu... de onde viemos, para onde vamos? Essa última parte foram apenas divagações minhas!
Tinha de se raciocinar a partir da pasta|diretório no qual está o arquivo onde se chamaria outros arquivos.

Então, o problema não é a constante __DIR__ em si, mas onde ela é definida.
Se tu cria e define, por exemplo, a constante RAIZ_SIS dentro do diretório /pasta3/, ela "nasce" com o DNA dessa pasta, ou seja, para o PHP essa é a "pasta raiz" a partir de onde ele vai procurar os arquivos.
Se tu usar essa mesma lógica em outro arquivo que não esteja dentro dessa mesma pasta, a constante terá outro valor.
Isso é o que causava e causa a confusão.

Por exemplo:
if (!defined('RAIZ_SIS')) {
define('RAIZ_SIS', __DIR__ . DIRECTORY_SEPARATOR);
}
sendo que RAIZ_SIS é o caminho real /opt/apache24/htdocs/pasta1/pasta2/, pois pasta2 é a "raiz" do projeto, do site, do aplicativo, etc.

Poderia ser /opt/apache24/htdocs/pasta1/ e daí pasta1 seria a "raiz", o diretório principal (__DIR__) do teu projeto, do teu site, do teu aplicativo, etc, és tu quem decides, pois o projeto é teu, bem como a culpa se tu errar, ela é tua, porém, justamente por ser tua, depois tu atribui esse parâmetro (a culpa) para quem tu quiser, mas vamos usar a estrutura pasta1/pasta2 para exemplificar.

EXEMPLOS

Lembrando que, em vez de /opt/apache24/htdocs/pasta1/pasta2/ pode ser /var/www/html/pasta1/pasta2 ou C:\Apache24\htdocs\pasta1\pasta2\ ou onde tu quiser de acordo com o que o sistema operacional permitir.

Lembrando também que:

DIRECTORY_SEPARATOR Janelas = \
DIRECTORY_SEPARATOR Linux|MacOS = /

Crio um diretório chamado "api" dentro de pasta2 que é a "raiz" (o diretório principal) do projeto.
O caminho para "api" é /opt/apache24/htdocs/pasta1/pasta2/api.

A partir de agora colocarei somente /pasta1/pasta2/, mas entenda que o localhost está antes.

Lembrando que /opt/apache24/htdocs e C:\Apache24\htdocs e /var/www/html são, por padrão, o famoso localhost no Linux e no Janelas, aquele que, quando tu digita localhost na barra de endereços, o Apache grita It works! porque encontrou o arquivo index.html (C:\Apache24\htdocs\index.html) e dentro dele tem escrito It works e se fica todo feliz porque conseguiu fazer o Apache funcionar.

No Linux, o localhost na instalação padrão é /var/www/html (a não ser que tu mude, mas ai é problema teu, não meu).

Você, caro leitor, se for uma pessoa inteligente, deve ter percebido a diferença do Linux para o Janelas do "detalhe" das barras separadoras que formam o caminho para os diretórios e arquivos.
Já veremos esse "detalhe" também.

Antes, caro leitor, veremos uma coisa chamada Estratégia do "Ponto de Entrada Único", do conceito de estratégia, em grego strateegia, em latim strategi, em francês stratégie... os senhores estão anotando?

Nessa estratégia profissional utilizamos um arquivo central chamado config.php ou autoload.php oude_o_nome_que_quiser.php na raiz real do projeto, ou seja, no nosso caso:/pasta1/pasta2/de_o_nome_que_quiser.php.

Neste arquivo definimos as constantes com seus caminhos que utilizaremos no projeto|site|aplicação, etc.

Define-se, por exemplo, a constante RAIZ_SIS (ou DE_O_NOME_QUE_QUISER, agora na versão maiúscula) uma única vez nesse arquivo de configuração.

Segue um exemplo:

<?php
// Este arquivo autoload.php fica na RAIZ do projeto|site|aplicativo...
// __DIR__ aqui SEMPRE serah a raiz.
// dirname(__DIR__) recua um nivel na estrutura de pastas (Funcao Nativa).
// Exemplo: dirname(__DIR__) . DIRECTORY_SEPARATOR . "autoload.php";
// __DIR__ . DIRECTORY_SEPARATOR; = C:\Apache24\htdocs\pasta1\pasta2\
// Janelas - RAIZ_SIS (ou PATH_ROOT) = C:\Apache24\htdocs\pasta1\pasta2\
// Linux - RAIZ_SIS (ou PATH_ROOT) = /opt/apache24/htdocs/pasta1/pasta2/
// Define a URL base do site:
// define('URL_BASE', 'http://localhost/pasta1/pasta2');
// PATH_ROOT: Caminho absoluto no disco (para utilizar com file_put_contents, require, include, etc).
// URL_BASE: Caminho do navegador (para utilizar com href, src de imagens, etc).
// DIRECTORY_SEPARATOR Janelas = \
// DIRECTORY_SEPARATOR Linux|MacOS = /
if (!defined('RAIZ_SIS')) {
    define('RAIZ_SIS', __DIR__ . DIRECTORY_SEPARATOR);
}
if (!defined('PASTA_API')) {
    define('PASTA_API', RAIZ_SIS . 'api' . DIRECTORY_SEPARATOR);
}

Aproveitamos e já definimos, também, uma constante para o diretório api chamada PASTA_API, cujo caminho é /opt/apache24/htdocs/pasta1/pasta2/api/.

Repare, vivente, que no final da tripa tem uma barra (/ ou \), essa é a constante predefinida DIRECTORY_SEPARATOR agindo.
O PHP identifica o sistema operacional e essa constante, se for chamada, finca no final da tripa a barra de acordo com o sistema operacional.

E assim tu vai, alegremente, definindo e/ou utilizando as constantes necessárias.

Lembrando que __DIR__ é uma das constantes mágicas do PHP.

Da página do PHP:

__DIR__ - "O diretório do arquivo. Se usada dentro de um include, o diretório do arquivo incluído será retornado.
Isso é equivalente a dirname(__FILE__).
O nome do diretório não possui barra no final, a menos que seja o diretório raiz."
Explica nada e diz coisa nenhuma.

Exemplo Real

Nesse exemplo real tenho essas chamadas aos arquivos:

require_once __DIR__ . DIRECTORY_SEPARATOR . 'autoload.php';
require_once PASTA_API . 'proc_criar.php';

E dentro do arquivo proc_criar.php tenho estas definições:

$arquivoCursos = RAIZ_SIS . 'cursos.txt';
$arquivoBkpExtra = $pastaExtra . DIRECTORY_SEPARATOR . 'cursos_bkp_extra.txt';
Criei um arquivo "teste.php", coloquei dentro o conteúdo abaixo para solucionar essa bagunça:

require_once  __DIR__ . DIRECTORY_SEPARATOR . "autoload.php";
$arquivoCursos = RAIZ_SIS . 'cursos.txt';
$pastaExtra = RAIZ_SIS . 'copiados';
$arquivoBkpExtra = $pastaExtra . DIRECTORY_SEPARATOR . 'cursos_bkp_extra.txt';
die("Caminho Real: " . realpath($arquivoBkpExtra));

Executei o arquivo e na saída auto-explicativa apareceu:

Caminho Real: C:\Apache24\htdocs\pasta1\pasta2\copiados\cursos_bkp_extra.txt
e
Caminho Real: /opt/apache24/htdocs/pasta1/pasta2/copiados/cursos_bkp_extra.txt

E executei também:

require_once  __DIR__ . DIRECTORY_SEPARATOR . "autoload.php";
$arquivoCursos = RAIZ_SIS . 'cursos.txt';
die("Caminho Real: " . realpath($arquivoCursos));
$pastaExtra = RAIZ_SIS . 'copiados';
$arquivoBkpExtra = $pastaExtra . DIRECTORY_SEPARATOR . 'cursos_bkp_extra.txt';

Saída:
Caminho Real: C:\Apache24\htdocs\pasta1\pasta2\cursos.txt
e
Caminho Real: /opt/apache24/htdocs/pasta1/pasta2/cursos.txt

Se tu mudar o nome da pasta "pasta2" para "projeto_final", não precisa mexer em nada porque o __DIR__ no arquivo central se atualiza sozinho.

RAIZ_SIS será exatamente a mesma string em qualquer lugar do sistema.
Se tu escrever "require_once RAIZ_SIS . 'cursos.txt'", o PHP sempre buscará o arquivo na pasta principal, esteja você no diretório /api/, /admin/ ou no index.php ou na casa do ... .

Se o arquivo ficasse dentro da pasta api, você usaria a constante que definiu no arquivo central: include_once PASTA_API . 'arquivo'.

Obs.: Ao usar a função realpath, o script em execução deve ter permissões de executável em todos os diretórios na hierarquia, caso contrário realpath() retornará false.

Liberdade total.
O PHP é o Linux das linguagens de programação!

CAMINHOS ABSOLUTOS E CAMINHOS RELATIVOS

Evitando o Inferno e Indo para o Céu

Não tem necessidade de usar explicitamente caminhos absolutos e/ou relativos em PHP.
No HTML, se você está em api/proc_criar.php e quer voltar para a raiz, você usa ../.
No PHP, se você usa __DIR__, ele te entrega o caminho completo do Windows/Linux/MacOS até aquela pasta específica.

Imagine que você tem o arquivo "processa.php" dentro da pasta api.

No Servidor (Linux): __DIR__ é /var/www/html/projeto/api
No seu PC (Windows): __DIR__ é C:\Apache24\htdocs\projeto\api

Temos esses arquivos nesses caminhos:
/var/www/html/projeto/api/processa.php;
/var/www/html/projeto/api/fincao.php;
/var/www/html/projeto/autoload.php;
ou
C:\Apache24\htdocs\projeto\api\processa.php
C:\Apache24\htdocs\projeto\api\fincao.php
C:\Apache24\htdocs\projeto\autoload.php

Quando você escreve no topo do arquivo processa.php:
require_once dirname(__DIR__) . DIRECTORY_SEPARATOR . "autoload.php";
require_once "fincao.php";
o PHP resolve isso "em tempo de execução".

A função nativa dirname retorna o caminho para o diretório pai, sendo que o diretório pai é um diretório acima.
O PHP não tem uma função nativa, por exemplo, childname, que avançasse para um diretório abaixo e nem precisa ter.

Ele (e você) não precisam mais ficar calculando onde estão e para onde ir.
O caminho do arquivo e as instruções estão ali, explícitos: mais explícitos do que um site pornográfico!
Isso funciona em qualquer sistema operacional e em qualquer pasta, desde que a estrutura de pastas do seu projeto continue a mesma.
Tu só teria de atualizar as chamadas se resolvesse mudar a estrutura de pastas do teu projeto, por exemplo, tirar um arquivo de dentro da pasta api e colocar ele na raiz.

Se a estrutura interna (diretórios e arquivos) for mantida, o código é 100% portátil.

Se um dia tu resolver atirar teu site do Janelas para Linux ou vice-versa, precisa fazer absolutamente nada, pois:

DIRECTORY_SEPARATOR Janelas = \
DIRECTORY_SEPARATOR Linux|MacOS = /

O próprio PHP se encarrega do trabalho.

Não coloque barras junto ao nome do arquivo chamado no require|include, coloque a constante predefinida DIRECTORY_SEPARATOR separada por um espaço e um ponto de cada lado, além de ser o jeito certo, a sintaxe fica linda de morrer! Fica um must!!!

Contudo, porém, todavia, entretanto, sempre dá para melhorar!

Imagine que teu projeto|site|aplicação cresceu e agora tu tem:

projeto/modulos/financeiro/scripts/processar.php

Em vez de escrever:

$arquivo = "../../../../cursos.txt"; (onde é fácil errar a quantidade de pontos e barras)

tu apenas faz:

$arquivo = RAIZ_SIS . 'cursos.txt';
É mais fino, elegante e sincero!

Pare de usar essa coisa pré-histórica de código morse: ../../.

Utilize constantes definidas por você junto com DIRECTORY_SEPARATOR (constante predefinida) E SEUSPROBLEMAS ACABARAM!!!

Repare vivente:

require_once __DIR__ . '/../arquivo.php';
Caminho Real: /opt/apache24/htdocs/pasta1/pasta2/pasta3/arquivo.php

Fica:
require_once RAIZ_SIS . 'pasta3'. DIRECTORY_SEPARATOR . 'arquivo.php';
Caminho Real: /opt/apache24/htdocs/pasta1/pasta2/pasta3/arquivo.php

O caminho do arquivo e as instruções estão ali, mais explícitos do que um site pornográfico!
Isso funciona em qualquer sistema operacional e em qualquer pasta, desde que a estrutura de pastas do seu projeto continue a mesma.
Lembrando que RAIZ_SIS = /opt/apache24/htdocs/pasta1/pasta2.

Caso um determinado diretório seja bastante utilizado, crie uma constante para ele dentro do autoload.php(ou config.php ou de_o_nome_que_quiser.php).

Com a constante DIR_PASTA3, o "include_once RAIZ_SIS . 'pasta3'. DIRECTORY_SEPARATOR .'arquivo.php';" transforma-se em:

include_once DIR_PASTA3 . 'arquivo.php';
É muito mais fino, mais elegante e mais sincero ainda!
Nem vou falar da facilidade, porque é óbvio.

O caminho real é, por exemplo,
/opt/apache24/htdocs/projeto/modulos/api/arquivo.php, pois DIR_PASTA3 é /var/www/html/projeto/modulos/api/, já com a barra (/) no final, pois ela foi definida junto com a constante no arquivo central.

Sem o arquivo central autoload|config|de_o_nome_que_quiser.php, em cada diretório do teu sistema (no/api, no /admin, no /relatorios, etc), você tem de calcular manual e mentalmente onde está cada arquivo a ser chamado.
Se você mudar o arquivo de lugar, tem que editar todos os arquivos referenciados.
Com o arquivo central só precisa acertar o arquivo central, por isso se chama arquivo central, porque centraliza centralizando a centralização.

Uma vez que o arquivo central de constantes foi carregado, todas as variáveis de caminho ($arquivoCursos, $arquivoBackup, $arquivoseilaoque, etc) funcionam magicamente com o mesmo nome em qualquer lugar, sem precisar calcular manual e mentalmente.

Com isso evita o inferno e vai para o céu.

Liberdade total.
O PHP é o Linux das linguagens de programação!

CONCLUSÃO

Utilize a constante mágica __DIR__ do PHP para chamar arquivos.
Utilize a propriedade do PHP de criar constantes.
Utilize as constantes predefiniddas do PHP.
Utilize, em especial, as constantes predefinidas DIRECTORY_SEPARATOR e dirname.

DIRECTORY_SEPARATOR Windows = \
DIRECTORY_SEPARATORLinux|MacOS = /

Em vez de escrever:
$arquivo = "../../../../cursos.txt"; (onde é fácil errar a quantidade de pontos e barras).

Faça:
$arquivo = dirname(RAIZ_SIS, 3) . DIRECTORY_SEPARATOR . 'cursos.txt';

A função dirname aceita o formato acima onde RAIZ_SIS é a constante criada por você e 3 é o número de diretórios para retornar e buscar o arquivo desejado.

Utilize constantes definidas por você junto com DIRECTORY_SEPARATOR e dirname.

Exemplo:
require_once __DIR__ . '/../arquivo.php';
Caminho Real: /opt/apache24/htdocs/pasta1/pasta2/pasta3/arquivo.php

Defina a constante no arquivo central:
if (!defined('RAIZ_SIS')) {
    define('RAIZ_SIS', __DIR__ . DIRECTORY_SEPARATOR); }

Faça:
require_once RAIZ_SIS . 'pasta3'. DIRECTORY_SEPARATOR . 'arquivo.php';
Caminho Real: /opt/apache24/htdocs/pasta1/pasta2/pasta3/arquivo.php

ou melhor ainda:
arquivo = dirname(RAIZ_SIS, 1) . DIRECTORY_SEPARATOR . 'cursos.txt';
A função dirname aceita o formato acima onde RAIZ_SIS é a constante criada por você e 1 é o número de diretórios para retornar e buscar o arquivo desejado.

O caminho do arquivo e as instruções estão ali explicitamente.
Isso funciona em qualquer sistema operacional e em qualquer pasta, desde que a estrutura de pastas do seuprojeto continue a mesma.
Com o arquivo central com as constantes predefinidas e criadas/definidas por você (autoload.php,config.php, de_o_nome_que_quiser.php) só precisa acertar o arquivo central sem mexer em mais nada.

Uma vez que o arquivo central de constantes foi carregado, todas as variáveis de caminho ($arquivoCursos,$arquivoBackup, $arquivoseilaoque, etc) funcionam magicamente com o mesmo nome em qualquer lugar, sem precisar calcular manual e mentalmente.

Você só precisa ter o cuidado ao chamar o arquivo central de acordo com o arquivo que o chama.

Em um arquivo que está no mesmo diretório (diretório principal) do arquivo autoload.php:

require_once __DIR__ . DIRECTORY_SEPARATOR . "autoload.php";

Em um arquivo que está um diretório abaixo do diretório "raiz" onde está o arquivo autoload.php (dirnameretorna um nível):
require_once __DIR__ . DIRECTORY_SEPARATOR . "autoload.php";

Receita de Bolo

Levando-se em conta que o arquivo "carretel.php" esteja em um diretório abaixo do diretório "raiz" do site|projeto|aplicação, etc, o diretório principal, no caso, é a pasta2, ou seja, o arquivo está no caminho real:

/var/www/html/pasta1/pasta2/pasta3/carretel.php.

Para chamar outros arquivos no topo do arquivo "carretel.php":
require_once __DIR__ . DIRECTORY_SEPARATOR . "bagoal.php"; <<< aqui tu caça um bagoal no mesmo diretório;
require_ONCE DIRNAME(__dir__) . DIRECTORY_SEPARATOR . 'fincao.php' <<< aqui tu encontra um fincao um diretório acima;
require_once RAIZ_SIS . 'api' . DIRECTORY_SEPARATOR . 'proc_criar.php'; <<< aqui tu deseja proc_criar um diretório abaixo;
$arquivoCursos = RAIZ_SIS . 'texticulo.txt'; <<< aqui tu pega o texticulo no mesmo nível e introduz o barbante numa variável.

Mais realmente e seriamente:
require_once __DIR__ . DIRECTORY_SEPARATOR . 'autoload.php'; <<< aqui busca autoload.php no mesmo diretório;
require_ONCE DIRNAME(__dir__) . DIRECTORY_SEPARATOR . 'funcao.php' <<< aqui busca funcao.php um diretório acima;
require_once RAIZ_SIS . 'api' . DIRECTORY_SEPARATOR . 'processa.php'; <<< aqui busca processa.php um diretório abaixo;
$arquivoCursos = RAIZ_SIS . 'cursos.txt'; <<< aqui busca cursos.txt no mesmo diretório e salva a string numa variável.

Liberdade total.
O PHP é o Linux das linguagens de programação!




Nenhum comentário:

Postar um comentário