PHP

Artigo 40 — Boas Práticas e Código Limpo em PHP Já leu

6 min de leitura

Artigo 40 — Boas Práticas e Código Limpo em PHP
Boas práticas não são regras arbitrárias inventadas por puristas. São soluções destiladas de décadas de experiência coletiva com código que ficou difícil de

Artigo 40 — Boas Práticas e Código Limpo em PHP

Módulo 6 · Semana 40 · Nível: Intermediário–Avançado


Introdução

Boas práticas não são regras arbitrárias inventadas por puristas. São soluções destiladas de décadas de experiência coletiva com código que ficou difícil de manter, difícil de testar, e difícil de entender. Neste artigo reunimos os princípios mais impactantes para escrever PHP profissional: código que se lê como prosa, que resiste ao tempo e que você não se envergonha de mostrar.


Declare tipos em tudo

O sistema de tipos do PHP 8 é expressivo e poderoso. Use-o:

<?php
declare(strict_types=1); // Sempre — ativa verificação estrita de tipos

// Ruim: sem tipos
function calcularDesconto($preco, $percentual) {
    return $preco * (1 - $percentual / 100);
}

// Bom: tipos completos
function calcularDesconto(float $preco, float $percentual): float
{
    if ($percentual < 0 || $percentual > 100) {
        throw new \InvalidArgumentException("Percentual inválido: {$percentual}");
    }
    return $preco * (1 - $percentual / 100);
}

// Ainda melhor: Value Objects eliminam a necessidade de validar sempre
function calcularDesconto(Dinheiro $preco, Percentual $percentual): Dinheiro
{
    return $preco->aplicarDesconto($percentual);
}

// Union types — quando necessário
function buscarOuCriar(int|string $id): Usuario { /* ... */ }

// Nullable — quando null é um valor legítimo
function buscarPorEmail(string $email): ?Usuario { /* ... */ }

// Never — função que nunca retorna (sempre lança exceção ou encerra)
function falhar(string $mensagem): never
{
    throw new \RuntimeException($mensagem);
}

Nomes que explicam a intenção

<?php
// Ruim: o que é $d? O que faz $proc?
$d = new DateTime();
function proc($u, $p): bool { /* ... */ }

// Bom: nomes revelam intenção
$dataDeNascimento = new DateTimeImmutable('1990-05-15');
function verificarSenha(string $senhaDigitada, string $hashArmazenado): bool { /* ... */ }

// Ruim: abreviações crípticas
$usr = $uRepo->findById($uid);
$ord->addItm($prd, $qty, $prc);

// Bom: verbosidade que clarifica
$usuario = $usuarioRepositorio->buscarPorId($usuarioId);
$pedido->adicionarItem($produto, $quantidade, $preco);

// Booleans: prefixos que comunicam
$estaAtivo        = true;
$podeEditarPerfil = false;
$temEstoqueDisponivel = $produto->estoque > 0;

// Funções booleans: verbos is/has/can/deve
function isEmailValido(string $email): bool { /* ... */ }
function temPermissao(Usuario $usuario, string $recurso): bool { /* ... */ }
function podeRealizarCompra(Usuario $usuario, Pedido $pedido): bool { /* ... */ }

Funções pequenas com uma responsabilidade

<?php
// Ruim: uma função fazendo tudo
function processarCadastro(array $dados): array
{
    // validação
    if (empty($dados['nome'])) return ['erro' => 'Nome obrigatório'];
    if (!filter_var($dados['email'], FILTER_VALIDATE_EMAIL)) return ['erro' => 'Email inválido'];

    // hash de senha
    $hash = password_hash($dados['senha'], PASSWORD_ARGON2ID);

    // inserção no banco
    global $pdo;
    $pdo->prepare('INSERT INTO usuarios ...');

    // envio de email
    mail($dados['email'], 'Bem-vindo!', '...');

    return ['sucesso' => true];
}

// Bom: responsabilidades separadas
function validarDadosDeCadastro(array $dados): array // retorna erros
{
    $erros = [];
    if (empty(trim($dados['nome'] ?? ''))) $erros['nome'] = 'Nome obrigatório.';
    if (!filter_var($dados['email'] ?? '', FILTER_VALIDATE_EMAIL)) $erros['email'] = 'Email inválido.';
    if (strlen($dados['senha'] ?? '') < 8) $erros['senha'] = 'Senha deve ter 8+ caracteres.';
    return $erros;
}

class CadastroService
{
    public function __construct(
        private readonly UsuarioRepositorioInterface $repositorio,
        private readonly EmailService                $emailService,
    ) {}

    public function cadastrar(DadosDeCadastro $dados): Usuario
    {
        $usuario = $this->repositorio->salvar(
            new Usuario(
                nome:      $dados->nome,
                email:     $dados->email,
                senhaHash: password_hash($dados->senha, PASSWORD_ARGON2ID),
            )
        );

        $this->emailService->enviarBoasVindas($usuario);

        return $usuario;
    }
}

Early return — evite else desnecessário

<?php
// Ruim: aninhamento excessivo
function processarPagamento(Pedido $pedido, Usuario $usuario): string
{
    if ($pedido->estaAtivo()) {
        if ($usuario->temSaldoSuficiente($pedido->total())) {
            if (!$pedido->jaFoiPago()) {
                // processa...
                return 'Pagamento realizado.';
            } else {
                return 'Pedido já foi pago.';
            }
        } else {
            return 'Saldo insuficiente.';
        }
    } else {
        return 'Pedido inativo.';
    }
}

// Bom: early return elimina o aninhamento
function processarPagamento(Pedido $pedido, Usuario $usuario): string
{
    if (!$pedido->estaAtivo()) {
        return 'Pedido inativo.';
    }

    if ($pedido->jaFoiPago()) {
        return 'Pedido já foi pago.';
    }

    if (!$usuario->temSaldoSuficiente($pedido->total())) {
        return 'Saldo insuficiente.';
    }

    // Chegou aqui: todas as condições satisfeitas
    // processa...
    return 'Pagamento realizado.';
}

Imutabilidade onde possível

<?php
// Value Objects são imutáveis por definição
final class Email
{
    public function __construct(
        private readonly string $valor // readonly: não pode ser alterado após construção
    ) {
        if (!filter_var($valor, FILTER_VALIDATE_EMAIL)) {
            throw new \InvalidArgumentException("Email inválido: {$valor}");
        }
    }

    // "Modificação" retorna nova instância
    public function comDominio(string $novoDominio): self
    {
        [$local] = explode('@', $this->valor);
        return new self("{$local}@{$novoDominio}");
    }

    public function valor(): string { return $this->valor; }
}

// PHP 8.1+ readonly classes
final readonly class Coordenada
{
    public function __construct(
        public float $latitude,
        public float $longitude,
    ) {
        if ($this->latitude < -90 || $this->latitude > 90) {
            throw new \InvalidArgumentException('Latitude inválida.');
        }
    }
}

Prefira exceções a códigos de erro

<?php
// Ruim: retorna false/null em caso de erro — o chamador pode ignorar
function dividir(float $a, float $b): float|false
{
    if ($b === 0.0) return false;
    return $a / $b;
}

$resultado = dividir(10, 0); // false — fácil de ignorar acidentalmente

// Bom: exceção que não pode ser ignorada silenciosamente
function dividir(float $a, float $b): float
{
    if ($b === 0.0) {
        throw new \DivisionByZeroError('Divisor não pode ser zero.');
    }
    return $a / $b;
}

// Hierarquia de exceções próprias — comunicam o problema com precisão
class DomainException extends \RuntimeException {}
class EstoqueInsuficienteException extends DomainException
{
    public function __construct(
        public readonly int $disponivel,
        public readonly int $solicitado,
    ) {
        parent::__construct(
            "Estoque insuficiente: {$disponivel} disponível, {$solicitado} solicitado."
        );
    }
}

Resumo das boas práticas

Prática Por que importa
declare(strict_types=1) Evita coerções silenciosas de tipo
Nomes descritivos Código é lido muito mais do que escrito
Funções pequenas Fáceis de testar, entender e reutilizar
Early return Reduz aninhamento e aumenta clareza
Imutabilidade Elimina bugs de estado compartilhado
Exceções em vez de null/false Erros não podem ser ignorados silenciosamente
Injeção de dependência Código testável e desacoplado

Exercício da semana

  1. Pegue um arquivo PHP antigo seu (ou de um repositório open-source) e aplique as práticas deste artigo: adicione tipos, renomeie variáveis, extraia funções, aplique early return. Documente as mudanças e compare a legibilidade.

  2. Implemente uma hierarquia de exceções para um domínio de sua escolha: DomainException como base, e pelo menos três exceções específicas com dados contextuais nos construtores.

  3. Crie uma classe Dinheiro completamente imutável com readonly, final, e sem setters. Implemente: somar(), subtrair(), multiplicar(), dividir(), aplicarDesconto(), equals(). Escreva testes para cada método.

  4. Desafio: refatore uma função longa (20+ linhas) extraindo subfunções nomeadas que expliquem cada etapa do processo. O objetivo é que o corpo da função original leia como uma sequência de frases em português: $this->validarDados($dados); $usuario = $this->criarUsuario($dados); $this->notificar($usuario);


Referências

  • Clean Code (livro) — Robert C. Martin: https://www.amazon.com.br/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882
  • PHP The Right Way: https://phptherightway.com/
  • PHPStan — análise estática: https://phpstan.org/
  • Refactoring Guru — padrões e refatoração: https://refactoring.guru/

Módulo 6 completo — 3 artigos (38–40) cobrindo Composer, namespaces, PSR e boas práticas.

Progresso total: 40/52 artigos — só textos, conforme combinado.

Próximo: Módulo 7 — Laravel (artigos 41–46). São 6 artigos cobrindo instalação, rotas, Eloquent, Blade, autenticação e APIs com Laravel. Continuo?


you asked

Sim


claude response

Comentários

Mais em PHP

Formulários HTML e PHP
Formulários HTML e PHP

Formulários são a porta de entrada dos dados do usuário para o servidor. Tudo...

APIs REST com PHP
APIs REST com PHP

REST (Representational State Transfer) é o estilo arquitetural dominante para...

Artigo 51 — Projeto Final Parte 2: Implementação
Artigo 51 — Projeto Final Parte 2: Implementação

Com o planejamento sólido do artigo anterior, agora implementamos o sistema c...