Muita gente começa no PHP jogando tudo na mesma pasta: arquivos .php, imagens, CSS, conexão com banco, tudo misturado.
Funciona? Funciona. Mas vira um caos rapidinho:
- Difícil de achar os arquivos;
- Difícil de reaproveitar código;
- Difícil de deixar o site mais seguro;
- E bem mais difícil de crescer o projeto depois.
Neste guia você vai aprender a montar uma estrutura de pastas simples e moderna, parecida com o que frameworks usam, mas sem framework, só com PHP puro.
Objetivo: no final você terá um modelo pronto de organização para usar em qualquer projeto PHP.
1. Estrutura sugerida (visão geral)
Um exemplo de organização limpa:
/meu-projeto
/public -> Arquivos acessados pelo navegador
index.php
login.php
css/
js/
imagens/
/app -> Regras de negócio (código PHP do sistema)
Controladores/
Modelos/
Helpers/
/config -> Arquivos de configuração
Conexao.php
rotas.php
/storage -> Uploads, logs, arquivos gerados
uploads/
logs/
/vendor -> Bibliotecas instaladas com Composer
.env -> Variáveis de ambiente (credenciais etc.)
composer.json
Por que isso é melhor?
- Segurança: só a pasta
publicprecisa ser exposta no servidor; - Organização: você sabe exatamente onde cada tipo de arquivo deve ficar;
- Escalabilidade: fica muito mais fácil separar responsabilidades (controle, modelo, visão).
2. A pasta public/: o que realmente aparece para o usuário
A ideia é: o servidor (Apache, Nginx, etc.) deve apontar o DocumentRoot para a pasta public.
Tudo que o usuário acessa diretamente fica ali:
/public
index.php -> Página inicial
login.php -> Página de login
dashboard.php -> Painel após login
css/
js/
imagens/
Exemplo de public/index.php com autoload e estrutura limpa:
<?php
// Carrega o autoload do Composer
require __DIR__ . '/../vendor/autoload.php';
use App\Controladores\PaginaInicialControlador;
// Aqui você poderia usar um sistema de rotas simples.
// Para manter o exemplo básico, vamos só chamar o controlador.
$controlador = new PaginaInicialControlador();
$controlador->exibir();
Repare que o
index.phpnão faz regra de negócio.
Ele só chama uma classe responsável por isso (PaginaInicialControlador).
3. A pasta config/: configurações centralizadas
Tudo que é configuração e infraestrutura fica em config/:
- Conexão com banco (
Conexao.php); - Arquivo de rotas (
rotas.php); - Qualquer constante global importante.
Exemplo de config/Conexao.php (usando o padrão do post anterior)
<?php
namespace Config;
use PDO;
use PDOException;
use Dotenv\Dotenv;
class Conexao
{
private static ?PDO $instancia = null;
private function __construct() {}
public static function getConexao(): PDO
{
if (self::$instancia === null) {
try {
$caminhoBase = dirname(__DIR__);
$dotenv = Dotenv::createImmutable($caminhoBase);
$dotenv->load();
$driver = $_ENV['DB_DRIVER'] ?? 'mysql';
$host = $_ENV['DB_HOST'] ?? 'localhost';
$porta = $_ENV['DB_PORT'] ?? '3306';
$banco = $_ENV['DB_NAME'] ?? '';
$usuario = $_ENV['DB_USER'] ?? '';
$senha = $_ENV['DB_PASS'] ?? '';
$charset = $_ENV['DB_CHARSET'] ?? 'utf8mb4';
$dsn = "{$driver}:host={$host};port={$porta};dbname={$banco};charset={$charset}";
$opcoes = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
self::$instancia = new PDO($dsn, $usuario, $senha, $opcoes);
} catch (PDOException $e) {
// Em produção, grave em log em vez de exibir
die("Erro ao conectar ao banco de dados.");
}
}
return self::$instancia;
}
}
4. A pasta /app: cérebro do seu sistema
Aqui fica o código que realmente faz o sistema funcionar:
/app
/Controladores
PaginaInicialControlador.php
UsuarioControlador.php
/Modelos
Usuario.php
/Helpers
FuncoesData.php
FuncoesTexto.php
4.1. Controladores
Os Controladores decidem o que fazer com a requisição:
- Chamam modelos;
- Tratam dados do formulário;
- Escolhem qual “view” (página) exibir.
Exemplo simplificado: app/Controladores/PaginaInicialControlador.php:
<?php
namespace App\Controladores;
class PaginaInicialControlador
{
public function exibir(): void
{
$titulo = "Bem-vindo ao meu site em PHP organizado";
$mensagem = "Essa página está usando uma estrutura de pastas moderna.";
// Você poderia usar um sistema de views, mas aqui vamos só incluir um arquivo:
require __DIR__ . '/../../public/views/pagina_inicial.php';
}
}
Note que o controlador não imprime HTML direto.
Ele prepara os dados e inclui uma view para exibição.
4.2. Modelos
Os Modelos representam dados e regras ligadas ao banco (Usuário, Produto, etc.).
Exemplo: app/Modelos/Usuario.php:
<?php
namespace App\Modelos;
use Config\Conexao;
use PDO;
class Usuario
{
public function buscarTodos(): array
{
$pdo = Conexao::getConexao();
$sql = "SELECT id, nome, email FROM usuarios ORDER BY nome";
$comando = $pdo->prepare($sql);
$comando->execute();
return $comando->fetchAll();
}
}
4.3. Helpers
Helpers são funções auxiliares para não repetir código.
Exemplo: app/Helpers/FuncoesTexto.php:
<?php
namespace App\Helpers;
class FuncoesTexto
{
public static function limitarTexto(string $texto, int $limite = 100): string
{
if (mb_strlen($texto) <= $limite) {
return $texto;
}
return mb_substr($texto, 0, $limite) . '...';
}
}
Você pode usar isso em qualquer lugar do projeto:
use App\Helpers\FuncoesTexto;
$descricaoResumida = FuncoesTexto::limitarTexto($descricaoGrande, 150);
5. Pasta storage/: uploads e arquivos internos
Tudo que o usuário envia (imagens, PDFs, etc.) ou que o sistema gera (logs, relatórios) deve ir para fora da pasta pública. Exemplo:
/storage
/uploads
/usuarios
/produtos
/logs
app.log
Depois você pode criar um script PHP específico para entregar esses arquivos com autenticação, em vez de deixar o navegador acessar direto.
6. Configurando namespaces e autoload no Composer
Para essa estrutura ser agradável de usar, vamos configurar o autoload do Composer.
No composer.json:
{
"require": {
"vlucas/phpdotenv": "^5.6"
},
"autoload": {
"psr-4": {
"App\\": "app/",
"Config\\": "config/"
}
}
}
Depois rode:
composer dump-autoload
Agora você pode usar:
use App\Modelos\Usuario;
use Config\Conexao;
em qualquer arquivo, que o Composer vai encontrar as classes automaticamente.
7. Exemplo completo de fluxo (Home listando usuários)
7.1. Rota (index.php em /public)
<?php
require __DIR__ . '/../vendor/autoload.php';
use App\Controladores\PaginaInicialControlador;
$controlador = new PaginaInicialControlador();
$controlador->exibir();
7.2. Controlador (PaginaInicialControlador.php)
<?php
namespace App\Controladores;
use App\Modelos\Usuario;
class PaginaInicialControlador
{
public function exibir(): void
{
$modeloUsuario = new Usuario();
$usuarios = $modeloUsuario->buscarTodos();
$titulo = "Lista de usuários";
require __DIR__ . '/../../public/views/pagina_inicial.php';
}
}
7.3. View (public/views/pagina_inicial.php)
<!doctype html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<title><?= htmlspecialchars($titulo) ?></title>
</head>
<body>
<h1><?= htmlspecialchars($titulo) ?></h1>
<?php if (empty($usuarios)): ?>
<p>Nenhum usuário encontrado.</p>
<?php else: ?>
<ul>
<?php foreach ($usuarios as $usuario): ?>
<li>
<?= htmlspecialchars($usuario['nome']) ?> -
<?= htmlspecialchars($usuario['email']) ?>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</body>
</html>
8. Checklist para manter o projeto organizado
Antes de seguir em frente, confira:
- A pasta exposta no servidor é somente a
public/; - Arquivos PHP de regra de negócio estão em
/app, não na raiz do site; - Configurações de banco, e-mail, etc. estão em
/config+.env; - Uploads e logs estão em
/storage, fora da pasta pública; - Autoload do Composer configurado com namespaces (
App\\,Config\\); - Cada arquivo tem uma responsabilidade clara (controlador, modelo, helper, view).
9. Vantagens para o futuro do projeto
Com essa estrutura você ganha:
- Facilidade de manutenção (qualquer ajuste tem um lugar óbvio para ir);
- Segurança melhor (nada sensível exposto direto ao público);
- Código mais profissional, mais próximo de frameworks sem perder a liberdade do PHP puro;
- E um ambiente perfeito para produzir vários artigos, exemplos e tutoriais (coisa que o AdSense adora: conteúdo consistente e organizado).