Introducción a la programación orientada a objetos

El término “POO” significa Programación Orientada a Objetos, que es una forma de organizar y estructurar el código. La POO nos permite ver un programa como una colección de objetos que se comunican entre sí, en lugar de como una secuencia de comandos y funciones.

En la programación orientada a objetos, un “objeto” es una unidad que contiene datos y funciones que operan con esos datos. Los objetos se crean a partir de “clases”, que pueden entenderse como planos o plantillas de objetos. Una vez que tenemos una clase, podemos crear su “instancia”, que es un objeto específico hecho a partir de esa clase.

Veamos cómo podemos crear una clase simple en PHP. Cuando definimos una clase, usamos la palabra clave “class”, seguida del nombre de la clase, y luego llaves que encierran las funciones de la clase (llamadas “métodos”) y las variables de la clase (llamadas “propiedades” o “atributos”):

class Car
{
	function honk()
	{
		echo 'Beep beep!';
	}
}

En este ejemplo, hemos creado una clase llamada Car con una función (o “método”) llamada honk.

Cada clase debe resolver una sola tarea principal. Si una clase hace demasiadas cosas, puede ser conveniente dividirla en clases más pequeñas y especializadas.

Las clases suelen almacenarse en archivos separados para mantener el código organizado y facilitar la navegación. El nombre del archivo debe coincidir con el nombre de la clase, por lo que para la clase Car, el nombre del archivo sería Car.php.

Cuando se nombran las clases, es bueno seguir la convención “PascalCase”, lo que significa que cada palabra en el nombre comienza con una letra mayúscula, y no hay guiones bajos u otros separadores. Los métodos y propiedades siguen la convención “camelCase”, lo que significa que empiezan con minúscula.

Algunos métodos en PHP tienen roles especiales y están prefijados con __ (dos guiones bajos). Uno de los métodos especiales más importantes es el “constructor”, etiquetado como __construct. El constructor es un método que es llamado automáticamente cuando se crea una nueva instancia de una clase.

A menudo utilizamos el constructor para establecer el estado inicial de un objeto. Por ejemplo, al crear un objeto que representa a una persona, puedes utilizar el constructor para establecer su edad, nombre u otros atributos.

Veamos como usar un constructor en 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

En este ejemplo, la clase Person tiene una propiedad (variable) $age y un constructor que establece esta propiedad. A continuación, el método howOldAreYou() proporciona acceso a la edad de la persona.

La pseudo-variable $this se utiliza dentro de la clase para acceder a las propiedades y métodos del objeto.

La palabra clave new se utiliza para crear una nueva instancia de una clase. En el ejemplo anterior, hemos creado una nueva persona de 25 años.

También puedes establecer valores por defecto para los parámetros del constructor si no se especifican al crear un objeto. Por ejemplo:

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

En este ejemplo, si no se especifica una edad al crear un objeto Person, se utilizará el valor por defecto de 20.

Lo bueno es que la definición de la propiedad con su inicialización a través del constructor se puede acortar y simplificar así:

class Person
{
	function __construct(
		private $age = 20,
	) {
	}
}

Para completar, además de constructores, los objetos pueden tener destructores (método __destruct) que son llamados antes de que el objeto sea liberado de la memoria.

Espacios de nombres

Los espacios de nombres nos permiten organizar y agrupar clases, funciones y constantes relacionadas evitando conflictos de nombres. Puedes pensar en ellos como carpetas en un ordenador, donde cada carpeta contiene archivos relacionados con un proyecto o tema específico.

Los espacios de nombres son especialmente útiles en proyectos grandes o cuando se utilizan librerías de terceros donde pueden surgir conflictos de nombres de clases.

Imagina que tienes una clase llamada Car en tu proyecto, y quieres colocarla en un espacio de nombres llamado Transport. Lo harías de la siguiente manera

namespace Transport;

class Car
{
	function honk()
	{
		echo 'Beep beep!';
	}
}

Si quieres utilizar la clase Car en otro archivo, tienes que especificar de qué espacio de nombres procede la clase:

$car = new Transport\Car;

Para simplificar, puede especificar al principio del archivo qué clase de un espacio de nombres concreto desea utilizar, lo que le permitirá crear instancias sin mencionar la ruta completa:

use Transport\Car;

$car = new Car;

Herencia

La herencia es una herramienta de la programación orientada a objetos que permite crear nuevas clases a partir de otras ya existentes, heredando sus propiedades y métodos, y ampliándolas o redefiniéndolas según sea necesario. La herencia garantiza la reutilización del código y la jerarquía de las clases.

En pocas palabras, si tenemos una clase y queremos crear otra derivada de ella pero con algunas modificaciones, podemos “heredar” la nueva clase de la original.

En PHP, la herencia se implementa utilizando la palabra clave extends.

Nuestra clase Person almacena información sobre la edad. Podemos tener otra clase, Student, que extienda Person y añada información sobre el campo de estudio.

Veamos un ejemplo:

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();

¿Cómo funciona este código?

  • Utilizamos la palabra clave extends para extender la clase Person, lo que significa que la clase Student hereda todos los métodos y propiedades de Person.
  • La palabra clave parent:: nos permite llamar a métodos de la clase padre. En este caso, llamamos al constructor de la clase Person antes de añadir nuestra propia funcionalidad a la clase Student. Y de forma similar, el método del ancestro printInformation() antes de listar la información del estudiante.

La herencia está pensada para situaciones en las que existe una relación “es un” entre clases. Por ejemplo, un Student es un Person. Un gato es un animal. Nos permite, en casos en los que esperamos un objeto (por ejemplo, “Persona”) en el código, utilizar un objeto derivado en su lugar (por ejemplo, “Estudiante”).

Es esencial darse cuenta de que el propósito principal de la herencia no es evitar la duplicación de código. Al contrario, un mal uso de la herencia puede llevar a un código complejo y difícil de mantener. Si no existe una relación “es un” entre clases, deberíamos considerar la composición en lugar de la herencia.

Observe que los métodos printInformation() de las clases Person y Student proporcionan información ligeramente diferente. Y podemos añadir otras clases (como Employee) que proporcionarán otras implementaciones de este método. La capacidad de los objetos de diferentes clases para responder al mismo método de diferentes maneras se llama polimorfismo:

$people = [
	new Person(30),
	new Student(20, 'Computer Science'),
	new Employee(45, 'Director'),
];

foreach ($people as $person) {
	$person->printInformation();
}

Composición

La composición es una técnica en la que, en lugar de heredar propiedades y métodos de otra clase, simplemente utilizamos su instancia en nuestra clase. Esto nos permite combinar funcionalidades y propiedades de múltiples clases sin crear complejas estructuras de herencia.

Por ejemplo, tenemos una clase Engine y otra Car. En lugar de decir “Un coche es un motor”, decimos “Un coche tiene un motor”, que es una relación de composición 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();

Aquí, la Car no tiene todas las propiedades y métodos de la Engine, pero tiene acceso a ella a través de la propiedad $engine.

La ventaja de la composición es una mayor flexibilidad de diseño y una mejor adaptabilidad a futuros cambios.

Visibilidad

En PHP, usted puede definir “visibilidad” para propiedades de clases, métodos y constantes. La visibilidad determina dónde se puede acceder a estos elementos.

  1. Público: Si un elemento está marcado como public, significa que puede acceder a él desde cualquier lugar, incluso fuera de la clase.
  2. Protegido: Un elemento marcado como protected sólo es accesible dentro de la clase y todos sus descendientes (clases que heredan de ella).
  3. Privado: Si un elemento es private, sólo se puede acceder a él desde dentro de la clase en la que se definió.

Si no especifica la visibilidad, PHP la establecerá automáticamente en public.

Veamos un ejemplo 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 con la herencia de clases:

class ChildClass extends VisibilityExample
{
	public function printProperties()
	{
		echo $this->publicProperty;     // Works
		echo $this->protectedProperty;  // Works
		// echo $this->privateProperty;   // Throws an error
	}
}

En este caso, el método printProperties() de ChildClass puede acceder a las propiedades públicas y protegidas pero no puede acceder a las propiedades privadas de la clase padre.

Los datos y métodos deben estar lo más ocultos posible y sólo se puede acceder a ellos a través de una interfaz definida. Esto permite cambiar la implementación interna de la clase sin afectar al resto del código.

Palabra clave final

En PHP, podemos usar la palabra clave final si queremos evitar que una clase, método o constante sea heredada o sobrescrita. Cuando una clase es marcada como final, no puede ser extendida. Cuando un método es marcado como final, no puede ser sobrescrito en una subclase.

Ser conscientes de que una determinada clase o método ya no se modificará nos permite realizar cambios más fácilmente sin preocuparnos por posibles conflictos. Por ejemplo, podemos añadir un nuevo método sin temor a que un descendiente pueda tener ya un método con el mismo nombre, provocando una colisión. O podemos cambiar los parámetros de un método, de nuevo sin el riesgo de causar inconsistencia con un método anulado en un descendiente.

final class FinalClass
{
}

// The following code will throw an error because we cannot inherit from a final class.
class ChildOfFinalClass extends FinalClass
{
}

En este ejemplo, si se intenta heredar de la clase final FinalClass se producirá un error.

Propiedades estáticas y métodos

Cuando hablamos de elementos “estáticos” de una clase en PHP, nos referimos a métodos y propiedades que pertenecen a la clase misma, no a una instancia específica de la clase. Esto significa que no tiene que crear una instancia de la clase para acceder a ellos. En su lugar, se les llama o accede directamente a través del nombre de la clase.

Ten en cuenta que como los elementos estáticos pertenecen a la clase y no a sus instancias, no puedes utilizar la pseudo-variable $this dentro de métodos estáticos.

El uso de propiedades estáticas conduce a código ofuscado lleno de trampas, por lo que nunca deberías usarlas, y no mostraremos un ejemplo aquí. Por otro lado, los métodos estáticos son útiles. Aquí tienes un ejemplo:

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

En este ejemplo, creamos una clase Calculator con dos métodos estáticos. Podemos llamar a estos métodos directamente sin crear una instancia de la clase utilizando el operador ::. Los métodos estáticos son especialmente útiles para operaciones que no dependen del estado de una instancia específica de la clase.

Constantes de clase

Dentro de las clases, tenemos la opción de definir constantes. Las constantes son valores que nunca cambian durante la ejecución del programa. A diferencia de las variables, el valor de una constante permanece igual.

class Car
{
	public const NumberOfWheels = 4;

	public function displayNumberOfWheels(): int
	{
		echo self::NumberOfWheels;
	}
}

echo Car::NumberOfWheels;  // Output: 4

En este ejemplo, tenemos una clase Car con la constante NumberOfWheels. Al acceder a la constante dentro de la clase, podemos utilizar la palabra clave self en lugar del nombre de la clase.

Interfaces de objetos

Las interfaces de objetos actúan como “contratos” para las clases. Si una clase va a implementar una interfaz de objetos, debe contener todos los métodos que la interfaz define. Es una gran manera de asegurar que ciertas clases se adhieren al mismo “contrato” o estructura.

En PHP, las interfaces se definen usando la palabra clave interface. Todos los métodos definidos en una interfaz son públicos (public). Cuando una clase implementa una interfaz, usa la palabra clave implements.

interface Animal
{
	function makeSound();
}

class Cat implements Animal
{
	public function makeSound()
	{
		echo 'Meow';
	}
}

$cat = new Cat;
$cat->makeSound();

Si una clase implementa una interfaz, pero no todos los métodos esperados están definidos, PHP lanzará un error.

Una clase puede implementar múltiples interfaces a la vez, que es diferente de la herencia, donde una clase sólo puede heredar de una clase:

interface Guardian
{
	function guardHouse();
}

class Dog implements Animal, Guardian
{
	public function makeSound()
	{
		echo 'Bark';
	}

	public function guardHouse()
	{
		echo 'Dog diligently guards the house';
	}
}

Clases Abstractas

Las clases abstractas sirven como plantillas base para otras clases, pero no se pueden crear instancias directamente. Contienen una mezcla de métodos completos y métodos abstractos que no tienen un contenido definido. Las clases que heredan de clases abstractas deben proporcionar definiciones para todos los métodos abstractos del padre.

Utilizamos la palabra clave abstract para definir una clase abstracta.

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();

En este ejemplo, tenemos una clase abstracta con un método normal y otro abstracto. Luego tenemos una clase Child que hereda de AbstractClass y proporciona una implementación para el método abstracto.

¿En qué se diferencian las interfaces de las clases abstractas? Las clases abstractas pueden contener métodos abstractos y concretos, mientras que las interfaces sólo definen qué métodos debe implementar la clase, pero no proporcionan ninguna implementación. Una clase sólo puede heredar de una clase abstracta, pero puede implementar cualquier número de interfaces.

Comprobación de tipos

En programación, es crucial asegurarse de que los datos con los que trabajamos son del tipo correcto. En PHP, tenemos herramientas que proporcionan esta seguridad. Verificar que los datos son del tipo correcto se llama “comprobación de tipo”.

Tipos que podemos encontrar en PHP:

  1. Tipos básicos: Estos incluyen int (enteros), float (números de punto flotante), bool (valores booleanos), string (cadenas), array (matrices), y null.
  2. Clases: Cuando queremos que un valor sea una instancia de una clase concreta.
  3. Interfaces: Define un conjunto de métodos que una clase debe implementar. Un valor que cumpla una interfaz debe tener estos métodos.
  4. Tipos mixtos: Podemos especificar que una variable puede tener múltiples tipos permitidos.
  5. Void: Este tipo especial indica que una función o método no devuelve ningún valor.

Veamos cómo modificar el 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();
}

De esta forma, nos aseguramos de que nuestro código espera y trabaja con datos del tipo correcto, ayudándonos a prevenir posibles errores.

Algunos tipos no pueden escribirse directamente en PHP. En este caso, se listan en el comentario phpDoc, que es el formato estándar para documentar código PHP, comenzando con /** y terminando con */. Permite añadir descripciones de clases, métodos, etcétera. Y también listar tipos complejos usando las llamadas anotaciones @var, @param y @return. Estos tipos son luego utilizados por herramientas de análisis estático de código, pero no son comprobados por el propio 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;
	}
}

Comparación e identidad

En PHP, puede comparar objetos de dos maneras:

  1. Comparación de valores ==: Comprueba si los objetos son de la misma clase y tienen los mismos valores en sus propiedades.
  2. Identidad ===: Comprueba si se trata de la misma instancia del 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

El operador instanceof

El operador instanceof permite determinar si un objeto dado es una instancia de una clase específica, un descendiente de esa clase o si implementa una determinada interfaz.

Imaginemos que tenemos una clase Person y otra clase Student, que es descendiente 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 de las salidas, es evidente que el objeto $student se considera una instancia tanto de la clase Student como de la clase Person.

Interfaces fluidas

Una “interfaz fluida” es una técnica de programación orientada a objetos que permite encadenar métodos en una sola llamada. Esto a menudo simplifica y clarifica el código.

El elemento clave de una interfaz fluida es que cada método de la cadena devuelve una referencia al objeto actual. Esto se consigue utilizando return $this; al final del método. Este estilo de programación se asocia a menudo con métodos llamados “setters”, que establecen los valores de las propiedades de un objeto.

Veamos cómo sería una interfaz fluida para enviar correos electrónicos:

public function sendMessage()
{
	$email = new Email;
	$email->setFrom('sender@example.com')
		  ->setRecipient('admin@example.com')
		  ->setMessage('Hello, this is a message.')
		  ->send();
}

En este ejemplo, los métodos setFrom(), setRecipient(), y setMessage() se utilizan para establecer los valores correspondientes (remitente, destinatario, contenido del mensaje). Después de establecer cada uno de estos valores, los métodos devuelven el objeto actual ($email), lo que nos permite encadenar otro método después de él. Finalmente, llamamos al método send(), que realmente envía el correo electrónico.

Gracias a las interfaces fluidas, podemos escribir código intuitivo y fácilmente legible.

Copiar con clone

En PHP, podemos crear una copia de un objeto utilizando el operador clone. De esta forma, obtenemos una nueva instancia con idéntico contenido.

Si necesitamos modificar algunas de sus propiedades al copiar un objeto, podemos definir un método especial __clone() en la clase. Este método se llama automáticamente cuando se clona el objeto.

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

En este ejemplo, tenemos una clase Sheep con una propiedad $name. Cuando clonamos una instancia de esta clase, el método __clone() se asegura de que el nombre de la oveja clonada obtenga el prefijo “Clone of”.

Rasgos

Los traits en PHP son una herramienta que permite compartir métodos, propiedades y constantes entre clases y evita la duplicación de código. Puede pensar en ellos como un mecanismo de “copiar y pegar” (Ctrl-C y Ctrl-V), donde el contenido de un trait se “pega” en las clases. Esto permite reutilizar código sin tener que crear complicadas jerarquías de clases.

Veamos un ejemplo sencillo de cómo usar traits en 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!'

En este ejemplo, tenemos un trait llamado Honking que contiene un método honk(). Luego tenemos dos clases: Car y Truck, las cuales usan el trait Honking. Como resultado, ambas clases “tienen” el método honk(), y podemos llamarlo en objetos de ambas clases.

Los traits permiten compartir código entre clases de forma fácil y eficiente. No entran en la jerarquía de herencia, es decir, $car instanceof Honking devolverá false.

Excepciones

Las excepciones en programación orientada a objetos nos permiten manejar con elegancia errores y situaciones inesperadas en nuestro código. Son objetos que contienen información sobre un error o una situación inusual.

En PHP, tenemos una clase incorporada Exception, que sirve como base para todas las excepciones. Esta tiene varios métodos que nos permiten obtener más información sobre la excepción, como el mensaje de error, el archivo y la línea donde ocurrió el error, etc.

Cuando se produce un error en el código, podemos “lanzar” la excepción utilizando la palabra clave throw.

function division(float $a, float $b): float
{
	if ($b === 0) {
		throw new Exception('Division by zero!');
	}
	return $a / $b;
}

Cuando la función division() recibe null como segundo argumento, lanza una excepción con el mensaje de error 'Division by zero!'. Para evitar que el programa se bloquee cuando se lanza la excepción, la atrapamos en el bloque try/catch:

try {
	echo division(10, 0);
} catch (Exception $e) {
	echo 'Exception caught: '. $e->getMessage();
}

El código que puede lanzar una excepción se envuelve en un bloque try. Si se lanza la excepción, la ejecución del código se mueve a un bloque catch, donde podemos manejar la excepción (por ejemplo, escribir un mensaje de error).

Después de los bloques try y catch, podemos añadir un bloque opcional finally, que siempre se ejecuta, se haya lanzado o no la excepción (incluso si usamos return, break, o continue en el bloque try o 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
}

También podemos crear nuestras propias clases de excepción (jerarquía) que hereden de la clase Exception. Como ejemplo, consideremos una simple aplicación bancaria que permite depósitos y retiros:

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;
	}
}

Se pueden especificar varios bloques catch para un único bloque try si se esperan diferentes tipos de excepciones.

$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.';
}

En este ejemplo, es importante tener en cuenta el orden de los bloques catch. Dado que todas las excepciones heredan de BankingException, si tuviéramos este bloque en primer lugar, todas las excepciones se atraparían en él sin que el código llegara a los bloques catch posteriores. Por lo tanto, es importante tener las excepciones más específicas (es decir, aquellas que heredan de otras) más arriba en el orden del bloque catch que sus excepciones padre.

Iteraciones

En PHP, puedes hacer un bucle a través de objetos usando el bucle foreach, de la misma manera que lo haces a través de un array. Para que esto funcione, el objeto debe implementar una interfaz especial.

La primera opción es implementar la interfaz Iterator, que tiene métodos current() que devuelve el valor actual, key() que devuelve la clave, next() que se mueve al siguiente valor, rewind() que se mueve al principio, y valid() que comprueba si ya hemos llegado al final.

La otra opción es implementar una interfaz IteratorAggregate, que sólo tiene un método getIterator(). Esto devuelve un objeto marcador de posición que proporcionará el recorrido, o puede ser un generador, que es una función especial que utiliza yield para devolver claves y valores secuencialmente:

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";
}

Buenas prácticas

Una vez que hayas aprendido los principios básicos de la programación orientada a objetos, es crucial centrarse en las mejores prácticas de la programación orientada a objetos. Éstas te ayudarán a escribir código que no sólo sea funcional, sino también legible, comprensible y fácil de mantener.

  1. Separación de responsabilidades: Cada clase debe tener una responsabilidad claramente definida y debe abordar sólo una tarea principal. Si una clase hace demasiadas cosas, puede ser conveniente dividirla en clases más pequeñas y especializadas.
  2. Encapsulación: Los datos y métodos deben estar lo más ocultos posible y ser accesibles únicamente a través de una interfaz definida. Esto permite cambiar la implementación interna de una clase sin afectar al resto del código.
  3. Inyección de dependencias: En lugar de crear dependencias directamente dentro de una clase, debes “inyectarlas” desde el exterior. Para una comprensión más profunda de este principio, recomendamos los capítulos sobre Inyección de Dependencias.
versión: 4.0