Introdução à programação orientada a objetos
O termo “OOP” significa Object-Oriented Programming (Programação orientada a objetos), que é uma forma de organizar e estruturar o código. A OOP nos permite ver um programa como uma coleção de objetos que se comunicam entre si, em vez de uma sequência de comandos e funções.
Na OOP, um “objeto” é uma unidade que contém dados e funções que operam com esses dados. Os objetos são criados com base em “classes”, que podem ser entendidas como planos ou modelos para objetos. Quando temos uma classe, podemos criar sua “instância”, que é um objeto específico criado a partir dessa classe.
Vamos ver como podemos criar uma classe simples no PHP. Quando definimos uma classe, usamos a palavra-chave “class” (classe), seguida pelo nome da classe e, em seguida, chaves que envolvem as funções da classe (chamadas de “métodos”) e as variáveis da classe (chamadas de “propriedades” ou “atributos”):
class Car
{
function honk()
{
echo 'Beep beep!';
}
}
Neste exemplo, criamos uma classe chamada Car
com uma função (ou “método”) chamada honk
.
Cada classe deve resolver apenas uma tarefa principal. Se uma classe estiver fazendo muitas coisas, pode ser apropriado dividi-la em classes menores e especializadas.
Normalmente, as classes são armazenadas 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 Car
, o nome do arquivo seria
Car.php
.
Ao nomear as classes, é bom seguir a convenção “PascalCase”, o que significa que cada palavra do nome começa com uma letra maiúscula e não há sublinhados ou outros separadores. Os métodos e as propriedades seguem a convenção “camelCase”, ou seja, começam com uma letra minúscula.
Alguns métodos no PHP têm funções especiais e são prefixados com __
(dois sublinhados). Um dos métodos
especiais mais importantes é o “construtor”, rotulado como __construct
. O construtor é um método que é
chamado automaticamente ao criar uma nova instância de uma classe.
Geralmente 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 outros atributos.
Vamos ver como usar um construtor no PHP:
class Person
{
private $age;
function __construct($age)
{
$this->age = $age;
}
function howOldAreYou()
{
return $this->age;
}
}
$person = new Person(25);
echo $person->howOldAreYou(); // Outputs: 25
Neste exemplo, a classe Person
tem uma propriedade (variável) $age
e um construtor que define essa
propriedade. O método howOldAreYou()
fornece acesso à idade da pessoa.
A pseudovariável $this
é usada dentro da classe para acessar as propriedades e os métodos do objeto.
A palavra-chave new
é usada para criar uma nova instância de uma 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 se eles não forem especificados ao criar um objeto. Por exemplo:
class Person
{
private $age;
function __construct($age = 20)
{
$this->age = $age;
}
function howOldAreYou()
{
return $this->age;
}
}
$person = new Person; // if no argument is passed, parentheses can be omitted
echo $person->howOldAreYou(); // Outputs: 20
Neste exemplo, se você não especificar uma idade ao criar um objeto Person
, o valor padrão de
20 será usado.
O bom é que a definição da propriedade com sua inicialização por meio do construtor pode ser encurtada e simplificada da seguinte forma:
class Person
{
function __construct(
private $age = 20,
) {
}
}
Para ser mais completo, além dos construtores, os objetos podem ter destrutores (método __destruct
) que são
chamados antes de o objeto ser liberado da memória.
Namespaces
Os namespaces nos permitem organizar e agrupar classes, funções e constantes relacionadas, evitando conflitos de nomes. Você pode pensar neles como pastas em um computador, onde cada pasta contém arquivos relacionados a um projeto ou tópico específico.
Os namespaces são especialmente úteis em projetos maiores ou ao usar bibliotecas de terceiros, onde podem surgir conflitos de nomes de classes.
Imagine que você tenha uma classe chamada Car
em seu projeto e queira colocá-la em um namespace chamado
Transport
. Você faria isso da seguinte forma:
namespace Transport;
class Car
{
function honk()
{
echo 'Beep beep!';
}
}
Se quiser usar a classe Car
em outro arquivo, precisará especificar de qual namespace a classe se origina:
$car = new Transport\Car;
Para simplificar, você pode especificar no início do arquivo qual classe de um determinado namespace deseja usar, o que lhe permite criar instâncias sem mencionar o caminho completo:
use Transport\Car;
$car = new Car;
Herança
A herança é uma ferramenta de programação orientada a objetos que permite a criação de novas classes com base nas existentes, herdando suas propriedades e métodos e estendendo-os ou redefinindo-os conforme necessário. A herança garante a reutilização do código e a hierarquia de classes.
Em termos simples, se tivermos uma classe e quisermos criar outra derivada dela, mas com algumas modificações, podemos “herdar” a nova classe da original.
No PHP, a herança é implementada usando a palavra-chave extends
.
Nossa classe Person
armazena informações de idade. Podemos ter outra classe, Student
, que estende a
Person
e acrescenta informações sobre o campo de estudo.
Vamos dar uma olhada em um exemplo:
class Person
{
private $age;
function __construct($age)
{
$this->age = $age;
}
function printInformation()
{
echo "Age: {$this->age} years\n";
}
}
class Student extends Person
{
private $fieldOfStudy;
function __construct($age, $fieldOfStudy)
{
parent::__construct($age);
$this->fieldOfStudy = $fieldOfStudy;
}
function printInformation()
{
parent::printInformation();
echo "Field of study: {$this->fieldOfStudy} \n";
}
}
$student = new Student(20, 'Computer Science');
$student->printInformation();
Como esse código funciona?
- Usamos a palavra-chave
extends
para estender a classePerson
, o que significa que a classeStudent
herda todos os métodos e propriedades dePerson
. - A palavra-chave
parent::
nos permite chamar métodos da classe principal. Nesse caso, chamamos o construtor da classePerson
antes de adicionar nossa própria funcionalidade à classeStudent
. E, da mesma forma, o método ancestralprintInformation()
antes de listar as informações do aluno.
A herança é destinada a situações em que há um relacionamento “é um” entre as classes. Por exemplo, um
Student
é um Person
. Um gato é um animal. Ela nos permite, nos casos em que esperamos um objeto (por
exemplo, “Person”) no código, usar um objeto derivado (por exemplo, “Student”).
É essencial perceber que o objetivo principal da herança não é evitar a duplicação de código. Pelo contrário, o uso incorreto da herança pode levar a um código complexo e de difícil manutenção. Se não houver uma relação “é um” entre as classes, devemos considerar a composição em vez da herança.
Observe que os métodos printInformation()
nas classes Person
e Student
produzem
informações ligeiramente diferentes. E podemos adicionar outras classes (como Employee
) que fornecerão outras
implementações desse método. A capacidade de objetos de classes diferentes responderem ao mesmo método de maneiras diferentes
é chamada de polimorfismo:
$people = [
new Person(30),
new Student(20, 'Computer Science'),
new Employee(45, 'Director'),
];
foreach ($people as $person) {
$person->printInformation();
}
Composição
A composição é uma técnica em que, em vez de herdar propriedades e métodos de outra classe, simplesmente usamos sua instância em nossa classe. Isso nos permite combinar funcionalidades e propriedades de várias classes sem criar estruturas de herança complexas.
Por exemplo, temos uma classe Engine
e uma classe Car
. Em vez de dizer “Um carro é um motor”,
dizemos “Um carro tem um motor”, que é uma relação de composição típica.
class Engine
{
function start()
{
echo 'Engine is running.';
}
}
class Car
{
private $engine;
function __construct()
{
$this->engine = new Engine;
}
function start()
{
$this->engine->start();
echo 'The car is ready to drive!';
}
}
$car = new Car;
$car->start();
Aqui, o Car
não tem todas as propriedades e métodos do Engine
, mas tem acesso a eles por meio da
propriedade $engine
.
A vantagem da composição é a maior flexibilidade de design e a melhor adaptabilidade a mudanças futuras.
Visibilidade
No PHP, você pode definir “visibilidade” para propriedades de classe, métodos e constantes. A visibilidade determina onde você pode acessar esses elementos.
- Public: Se um elemento estiver marcado como
public
, isso significa que você pode acessá-lo de qualquer lugar, mesmo fora da classe. - Protegido: Um elemento marcado como
protected
é acessível somente dentro da classe e de todos os seus descendentes (classes que herdam dela). - Private: Se um elemento for
private
, você poderá acessá-lo somente na classe em que ele foi definido.
Se você não especificar a visibilidade, o PHP a definirá automaticamente como public
.
Vamos dar uma olhada em um exemplo de código:
class VisibilityExample
{
public $publicProperty = 'Public';
protected $protectedProperty = 'Protected';
private $privateProperty = 'Private';
public function printProperties()
{
echo $this->publicProperty; // Works
echo $this->protectedProperty; // Works
echo $this->privateProperty; // Works
}
}
$object = new VisibilityExample;
$object->printProperties();
echo $object->publicProperty; // Works
// echo $object->protectedProperty; // Throws an error
// echo $object->privateProperty; // Throws an error
Continuando com a herança de classes:
class ChildClass extends VisibilityExample
{
public function printProperties()
{
echo $this->publicProperty; // Works
echo $this->protectedProperty; // Works
// echo $this->privateProperty; // Throws an error
}
}
Nesse caso, o método printProperties()
no ChildClass
pode acessar as propriedades públicas e
protegidas, mas não pode acessar as propriedades privadas da classe principal.
Os dados e os métodos devem ser tão ocultos quanto possível e acessíveis somente por meio de uma interface definida. Isso permite que você altere a implementação interna da classe sem afetar o restante do código.
Palavra-chave final
No PHP, podemos usar a palavra-chave final
se quisermos impedir que uma classe, um método ou uma constante seja
herdado ou substituído. Quando uma classe é marcada como final
, ela não pode ser estendida. Quando um método é
marcado como final
, ele não pode ser substituído em uma subclasse.
O fato de saber que uma determinada classe ou método não será mais modificado nos permite fazer alterações com mais facilidade sem nos preocuparmos com possíveis conflitos. Por exemplo, podemos adicionar um novo método sem medo de que um descendente já tenha um método com o mesmo nome, levando a uma colisão. Ou podemos alterar os parâmetros de um método, novamente sem o risco de causar inconsistência com um método substituído em um descendente.
final class FinalClass
{
}
// The following code will throw an error because we cannot inherit from a final class.
class ChildOfFinalClass extends FinalClass
{
}
Neste exemplo, a tentativa de herdar da classe final FinalClass
resultará em um erro.
Propriedades e métodos estáticos
Quando falamos de elementos “estáticos” de uma classe no PHP, queremos dizer métodos e propriedades que pertencem à própria classe, não a uma instância específica da classe. Isso significa que não é necessário criar uma instância da classe para acessá-los. Em vez disso, você os chama ou acessa diretamente por meio do nome da classe.
Lembre-se de que, como os elementos estáticos pertencem à classe e não às suas instâncias, você não pode usar a
pseudovariável $this
dentro dos métodos estáticos.
O uso de propriedades estáticas leva a um código ofuscado e cheio de armadilhas, portanto, você nunca deve usá-las, e não mostraremos um exemplo aqui. Por outro lado, os métodos estáticos são úteis. Aqui está um exemplo:
class Calculator
{
public static function add($a, $b)
{
return $a + $b;
}
public static function subtract($a, $b)
{
return $a - $b;
}
}
// Using the static method without creating an instance of the class
echo Calculator::add(5, 3); // Output: 8
echo Calculator::subtract(5, 3); // Output: 2
Neste exemplo, criamos uma classe Calculator
com dois métodos estáticos. Podemos chamar esses métodos
diretamente sem criar uma instância da classe usando o operador ::
. Os 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
Nas 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 o mesmo.
class Car
{
public const NumberOfWheels = 4;
public function displayNumberOfWheels(): int
{
echo self::NumberOfWheels;
}
}
echo Car::NumberOfWheels; // Output: 4
Neste exemplo, temos uma classe Car
com a constante NumberOfWheels
. Ao acessar a constante dentro da
classe, podemos usar a palavra-chave self
em vez do nome da classe.
Interfaces de objeto
As interfaces de objetos funcionam como “contratos” para as classes. Se uma classe tiver que implementar uma interface de objeto, ela deverá conter todos os métodos que a interface define. Essa é uma ótima maneira de garantir que determinadas classes sigam o mesmo “contrato” ou estrutura.
No PHP, as interfaces são definidas usando a palavra-chave interface
. Todos os métodos definidos em uma
interface são públicos (public
). Quando uma classe implementa uma interface, ela usa a palavra-chave
implements
.
interface Animal
{
function makeSound();
}
class Cat implements Animal
{
public function makeSound()
{
echo 'Meow';
}
}
$cat = new Cat;
$cat->makeSound();
Se uma classe implementar uma interface, mas nem todos os métodos esperados estiverem definidos, o PHP lançará um erro.
Uma classe pode implementar várias interfaces de uma vez, o que é diferente da herança, em que uma classe só pode herdar de uma classe:
interface Guardian
{
function guardHouse();
}
class Dog implements Animal, Guardian
{
public function makeSound()
{
echo 'Bark';
}
public function guardHouse()
{
echo 'Dog diligently guards the house';
}
}
Classes abstratas
As classes abstratas servem como modelos de base para outras classes, mas você não pode criar suas instâncias diretamente. Elas contêm uma mistura de métodos completos e métodos abstratos que não têm um conteúdo definido. As classes que herdam de classes abstratas devem fornecer definições para todos os métodos abstratos da classe pai.
Usamos a palavra-chave abstract
para definir uma classe abstrata.
abstract class AbstractClass
{
public function regularMethod()
{
echo 'This is a regular method';
}
abstract public function abstractMethod();
}
class Child extends AbstractClass
{
public function abstractMethod()
{
echo 'This is the implementation of the abstract method';
}
}
$instance = new Child;
$instance->regularMethod();
$instance->abstractMethod();
Neste exemplo, temos uma classe abstrata com um método regular e um método abstrato. Em seguida, temos uma classe
Child
que herda de AbstractClass
e fornece uma implementação para o método abstrato.
Qual é a diferença entre interfaces e classes abstratas? As classes abstratas podem conter métodos abstratos e concretos, enquanto as interfaces definem apenas os métodos que a classe deve implementar, mas não fornecem nenhuma implementação. Uma classe pode herdar de apenas uma classe abstrata, mas pode implementar qualquer número de interfaces.
Verificação de tipo
Na programação, é fundamental garantir que os dados com os quais trabalhamos sejam do tipo correto. No PHP, temos ferramentas que oferecem essa garantia. A verificação de que os dados são do tipo correto é chamada de “verificação de tipo”.
Tipos que podemos encontrar no PHP:
- Tipos básicos: Incluem
int
(inteiros),float
(números de ponto flutuante),bool
(valores booleanos),string
(strings),array
(arrays) enull
. - Classes: Quando queremos que um 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 atende a uma interface deve ter esses métodos.
- Tipos mistos: Podemos especificar que uma variável pode ter vários tipos permitidos.
- Void: Esse tipo especial indica que uma função ou método não retorna nenhum valor.
Vamos ver como modificar o código para incluir tipos:
class Person
{
private int $age;
public function __construct(int $age)
{
$this->age = $age;
}
public function printAge(): void
{
echo "This person is {$this->age} years old.";
}
}
/**
* A function that accepts a Person object and prints the person's age.
*/
function printPersonAge(Person $person): void
{
$person->printAge();
}
Dessa forma, garantimos que nosso código espera e trabalha com dados do tipo correto, o que nos ajuda a evitar possíveis erros.
Alguns tipos não podem ser escritos diretamente no PHP. Nesse caso, eles são listados no comentário phpDoc, que é
o formato padrão para documentar o código PHP, começando com /**
e terminando com */
. Ele permite
que você adicione descrições de classes, métodos e assim por diante. E também para listar tipos complexos usando as chamadas
anotações @var
, @param
e @return
. Esses tipos são usados por ferramentas de análise de
código estático, mas não são verificados pelo próprio PHP.
class Registry
{
/** @var array<Person> indicates that it's an array of Person objects */
private array $persons = [];
public function addPerson(Person $person): void
{
$this->persons[] = $person;
}
}
Comparação e identidade
No PHP, você 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 é a mesma instância do objeto.
class Car
{
public string $brand;
public function __construct(string $brand)
{
$this->brand = $brand;
}
}
$car1 = new Car('Skoda');
$car2 = new Car('Skoda');
$car3 = $car1;
var_dump($car1 == $car2); // true, because they have the same value
var_dump($car1 === $car2); // false, because they are not the same instance
var_dump($car1 === $car3); // true, because $car3 is the same instance as $car1
O operador do instanceof
O operador instanceof
permite que você determine se um determinado objeto é uma instância de uma classe
específica, um descendente dessa classe ou se implementa uma determinada interface.
Imagine que temos uma classe Person
e outra classe Student
, que é descendente de
Person
:
class Person
{
private int $age;
public function __construct(int $age)
{
$this->age = $age;
}
}
class Student extends Person
{
private string $major;
public function __construct(int $age, string $major)
{
parent::__construct($age);
$this->major = $major;
}
}
$student = new Student(20, 'Computer Science');
// Check if $student is an instance of the Student class
var_dump($student instanceof Student); // Output: bool(true)
// Check if $student is an instance of the Person class (because Student is a descendant of Person)
var_dump($student instanceof Person); // Output: bool(true)
A partir dos resultados, fica evidente que o objeto $student
é considerado uma instância das classes
Student
e Person
.
Interfaces fluentes
Uma “Interface Fluente” é uma técnica em OOP que permite encadear métodos em uma única chamada. Isso geralmente simplifica e esclarece o código.
O principal elemento de uma interface fluente é que cada método na cadeia retorna uma referência ao objeto atual. Isso é
obtido com o uso do endereço return $this;
no final do método. Esse estilo de programação é frequentemente
associado a métodos chamados “setters”, que definem os valores das propriedades de um objeto.
Vamos ver como seria uma interface fluente para o envio de e-mails:
public function sendMessage()
{
$email = new Email;
$email->setFrom('sender@example.com')
->setRecipient('admin@example.com')
->setMessage('Hello, this is a message.')
->send();
}
Neste exemplo, os métodos setFrom()
, setRecipient()
e setMessage()
são usados para
definir os valores correspondentes (remetente, destinatário, conteúdo da mensagem). Depois de definir cada um desses valores, os
métodos retornam o objeto atual ($email
), o que nos permite encadear outro método depois dele. Por fim, chamamos
o método send()
, que de fato envia o e-mail.
Graças às interfaces fluentes, podemos escrever códigos intuitivos e de fácil leitura.
Copiando com clone
No PHP, podemos criar uma cópia de um objeto usando o operador clone
. Dessa forma, obtemos uma nova instância
com conteúdo idêntico.
Se precisarmos modificar algumas de suas propriedades ao copiar um objeto, podemos definir um método __clone()
especial na classe. Esse método é chamado automaticamente quando o objeto é clonado.
class Sheep
{
public string $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function __clone()
{
$this->name = 'Clone of ' . $this->name;
}
}
$original = new Sheep('Dolly');
echo $original->name . "\n"; // Outputs: Dolly
$clone = clone $original;
echo $clone->name . "\n"; // Outputs: Clone of Dolly
Neste exemplo, temos uma classe Sheep
com uma propriedade $name
. Quando clonamos uma instância dessa
classe, o método __clone()
garante que o nome da ovelha clonada receba o prefixo “Clone of”.
Características
As características no PHP são uma ferramenta que permite o compartilhamento de métodos, propriedades e constantes entre classes e evita a duplicação de código. Você pode pensar nelas como um mecanismo de “copiar e colar” (Ctrl-C e Ctrl-V), em que o conteúdo de uma característica é “colado” nas classes. Isso permite que você reutilize o código sem precisar criar hierarquias de classe complicadas.
Vamos dar uma olhada em um exemplo simples de como usar características no PHP:
trait Honking
{
public function honk()
{
echo 'Beep beep!';
}
}
class Car
{
use Honking;
}
class Truck
{
use Honking;
}
$car = new Car;
$car->honk(); // Outputs 'Beep beep!'
$truck = new Truck;
$truck->honk(); // Also outputs 'Beep beep!'
Neste exemplo, temos uma característica chamada Honking
que contém um método honk()
. Em seguida,
temos duas classes: Car
e Truck
, ambas as quais usam a característica Honking
. Como
resultado, ambas as classes “têm” o método honk()
e podemos chamá-lo em objetos de ambas as classes.
As características permitem o compartilhamento fácil e eficiente de código entre classes. Elas não entram na hierarquia de
herança, ou seja, $car instanceof Honking
retornará false
.
Exceções
As exceções na OOP nos permitem lidar com erros e situações inesperadas em nosso código. Elas são objetos que contêm informações sobre um erro ou uma situação incomum.
No PHP, temos uma classe interna Exception
, que serve de 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 em que o erro
ocorreu etc.
Quando ocorre um erro no código, podemos “lançar” a exceção usando a palavra-chave throw
.
function division(float $a, float $b): float
{
if ($b === 0) {
throw new Exception('Division by zero!');
}
return $a / $b;
}
Quando a função division()
recebe null como seu segundo argumento, ela lança uma exceção com a mensagem de
erro 'Division by zero!'
. Para evitar que o programa seja interrompido quando a exceção for lançada, nós a
capturamos no bloco try/catch
:
try {
echo division(10, 0);
} catch (Exception $e) {
echo 'Exception caught: '. $e->getMessage();
}
O código que pode lançar uma exceção é envolvido em um bloco try
. Se a exceção for lançada, a execução
do código será movida para um bloco catch
, onde poderemos tratar a exceção (por exemplo, escrever uma mensagem
de erro).
Após os blocos try
e catch
, podemos adicionar um bloco opcional finally
, que é sempre
executado, independentemente de a exceção ter sido lançada ou não (mesmo se usarmos return
, break
ou
continue
no bloco try
ou catch
):
try {
echo division(10, 0);
} catch (Exception $e) {
echo 'Exception caught: '. $e->getMessage();
} finally {
// Code that is always executed whether the exception has been thrown or not
}
Também podemos criar nossas próprias classes de exceção (hierarquia) que herdam da classe Exception. Como exemplo, considere um aplicativo bancário simples que permite depósitos e saques:
class BankingException extends Exception {}
class InsufficientFundsException extends BankingException {}
class ExceededLimitException extends BankingException {}
class BankAccount
{
private int $balance = 0;
private int $dailyLimit = 1000;
public function deposit(int $amount): int
{
$this->balance += $amount;
return $this->balance;
}
public function withdraw(int $amount): int
{
if ($amount > $this->balance) {
throw new InsufficientFundsException('Not enough funds in the account.');
}
if ($amount > $this->dailyLimit) {
throw new ExceededLimitException('Daily withdrawal limit exceeded.');
}
$this->balance -= $amount;
return $this->balance;
}
}
Vários blocos catch
podem ser especificados para um único bloco try
se você espera diferentes
tipos de exceções.
$account = new BankAccount;
$account->deposit(500);
try {
$account->withdraw(1500);
} catch (ExceededLimitException $e) {
echo $e->getMessage();
} catch (InsufficientFundsException $e) {
echo $e->getMessage();
} catch (BankingException $e) {
echo 'An error occurred during the operation.';
}
Nesse exemplo, é importante observar a ordem dos blocos catch
. Como todas as exceções são herdadas de
BankingException
, se tivéssemos esse bloco primeiro, todas as exceções seriam capturadas nele sem que o código
chegasse aos blocos catch
subsequentes. Portanto, é importante que as exceções mais específicas (ou seja, aquelas
que herdam de outras) estejam em uma posição mais alta na ordem do bloco catch
do que suas exceções pai.
Iterações
No PHP, você pode percorrer objetos usando o loop foreach
, da mesma forma que percorre uma matriz. Para que isso
funcione, o objeto deve implementar uma interface especial.
A primeira opção é implementar a interface Iterator
, que tem os métodos current()
que retorna
o valor atual, key()
que retorna a chave, next()
que passa para o próximo valor,
rewind()
que passa para o início e valid()
que verifica se já estamos no final.
A outra opção é implementar uma interface IteratorAggregate
, que tem apenas um método
getIterator()
. Ele retorna um objeto de espaço reservado que fornecerá a passagem ou pode ser um gerador, que é
uma função especial que usa yield
para retornar chaves e valores sequencialmente:
class Person
{
public function __construct(
public int $age,
) {
}
}
class Registry implements IteratorAggregate
{
private array $people = [];
public function addPerson(Person $person): void
{
$this->people[] = $person;
}
public function getIterator(): Generator
{
foreach ($this->people as $person) {
yield $person;
}
}
}
$list = new Registry;
$list->addPerson(new Person(30));
$list->addPerson(new Person(25));
foreach ($list as $person) {
echo "Age: {$person->age} years\n";
}
Melhores práticas
Depois de aprender os princípios básicos da programação orientada a objetos, é fundamental concentrar-se nas práticas recomendadas de OOP. Elas o ajudarão a escrever códigos que não sejam apenas funcionais, mas também legíveis, compreensíveis e de fácil manutenção.
- Separação de preocupações: Cada classe deve ter uma responsabilidade claramente definida e deve tratar apenas de uma tarefa principal. Se uma classe fizer muitas coisas, pode ser apropriado dividi-la em classes menores e especializadas.
- Encapsulamento: Os dados e métodos devem ser tão ocultos quanto possível e acessíveis somente por meio de uma interface definida. Isso permite que você altere a implementação interna de uma classe sem afetar o restante do código.
- Injeção de dependência: Em vez de criar dependências diretamente em uma classe, você deve “injetá-las” do lado de fora. Para entender melhor esse princípio, recomendamos os capítulos sobre Injeção de Dependência.