Como organizar a estrutura de pastas de um projeto PHP moderno (sem framework)

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 public precisa 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.php nã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).

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Rolar para cima