Introdução: Por que SOLID não é apenas um dogma, mas uma ferramenta
A maioria dos desenvolvedores sabe que SOLID são cinco princípios da programação orientada a objetos: Responsabilidade Única (Single Responsibility), Aberto/Fechado (Open-Closed), Substituição de Liskov (Liskov Substitution), Segregação de Interface (Interface Segregation) e Inversão de Dependência (Dependency Inversion). No entanto, na prática, a compreensão muitas vezes se resume a seguir regras mecanicamente. Neste artigo, iremos mais longe: analisaremos cenários avançados de aplicação do SOLID em PHP, consideraremos antipadrões que violam os princípios e mostraremos como a refatoração com SOLID torna o código flexível e testável.
O material é voltado para desenvolvedores de nível Pleno+ e Sênior que já programam em PHP e desejam melhorar a arquitetura de seus projetos.
1. Princípio da Responsabilidade Única (SRP): quando a classe "sabe demais"
A formulação clássica: "Uma classe deve ter apenas um motivo para mudar". Em um nível avançado, é importante entender que "motivo para mudar" é um requisito de negócio. Se sua classe muda devido a alterações nas regras de validação, formato de saída ou lógica de persistência, isso é uma violação do SRP.
Exemplo de violação (God Object)
class OrderProcessor { public function process(array $data): void { // 1. Validação dos dados if (empty($data['user_id']) || empty($data['items'])) { throw new InvalidArgumentException('Invalid order data'); } // 2. Cálculo do total $total = 0; foreach ($data['items'] as $item) { $total += $item['price'] * $item['quantity']; } // 3. Persistência no banco de dados $sql = "INSERT INTO orders (user_id, total, status) VALUES (?, ?, 'pending')"; // ... execução da consulta // 4. Envio de e-mail mail($data['user_email'], 'Order confirmation', "Total: $total"); }}Refatoração com SRP
Dividimos em três classes com áreas de responsabilidade claras:
class OrderValidator { public function validate(array $data): void { if (empty($data['user_id']) || empty($data['items'])) { throw new InvalidArgumentException('Invalid order data'); } }}
class OrderCalculator { public function calculateTotal(array $items): float { return array_reduce($items, function ($carry, $item) { return $carry + $item['price'] * $item['quantity']; }, 0.0); }}
class OrderRepository { public function save(int $userId, float $total, string $status = 'pending'): int { // Lógica de persistência no banco de dados return 42; // ID do pedido }}
class OrderProcessor { public function __construct( private OrderValidator $validator, private OrderCalculator $calculator, private OrderRepository $repository, private Mailer $mailer ) {} public function process(array $data): void { $this->validator->validate($data); $total = $this->calculator->calculateTotal($data['items']); $orderId = $this->repository->save($data['user_id'], $total); $this->mailer->send($data['user_email'], 'Order #' . $orderId); }}2. Princípio Aberto/Fechado (OCP): extensão sem modificação
A essência do OCP: as classes devem estar abertas para extensão, mas fechadas para modificação. Na prática, isso significa usar polimorfismo e abstrações em vez de construções condicionais.
Violação do OCP: switch-case para cada novo tipo
class PaymentProcessor { public function pay(string $type, float $amount): void { switch ($type) { case 'credit_card': // processamento de cartão break; case 'paypal': // processamento PayPal break; // Ao adicionar um novo tipo — alteramos a classe! } }