Introdução à programação orientada a objetos
O termo “POO” refere-se à programação orientada a objetos, que é uma forma de organizar e estruturar o código. A POO permite-nos ver um programa como um conjunto de objetos que comunicam entre si, em vez de uma sequência de comandos e funções.
Na POO, um “objeto” é uma unidade que contém dados e funções que operam sobre esses dados. Os objetos são criados a partir de “classes”, que podemos entender como projetos ou modelos para objetos. Quando temos uma classe, podemos criar a sua “instância”, que é um objeto específico criado a partir dessa classe.
Vamos mostrar como podemos criar uma classe simples em PHP. Ao definir uma classe, usamos a palavra-chave “class”, seguida pelo nome da classe e, em seguida, chaves que envolvem as funções (chamadas de “métodos”) e variáveis da classe (chamadas de “propriedades”):
class Carro
{
function buzinar()
{
echo 'Bip bip!';
}
}
Neste exemplo, criamos uma classe chamada Carro
com uma função (ou “método”) chamada
buzinar
.
Cada classe deve resolver apenas uma tarefa principal. Se uma classe faz muitas coisas, pode ser apropriado dividi-la em classes menores e especializadas.
Normalmente, guardamos as classes em arquivos separados para manter o código organizado e fácil de navegar. O nome do
arquivo deve corresponder ao nome da classe, portanto, para a classe Carro
, o nome do arquivo seria
Carro.php
.
Ao nomear classes, é bom seguir a convenção “PascalCase”, o que significa que cada palavra no nome começa com uma letra maiúscula e não há sublinhados ou outros separadores entre elas. Métodos e propriedades usam a convenção “camelCase”, o que significa que começam com uma letra minúscula.
Alguns métodos em PHP têm tarefas especiais e são prefixados com __
(dois sublinhados). Um dos métodos
especiais mais importantes é o “construtor”, que é marcado como __construct
. O construtor é um método que é
chamado automaticamente quando você cria uma nova instância da classe.
Frequentemente usamos o construtor para definir o estado inicial de um objeto. Por exemplo, ao criar um objeto que representa uma pessoa, você pode usar o construtor para definir sua idade, nome ou outras propriedades.
Vamos ver como usar um construtor em PHP:
class Pessoa
{
private $idade;
function __construct($idade)
{
$this->idade = $idade;
}
function quantosAnosVoceTem()
{
return $this->idade;
}
}
$pessoa = new Pessoa(25);
echo $pessoa->quantosAnosVoceTem(); // Imprime: 25
Neste exemplo, a classe Pessoa
tem uma propriedade (variável) $idade
e um construtor que define essa
propriedade. O método quantosAnosVoceTem()
permite então aceder à idade da pessoa.
A pseudovariável $this
é usada dentro da classe para aceder às propriedades e métodos do objeto.
A palavra-chave new
é usada para criar uma nova instância da classe. No exemplo acima, criamos uma nova pessoa
com 25 anos.
Você também pode definir valores padrão para os parâmetros do construtor, caso não sejam especificados ao criar o objeto. Por exemplo:
class Pessoa
{
private $idade;
function __construct($idade = 20)
{
$this->idade = $idade;
}
function quantosAnosVoceTem()
{
return $this->idade;
}
}
$pessoa = new Pessoa; // se não passarmos nenhum argumento, os parênteses podem ser omitidos
echo $pessoa->quantosAnosVoceTem(); // Imprime: 20
Neste exemplo, se você não especificar a idade ao criar o objeto Pessoa
, o valor padrão 20 será usado.
É conveniente que a definição da propriedade com sua inicialização através do construtor possa ser abreviada e simplificada desta forma:
class Pessoa
{
function __construct(
private $idade = 20,
) {
}
}
Para completar, além dos construtores, os objetos também podem ter destrutores (método __destruct
), que são
chamados antes que o objeto seja libertado da memória.
Namespaces
Namespaces (ou “namespaces” em inglês) permitem-nos organizar e agrupar classes, funções e constantes relacionadas, evitando ao mesmo tempo conflitos de nomes. Pode imaginá-los como pastas num computador, onde cada pasta contém arquivos que pertencem a um determinado projeto ou tópico.
Namespaces são especialmente úteis em projetos maiores ou quando se usam bibliotecas de terceiros, onde podem ocorrer conflitos nos nomes das classes.
Imagine que tem uma classe chamada Carro
no seu projeto e quer colocá-la num namespace chamado
Transporte
. Faria isso da seguinte forma:
namespace Transporte;
class Carro
{
function buzinar()
{
echo 'Bip bip!';
}
}
Se quiser usar a classe Carro
noutro arquivo, precisa especificar de qual namespace a classe vem:
$carro = new Transporte\Carro;
Para simplificar, pode indicar no início do arquivo qual classe de um determinado namespace deseja usar, o que permite criar instâncias sem a necessidade de especificar o caminho completo:
use Transporte\Carro;
$carro = new Carro;
Herança
A herança é uma ferramenta da programação orientada a objetos que permite criar novas classes com base em classes existentes, herdando suas propriedades e métodos e estendendo-os ou redefinindo-os conforme necessário. A herança permite garantir a reutilização de código e a hierarquia de classes.
Simplificando, se tivermos uma classe e quisermos criar outra derivada dela, mas com algumas alterações, podemos “herdar” a nova classe da classe original.
Em PHP, a herança é realizada usando a palavra-chave extends
.
Nossa classe Pessoa
armazena informações sobre a idade. Podemos ter outra classe Estudante
, que
estende Pessoa
e adiciona informações sobre a área de estudo.
Vejamos um exemplo:
class Pessoa
{
private $idade;
function __construct($idade)
{
$this->idade = $idade;
}
function exibirInformacoes()
{
echo "Idade: {$this->idade} anos\n";
}
}
class Estudante extends Pessoa
{
private $curso;
function __construct($idade, $curso)
{
parent::__construct($idade);
$this->curso = $curso;
}
function exibirInformacoes()
{
parent::exibirInformacoes();
echo "Curso: {$this->curso} \n";
}
}
$estudante = new Estudante(20, 'Informática');
$estudante->exibirInformacoes();
Como este código funciona?
- Usamos a palavra-chave
extends
para estender a classePessoa
, o que significa que a classeEstudante
herdará todos os métodos e propriedades dePessoa
. - A palavra-chave
parent::
permite-nos chamar métodos da classe pai. Neste caso, chamamos o construtor da classePessoa
antes de adicionar a nossa própria funcionalidade à classeEstudante
. E da mesma forma, o métodoexibirInformacoes()
do ancestral antes de exibir as informações do estudante.
A herança destina-se a situações em que existe uma relação “é um” entre as classes. Por exemplo,
Estudante
é uma Pessoa
. Gato é um animal. Dá-nos a possibilidade, nos casos em que esperamos um
objeto (por exemplo, “Pessoa”) no código, usar em vez disso um objeto herdado (por exemplo, “Estudante”).
É importante notar que o principal propósito da herança não é evitar a duplicação de código. Pelo contrário, o uso incorreto da herança pode levar a código complexo e difícil de manter. Se a relação “é um” entre as classes não existir, devemos considerar a composição em vez da herança.
Note que os métodos exibirInformacoes()
nas classes Pessoa
e Estudante
exibem
informações ligeiramente diferentes. E podemos adicionar outras classes (por exemplo, Funcionario
), que fornecerão
outras implementações deste método. A capacidade de objetos de diferentes classes responderem ao mesmo método de maneiras
diferentes é chamada de polimorfismo:
$pessoas = [
new Pessoa(30),
new Estudante(20, 'Informática'),
new Funcionario(45, 'Diretor'), // Supondo que a classe Funcionario existe
];
foreach ($pessoas as $pessoa) {
$pessoa->exibirInformacoes();
}
Composição
A composição é uma técnica em que, em vez de herdarmos as propriedades e métodos de outra classe, simplesmente usamos a sua instância na nossa classe. Isso permite-nos combinar funcionalidades e propriedades de várias classes sem a necessidade de criar estruturas de herança complexas.
Vejamos um exemplo. Temos uma classe Motor
e uma classe Carro
. Em vez de dizermos “Carro é um
Motor”, dizemos “Carro tem um Motor”, que é uma relação típica de composição.
class Motor
{
function ligar()
{
echo 'Motor a funcionar.';
}
}
class Carro
{
private $motor;
function __construct()
{
$this->motor = new Motor;
}
function iniciar()
{
$this->motor->ligar();
echo 'Carro pronto para andar!';
}
}
$carro = new Carro;
$carro->iniciar();
Aqui, Carro
não tem todas as propriedades e métodos de Motor
, mas tem acesso a ele através da
propriedade $motor
.
A vantagem da composição é maior flexibilidade no design e melhor capacidade de modificação no futuro.
Visibilidade
Em PHP, pode definir a “visibilidade” para propriedades, métodos e constantes de uma classe. A visibilidade determina de onde pode aceder a esses elementos.
- Public: Se um elemento é marcado como
public
, significa que pode aceder a ele de qualquer lugar, mesmo fora da classe. - Protected: Um elemento marcado como
protected
é acessível apenas dentro da classe e de todos os seus descendentes (classes que herdam desta classe). - Private: Se um elemento é
private
, só pode aceder a ele de dentro da classe em que foi definido.
Se não especificar a visibilidade, o PHP definirá automaticamente como public
.
Vejamos um código de exemplo:
class ExemploVisibilidade
{
public $propriedadePublica = 'Pública';
protected $propriedadeProtegida = 'Protegida';
private $propriedadePrivada = 'Privada';
public function exibirPropriedades()
{
echo $this->propriedadePublica; // Funciona
echo $this->propriedadeProtegida; // Funciona
echo $this->propriedadePrivada; // Funciona
}
}
$objeto = new ExemploVisibilidade;
$objeto->exibirPropriedades();
echo $objeto->propriedadePublica; // Funciona
// echo $objeto->propriedadeProtegida; // Lança um erro
// echo $objeto->propriedadePrivada; // Lança um erro
Continuamos com a herança da classe:
class DescendenteClasse extends ExemploVisibilidade
{
public function exibirPropriedades()
{
echo $this->propriedadePublica; // Funciona
echo $this->propriedadeProtegida; // Funciona
// echo $this->propriedadePrivada; // Lança um erro
}
}
Neste caso, o método exibirPropriedades()
na classe DescendenteClasse
pode aceder às propriedades
públicas e protegidas, mas não pode aceder às propriedades privadas da classe pai.
Dados e métodos devem ser o mais ocultos possível e acessíveis apenas através de uma interface definida. Isso permite que você altere a implementação interna da classe sem afetar o resto do código.
Palavra-chave final
Em PHP, podemos usar a palavra-chave final
se quisermos impedir que uma classe, método ou constante seja herdada
ou sobrescrita. Quando marcamos uma classe como final
, ela não pode ser estendida. Quando marcamos um método como
final
, ele não pode ser sobrescrito numa classe descendente.
Saber que uma determinada classe ou método não será mais modificado permite-nos fazer alterações mais facilmente, sem ter que nos preocupar com possíveis conflitos. Por exemplo, podemos adicionar um novo método sem medo de que algum descendente já tenha um método com o mesmo nome e ocorra uma colisão. Ou podemos alterar os parâmetros de um método, pois novamente não há risco de causar inconsistência com um método sobrescrito num descendente.
final class ClasseFinal
{
}
// O código a seguir causará um erro, pois não podemos herdar de uma classe final.
class DescendenteClasseFinal extends ClasseFinal
{
}
Neste exemplo, a tentativa de herdar da classe final ClasseFinal
causará um erro.
Propriedades e Métodos Estáticos
Quando falamos em PHP sobre elementos “estáticos” de uma classe, referimo-nos a métodos e propriedades que pertencem à própria classe, e não a uma instância específica dessa classe. Isso significa que não precisa criar uma instância da classe para aceder a eles. Em vez disso, chama-os ou acede a eles diretamente através do nome da classe.
Tenha em mente que, como os elementos estáticos pertencem à classe e não às suas instâncias, não pode usar a
pseudovariável $this
dentro de métodos estáticos.
O uso de propriedades estáticas leva a código confuso cheio de armadilhas, portanto, nunca deve usá-las e nem mostraremos um exemplo de uso aqui. Por outro lado, os métodos estáticos são úteis. Exemplo de uso:
class Calculadora
{
public static function adicao($a, $b)
{
return $a + $b;
}
public static function subtracao($a, $b)
{
return $a - $b;
}
}
// Uso de método estático sem criar uma instância da classe
echo Calculadora::adicao(5, 3); // Resultado: 8
echo Calculadora::subtracao(5, 3); // Resultado: 2
Neste exemplo, criamos uma classe Calculadora
com dois métodos estáticos. Podemos chamar esses métodos
diretamente sem criar uma instância da classe usando o operador ::
. Métodos estáticos são especialmente úteis
para operações que não dependem do estado de uma instância específica da classe.
Constantes de Classe
Dentro das classes, temos a opção de definir constantes. Constantes são valores que nunca mudam durante a execução do programa. Ao contrário das variáveis, o valor de uma constante permanece sempre o mesmo.
class Carro
{
public const NumeroDeRodas = 4;
public function exibirNumeroDeRodas(): int
{
echo self::NumeroDeRodas;
}
}
echo Carro::NumeroDeRodas; // Saída: 4
Neste exemplo, temos a classe Carro
com a constante NumeroDeRodas
. Quando queremos aceder à
constante dentro da classe, podemos usar a palavra-chave self
em vez do nome da classe.
Interfaces de Objeto
Interfaces de objeto funcionam como “contratos” para classes. Se uma classe deve implementar uma interface de objeto, ela deve conter todos os métodos que essa interface define. É uma ótima maneira de garantir que certas classes sigam o mesmo “contrato” ou estrutura.
Em PHP, uma interface é definida com a palavra-chave interface
. Todos os métodos definidos na interface são
públicos (public
). Quando uma classe implementa uma interface, ela usa a palavra-chave implements
.
interface Animal
{
function emitirSom();
}
class Gato implements Animal
{
public function emitirSom()
{
echo 'Miau';
}
}
$gato = new Gato;
$gato->emitirSom();
Se uma classe implementa uma interface, mas nem todos os métodos esperados são definidos nela, o PHP lançará um erro.
Uma classe pode implementar várias interfaces ao mesmo tempo, o que é uma diferença em relação à herança, onde uma classe pode herdar apenas de uma classe:
interface Guarda
{
function guardarCasa();
}
class Cachorro implements Animal, Guarda
{
public function emitirSom()
{
echo 'Au au';
}
public function guardarCasa()
{
echo 'O cachorro guarda a casa atentamente';
}
}
Classes Abstratas
Classes abstratas servem como modelos básicos para outras classes, mas não pode criar instâncias delas diretamente. Elas contêm uma combinação de métodos completos e métodos abstratos, que não têm conteúdo definido. Classes que herdam de classes abstratas devem fornecer definições para todos os métodos abstratos do ancestral.
Para definir uma classe abstrata, usamos a palavra-chave abstract
.
abstract class ClasseAbstrata
{
public function metodoComum()
{
echo 'Este é um método comum';
}
abstract public function metodoAbstrato();
}
class Descendente extends ClasseAbstrata
{
public function metodoAbstrato()
{
echo 'Esta é a implementação do método abstrato';
}
}
$instancia = new Descendente;
$instancia->metodoComum();
$instancia->metodoAbstrato();
Neste exemplo, temos uma classe abstrata com um método comum e um método abstrato. Em seguida, temos a classe
Descendente
, que herda de ClasseAbstrata
e fornece a implementação para o método abstrato.
Qual a diferença entre interfaces e classes abstratas? Classes abstratas podem conter métodos abstratos e concretos, enquanto interfaces apenas definem quais métodos uma classe deve implementar, mas não fornecem nenhuma implementação. Uma classe pode herdar apenas de uma classe abstrata, mas pode implementar qualquer número de interfaces.
Verificação de Tipo
Na programação, é muito importante ter certeza de que os dados com os quais trabalhamos são do tipo correto. Em PHP, temos ferramentas que nos garantem isso. A verificação se os dados têm o tipo correto é chamada de “verificação de tipo” ou “type hinting”.
Tipos que podemos encontrar em PHP:
- Tipos básicos: Incluem
int
(números inteiros),float
(números decimais),bool
(valores booleanos),string
(cadeias de caracteres),array
(arrays) enull
. - Classes: Se quisermos que o valor seja uma instância de uma classe específica.
- Interfaces: Define um conjunto de métodos que uma classe deve implementar. Um valor que satisfaz a interface deve ter esses métodos.
- Tipos mistos (Union Types): Podemos especificar que uma variável pode ter vários tipos permitidos.
- Void: Este tipo especial indica que a função ou método não retorna nenhum valor.
Vamos ver como modificar o código para incluir tipos:
class Pessoa
{
private int $idade;
public function __construct(int $idade)
{
$this->idade = $idade;
}
public function exibirIdade(): void
{
echo "Esta pessoa tem {$this->idade} anos.";
}
}
/**
* Função que recebe um objeto da classe Pessoa e exibe a idade da pessoa.
*/
function exibirIdadePessoa(Pessoa $pessoa): void
{
$pessoa->exibirIdade();
}
Desta forma, garantimos que nosso código espera e trabalha com dados do tipo correto, o que nos ajuda a prevenir erros potenciais.
Alguns tipos não podem ser escritos diretamente em PHP. Nesse caso, eles são indicados num comentário phpDoc, que é um
formato padrão para documentar código PHP começando com /**
e terminando com */
. Permite adicionar
descrições de classes, métodos, etc. E também indicar tipos complexos usando as chamadas anotações @var
,
@param
e @return
. Esses tipos são então usados por ferramentas de análise estática de código, mas
o próprio PHP não os verifica.
class Lista
{
/** @var array<Pessoa> a notação diz que é um array de objetos Pessoa */
private array $pessoas = [];
public function adicionarPessoa(Pessoa $pessoa): void
{
$this->pessoas[] = $pessoa;
}
}
Comparação e Identidade
Em PHP, pode comparar objetos de duas maneiras:
- Comparação de valores
==
: Verifica se os objetos são da mesma classe e têm os mesmos valores em suas propriedades. - Identidade
===
: Verifica se se trata da mesma instância do objeto.
class Carro
{
public string $marca;
public function __construct(string $marca)
{
$this->marca = $marca;
}
}
$carro1 = new Carro('Skoda');
$carro2 = new Carro('Skoda');
$carro3 = $carro1;
var_dump($carro1 == $carro2); // true, porque têm o mesmo valor
var_dump($carro1 === $carro2); // false, porque não são a mesma instância
var_dump($carro1 === $carro3); // true, porque $carro3 é a mesma instância que $carro1
Operador instanceof
O operador instanceof
permite verificar se um determinado objeto é uma instância de uma classe específica, um
descendente dessa classe, ou se implementa uma determinada interface.
Imaginemos que temos uma classe Pessoa
e outra classe Estudante
, que é descendente da classe
Pessoa
:
class Pessoa
{
private int $idade;
public function __construct(int $idade)
{
$this->idade = $idade;
}
}
class Estudante extends Pessoa
{
private string $curso;
public function __construct(int $idade, string $curso)
{
parent::__construct($idade);
$this->curso = $curso;
}
}
$estudante = new Estudante(20, 'Informática');
// Verificar se $estudante é uma instância da classe Estudante
var_dump($estudante instanceof Estudante); // Saída: bool(true)
// Verificar se $estudante é uma instância da classe Pessoa (porque Estudante é descendente de Pessoa)
var_dump($estudante instanceof Pessoa); // Saída: bool(true)
Das saídas, é evidente que o objeto $estudante
é considerado simultaneamente uma instância de ambas as
classes – Estudante
e Pessoa
.
Interfaces Fluentes
“Interface fluente” (em inglês “Fluent Interface”) é uma técnica em POO que permite encadear métodos juntos numa única chamada. Isso muitas vezes simplifica e torna o código mais claro.
O elemento chave de uma interface fluente é que cada método na cadeia retorna uma referência ao objeto atual. Conseguimos
isso usando return $this;
no final do método. Este estilo de programação é frequentemente associado a métodos
chamados “setters”, que definem os valores das propriedades do objeto.
Vamos mostrar como uma interface fluente pode parecer num exemplo de envio de e-mails:
public function sendMessage()
{
$email = new Email;
$email->setRemetente('sender@example.com')
->setDestinatario('admin@example.com')
->setMensagem('Olá, esta é uma mensagem.')
->enviar();
}
Neste exemplo, os métodos setRemetente()
, setDestinatario()
e setMensagem()
servem para
definir os valores correspondentes (remetente, destinatário, conteúdo da mensagem). Após definir cada um desses valores, os
métodos retornam o objeto atual ($email
), o que nos permite encadear outro método a seguir. Finalmente, chamamos
o método enviar()
, que realmente envia o e-mail.
Graças às interfaces fluentes, podemos escrever código que é intuitivo e fácil de ler.
Cópia usando clone
Em PHP, podemos criar uma cópia de um objeto usando o operador clone
. Desta forma, obtemos uma nova instância
com conteúdo idêntico.
Se precisarmos modificar algumas propriedades ao copiar um objeto, podemos definir um método especial __clone()
na classe. Este método é chamado automaticamente quando o objeto é clonado.
class Ovelha
{
public string $nome;
public function __construct(string $nome)
{
$this->nome = $nome;
}
public function __clone()
{
$this->nome = 'Clone ' . $this->nome;
}
}
$original = new Ovelha('Dolly');
echo $original->nome . "\n"; // Imprime: Dolly
$clone = clone $original;
echo $clone->nome . "\n"; // Imprime: Clone Dolly
Neste exemplo, temos a classe Ovelha
com uma propriedade $nome
. Quando clonamos uma instância desta
classe, o método __clone()
garante que o nome da ovelha clonada receba o prefixo “Clone”.
Traits
Traits em PHP são uma ferramenta que permite compartilhar métodos, propriedades e constantes entre classes e evitar a duplicação de código. Pode imaginá-los como um mecanismo de “copiar e colar” (Ctrl-C e Ctrl-V), onde o conteúdo do trait é “colado” nas classes. Isso permite reutilizar código sem a necessidade de criar hierarquias de classes complicadas.
Vamos mostrar um exemplo simples de como usar traits em PHP:
trait BuzinarTrait
{
public function buzinar()
{
echo 'Bip bip!';
}
}
class Carro
{
use BuzinarTrait;
}
class Caminhao
{
use BuzinarTrait;
}
$carro = new Carro;
$carro->buzinar(); // Imprime 'Bip bip!'
$caminhao = new Caminhao;
$caminhao->buzinar(); // Também imprime 'Bip bip!'
Neste exemplo, temos um trait chamado BuzinarTrait
, que contém um método buzinar()
. Em seguida,
temos duas classes: Carro
e Caminhao
, ambas usando o trait BuzinarTrait
. Graças a isso,
ambas as classes “têm” o método buzinar()
, e podemos chamá-lo em objetos de ambas as classes.
Traits permitem compartilhar código entre classes de forma fácil e eficiente. Ao mesmo tempo, eles não entram na hierarquia
de herança, ou seja, $carro instanceof BuzinarTrait
retornará false
.
Exceções
Exceções em POO permitem-nos lidar elegantemente com erros e situações inesperadas no nosso código. São objetos que carregam informações sobre o erro ou situação incomum.
Em PHP, temos a classe integrada Exception
, que serve como base para todas as exceções. Ela tem vários métodos
que nos permitem obter mais informações sobre a exceção, como a mensagem de erro, o arquivo e a linha onde o erro
ocorreu, etc.
Quando ocorre um erro no código, podemos “lançar” uma exceção usando a palavra-chave throw
.
function divisao(float $a, float $b): float
{
if ($b === 0.0) { // Comparação estrita para float
throw new Exception('Divisão por zero!');
}
return $a / $b;
}
Quando a função divisao()
recebe zero como segundo argumento, ela lança uma exceção com a mensagem de erro
'Divisão por zero!'
. Para evitar que o programa falhe ao lançar uma exceção, capturamo-la num bloco
try/catch
:
try {
echo divisao(10, 0);
} catch (Exception $e) {
echo 'Exceção capturada: '. $e->getMessage();
}
O código que pode lançar uma exceção é envolvido num bloco try
. Se uma exceção for lançada, a execução
do código move-se para o bloco catch
, onde podemos processar a exceção (por exemplo, exibir uma mensagem
de erro).
Após os blocos try
e catch
, podemos adicionar um bloco opcional finally
, que será
executado sempre, quer uma exceção tenha sido lançada ou não (mesmo no caso de usarmos a instrução return
,
break
ou continue
no bloco try
ou catch
):
try {
echo divisao(10, 0);
} catch (Exception $e) {
echo 'Exceção capturada: '. $e->getMessage();
} finally {
// Código que será executado sempre, quer a exceção tenha sido lançada ou não
}
Também podemos criar nossas próprias classes (hierarquia) de exceções que herdam da classe Exception. Como exemplo, imaginemos uma aplicação bancária simples que permite fazer depósitos e saques:
class ExcecaoBancaria extends Exception {}
class ExcecaoSaldoInsuficiente extends ExcecaoBancaria {}
class ExcecaoLimiteExcedido extends ExcecaoBancaria {}
class ContaBancaria
{
private int $saldo = 0;
private int $limiteDiario = 1000;
public function depositar(int $quantia): int
{
$this->saldo += $quantia;
return $this->saldo;
}
public function sacar(int $quantia): int
{
if ($quantia > $this->saldo) {
throw new ExcecaoSaldoInsuficiente('Saldo insuficiente na conta.');
}
if ($quantia > $this->limiteDiario) {
throw new ExcecaoLimiteExcedido('O limite diário para saques foi excedido.');
}
$this->saldo -= $quantia;
return $this->saldo;
}
}
Para um bloco try
, podem ser fornecidos vários blocos catch
, se esperar diferentes tipos de
exceções.
$conta = new ContaBancaria;
$conta->depositar(500);
try {
$conta->sacar(1500);
} catch (ExcecaoLimiteExcedido $e) {
echo $e->getMessage();
} catch (ExcecaoSaldoInsuficiente $e) {
echo $e->getMessage();
} catch (ExcecaoBancaria $e) {
echo 'Ocorreu um erro ao realizar a operação.';
}
Neste exemplo, é importante notar a ordem dos blocos catch
. Como todas as exceções herdam de
ExcecaoBancaria
, se tivéssemos este bloco primeiro, todas as exceções seriam capturadas nele, sem que o código
chegasse aos blocos catch
seguintes. Portanto, é importante ter exceções mais específicas (ou seja, aquelas que
herdam de outras) no bloco catch
mais acima na ordem do que suas exceções pai.
Iteração
Em PHP, pode percorrer objetos usando o loop foreach
, semelhante a como percorre arrays. Para que isso funcione,
o objeto deve implementar uma interface especial.
A primeira opção é implementar a interface Iterator
, que possui os métodos current()
que retorna
o valor atual, key()
que retorna a chave, next()
que move para o próximo valor, rewind()
que move para o início e valid()
que verifica se ainda não chegamos ao fim.
A segunda opção é implementar a interface IteratorAggregate
, que possui apenas um método
getIterator()
. Este retorna um objeto substituto que garantirá a iteração, ou pode representar um gerador, que é
uma função especial onde yield
é usado para retornar chaves e valores sequencialmente:
class Pessoa
{
public function __construct(
public int $idade,
) {
}
}
class Lista implements IteratorAggregate
{
private array $pessoas = [];
public function adicionarPessoa(Pessoa $pessoa): void
{
$this->pessoas[] = $pessoa;
}
public function getIterator(): Generator
{
foreach ($this->pessoas as $pessoa) {
yield $pessoa;
}
}
}
$lista = new Lista;
$lista->adicionarPessoa(new Pessoa(30));
$lista->adicionarPessoa(new Pessoa(25));
foreach ($lista as $pessoa) {
echo "Idade: {$pessoa->idade} anos \n";
}
Boas Práticas
Depois de dominar os princípios básicos da programação orientada a objetos, é importante focar nas boas práticas em POO. Elas ajudarão a escrever código que não é apenas funcional, mas também legível, compreensível e fácil de manter.
- Separação de Responsabilidades (Separation of Concerns): Cada classe deve ter uma responsabilidade claramente definida e deve resolver apenas uma tarefa principal. Se uma classe faz muitas coisas, pode ser apropriado dividi-la em classes menores e especializadas.
- Encapsulamento (Encapsulation): Dados e métodos devem ser o mais ocultos possível e acessíveis apenas através de uma interface definida. Isso permite que você altere a implementação interna da classe sem afetar o resto do código.
- Injeção de Dependência (Dependency Injection): Em vez de criar dependências diretamente na classe, deve “injetá-las” de fora. Para uma compreensão mais profunda deste princípio, recomendamos os capítulos sobre Injeção de Dependência.