Conteúdo
Organizar código não é só um capricho de programador metódico – é questão de sobrevivência. Se você já precisou voltar em um projeto PHP depois de alguns meses e não entendeu nada do que escreveu, bem-vindo ao clube. Um código bagunçado é como um quarto desorganizado: você até consegue encontrar as coisas, mas perde tempo, se estressa e, às vezes, descobre que deixou algo importante jogado no chão (ou pior, em produção).
Diferente dos frameworks como Laravel e Symfony, onde boa parte da organização já vem no pacote, um projeto PHP puro exige que você decida a estrutura desde o início. Sem um bom planejamento, é fácil cair na armadilha do “código espaguete”, onde regras de negócio, HTML e consultas SQL ficam todos misturados, transformando qualquer manutenção em um filme de terror.
Mas calma, não precisa reinventar a roda. Com algumas boas práticas, dá para manter um código organizado, modular e escalável, sem precisar de um framework gigante. E é exatamente isso que você vai aprender aqui: como estruturar seu projeto PHP puro de um jeito profissional, facilitando sua vida e a de quem (infelizmente) for dar manutenção no seu código no futuro.
Bora organizar essa bagunça?
Estruturando o Projeto
Se você já viu um projeto PHP puro onde todos os arquivos estão soltos na raiz, misturando HTML, lógica de negócio e queries SQL no mesmo lugar, sabe o que é o caos. Parece que alguém jogou código no editor e rezou para funcionar. Mas a gente pode (e deve) fazer melhor do que isso.
Uma boa organização de pastas e arquivos evita dores de cabeça, facilita a manutenção e impede que seu código vire um monstro incontrolável. Então, bora estruturar esse projeto do jeito certo:
Diretórios e Arquivos Essenciais
Aqui está um modelo de estrutura limpa e profissional para um projeto PHP puro:
1. /public
– O que o usuário pode acessar
Essa é a única pasta que deve estar disponível para o público. Nada de deixar arquivos sensíveis expostos. Aqui vão:
index.php
: O ponto de entrada do seu app. Ele recebe as requisições e repassa para o roteador.assets/
: CSS, JS, imagens e qualquer outra coisa estática.
Se o seu servidor estiver configurado corretamente, qualquer tentativa de acessar algo fora dessa pasta deve resultar em um belo 403 Forbidden. Segurança em primeiro lugar.
2. /src
– O cérebro do seu projeto
Aqui fica o código-fonte real do sistema, organizado em subpastas, como:
Controllers/
: Classes que lidam com as requisições e chamam os modelos e as views.Models/
: Suas classes de banco de dados. Cada entidade deve ter seu próprio model.Helpers/
: Funções auxiliares que você pode reaproveitar.
Esse diretório é onde a mágica acontece, então mantê-lo organizado faz toda a diferença.
3. /config
– Central de configurações
Aqui ficam os arquivos de configuração, como:
database.php
: Credenciais do banco de dados (de preferência, carregadas de um.env
e não hardcoded).app.php
: Outras configurações gerais do sistema.
Nada de misturar configurações dentro do código. Mantenha tudo separado para facilitar ajustes futuros.
4. /views
– Seu código front-end organizado
Se você está renderizando HTML direto no PHP, coloque os templates aqui. Se quiser algo mais profissional, pode usar um sistema de templates como Twig para separar lógica de exibição da lógica de aplicação.
Exemplo de organização:
/views
├── layout.twig
├── home.twig
├── perfil.twig
Isso evita aquele clássico cenário onde um arquivo .php
tem um bloco gigante de HTML misturado com PHP inline.
5. /vendor
– Dependências do Composer
Se você estiver usando Composer (e deveria estar), essa pasta armazena todas as bibliotecas instaladas. Nunca edite nada aqui manualmente. E nunca, jamais, suba essa pasta para o repositório! O .gitignore
já cuida disso por você.
6. /tests
– Código bom tem testes
Se você realmente se preocupa com a qualidade do seu código, aqui é onde entram os testes automatizados. Você pode usar PHPUnit para testar suas classes e garantir que tudo continue funcionando conforme esperado.
7. /storage
ou /logs
– Onde ficam os arquivos temporários
Se o seu projeto gera arquivos temporários, logs ou uploads, melhor mantê-los separados para não sujar o resto do código. Um bom exemplo é armazenar os logs de erro aqui para facilitar a depuração.
Com essa estrutura, seu projeto PHP puro fica muito mais organizado, profissional e fácil de manter. Seguir um padrão evita o caos e permite que qualquer desenvolvedor (incluindo o você do futuro) entenda rapidamente onde cada coisa está.
Agora que a casa está arrumada, podemos partir para o próximo passo: separar as responsabilidades e deixar esse código ainda mais profissional. Bora?
Separação de Responsabilidades (MVC simplificado)
Agora que organizamos a estrutura do projeto, precisamos garantir que cada parte do código tenha seu devido lugar. E nada melhor para isso do que seguir o bom e velho MVC (Model-View-Controller) – mas sem complicação. Não estamos usando frameworks gigantes como Laravel ou Symfony, então a ideia aqui é manter as coisas simples, sem burocracia.
Se você já viu código PHP onde HTML, SQL e lógica de negócio estão misturados no mesmo arquivo, sabe que aquilo é um pesadelo. Separar responsabilidades faz seu código ficar mais limpo, modular e fácil de manter. Então, bora entender como funciona essa divisão.
Como funciona o MVC?
O conceito é simples:
- Model → Cuida da comunicação com o banco de dados.
- View → Exibe a interface para o usuário.
- Controller → Processa as requisições e chama os Models e Views necessários.
Com essa separação, se amanhã você precisar mudar a interface sem alterar a lógica, ou trocar de banco de dados sem refazer tudo, seu código continua funcionando sem dramas.
Implementação do MVC na prática
1. Models – Lidando com o banco de dados
Os Models são responsáveis por tudo relacionado ao banco de dados. Eles devem conter apenas métodos que buscam, inserem, atualizam ou excluem informações, sem se preocupar com HTML ou regras de exibição.
Exemplo de um Model simples:
// src/Models/User.php
class User {
private $pdo;
public function __construct($pdo) {
$this->pdo = $pdo;
}
public function getUserById($id) {
$stmt = $this->pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);
return $stmt->fetch();
}
}
Aqui, o Model User apenas busca dados do banco. Ele não sabe nada sobre como essa informação será exibida, e é exatamente assim que deve ser.
2. Views – Exibindo a interface para o usuário
A View é responsável por mostrar os dados para o usuário. Ela não deve conter regras de negócio nem acesso ao banco. Apenas recebe as informações processadas e as exibe de forma bonita.
Exemplo de uma View usando Twig:
<!-- views/user.twig -->
<h1>Perfil de {{ user.name }}</h1>
<p>Email: {{ user.email }}</p>
Simples, direto e sem PHP bagunçado no meio. O Controller passa os dados e a View apenas exibe.
Se não quiser usar Twig, você pode fazer algo assim em um arquivo .php
:
<!-- views/user.php -->
<h1>Perfil de <?php echo htmlspecialchars($user['name']); ?></h1>
<p>Email: <?php echo htmlspecialchars($user['email']); ?></p>
O importante é manter a separação e evitar colocar lógica de programação misturada no HTML.
3. Controllers – Processando requisições e chamando os Models e Views
O Controller é o intermediário entre o usuário e o sistema. Ele recebe a requisição, chama o Model para buscar ou salvar dados e depois carrega a View correspondente.
Exemplo de um Controller básico:
// src/Controllers/UserController.php
class UserController {
private $userModel;
public function __construct($userModel) {
$this->userModel = $userModel;
}
public function show($id) {
$user = $this->userModel->getUserById($id);
if (!$user) {
die("Usuário não encontrado!");
}
include __DIR__ . "/../../views/user.php";
}
}
Aqui, o UserController pega o ID da requisição, busca os dados no Model e carrega a View correta.
Amarrando tudo com um roteador simples
Para fazer a conexão entre URLs e Controllers, podemos criar um roteador básico:
// public/index.php
require '../config/database.php';
require '../src/Models/User.php';
require '../src/Controllers/UserController.php';
$pdo = new PDO('mysql:host=localhost;dbname=meubanco', 'root', '');
$userModel = new User($pdo);
$userController = new UserController($userModel);
// Roteamento básico
$rota = $_GET['rota'] ?? '';
if ($rota === 'user' && isset($_GET['id'])) {
$userController->show($_GET['id']);
} else {
echo "Página não encontrada!";
}
Com isso, ao acessar meusite.com/user?id=1
, o Controller chamará o Model, buscará os dados e renderizará a View.
Separar responsabilidades no PHP puro não precisa ser um bicho de sete cabeças. Com um MVC simplificado, seu código fica mais limpo, organizado e fácil de manter.
Seguindo esse padrão, você pode expandir seu projeto sem medo, trocar o banco de dados sem refatorar tudo e, principalmente, evitar aquele código espaguete que dá dor de cabeça na manutenção.
Agora que você já tem uma estrutura decente, que tal implementar um roteador mais robusto e adicionar camadas extras de segurança? Bora pro próximo nível!
Autoload e Composer
Se você já precisou incluir vários arquivos PHP manualmente usando require
ou include
, sabe como isso pode virar um caos. Quanto maior o projeto, pior fica: arquivos espalhados, dependências difíceis de rastrear e manutenção dolorosa.
É aqui que o Composer entra como um verdadeiro salvador. Além de gerenciar pacotes externos, ele nos permite configurar um autoloading eficiente, seguindo o padrão PSR-4, que facilita a organização e carregamento das classes automaticamente.
Vamos ver como configurar isso de maneira simples e eficaz.
Configurando o Composer para Autoloading
O primeiro passo é garantir que o Composer esteja instalado. Se ainda não tem ele no seu sistema, pode instalar seguindo as instruções no site oficial.
Agora, dentro da raiz do seu projeto, rode o seguinte comando:
composer init
Ele fará algumas perguntas sobre o projeto e, ao final, gerará um arquivo composer.json
. Caso já tenha esse arquivo, basta editá-lo manualmente.
Agora, configure o autoloading para seguir o padrão PSR-4. Abra o composer.json
e adicione:
{
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}
Isso significa que toda classe que estiver dentro do diretório src/
será automaticamente carregada quando for usada no código, desde que siga a estrutura de namespaces corretamente.
Após isso, rode o comando para gerar os arquivos de autoloading:
composer dump-autoload
Agora o Composer está pronto para carregar suas classes sem precisar de require
manualmente.
Exemplo prático de uso do Autoload
Com o autoload configurado, podemos estruturar nossas classes com namespaces e evitar aquela bagunça de includes.
1. Criando um Model usando namespace
Crie o arquivo src/Models/User.php
:
namespace App\Models;
class User {
public function getName() {
return "João da Silva";
}
}
Repare que a classe User agora está dentro do namespace App\Models
. Isso permite que ela seja carregada automaticamente.
2. Criando um Controller que usa o Model
Agora, crie o arquivo src/Controllers/UserController.php
:
namespace App\Controllers;
use App\Models\User;
class UserController {
public function show() {
$user = new User();
echo "Nome do usuário: " . $user->getName();
}
}
Aqui, estamos usando use App\Models\User;
para importar a classe User, sem precisar de require
manualmente.
3. Inicializando tudo no index.php
Por fim, no public/index.php
, basta carregar o autoload do Composer e chamar as classes diretamente:
require '../vendor/autoload.php';
use App\Controllers\UserController;
$controller = new UserController();
$controller->show();
Isso já basta para carregar todas as classes automaticamente! Nada de ficar caçando arquivos e escrevendo dezenas de require
.
Benefícios de usar Namespaces e Autoloading
Ao adotar essa abordagem, você ganha vários benefícios:
Código mais organizado: Classes bem separadas por diretórios e namespaces.
Menos código manual: O Composer faz todo o trabalho sujo de carregar arquivos.
Facilidade de manutenção: Se precisar mover ou renomear uma classe, basta ajustar o namespace.
Escalabilidade: Projetos grandes ficam mais gerenciáveis e modulares.
Padronização: O uso de PSR-4 torna seu código compatível com boas práticas do mercado.
Com isso, seu projeto PHP puro já começa a se parecer com um sistema profissional, sem depender de frameworks gigantes.
No próximo passo, podemos implementar um roteador mais robusto para gerenciar as requisições de forma dinâmica. Bora evoluir o projeto?
Organização das Requisições (Rotas)
Se você já trabalhou em projetos PHP sem frameworks, provavelmente enfrentou aquele famoso index.php
gigante, cheio de if
e switch
para lidar com requisições. Isso pode até funcionar em projetos pequenos, mas conforme o código cresce, vira um pesadelo para manter.
A solução? Criar um roteador simples e eficiente, que permita organizar requisições de forma limpa e escalável. Vamos ver como fazer isso do jeito certo!
Criando um roteador simples sem frameworks
Nosso objetivo é criar um sistema que:
- Mapeie URLs para Controllers e Métodos automaticamente.
- Suporte múltiplos tipos de requisição (GET, POST, PUT, DELETE).
- Permita URLs amigáveis e organizadas.
1. Estruturando as Rotas
Primeiro, crie um arquivo routes.php
na raiz do projeto. Ele servirá para definir as rotas do sistema:
return [
'GET' => [
'/' => 'HomeController@index',
'/usuarios' => 'UserController@index',
'/usuario/{id}' => 'UserController@show'
],
'POST' => [
'/usuario' => 'UserController@store'
],
'PUT' => [
'/usuario/{id}' => 'UserController@update'
],
'DELETE' => [
'/usuario/{id}' => 'UserController@destroy'
]
];
Aqui estamos criando uma estrutura onde cada método HTTP (GET
, POST
, etc.) tem suas rotas associadas a controllers e métodos. O {id}
indica um parâmetro dinâmico, que capturaremos mais tarde.
2. Criando o Arquivo do Roteador
Agora, precisamos processar as rotas e chamar os métodos corretos dos controllers. Crie o arquivo src/Core/Router.php
:
namespace App\Core;
class Router {
private $routes;
public function __construct($routes) {
$this->routes = $routes;
}
public function handleRequest() {
$method = $_SERVER['REQUEST_METHOD'];
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
// Verifica se a rota existe
if (!isset($this->routes[$method])) {
http_response_code(405);
die('Método não permitido.');
}
foreach ($this->routes[$method] as $route => $controllerAction) {
$pattern = preg_replace('/\{[^\}]+\}/', '([^/]+)', $route);
$pattern = "#^" . $pattern . "$#";
if (preg_match($pattern, $uri, $matches)) {
array_shift($matches);
return $this->dispatch($controllerAction, $matches);
}
}
http_response_code(404);
die('Página não encontrada.');
}
private function dispatch($controllerAction, $params) {
[$controller, $method] = explode('@', $controllerAction);
$controller = "App\\Controllers\\" . $controller;
if (!class_exists($controller) || !method_exists($controller, $method)) {
http_response_code(500);
die('Erro interno: Controller ou método não encontrado.');
}
return call_user_func_array([new $controller, $method], $params);
}
}
Agora temos um roteador que:
- Lê o método HTTP e a URL.
- Compara com as rotas registradas.
- Suporta parâmetros dinâmicos.
- Chama o controller correto automaticamente.
Integrando o Router no index.php
No arquivo public/index.php
, basta incluir o roteador e deixar ele fazer seu trabalho:
require '../vendor/autoload.php';
use App\Core\Router;
$routes = require '../routes.php';
$router = new Router($routes);
$router->handleRequest();
Agora, qualquer requisição feita ao site em php passará pelo roteador, que encaminhará para o controller correto.
Diferença entre GET, POST, PUT e DELETE
Nosso roteador já está preparado para lidar com diferentes métodos HTTP. Mas quando devemos usar cada um?
- GET → Para buscar informações, como listar usuários ou exibir um perfil.
- POST → Para criar um novo recurso, como cadastrar um usuário.
- PUT → Para atualizar um recurso existente.
- DELETE → Para excluir um recurso.
Exemplo de requisição POST para criar um usuário:
curl -X POST http://seusite.com/usuario -d "nome=João&[email protected]"
Isso chamará UserController@store()
, que processará os dados.
URLs Amigáveis e Boas Práticas
Ter URLs limpas e compreensíveis é essencial para uma boa experiência do usuário e SEO. Algumas boas práticas:
URLs feias:
http://seusite.com/index.php?pagina=usuario&id=10
URLs amigáveis:
http://seusite.com/usuario/10
Com nosso roteador, isso já é feito de forma automática!
Agora seu projeto PHP puro tem um sistema de rotas profissional, sem depender de frameworks. Esse roteador simples já permite:
- Organizar melhor suas requisições.
- Separar a lógica dos controllers corretamente.
- Criar URLs mais limpas e dinâmicas.
No próximo passo, podemos integrar Controllers mais robustos e começar a lidar com respostas JSON para criar APIs eficientes. Que tal evoluir ainda mais o projeto?
Uso de Configurações e Variáveis de Ambiente
Se tem uma coisa que pode transformar um código PHP bem escrito em um verdadeiro desastre é espalhar credenciais e configurações sensíveis pelo projeto. Nada de deixar senha de banco de dados dentro do código ou definir a URL da API no meio do controller. Isso torna a manutenção difícil, além de representar um risco de segurança gigante.
A solução? Variáveis de ambiente e arquivos de configuração. Vamos ver como organizar isso do jeito certo!
Por que evitar credenciais hardcoded?
Hardcoding é aquele hábito terrível de colocar informações sensíveis direto no código. Exemplo clássico:
$host = 'localhost';
$user = 'root';
$password = '123456';
$dbname = 'meu_banco';
Isso até funciona em um ambiente local, mas e quando o projeto for para produção? E se precisar mudar o banco de dados?
Além disso, se o código for versionado em um repositório como GitHub, essas credenciais podem vazar para o mundo inteiro. Então, nunca faça isso!
Usando um Arquivo .env
para Configurações
A maneira mais organizada e segura de lidar com configurações é utilizando um arquivo .env
, que armazena variáveis de ambiente fora do código-fonte. Vamos ver como fazer isso na prática!
1. Instalando a Biblioteca vlucas/phpdotenv
O PHP puro não tem suporte nativo para arquivos .env
, mas podemos usar a biblioteca vlucas/phpdotenv
para facilitar o trabalho.
Se ainda não tem o Composer no projeto, instale ele primeiro:
composer require vlucas/phpdotenv
Agora, crie um arquivo .env
na raiz do projeto:
APP_ENV=development
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=123456
DB_NAME=meu_banco
Este arquivo não deve ser commitado no Git. Para evitar isso, adicione a seguinte linha ao .gitignore
:
.env
Carregando Variáveis do .env
no Projeto
Agora que temos um .env
, precisamos carregá-lo no nosso código. No public/index.php
, adicione:
require '../vendor/autoload.php';
use Dotenv\Dotenv;
$dotenv = Dotenv::createImmutable(__DIR__ . '/../');
$dotenv->load();
Com isso, as variáveis do .env
estarão disponíveis no PHP via $_ENV
ou getenv()
. Exemplo:
$dbHost = $_ENV['DB_HOST'] ?? 'localhost';
$dbUser = $_ENV['DB_USER'] ?? 'root';
$dbPass = $_ENV['DB_PASSWORD'] ?? '';
$dbName = $_ENV['DB_NAME'] ?? 'meu_banco';
Se o .env
não existir, os valores padrão serão usados, evitando erros.
Criando um Arquivo de Configuração
Outra abordagem útil é centralizar as configurações em um arquivo config.php
, carregando os valores do .env
.
Crie config/config.php
:
return [
'app' => [
'env' => $_ENV['APP_ENV'] ?? 'production'
],
'database' => [
'host' => $_ENV['DB_HOST'] ?? 'localhost',
'user' => $_ENV['DB_USER'] ?? 'root',
'password' => $_ENV['DB_PASSWORD'] ?? '',
'dbname' => $_ENV['DB_NAME'] ?? 'meu_banco'
]
];
Agora, sempre que precisar de uma configuração, basta fazer:
$config = require '../config/config.php';
$dbHost = $config['database']['host'];
$dbUser = $config['database']['user'];
Isso mantém tudo organizado e flexível, sem poluir os controllers com credenciais.
Segurança: Protegendo Configurações Sensíveis
Mesmo com .env
, algumas práticas de segurança são essenciais:
- Não compartilhe o arquivo
.env
no GitHub. Use o.gitignore
. - Restrinja permissões do
.env
para que só o servidor possa acessá-lo. - Nunca exiba erros com informações sensíveis. Em produção, desative
display_errors
.
Agora seu projeto PHP puro está muito mais seguro e organizado! Com variáveis de ambiente e arquivos de configuração, garantimos que:
- As credenciais não fiquem expostas no código.
- Seja fácil trocar configurações entre ambientes (dev/prod).
- O código fique mais limpo e modular.
Conclusão de como organizar o código em um projeto PHP puro
Organizar um projeto PHP puro do jeito certo faz toda a diferença. Ao invés de um código caótico e difícil de manter, seguimos boas práticas que deixam tudo mais estruturado, escalável e legível.
Ao longo deste artigo, passamos por pontos essenciais:
- Estruturamos o projeto, separando diretórios e arquivos para manter a organização.
- Adotamos uma abordagem MVC simplificada, garantindo que cada parte do código tenha sua responsabilidade bem definida.
- Utilizamos Composer e autoloading, deixando o carregamento de classes dinâmico e eficiente.
- Criamos um sistema de rotas simples, permitindo URLs amigáveis e uma organização melhor das requisições.
- Gerenciamos configurações com
.env
, evitando credenciais expostas e tornando a aplicação mais segura.
Se você seguiu esses passos, seu código já está muito melhor do que a maioria dos projetos PHP soltos por aí. Mas a jornada não para por aqui!
Como Evoluir um Projeto PHP Puro?
Mesmo sem frameworks, é possível melhorar ainda mais seu código e evitar que ele se transforme em um código espaguete. Algumas sugestões para continuar evoluindo:
- Adote testes automatizados – Com uma pasta
/tests
, você pode usar PHPUnit para garantir que tudo funciona como esperado. - Implemente um sistema de logs – Armazene logs de erros e requisições para facilitar a depuração.
- Aprimore a segurança – Proteja formulários contra CSRF, sanitize entradas do usuário e use prepared statements no banco de dados.
- Integre bibliotecas úteis – Como Monolog para logs, PHPMailer para envio de e-mails e Guzzle para requisições HTTP.
- Use padrões de design – Como Repository Pattern para organizar queries no banco ou Dependency Injection para desacoplar classes.
PHP puro pode ser tão bem estruturado quanto qualquer aplicação feita em um framework robusto. Basta aplicar boas práticas e manter um código limpo desde o início.
Agora me conta: quais dessas práticas você já usa no seu código?