Boas práticas e anti-patterns
O que fazer, o que evitar e por quê.
✅ Boas práticas
1. Responsabilidade única por camada
php
<?php
// ✅ CORRETO — Controller só orquestra, não tem lógica de negócio
class ProdutoController
{
public function criar(Request $request): Response
{
$body = $request->body ?? [];
$produto = $this->service->criar($body['nome'], (float) $body['preco']);
return Response::json(['data' => $produto], 201);
}
}
// ❌ ERRADO — Controller com lógica de negócio e SQL direto
class ProdutoController
{
public function criar(Request $request): Response
{
$stmt = $this->pdo->prepare("INSERT INTO produtos ...");
// validações, regras de negócio, SQL — tudo misturado
}
}
2. Sempre use prepared statements
php
<?php
// ✅ CORRETO — prepared statement
$stmt = $this->pdo->prepare("SELECT * FROM produtos WHERE id = :id");
$stmt->execute([':id' => $id]);
// ❌ ERRADO — SQL injection
$stmt = $this->pdo->query("SELECT * FROM produtos WHERE id = '$id'");
3. Suporte a múltiplos drivers na migration
php
<?php
// ✅ CORRETO — suporta PostgreSQL e MySQL
return [
'up' => function (PDO $pdo): void {
$driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
if ($driver === 'pgsql') {
$pdo->exec("CREATE TABLE IF NOT EXISTS itens (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW()
)");
} else {
$pdo->exec("CREATE TABLE IF NOT EXISTS itens (
id CHAR(36) PRIMARY KEY,
criado_em DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
}
},
];
4. Use Auth:: para proteger rotas
php
<?php
// ✅ CORRETO — fachada Auth com composição clara
$router->get('/api/produtos', [Controller::class, 'listar']); // pública
$router->post('/api/produtos', [Controller::class, 'criar'], Auth::user()); // autenticada
$router->delete('/api/produtos/{id}', [Controller::class, 'deletar'], Auth::admin()); // admin
$router->post('/api/webhook', [Controller::class, 'receber'], Auth::api()); // M2M
// ✅ Rate limit sem autenticação
$router->post('/api/contato', [Controller::class, 'enviar'], Auth::limit(5));
5. Dependências externas — constraints flexíveis
json
// ✅ CORRETO — aceita múltiplas versões
{
"require": {
"guzzlehttp/guzzle": "^7.0 || ^6.5"
}
}
// ❌ ERRADO — versão rígida causa conflito
{
"require": {
"guzzlehttp/guzzle": "7.8.1"
}
}
❌ Anti-patterns
| Anti-pattern | Problema | Solução |
|---|---|---|
Editar index.php para adicionar rotas |
Acoplamento com o kernel, difícil de manter | Crie um módulo com Routes/web.php |
| Acessar tabela de outro módulo via SQL direto | Quebra o isolamento, cria dependência oculta | Use o Service do módulo via injeção de dependência |
Usar $_GET, $_POST, $_SESSION diretamente |
Bypassa o pipeline de segurança | Use $request->body, $request->query, Auth::current($request) |
| Lançar exceções genéricas com mensagens técnicas | Vaza informações internas para o cliente | Use throw new \DomainException('Mensagem amigável', 422) |
| Hardcodar credenciais no código | Segurança comprometida, impossível rotacionar | Use $_ENV['MINHA_CHAVE'] e configure no .env |
Usar die() ou exit() em controllers |
Interrompe o pipeline, headers não são enviados | Retorne Response::json(['error' => '...'], 400) |
Versões rígidas no composer.json do módulo |
Conflito com outros módulos no mesmo projeto | Use constraints flexíveis: ^7.0 || ^6.5 |
| Namespace errado no módulo | Autoloader não encontra a classe → 500 | Use Src\Modules\NomeModulo\Camada exatamente |
Diagrama mental — como tudo se conecta
text
HTTP Request
│
▼
index.php ──── carrega .env, Container, Application
│
▼
Application::run()
├── BotBlockerMiddleware ← bloqueia scanners e IPs suspeitos
├── HttpsEnforcerMiddleware ← força HTTPS em produção
└── SecurityHeadersMiddleware ← CSP, HSTS, X-Frame-Options
│
▼
Router::dispatch()
│
├── [RateLimitMiddleware] ← limita requisições por IP/usuário
├── [CircuitBreakerMiddleware]← protege o banco de falhas em cascata
├── [AuthHybridMiddleware] ← valida JWT, busca usuário no banco
├── [AdminOnlyMiddleware] ← verifica role admin_system
└── Controller::action()
│
▼
Service (regras de negócio)
│
▼
Repository (PDO → banco)
│
▼
Response::json()
│
▼
AuditLogger ← registra 401/403/429 automaticamente
│
▼
Response::send() → cliente
Diagrama de módulos
text
src/Modules/
│
├── Auth/ ← JWT, refresh tokens, recuperação de senha
│ └── usa → UserRepositoryInterface (implementado por Usuario)
│
├── Usuario/ ← CRUD de usuários, perfil, upload de avatar
│ └── implementa → UserRepositoryInterface, AuthenticatableInterface
│
├── SeuModulo/ ← sua regra de negócio aqui
│ ├── pode usar → EmailSenderInterface (injetado pelo container)
│ ├── pode usar → UsuarioService (injeção direta)
│ └── não deve → acessar tabelas de outros módulos via SQL
│
└── Email/ ← módulo externo (marketplace)
└── implementa → EmailSenderInterface
└── registrado no container via boot()