Referência v3
Voltar à plataforma

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-patternProblemaSoluçã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()