Curso Completo de PHP — Parte 05

Orientação a Objetos em PHP

Por que a Orientação a Objetos mudou o PHP para sempre

Havia um tem­po em que PHP era sinôn­i­mo de scripts soltos, funções globais e códi­go que ninguém ousa­va mex­er depois de pron­to. Esse tem­po ficou para trás. Hoje, o ecos­sis­tema PHP — Sym­fony, Lar­avel, Doc­trine, Com­pos­er — é inteira­mente con­struí­do sobre Ori­en­tação a Obje­tos, e enten­der OOP não é mais opcional para quem quer tra­bal­har de for­ma profis­sion­al com a lin­guagem.

A Ori­en­tação a Obje­tos não é ape­nas uma for­ma difer­ente de escr­ev­er códi­go. É uma for­ma difer­ente de pen­sar sobre prob­le­mas. Em vez de per­gun­tar “que função resolve isso?”, você per­gun­ta “que enti­dade é respon­sáv­el por isso?”. Em vez de pas­sar dados entre funções soltas, você encap­su­la com­por­ta­men­to e esta­do em obje­tos coesos que se comu­ni­cam entre si.

Este guia cobre a OOP em PHP do zero ao avança­do, com foco no que real­mente impor­ta em pro­je­tos reais: encap­su­la­men­to, her­ança, polimor­fis­mo, inter­faces, traits, tipos estri­tos e os princí­pios SOLID que sep­a­ram códi­go amador de códi­go profis­sion­al. Cada con­ceito vem acom­pan­hado de exem­p­los pron­tos para pro­dução, não de abstrações acadêmi­cas.

Para quem é este guia

Desen­volve­dores PHP que já con­hecem o bási­co da lin­guagem e querem dom­i­nar OOP com pro­fun­di­dade. Todo exem­p­lo é com­patív­el com PHP 8.0+ e segue as mel­hores práti­cas do ecos­sis­tema mod­er­no.


1. Classes e objetos: a base de tudo

Uma classe é um molde — ela define quais dados (pro­priedades) e quais com­por­ta­men­tos (méto­dos) um deter­mi­na­do tipo de enti­dade terá. Um obje­to é uma instân­cia conc­re­ta desse molde, com seus próprios val­ores para cada pro­priedade.

Pense em uma classe Produto como a plan­ta arquitetôni­ca de um pro­du­to. O pro­du­to real — um note­book com nome, preço e estoque especí­fi­cos — é o obje­to cri­a­do a par­tir dessa plan­ta.

<?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éto­do __construct() é exe­cu­ta­do auto­mati­ca­mente quan­do um obje­to é cri­a­do com new. É o lugar ide­al para garan­tir que o obje­to nasce em um esta­do váli­do e con­sis­tente. O PHP 8.0 intro­duz­iu a pro­moção de pro­priedades no con­stru­tor, elim­i­nan­do códi­go repet­i­ti­vo:

<?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+
Pro­priedades readonly só podem ser atribuí­das uma vez, no con­stru­tor. São ideais para Val­ue Objects e DTOs onde imutabil­i­dade é dese­ja­da. No PHP 8.2+, você pode declarar readonly na própria classe.

1.2 Métodos mágicos essenciais

O PHP ofer­ece méto­dos mági­cos (pre­fix­a­dos com __) que são chama­dos auto­mati­ca­mente em situ­ações especí­fi­cas. 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

Encap­su­la­men­to é o princí­pio de escon­der os detal­h­es de imple­men­tação de um obje­to e expor ape­nas o que é necessário para o mun­do exter­no. Em PHP, isso é feito com os mod­i­fi­cadores de vis­i­bil­i­dade: public, protected e private.

O encap­su­la­men­to não é buro­c­ra­cia. É a difer­ença entre um obje­to que pode ser usa­do de qual­quer jeito (e que­brar silen­ciosa­mente) e um obje­to que garante sua própria con­sistên­cia inter­na, inde­pen­dente de como é usa­do.

Vis­i­bil­i­dadePrópria classeSub­class­esCódi­go exter­no
publicSimSimSim
protectedSimSimNão
privateSimNãoNã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 sal­do nun­ca pode ser alter­ado dire­ta­mente de fora. Toda oper­ação pas­sa pelos méto­dos depositar() e sacar(), que val­i­dam as regras de negó­cio. Esse é o poder do encap­su­la­men­to: o obje­to é o guardião da sua própria inte­gri­dade.


3. Herança: reutilizando e especializando comportamento

Her­ança per­mite cri­ar uma classe nova basea­da em uma exis­tente, her­dan­do todas as suas pro­priedades e méto­dos e adi­cio­nan­do ou sobre­screven­do com­por­ta­men­tos con­forme necessário. Em PHP, usa-se a palavra-chave extends.

Her­ança deve mod­e­lar uma relação “é um”: um Gerente é um Funcionário, um Cachorro é um Animal. Se a relação for “tem um” ou “usa um”, pre­fi­ra com­posiçã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 abstra­ta não pode ser instan­ci­a­da dire­ta­mente — ela existe ape­nas para ser esten­di­da. Méto­dos abstratos declar­am uma assi­natu­ra sem imple­men­tação, forçan­do as sub­class­es a fornecerem a sua própria ver­são. É um con­tra­to inter­no ao nív­el da hier­ar­quia de her­ança.

Her­ança vs. Com­posição
Pre­fi­ra com­posição à her­ança quan­do o obje­ti­vo é reuti­lizar códi­go sem cri­ar uma hier­ar­quia rígi­da. Her­ança cria acopla­men­to forte entre class­es. Com­posição (inje­tar obje­tos colab­o­radores) é mais flexív­el e testáv­el. Regra práti­ca: se você não con­segue diz­er “X é um Y” com con­vicção, use com­posição.


4. Polimorfismo: o poder da substituição

Polimor­fis­mo sig­nifi­ca “muitas for­mas”. Em OOP, é a capaci­dade de tratar obje­tos de difer­entes class­es de for­ma uni­forme, des­de que com­par­til­hem uma inter­face comum. É o princí­pio que tor­na o códi­go exten­sív­el sem pre­cis­ar mod­i­ficá-lo.

Um exem­p­lo claro: um sis­tema de paga­men­tos que acei­ta cartão de crédi­to, bole­to e Pix não dev­e­ria ter um if gigante para cada méto­do. Com polimor­fis­mo, cada méto­do de paga­men­to é um obje­to que sabe proces­sar a si mes­mo, e o sis­tema os tra­ta de for­ma idên­ti­ca.

<?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 adi­cionar um novo méto­do de paga­men­to — dig­amos, crip­to­moe­da — bas­ta cri­ar uma nova classe que imple­men­ta MetodoPagamento. O ProcessadorPagamento não pre­cisa ser toca­do. Isso é o Princí­pio Aberto/Fechado em ação: aber­to para exten­são, fecha­do para mod­i­fi­cação.


5. Interfaces e contratos

Uma inter­face define um con­tra­to: qual­quer classe que a imple­mente deve fornecer todos os méto­dos declar­a­dos. Inter­faces não con­têm imple­men­tação — ape­nas assi­nat­uras. Uma classe pode imple­men­tar múlti­plas inter­faces, resol­ven­do a lim­i­tação do PHP de her­ança sim­ples.

<?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

Car­ac­terís­ti­caInter­faceClasse Abstra­ta
Imple­men­taçãoApe­nas assi­nat­uras (+ default PHP 8)Pode ter méto­dos con­cre­tos
Her­ança múlti­plaUma classe pode imple­men­tar váriasApe­nas uma por vez
Pro­priedadesApe­nas con­stantesPode ter pro­priedades
Instan­ci­açãoNãoNão
Quan­do usarDefinir con­tratos entre sis­temasCom­par­til­har imple­men­tação base

6. Traits: reutilização horizontal de código

Traits resolvem um prob­le­ma real: PHP só per­mite her­ança sim­ples, mas fre­quente­mente você quer reuti­lizar um com­por­ta­men­to em class­es que não com­par­til­ham uma hier­ar­quia comum. Traits são blo­cos de códi­go que podem ser incluí­dos em qual­quer classe, inde­pen­dente da sua posição na hier­ar­quia.

Pense em traits como mix­ins: com­por­ta­men­tos que você “cola” em uma classe. Um trait de Timestampable, por exem­p­lo, pode ser usa­do tan­to em Pedido quan­to em Usuario quan­to em Produto, sem que essas class­es pre­cisem her­dar 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
?>

Cuida­do com o abu­so de traits
Traits são poderosos, mas usa­dos em exces­so cri­am códi­go difí­cil de ras­trear. Pre­fi­ra traits para com­por­ta­men­tos ver­dadeira­mente trans­ver­sais (log­ging, time­stamps, soft delete). Para lóg­i­ca de negó­cio, pre­fi­ra com­posição explíci­ta.


7. Tipagem estrita e type hints

O PHP mod­er­no (7.0+) supor­ta tipagem forte, e no PHP 8.x ela está mais com­ple­ta do que nun­ca. Usar tipos explíc­i­tos não é pedan­tismo — é comu­ni­cação. Um méto­do tipa­do doc­u­men­ta a si mes­mo e fal­ha rápi­do quan­do recebe dados erra­dos, em vez de propa­gar silen­ciosa­mente um null ou um tipo ines­per­a­do por toda a apli­caçã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ôn­i­mo para cin­co princí­pios de design ori­en­ta­do a obje­tos for­mu­la­dos por Robert C. Mar­tin. Eles não são regras rígi­das — são dire­trizes que, quan­do seguidas, resul­tam em códi­go mais fácil de enten­der, tes­tar e mod­i­ficar.

8.1 S — Single Responsibility Principle

Uma classe deve ter ape­nas uma razão para mudar. Na práti­ca: sep­a­re respon­s­abil­i­dades. Uma classe que bus­ca dados no ban­co, for­ma­ta e envia e‑mail está com três respon­s­abil­i­dades — 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

Enti­dades devem estar aber­tas para exten­são e fechadas para mod­i­fi­cação. O exem­p­lo do sis­tema de paga­men­tos que vimos ante­ri­or­mente é o OCP em ação: novos méto­dos de paga­men­to são adi­ciona­dos sem tocar no ProcessadorPagamento.

8.3 L — Liskov Substitution Principle

Obje­tos de uma sub­classe devem poder sub­sti­tuir obje­tos da super­classe sem que­brar o sis­tema. Em ter­mos práti­cos: se uma sub­classe restringe ou altera o com­por­ta­men­to esper­a­do da super­classe, há uma vio­laçã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ça­dos a depen­der de inter­faces que não usam. Pre­fi­ra inter­faces peque­nas e focadas a inter­faces gor­das que agru­pam méto­dos não rela­ciona­dos.

<?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ódu­los de alto nív­el não devem depen­der de módu­los de baixo nív­el. Ambos devem depen­der de abstrações. Na práti­ca, isso sig­nifi­ca inje­tar dependên­cias em vez de instan­ciá-las inter­na­mente — o que tam­bém tor­na o códi­go muito mais fácil de tes­tar.

<?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 rep­re­sen­ta­dos como obje­tos — instân­cias de class­es que esten­dem Exception ou Error. Isso per­mite cri­ar hier­ar­quias de exceções que comu­ni­cam exata­mente o que acon­te­ceu, facili­tam o trata­men­to sele­ti­vo e car­regam con­tex­to 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 Pat­terns são soluções reuti­lizáveis para prob­le­mas recor­rentes de design. Não são códi­go copiáv­el — são esque­mas con­ceitu­ais. Con­hecer os prin­ci­pais faz você recon­hecer estru­turas em frame­works e tomar decisões de design mais con­scientes.

10.1 Singleton

Garante que uma classe ten­ha ape­nas uma instân­cia. Útil para conexões de ban­co de dados, log­gers e con­fig­u­rações. Use com cautela: tor­na 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

Del­e­ga a cri­ação de obje­tos a sub­class­es ou méto­dos especí­fi­cos, desacop­lan­do o códi­go que usa os obje­tos do códi­go 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 aces­so a dados atrás de uma inter­face, sep­a­ran­do a lóg­i­ca de negó­cio da infraestru­tu­ra de per­sistên­cia. É um dos padrões mais impor­tantes para apli­cações PHP profis­sion­ais.

<?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 — cen­te­nas de méto­dos, respon­s­abil­i­dades espal­hadas, acopla­men­to com tudo. Sin­toma claro de ausên­cia do Princí­pio de Respon­s­abil­i­dade Úni­ca. Que­bre em class­es menores e focadas.

11.2 Anemic Domain Model

Class­es que são ape­nas bags de dados — só têm get­ters e set­ters, sem lóg­i­ca. Se sua classe Pedido tem ape­nas getProdutos(), getTotal(), setStatus() e nada mais, a lóg­i­ca de negó­cio está provavel­mente espal­ha­da em ser­vices e con­trollers. Mova a lóg­i­ca de vol­ta para o domínio.

11.3 Law of Demeter

Um obje­to deve con­hecer ape­nas seus colab­o­radores ime­di­atos. Evite cadeias lon­gas como:

$order->getUser()->getAddress()->getCity()

Crie um méto­do $order->getCidadeDeEntrega() que encap­su­la essa nave­g­ação inter­na­mente.

11.4 Primitives Obsession

Usar prim­i­tivos (string, int, float) para rep­re­sen­tar con­ceitos do domínio. Um CEP, um CPF, um e‑mail são mais do que strings — têm regras de val­i­dação próprias. Encap­sule-os em Val­ue 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 Ori­en­tação a Obje­tos em PHP não ter­mi­na quan­do você aprende o que é uma classe. Ela começa quan­do você começa a pen­sar em ter­mos de respon­s­abil­i­dades, con­tratos, abstrações e colab­o­ração entre obje­tos.

Os con­ceitos deste guia — encap­su­la­men­to, her­ança, polimor­fis­mo, inter­faces, traits, tipagem forte, SOLID, padrões de pro­je­to — for­mam o vocab­ulário do desen­volvi­men­to PHP profis­sion­al. Frame­works como Lar­avel e Sym­fony são con­struí­dos inteira­mente sobre ess­es fun­da­men­tos. Com­preendê-los em pro­fun­di­dade é o que sep­a­ra quem usa o frame­work de quem entende o frame­work.

O próx­i­mo pas­so nat­ur­al nes­ta série é colo­car OOP em con­ta­to com dados reais: for­mulários, super­globais e, em segui­da, ban­co de dados com PDO. É quan­do os obje­tos saem do códi­go e pas­sam a rep­re­sen­tar enti­dades per­sis­ti­das, com toda a com­plex­i­dade e respon­s­abil­i­dade que isso impli­ca.

Cur­so Com­ple­to de PHP — Parte 06

Posts Similares

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *