
Orientação a Objetos em PHP
Por que a Orientação a Objetos mudou o PHP para sempre
Havia um tempo em que PHP era sinônimo de scripts soltos, funções globais e código que ninguém ousava mexer depois de pronto. Esse tempo ficou para trás. Hoje, o ecossistema PHP — Symfony, Laravel, Doctrine, Composer — é inteiramente construído sobre Orientação a Objetos, e entender OOP não é mais opcional para quem quer trabalhar de forma profissional com a linguagem.
A Orientação a Objetos não é apenas uma forma diferente de escrever código. É uma forma diferente de pensar sobre problemas. Em vez de perguntar “que função resolve isso?”, você pergunta “que entidade é responsável por isso?”. Em vez de passar dados entre funções soltas, você encapsula comportamento e estado em objetos coesos que se comunicam entre si.
Este guia cobre a OOP em PHP do zero ao avançado, com foco no que realmente importa em projetos reais: encapsulamento, herança, polimorfismo, interfaces, traits, tipos estritos e os princípios SOLID que separam código amador de código profissional. Cada conceito vem acompanhado de exemplos prontos para produção, não de abstrações acadêmicas.
Para quem é este guia
Desenvolvedores PHP que já conhecem o básico da linguagem e querem dominar OOP com profundidade. Todo exemplo é compatível com PHP 8.0+ e segue as melhores práticas do ecossistema moderno.
1. Classes e objetos: a base de tudo
Uma classe é um molde — ela define quais dados (propriedades) e quais comportamentos (métodos) um determinado tipo de entidade terá. Um objeto é uma instância concreta desse molde, com seus próprios valores para cada propriedade.
Pense em uma classe Produto como a planta arquitetônica de um produto. O produto real — um notebook com nome, preço e estoque específicos — é o objeto criado a partir dessa planta.
<?phpclass Produto {
public string $nome;
public float $preco;
public int $estoque; public function __construct(string $nome, float $preco, int $estoque) {
$this->nome = $nome;
$this->preco = $preco;
$this->estoque = $estoque;
} public function estaDisponivel(): bool {
return $this->estoque > 0;
} public function aplicarDesconto(float $percentual): void {
$this->preco *= (1 - $percentual / 100);
}
}// Criando objetos (instâncias)
$notebook = new Produto('Notebook Pro', 4999.90, 12);
$mouse = new Produto('Mouse Gamer', 189.90, 0);echo $notebook->estaDisponivel() ? 'Em estoque' : 'Esgotado'; // Em estoque
echo $mouse->estaDisponivel() ? 'Em estoque' : 'Esgotado'; // Esgotado$notebook->aplicarDesconto(10);
echo number_format($notebook->preco, 2, ',', '.'); // 4.499,91
?>
1.1 O construtor e o destrutor
O método __construct() é executado automaticamente quando um objeto é criado com new. É o lugar ideal para garantir que o objeto nasce em um estado válido e consistente. O PHP 8.0 introduziu a promoção de propriedades no construtor, eliminando código repetitivo:
<?php// PHP 8.0+: Promoção de propriedades no construtor
// Declara e inicializa em uma única linha
class Cliente {
public function __construct(
public readonly string $nome,
public readonly string $email,
private int $pontos = 0,
) {} public function adicionarPontos(int $qtd): void {
$this->pontos += $qtd;
} public function getPontos(): int {
return $this->pontos;
}
}$cliente = new Cliente('Ana Lima', 'ana@exemplo.com');
$cliente->adicionarPontos(150);
echo $cliente->getPontos(); // 150// readonly impede modificação após construção
// $cliente->nome = 'Outro'; // Fatal error
?>
Dica: readonly no PHP 8.1+
Propriedades readonly só podem ser atribuídas uma vez, no construtor. São ideais para Value Objects e DTOs onde imutabilidade é desejada. No PHP 8.2+, você pode declarar readonly na própria classe.
1.2 Métodos mágicos essenciais
O PHP oferece métodos mágicos (prefixados com __) que são chamados automaticamente em situações específicas. Os mais úteis no dia a dia são:
<?phpclass Colecao {
private array $items = []; // Chamado ao fazer: echo $colecao
public function __toString(): string {
return implode(', ', $this->items);
} // Chamado ao clonar: $copia = clone $colecao
public function __clone(): void {
// Garante deep copy de objetos internos se necessário
$this->items = array_map('clone_item', $this->items);
} // Chamado ao serializar: serialize($colecao)
public function __serialize(): array {
return ['items' => $this->items];
} public function adicionar(string $item): void {
$this->items[] = $item;
}
}$c = new Colecao();
$c->adicionar('PHP');
$c->adicionar('Laravel');
echo $c; // PHP, Laravel
?>
2. Encapsulamento: protegendo o estado interno
Encapsulamento é o princípio de esconder os detalhes de implementação de um objeto e expor apenas o que é necessário para o mundo externo. Em PHP, isso é feito com os modificadores de visibilidade: public, protected e private.
O encapsulamento não é burocracia. É a diferença entre um objeto que pode ser usado de qualquer jeito (e quebrar silenciosamente) e um objeto que garante sua própria consistência interna, independente de como é usado.
| Visibilidade | Própria classe | Subclasses | Código externo |
|---|---|---|---|
public | Sim | Sim | Sim |
protected | Sim | Sim | Não |
private | Sim | Não | Não |
<?phpclass ContaBancaria {
private float $saldo;
private array $transacoes = []; public function __construct(float $saldoInicial) {
if ($saldoInicial < 0) {
throw new InvalidArgumentException('Saldo inicial não pode ser negativo');
}
$this->saldo = $saldoInicial;
} public function depositar(float $valor): void {
$this->validarValor($valor);
$this->saldo += $valor;
$this->registrarTransacao('deposito', $valor);
} public function sacar(float $valor): void {
$this->validarValor($valor);
if ($valor > $this->saldo) {
throw new RuntimeException('Saldo insuficiente');
}
$this->saldo -= $valor;
$this->registrarTransacao('saque', $valor);
} public function getSaldo(): float {
return $this->saldo;
} // Método privado: detalhe de implementação, não exposto
private function validarValor(float $valor): void {
if ($valor <= 0) {
throw new InvalidArgumentException('Valor deve ser positivo');
}
} private function registrarTransacao(string $tipo, float $valor): void {
$this->transacoes[] = [
'tipo' => $tipo,
'valor' => $valor,
'data' => date('Y-m-d H:i:s'),
];
}
}$conta = new ContaBancaria(1000.00);
$conta->depositar(500.00);
$conta->sacar(200.00);
echo $conta->getSaldo(); // 1300// $conta->saldo = 999999; // Fatal error: Cannot access private property
?>
Repare que o saldo nunca pode ser alterado diretamente de fora. Toda operação passa pelos métodos depositar() e sacar(), que validam as regras de negócio. Esse é o poder do encapsulamento: o objeto é o guardião da sua própria integridade.
3. Herança: reutilizando e especializando comportamento
Herança permite criar uma classe nova baseada em uma existente, herdando todas as suas propriedades e métodos e adicionando ou sobrescrevendo comportamentos conforme necessário. Em PHP, usa-se a palavra-chave extends.
Herança deve modelar uma relação “é um”: um Gerente é um Funcionário, um Cachorro é um Animal. Se a relação for “tem um” ou “usa um”, prefira composição.
<?phpabstract class Funcionario {
public function __construct(
protected string $nome,
protected float $salarioBase,
) {} // Método concreto: comportamento compartilhado
public function getNome(): string {
return $this->nome;
} // Método abstrato: cada subclasse DEVE implementar
abstract public function calcularSalario(): float; public function __toString(): string {
return sprintf(
'%s — Salário: R$ %s',
$this->nome,
number_format($this->calcularSalario(), 2, ',', '.')
);
}
}class FuncionarioCLT extends Funcionario {
private float $beneficios; public function __construct(string $nome, float $salarioBase, float $beneficios) {
parent::__construct($nome, $salarioBase);
$this->beneficios = $beneficios;
} public function calcularSalario(): float {
return $this->salarioBase + $this->beneficios;
}
}class Gerente extends FuncionarioCLT {
private float $bonus; public function __construct(
string $nome, float $salarioBase, float $beneficios, float $bonus
) {
parent::__construct($nome, $salarioBase, $beneficios);
$this->bonus = $bonus;
} public function calcularSalario(): float {
return parent::calcularSalario() + $this->bonus;
}
}$dev = new FuncionarioCLT('Ana', 5000, 800);
$gerente = new Gerente('Bruno', 8000, 1200, 3000);echo $dev; // Ana — Salário: R$ 5.800,00
echo $gerente; // Bruno — Salário: R$ 12.200,00
?>
3.1 Classes e métodos abstratos
Uma classe abstrata não pode ser instanciada diretamente — ela existe apenas para ser estendida. Métodos abstratos declaram uma assinatura sem implementação, forçando as subclasses a fornecerem a sua própria versão. É um contrato interno ao nível da hierarquia de herança.
Herança vs. Composição
Prefira composição à herança quando o objetivo é reutilizar código sem criar uma hierarquia rígida. Herança cria acoplamento forte entre classes. Composição (injetar objetos colaboradores) é mais flexível e testável. Regra prática: se você não consegue dizer “X é um Y” com convicção, use composição.
4. Polimorfismo: o poder da substituição
Polimorfismo significa “muitas formas”. Em OOP, é a capacidade de tratar objetos de diferentes classes de forma uniforme, desde que compartilhem uma interface comum. É o princípio que torna o código extensível sem precisar modificá-lo.
Um exemplo claro: um sistema de pagamentos que aceita cartão de crédito, boleto e Pix não deveria ter um if gigante para cada método. Com polimorfismo, cada método de pagamento é um objeto que sabe processar a si mesmo, e o sistema os trata de forma idêntica.
<?phpinterface MetodoPagamento {
public function processar(float $valor): bool;
public function getNome(): string;
}class CartaoCredito implements MetodoPagamento {
public function __construct(private string $numero, private int $parcelas) {} public function processar(float $valor): bool {
$valorParcela = $valor / $this->parcelas;
echo "Cartão {$this->numero}: {$this->parcelas}x de R$ ";
echo number_format($valorParcela, 2, ',', '.');
return true; // Simula aprovação
} public function getNome(): string { return 'Cartão de Crédito'; }
}class Boleto implements MetodoPagamento {
public function processar(float $valor): bool {
$vencimento = date('d/m/Y', strtotime('+3 days'));
echo "Boleto gerado: R$ " . number_format($valor, 2, ',', '.');
echo " — Vence em: {$vencimento}";
return true;
} public function getNome(): string { return 'Boleto Bancário'; }
}class Pix implements MetodoPagamento {
public function __construct(private string $chave) {} public function processar(float $valor): bool {
echo "PIX para {$this->chave}: R$ " . number_format($valor, 2, ',', '.');
return true;
} public function getNome(): string { return 'PIX'; }
}// O processador não sabe qual método é — e não precisa saber
class ProcessadorPagamento {
public function pagar(MetodoPagamento $metodo, float $valor): void {
echo 'Processando via ' . $metodo->getNome() . PHP_EOL;
$sucesso = $metodo->processar($valor);
echo $sucesso ? 'Pagamento aprovado!' : 'Pagamento recusado.';
echo PHP_EOL;
}
}$processador = new ProcessadorPagamento();
$processador->pagar(new CartaoCredito('**** 1234', 3), 900.00);
$processador->pagar(new Boleto(), 900.00);
$processador->pagar(new Pix('ana@email.com'), 900.00);
?>
Para adicionar um novo método de pagamento — digamos, criptomoeda — basta criar uma nova classe que implementa MetodoPagamento. O ProcessadorPagamento não precisa ser tocado. Isso é o Princípio Aberto/Fechado em ação: aberto para extensão, fechado para modificação.
5. Interfaces e contratos
Uma interface define um contrato: qualquer classe que a implemente deve fornecer todos os métodos declarados. Interfaces não contêm implementação — apenas assinaturas. Uma classe pode implementar múltiplas interfaces, resolvendo a limitação do PHP de herança simples.
<?phpinterface Persistivel {
public function salvar(): bool;
public function deletar(): bool;
}interface Auditavel {
public function getCriadoEm(): DateTimeImmutable;
public function getAtualizadoEm(): DateTimeImmutable;
}interface Exportavel {
public function toArray(): array;
public function toJson(): string;
}// Implementando múltiplas interfaces
class Pedido implements Persistivel, Auditavel, Exportavel {
private DateTimeImmutable $criadoEm;
private DateTimeImmutable $atualizadoEm; public function __construct(
private int $id,
private float $total,
) {
$this->criadoEm = new DateTimeImmutable();
$this->atualizadoEm = new DateTimeImmutable();
} public function salvar(): bool {
// Lógica de persistência
return true;
} public function deletar(): bool {
return true;
} public function getCriadoEm(): DateTimeImmutable {
return $this->criadoEm;
} public function getAtualizadoEm(): DateTimeImmutable {
return $this->atualizadoEm;
} public function toArray(): array {
return ['id' => $this->id, 'total' => $this->total];
} public function toJson(): string {
return json_encode($this->toArray());
}
}
?>
5.1 Interfaces vs. classes abstratas
| Característica | Interface | Classe Abstrata |
|---|---|---|
| Implementação | Apenas assinaturas (+ default PHP 8) | Pode ter métodos concretos |
| Herança múltipla | Uma classe pode implementar várias | Apenas uma por vez |
| Propriedades | Apenas constantes | Pode ter propriedades |
| Instanciação | Não | Não |
| Quando usar | Definir contratos entre sistemas | Compartilhar implementação base |
6. Traits: reutilização horizontal de código
Traits resolvem um problema real: PHP só permite herança simples, mas frequentemente você quer reutilizar um comportamento em classes que não compartilham uma hierarquia comum. Traits são blocos de código que podem ser incluídos em qualquer classe, independente da sua posição na hierarquia.
Pense em traits como mixins: comportamentos que você “cola” em uma classe. Um trait de Timestampable, por exemplo, pode ser usado tanto em Pedido quanto em Usuario quanto em Produto, sem que essas classes precisem herdar de uma base comum.
<?phptrait Timestampable {
private ?DateTimeImmutable $criadoEm = null;
private ?DateTimeImmutable $atualizadoEm = null; public function marcarCriacao(): void {
$this->criadoEm = new DateTimeImmutable();
$this->atualizadoEm = new DateTimeImmutable();
} public function marcarAtualizacao(): void {
$this->atualizadoEm = new DateTimeImmutable();
} public function getCriadoEm(): ?DateTimeImmutable { return $this->criadoEm; }
public function getAtualizadoEm(): ?DateTimeImmutable { return $this->atualizadoEm; }
}trait SoftDeletable {
private ?DateTimeImmutable $deletadoEm = null; public function deletar(): void {
$this->deletadoEm = new DateTimeImmutable();
} public function restaurar(): void {
$this->deletadoEm = null;
} public function estaDeletado(): bool {
return $this->deletadoEm !== null;
}
}class Artigo {
use Timestampable, SoftDeletable; public function __construct(public string $titulo) {
$this->marcarCriacao();
}
}class Comentario {
use Timestampable, SoftDeletable; public function __construct(public string $texto) {
$this->marcarCriacao();
}
}$artigo = new Artigo('OOP em PHP');
echo $artigo->getCriadoEm()->format('d/m/Y H:i');$artigo->deletar();
echo $artigo->estaDeletado() ? 'Deletado' : 'Ativo'; // Deletado
?>
Cuidado com o abuso de traits
Traits são poderosos, mas usados em excesso criam código difícil de rastrear. Prefira traits para comportamentos verdadeiramente transversais (logging, timestamps, soft delete). Para lógica de negócio, prefira composição explícita.
7. Tipagem estrita e type hints
O PHP moderno (7.0+) suporta tipagem forte, e no PHP 8.x ela está mais completa do que nunca. Usar tipos explícitos não é pedantismo — é comunicação. Um método tipado documenta a si mesmo e falha rápido quando recebe dados errados, em vez de propagar silenciosamente um null ou um tipo inesperado por toda a aplicação.
<?phpdeclare(strict_types=1); // Ativa verificação estrita de tiposclass Calculadora {
// PHP 8.0: Union Types
public function somar(int|float $a, int|float $b): int|float {
return $a + $b;
} // PHP 8.1: Intersection Types
public function processar(Stringable&Countable $obj): string {
return (string) $obj;
} // PHP 8.0: Nullable type com ?
public function dividir(float $a, float $b): ?float {
if ($b == 0) return null;
return $a / $b;
} // PHP 8.1: Enums como tipo
public function operacao(Operacao $op, float $a, float $b): float {
return match($op) {
Operacao::Soma => $a + $b,
Operacao::Subtracao => $a - $b,
Operacao::Divisao => $a / $b,
Operacao::Multiplic => $a * $b,
};
}
}enum Operacao {
case Soma;
case Subtracao;
case Divisao;
case Multiplic;
}
?>
7.1 Tipos de retorno especiais
<?phpclass Repositorio {
// void: método não retorna nada
public function salvar(object $entidade): void {
// persistir...
} // never: método nunca retorna (lança exceção ou para execução)
public function falharCom(string $msg): never {
throw new RuntimeException($msg);
} // static: retorna instância da classe chamada (útil em herança)
public static function criar(): static {
return new static();
} // mixed: aceita qualquer tipo (use com moderação)
public function buscarRaw(int $id): mixed {
return null; // pode retornar qualquer coisa
}
}
?>
8. Princípios SOLID na prática com PHP
SOLID é um acrônimo para cinco princípios de design orientado a objetos formulados por Robert C. Martin. Eles não são regras rígidas — são diretrizes que, quando seguidas, resultam em código mais fácil de entender, testar e modificar.
8.1 S — Single Responsibility Principle
Uma classe deve ter apenas uma razão para mudar. Na prática: separe responsabilidades. Uma classe que busca dados no banco, formata e envia e‑mail está com três responsabilidades — divida‑a.
<?php// Ruim: uma classe com três responsabilidades
class RelatorioVendas {
public function buscarVendas(): array { /* query SQL */ }
public function formatar(array $vendas): string { /* HTML */ }
public function enviarEmail(string $html): void { /* SMTP */ }
}// Bom: cada classe com uma responsabilidade
class RepositorioVendas {
public function buscar(): array { /* query SQL */ }
}class FormataRelatorio {
public function html(array $vendas): string { /* HTML */ }
}class EnviadorEmail {
public function enviar(string $para, string $html): void { /* SMTP */ }
}
?>
8.2 O — Open/Closed Principle
Entidades devem estar abertas para extensão e fechadas para modificação. O exemplo do sistema de pagamentos que vimos anteriormente é o OCP em ação: novos métodos de pagamento são adicionados sem tocar no ProcessadorPagamento.
8.3 L — Liskov Substitution Principle
Objetos de uma subclasse devem poder substituir objetos da superclasse sem quebrar o sistema. Em termos práticos: se uma subclasse restringe ou altera o comportamento esperado da superclasse, há uma violação do LSP.
<?php// Violação do LSP: Quadrado herda de Retangulo mas quebra o contrato
class Retangulo {
public function setLargura(float $l): void { $this->largura = $l; }
public function setAltura(float $a): void { $this->altura = $a; }
public function area(): float { return $this->largura * $this->altura; }
}// Quadrado viola LSP: ao definir largura, deve definir altura também
// Isso quebra código que usa Retangulo e espera comportamento independente// Solução: hierarquia separada ou interface comum
interface Forma {
public function area(): float;
}class Retangulo2 implements Forma {
public function __construct(private float $l, private float $a) {}
public function area(): float { return $this->l * $this->a; }
}class Quadrado2 implements Forma {
public function __construct(private float $lado) {}
public function area(): float { return $this->lado ** 2; }
}
?>
8.4 I — Interface Segregation Principle
Clientes não devem ser forçados a depender de interfaces que não usam. Prefira interfaces pequenas e focadas a interfaces gordas que agrupam métodos não relacionados.
<?php// Ruim: interface gorda
interface Animal {
public function comer(): void;
public function dormir(): void;
public function voar(): void; // nem todo animal voa!
public function nadar(): void; // nem todo animal nada!
}// Bom: interfaces segregadas
interface Comedor { public function comer(): void; }
interface Dormidor { public function dormir(): void; }
interface Voador { public function voar(): void; }
interface Nadador { public function nadar(): void; }class Pato implements Comedor, Dormidor, Voador, Nadador {
public function comer(): void { echo 'Pato come'; }
public function dormir(): void { echo 'Pato dorme'; }
public function voar(): void { echo 'Pato voa'; }
public function nadar(): void { echo 'Pato nada'; }
}class Cachorro implements Comedor, Dormidor, Nadador {
public function comer(): void { echo 'Cachorro come'; }
public function dormir(): void { echo 'Cachorro dorme'; }
public function nadar(): void { echo 'Cachorro nada'; }
}
?>
8.5 D — Dependency Inversion Principle
Módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações. Na prática, isso significa injetar dependências em vez de instanciá-las internamente — o que também torna o código muito mais fácil de testar.
<?php// Ruim: dependência concreta acoplada
class PedidoService {
private MySQLPedidoRepositorio $repo; public function __construct() {
$this->repo = new MySQLPedidoRepositorio(); // acoplamento duro
}
}// Bom: depende de abstração, recebe implementação por injeção
interface PedidoRepositorioInterface {
public function buscarPorId(int $id): ?Pedido;
public function salvar(Pedido $pedido): void;
}class PedidoService2 {
public function __construct(
private PedidoRepositorioInterface $repo // abstração
) {} public function processar(int $id): void {
$pedido = $this->repo->buscarPorId($id);
// lógica...
}
}// Em produção: injetar o repositório real
$service = new PedidoService2(new MySQLPedidoRepositorio());// Em testes: injetar um mock
$service = new PedidoService2(new FakePedidoRepositorio());
?>
9. Exceções e tratamento de erros
Em OOP, erros são representados como objetos — instâncias de classes que estendem Exception ou Error. Isso permite criar hierarquias de exceções que comunicam exatamente o que aconteceu, facilitam o tratamento seletivo e carregam contexto rico sobre o erro.
<?php// Hierarquia de exceções customizadas
class AppException extends RuntimeException {}
class NegocioException extends AppException {}
class EstoqueInsuficienteException extends NegocioException {
public function __construct(
private string $produto,
private int $disponivel,
private int $solicitado,
) {
parent::__construct(
"Estoque insuficiente para '{$produto}': ",
);
} public function getProduto(): string { return $this->produto; }
public function getDisponivel(): int { return $this->disponivel; }
public function getSolicitado(): int { return $this->solicitado; }
}class PedidoService3 {
public function realizarPedido(Produto $prod, int $qtd): void {
if ($prod->estoque < $qtd) {
throw new EstoqueInsuficienteException(
$prod->nome,
$prod->estoque,
$qtd
);
}
// processar pedido...
}
}try {
$service = new PedidoService3();
$service->realizarPedido($produto, 50);
} catch (EstoqueInsuficienteException $e) {
// Tratamento específico com contexto
echo 'Produto: ' . $e->getProduto();
echo 'Disponível: ' . $e->getDisponivel();
} catch (NegocioException $e) {
// Tratamento genérico para erros de negócio
echo 'Erro de negócio: ' . $e->getMessage();
} catch (Throwable $e) {
// Captura tudo: Exception e Error
echo 'Erro inesperado: ' . $e->getMessage();
} finally {
// Sempre executado
echo 'Operação concluída.';
}
?>
10. Padrões de projeto mais usados em PHP
Design Patterns são soluções reutilizáveis para problemas recorrentes de design. Não são código copiável — são esquemas conceituais. Conhecer os principais faz você reconhecer estruturas em frameworks e tomar decisões de design mais conscientes.
10.1 Singleton
Garante que uma classe tenha apenas uma instância. Útil para conexões de banco de dados, loggers e configurações. Use com cautela: torna testes mais difíceis.
<?phpclass Configuracao {
private static ?self $instancia = null;
private array $dados = []; private function __construct() {
$this->dados = require 'config.php';
} public static function getInstance(): static {
if (static::$instancia === null) {
static::$instancia = new static();
}
return static::$instancia;
} public function get(string $chave, mixed $default = null): mixed {
return $this->dados[$chave] ?? $default;
}
}$config = Configuracao::getInstance();
$config->get('db_host'); // sempre a mesma instância
?>
10.2 Factory Method
Delega a criação de objetos a subclasses ou métodos específicos, desacoplando o código que usa os objetos do código que os cria.
<?phpinterface Notificacao {
public function enviar(string $mensagem, string $destinatario): void;
}class NotificacaoEmail implements Notificacao {
public function enviar(string $msg, string $dest): void {
echo "E-mail para {$dest}: {$msg}";
}
}class NotificacaoSMS implements Notificacao {
public function enviar(string $msg, string $dest): void {
echo "SMS para {$dest}: {$msg}";
}
}class NotificacaoFactory {
public static function criar(string $tipo): Notificacao {
return match($tipo) {
'email' => new NotificacaoEmail(),
'sms' => new NotificacaoSMS(),
default => throw new InvalidArgumentException("Tipo desconhecido: {$tipo}"),
};
}
}$notif = NotificacaoFactory::criar('email');
$notif->enviar('Seu pedido foi aprovado!', 'cliente@email.com');
?>
10.3 Repository Pattern
Abstrai o acesso a dados atrás de uma interface, separando a lógica de negócio da infraestrutura de persistência. É um dos padrões mais importantes para aplicações PHP profissionais.
<?phpinterface UsuarioRepository {
public function encontrarPorId(int $id): ?Usuario;
public function encontrarPorEmail(string $email): ?Usuario;
public function salvar(Usuario $usuario): void;
public function deletar(int $id): void;
}class MySQLUsuarioRepository implements UsuarioRepository {
public function __construct(private PDO $pdo) {} public function encontrarPorId(int $id): ?Usuario {
$stmt = $this->pdo->prepare('SELECT * FROM usuarios WHERE id = ?');
$stmt->execute([$id]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return $row ? Usuario::fromArray($row) : null;
} public function encontrarPorEmail(string $email): ?Usuario {
$stmt = $this->pdo->prepare('SELECT * FROM usuarios WHERE email = ?');
$stmt->execute([$email]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return $row ? Usuario::fromArray($row) : null;
} public function salvar(Usuario $usuario): void { /* INSERT/UPDATE */ }
public function deletar(int $id): void { /* DELETE */ }
}
?>
11. Boas práticas e code smells para evitar
11.1 God Class
Uma classe que sabe e faz demais — centenas de métodos, responsabilidades espalhadas, acoplamento com tudo. Sintoma claro de ausência do Princípio de Responsabilidade Única. Quebre em classes menores e focadas.
11.2 Anemic Domain Model
Classes que são apenas bags de dados — só têm getters e setters, sem lógica. Se sua classe Pedido tem apenas getProdutos(), getTotal(), setStatus() e nada mais, a lógica de negócio está provavelmente espalhada em services e controllers. Mova a lógica de volta para o domínio.
11.3 Law of Demeter
Um objeto deve conhecer apenas seus colaboradores imediatos. Evite cadeias longas como:
$order->getUser()->getAddress()->getCity()
Crie um método $order->getCidadeDeEntrega() que encapsula essa navegação internamente.
11.4 Primitives Obsession
Usar primitivos (string, int, float) para representar conceitos do domínio. Um CEP, um CPF, um e‑mail são mais do que strings — têm regras de validação próprias. Encapsule-os em Value Objects:
<?php// Ruim: string sem validação
function criarUsuario(string $email) { /* confia que é válido */ }// Bom: Value Object com validação embutida
class Email {
private string $valor; public function __construct(string $email) {
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException("E-mail inválido: {$email}");
}
$this->valor = strtolower($email);
} public function getValor(): string { return $this->valor; }
public function __toString(): string { return $this->valor; }
public function equals(Email $outro): bool { return $this->valor === $outro->valor; }
}function criarUsuario(Email $email) { /* garantido que é válido */ }
?>
Conclusão
A Orientação a Objetos em PHP não termina quando você aprende o que é uma classe. Ela começa quando você começa a pensar em termos de responsabilidades, contratos, abstrações e colaboração entre objetos.
Os conceitos deste guia — encapsulamento, herança, polimorfismo, interfaces, traits, tipagem forte, SOLID, padrões de projeto — formam o vocabulário do desenvolvimento PHP profissional. Frameworks como Laravel e Symfony são construídos inteiramente sobre esses fundamentos. Compreendê-los em profundidade é o que separa quem usa o framework de quem entende o framework.
O próximo passo natural nesta série é colocar OOP em contato com dados reais: formulários, superglobais e, em seguida, banco de dados com PDO. É quando os objetos saem do código e passam a representar entidades persistidas, com toda a complexidade e responsabilidade que isso implica.