Introduction à la programmation orientée objet

Le sigle “POO” signifie programmation orientée objet, une façon d'organiser et de structurer le code. La POO nous permet de voir un programme comme un ensemble d'objets qui communiquent entre eux, plutôt que comme une suite de commandes et de fonctions.

En POO, un “objet” est une unité qui contient des données et des fonctions travaillant sur ces données. Les objets sont créés à partir de “classes”, que l'on peut comprendre comme des plans ou des modèles d'objets. Une fois que nous avons une classe, nous pouvons en créer une “instance”, c'est-à-dire un objet concret fabriqué à partir de cette classe.

Voyons comment créer une classe simple en PHP. Pour définir une classe, nous utilisons le mot-clé “class”, suivi du nom de la classe, puis des accolades qui enferment les fonctions de la classe (appelées “méthodes”) et ses variables (appelées “propriétés” ou “attributs”) :

class Car
{
	function honk()
	{
		echo 'Bip bip !';
	}
}

Dans cet exemple, nous avons créé une classe nommée Car avec une fonction (ou “méthode”) appelée honk.

Chaque classe ne devrait résoudre qu'une seule tâche principale. Si une classe fait trop de choses, il peut être approprié de la diviser en classes plus petites et spécialisées.

Les classes sont généralement enregistrées dans des fichiers distincts afin que le code reste organisé et facile à parcourir. Le nom du fichier devrait correspondre au nom de la classe : pour la classe Car, le nom du fichier serait donc Car.php.

Pour nommer les classes, il est bon de suivre la convention “PascalCase”, c'est-à-dire que chaque mot du nom commence par une majuscule et qu'il n'y a ni tirets bas ni autres séparateurs. Les méthodes et les propriétés suivent la convention “camelCase”, c'est-à-dire qu'elles commencent par une minuscule.

Certaines méthodes en PHP ont des rôles particuliers et sont préfixées par __ (deux tirets bas). L'une des méthodes spéciales les plus importantes est le “constructeur”, noté __construct. Le constructeur est une méthode appelée automatiquement lors de la création d'une nouvelle instance de la classe.

Nous utilisons souvent le constructeur pour définir l'état initial d'un objet. Par exemple, en créant un objet représentant une personne, vous pouvez utiliser le constructeur pour définir son âge, son nom ou d'autres attributs.

Voyons comment utiliser un constructeur en PHP :

class Person
{
	private $age;

	function __construct($age)
	{
		$this->age = $age;
	}

	function howOldAreYou()
	{
		return $this->age;
	}
}

$person = new Person(25);
echo $person->howOldAreYou(); // Affiche : 25

Dans cet exemple, la classe Person a une propriété (variable) $age et un constructeur qui la définit. La méthode howOldAreYou() donne ensuite accès à l'âge de la personne.

La pseudo-variable $this sert, à l'intérieur de la classe, à accéder aux propriétés et méthodes de l'objet.

Le mot-clé new sert à créer une nouvelle instance d'une classe. Dans l'exemple ci-dessus, nous avons créé une nouvelle personne âgée de 25 ans.

Vous pouvez aussi définir des valeurs par défaut pour les paramètres du constructeur, au cas où ils ne seraient pas indiqués lors de la création d'un objet. Par exemple :

class Person
{
	private $age;

	function __construct($age = 20)
	{
		$this->age = $age;
	}

	function howOldAreYou()
	{
		return $this->age;
	}
}

$person = new Person;  // si aucun argument n'est passé, les parenthèses peuvent être omises
echo $person->howOldAreYou(); // Affiche : 20

Dans cet exemple, si vous n'indiquez pas d'âge à la création d'un objet Person, la valeur par défaut 20 sera utilisée.

Ce qui est agréable, c'est que la définition de la propriété et son initialisation par le constructeur peuvent être raccourcies et simplifiées ainsi :

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

Pour être complet : outre les constructeurs, les objets peuvent avoir des destructeurs (méthode __destruct), appelés avant que l'objet ne soit libéré de la mémoire.

Espaces de noms

Les espaces de noms nous permettent d'organiser et de regrouper des classes, des fonctions et des constantes apparentées tout en évitant les conflits de noms. Vous pouvez vous les représenter comme des dossiers sur un ordinateur, où chaque dossier contient les fichiers liés à un projet ou à un sujet donné.

Les espaces de noms sont particulièrement utiles dans les grands projets ou lors de l'utilisation de bibliothèques tierces, où des conflits de noms de classes peuvent survenir.

Imaginez que vous ayez dans votre projet une classe nommée Car et que vous vouliez la placer dans un espace de noms appelé Transport. Vous procéderiez ainsi :

namespace Transport;

class Car
{
	function honk()
	{
		echo 'Bip bip !';
	}
}

Si vous voulez utiliser la classe Car dans un autre fichier, vous devez indiquer de quel espace de noms elle provient :

$car = new Transport\Car;

Pour simplifier, vous pouvez indiquer au début du fichier quelle classe de quel espace de noms vous voulez utiliser, ce qui vous permet d'en créer des instances sans mentionner le chemin complet :

use Transport\Car;

$car = new Car;

Héritage

L'héritage est un outil de la programmation orientée objet qui permet de créer de nouvelles classes à partir de classes existantes, en héritant de leurs propriétés et méthodes et en les étendant ou les redéfinissant au besoin. L'héritage assure la réutilisabilité du code et la hiérarchie des classes.

Dit simplement, si nous avons une classe et que nous voulons en créer une autre qui en dérive mais comporte quelques modifications, nous pouvons faire “hériter” la nouvelle classe de l'originale.

En PHP, l'héritage se met en œuvre à l'aide du mot-clé extends.

Notre classe Person stocke l'information sur l'âge. Nous pouvons avoir une autre classe, Student, qui étend Person et ajoute l'information sur le domaine d'études.

Regardons un exemple :

class Person
{
	private $age;

	function __construct($age)
	{
		$this->age = $age;
	}

	function printInformation()
	{
		echo "Âge : {$this->age} ans\n";
	}
}

class Student extends Person
{
	private $fieldOfStudy;

	function __construct($age, $fieldOfStudy)
	{
		parent::__construct($age);
		$this->fieldOfStudy = $fieldOfStudy;
	}

	function printInformation()
	{
		parent::printInformation();
		echo "Domaine d'études : {$this->fieldOfStudy} \n";
	}
}

$student = new Student(20, 'Informatique');
$student->printInformation();

Comment ce code fonctionne-t-il ?

  • Nous avons utilisé le mot-clé extends pour étendre la classe Person, ce qui signifie que la classe Student hérite de toutes les méthodes et propriétés de Person.
  • Le mot-clé parent:: nous permet d'appeler les méthodes de la classe parente. Dans ce cas, nous avons appelé le constructeur de la classe Person avant d'ajouter nos propres fonctionnalités à la classe Student. Et de même la méthode ancêtre printInformation() avant d'afficher les informations de l'étudiant.

L'héritage est destiné aux situations où il existe une relation “est un” entre les classes. Par exemple, un Student est une Person. Un chat est un animal. Il nous permet, là où le code attend un objet (par exemple “Person”), d'utiliser à la place un objet dérivé (par exemple “Student”).

Il est essentiel de comprendre que le but premier de l'héritage n'est pas d'éviter la duplication de code. Au contraire, un mauvais usage de l'héritage peut conduire à un code complexe et difficile à maintenir. S'il n'y a pas de relation “est un” entre les classes, nous devrions envisager la composition plutôt que l'héritage.

Remarquez que les méthodes printInformation() des classes Person et Student affichent des informations légèrement différentes. Et nous pouvons ajouter d'autres classes (comme Employee) qui fourniront d'autres implémentations de cette méthode. La capacité d'objets de classes différentes à répondre à la même méthode de façons différentes s'appelle le polymorphisme :

$people = [
	new Person(30),
	new Student(20, 'Informatique'),
	new Employee(45, 'Directeur'),
];

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

Composition

La composition est une technique où, au lieu d'hériter des propriétés et méthodes d'une autre classe, nous utilisons simplement une de ses instances dans notre classe. Cela nous permet de combiner les fonctionnalités et les propriétés de plusieurs classes sans créer de structures d'héritage complexes.

Par exemple, nous avons une classe Engine et une classe Car. Au lieu de dire “Une voiture est un moteur”, nous disons “Une voiture a un moteur”, ce qui est une relation de composition typique.

class Engine
{
	function start()
	{
		echo 'Le moteur tourne.';
	}
}

class Car
{
	private $engine;

	function __construct()
	{
		$this->engine = new Engine;
	}

	function start()
	{
		$this->engine->start();
		echo 'La voiture est prête à rouler !';
	}
}

$car = new Car;
$car->start();

Ici, la classe Car n'a pas toutes les propriétés et méthodes d'Engine, mais elle y a accès par la propriété $engine.

L'avantage de la composition est une plus grande souplesse de conception et une meilleure adaptabilité aux changements futurs.

Visibilité

En PHP, vous pouvez définir la “visibilité” des propriétés, méthodes et constantes d'une classe. La visibilité détermine d'où vous pouvez accéder à ces éléments.

  1. Public : si un élément est marqué public, cela signifie que vous pouvez y accéder de n'importe où, même en dehors de la classe.
  2. Protected : un élément marqué protected n'est accessible qu'à l'intérieur de la classe et de tous ses descendants (les classes qui en héritent).
  3. Private : si un élément est private, vous ne pouvez y accéder que depuis la classe où il a été défini.

Si vous n'indiquez pas de visibilité, PHP la mettra automatiquement à public.

Regardons un exemple de code :

class VisibilityExample
{
	public $publicProperty = 'Public';
	protected $protectedProperty = 'Protected';
	private $privateProperty = 'Private';

	public function printProperties()
	{
		echo $this->publicProperty;     // Fonctionne
		echo $this->protectedProperty;  // Fonctionne
		echo $this->privateProperty;    // Fonctionne
	}
}

$object = new VisibilityExample;
$object->printProperties();
echo $object->publicProperty;        // Fonctionne
// echo $object->protectedProperty;   // Lève une erreur
// echo $object->privateProperty;     // Lève une erreur

Poursuivons avec l'héritage de classes :

class ChildClass extends VisibilityExample
{
	public function printProperties()
	{
		echo $this->publicProperty;     // Fonctionne
		echo $this->protectedProperty;  // Fonctionne
		// echo $this->privateProperty;   // Lève une erreur
	}
}

Dans ce cas, la méthode printProperties() de ChildClass peut accéder aux propriétés publiques et protégées, mais pas aux propriétés privées de la classe parente.

Les données et les méthodes devraient être aussi cachées que possible et accessibles uniquement par une interface définie. Cela vous permet de changer l'implémentation interne de la classe sans toucher au reste du code.

Le mot-clé final

En PHP, nous pouvons utiliser le mot-clé final si nous voulons empêcher qu'une classe, une méthode ou une constante soit héritée ou redéfinie. Quand une classe est marquée final, elle ne peut pas être étendue. Quand une méthode est marquée final, elle ne peut pas être redéfinie dans une sous-classe.

Savoir qu'une classe ou une méthode donnée ne sera plus modifiée nous permet d'apporter des changements plus facilement, sans craindre d'éventuels conflits. Nous pouvons par exemple ajouter une nouvelle méthode sans redouter qu'un descendant possède déjà une méthode du même nom, ce qui provoquerait une collision. Ou changer les paramètres d'une méthode, là encore sans risquer une incohérence avec une méthode redéfinie dans un descendant.

final class FinalClass
{
}

// Le code suivant lèvera une erreur, car nous ne pouvons pas hériter d'une classe finale.
class ChildOfFinalClass extends FinalClass
{
}

Dans cet exemple, tenter d'hériter de la classe finale FinalClass produira une erreur.

Propriétés et méthodes statiques

Quand nous parlons d'éléments “statiques” d'une classe en PHP, nous entendons des méthodes et des propriétés qui appartiennent à la classe elle-même, et non à une instance particulière. Cela signifie que vous n'avez pas besoin de créer une instance de la classe pour y accéder. Vous les appelez ou y accédez directement par le nom de la classe.

Gardez à l'esprit que, les éléments statiques appartenant à la classe et non à ses instances, vous ne pouvez pas utiliser la pseudo-variable $this à l'intérieur des méthodes statiques.

L'utilisation de propriétés statiques conduit à un code obscur et plein de pièges, vous ne devriez donc jamais les utiliser, et nous n'en montrerons pas d'exemple ici. Les méthodes statiques, en revanche, sont utiles. En voici un exemple :

class Calculator
{
	public static function add($a, $b)
	{
		return $a + $b;
	}

	public static function subtract($a, $b)
	{
		return $a - $b;
	}
}

// Utilisation de la méthode statique sans créer d'instance de la classe
echo Calculator::add(5, 3); // Affiche : 8
echo Calculator::subtract(5, 3); // Affiche : 2

Dans cet exemple, nous avons créé une classe Calculator avec deux méthodes statiques. Nous pouvons appeler ces méthodes directement, sans créer d'instance de la classe, à l'aide de l'opérateur ::. Les méthodes statiques sont particulièrement utiles pour les opérations qui ne dépendent pas de l'état d'une instance donnée.

Constantes de classe

Au sein des classes, nous avons la possibilité de définir des constantes. Les constantes sont des valeurs qui ne changent jamais pendant l'exécution du programme. Contrairement aux variables, la valeur d'une constante reste la même.

class Car
{
	public const NumberOfWheels = 4;

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

echo Car::NumberOfWheels;  // Affiche : 4

Dans cet exemple, nous avons une classe Car avec la constante NumberOfWheels. Pour accéder à la constante à l'intérieur de la classe, nous pouvons utiliser le mot-clé self au lieu du nom de la classe.

Interfaces d'objets

Les interfaces d'objets font office de “contrats” pour les classes. Si une classe doit implémenter une interface d'objet, elle doit contenir toutes les méthodes que l'interface définit. C'est un excellent moyen de garantir que certaines classes respectent le même “contrat”, ou la même structure.

En PHP, les interfaces se définissent à l'aide du mot-clé interface. Toutes les méthodes définies dans une interface sont publiques (public). Quand une classe implémente une interface, elle utilise le mot-clé implements.

interface Animal
{
	function makeSound();
}

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

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

Si une classe implémente une interface mais que toutes les méthodes attendues ne sont pas définies, PHP lèvera une erreur.

Une classe peut implémenter plusieurs interfaces à la fois, ce qui la distingue de l'héritage, où une classe ne peut hériter que d'une seule classe :

interface Guardian
{
	function guardHouse();
}

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

	public function guardHouse()
	{
		echo 'Le chien garde consciencieusement la maison';
	}
}

Classes abstraites

Les classes abstraites servent de modèles de base à d'autres classes, mais vous ne pouvez pas en créer d'instances directement. Elles contiennent un mélange de méthodes complètes et de méthodes abstraites, qui n'ont pas de contenu défini. Les classes qui héritent de classes abstraites doivent fournir les définitions de toutes les méthodes abstraites du parent.

Nous utilisons le mot-clé abstract pour définir une classe abstraite.

abstract class AbstractClass
{
	public function regularMethod()
	{
		echo 'Ceci est une méthode ordinaire';
	}

	abstract public function abstractMethod();
}

class Child extends AbstractClass
{
	public function abstractMethod()
	{
		echo 'Ceci est l\'implémentation de la méthode abstraite';
	}
}

$instance = new Child;
$instance->regularMethod();
$instance->abstractMethod();

Dans cet exemple, nous avons une classe abstraite avec une méthode ordinaire et une méthode abstraite. Nous avons ensuite une classe Child qui hérite d'AbstractClass et fournit une implémentation de la méthode abstraite.

En quoi les interfaces et les classes abstraites diffèrent-elles ? Les classes abstraites peuvent contenir à la fois des méthodes abstraites et concrètes, tandis que les interfaces définissent seulement quelles méthodes la classe doit implémenter, sans en fournir l'implémentation. Une classe ne peut hériter que d'une seule classe abstraite, mais peut implémenter un nombre quelconque d'interfaces.

Contrôle des types

En programmation, il est essentiel de garantir que les données avec lesquelles nous travaillons sont du bon type. En PHP, nous disposons d'outils qui apportent cette garantie. Vérifier que les données sont du bon type s'appelle le “contrôle des types”.

Types que nous pouvons rencontrer en PHP :

  1. Types de base : ils comprennent int (entiers), float (nombres à virgule flottante), bool (valeurs booléennes), string (chaînes), array (tableaux) et null.
  2. Classes : lorsque nous voulons qu'une valeur soit une instance d'une classe précise.
  3. Interfaces : définissent un ensemble de méthodes que la classe doit implémenter. Une valeur satisfaisant une interface doit posséder ces méthodes.
  4. Types mixtes : nous pouvons indiquer qu'une variable peut avoir plusieurs types autorisés.
  5. Void : ce type particulier indique qu'une fonction ou une méthode ne renvoie aucune valeur.

Voyons comment modifier le code pour y ajouter les types :

class Person
{
	private int $age;

	public function __construct(int $age)
	{
		$this->age = $age;
	}

	public function printAge(): void
	{
		echo "Cette personne a {$this->age} ans.";
	}
}

/**
 * Une fonction qui accepte un objet Person et affiche l'âge de la personne.
 */
function printPersonAge(Person $person): void
{
	$person->printAge();
}

De cette façon, nous garantissons que notre code attend et manipule des données du bon type, ce qui nous aide à éviter d'éventuelles erreurs.

Certains types ne peuvent pas être écrits directement en PHP. Dans ce cas, ils sont indiqués dans le commentaire phpDoc, le format standard de documentation du code PHP, qui commence par /** et se termine par */. Il permet d'ajouter des descriptions de classes, de méthodes, etc. Et aussi d'indiquer des types complexes à l'aide de ce qu'on appelle les annotations @var, @param et @return. Ces types sont ensuite utilisés par les outils d'analyse statique du code, mais ne sont pas contrôlés par PHP lui-même.

class Registry
{
	/** @var array<Person>  indique qu'il s'agit d'un tableau d'objets Person */
	private array $persons = [];

	public function addPerson(Person $person): void
	{
		$this->persons[] = $person;
	}
}

Comparaison et identité

En PHP, vous pouvez comparer les objets de deux façons :

  1. Comparaison de valeurs == : vérifie si les objets sont de la même classe et ont les mêmes valeurs dans leurs propriétés.
  2. Identité === : vérifie s'il s'agit de la même instance de l'objet.
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, car ils ont la même valeur
var_dump($car1 === $car2);  // false, car ce n'est pas la même instance
var_dump($car1 === $car3);  // true, car $car3 est la même instance que $car1

L'opérateur instanceof

L'opérateur instanceof permet de déterminer si un objet donné est une instance d'une classe précise, un descendant de cette classe, ou s'il implémente une certaine interface.

Imaginons que nous ayons une classe Person et une autre classe Student, qui est un descendant 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, 'Informatique');

// Vérifie si $student est une instance de la classe Student
var_dump($student instanceof Student);  // Affiche : bool(true)

// Vérifie si $student est une instance de la classe Person (car Student descend de Person)
var_dump($student instanceof Person);   // Affiche : bool(true)

Ces sorties montrent que l'objet $student est considéré comme une instance des classes Student et Person à la fois.

Interfaces fluides

Une “interface fluide” est une technique de POO qui permet d'enchaîner les méthodes en un seul appel. Cela simplifie et clarifie souvent le code.

L'élément clé d'une interface fluide est que chaque méthode de la chaîne renvoie une référence à l'objet courant. On y parvient en utilisant return $this; à la fin de la méthode. Ce style de programmation est souvent associé aux méthodes appelées “setters”, qui définissent les valeurs des propriétés d'un objet.

Voyons à quoi peut ressembler une interface fluide pour l'envoi d'e-mails :

public function sendMessage()
{
	$email = new Email;
	$email->setFrom('sender@example.com')
		  ->setRecipient('admin@example.com')
		  ->setMessage('Bonjour, ceci est un message.')
		  ->send();
}

Dans cet exemple, les méthodes setFrom(), setRecipient() et setMessage() servent à définir les valeurs correspondantes (expéditeur, destinataire, contenu du message). Après avoir défini chacune de ces valeurs, les méthodes renvoient l'objet courant ($email), ce qui nous permet d'enchaîner une autre méthode. Nous appelons enfin la méthode send(), qui envoie réellement l'e-mail.

Grâce aux interfaces fluides, nous pouvons écrire un code intuitif et facilement lisible.

Copier avec clone

En PHP, nous pouvons créer une copie d'un objet à l'aide de l'opérateur clone. Nous obtenons ainsi une nouvelle instance au contenu identique.

Si nous devons modifier certaines de ses propriétés lors de la copie, nous pouvons définir dans la classe une méthode spéciale __clone(). Cette méthode est appelée automatiquement lors du clonage de l'objet.

class Sheep
{
	public string $name;

	public function __construct(string $name)
	{
		$this->name = $name;
	}

	public function __clone()
	{
		$this->name = 'Clone de ' . $this->name;
	}
}

$original = new Sheep('Dolly');
echo $original->name . "\n";  // Affiche : Dolly

$clone = clone $original;
echo $clone->name . "\n";     // Affiche : Clone de Dolly

Dans cet exemple, nous avons une classe Sheep avec une propriété $name. Lorsque nous clonons une instance de cette classe, la méthode __clone() fait en sorte que le nom du mouton cloné reçoive le préfixe “Clone de”.

Traits

Les traits en PHP sont un outil qui permet de partager des méthodes, des propriétés et des constantes entre des classes et évite la duplication de code. Vous pouvez vous les représenter comme un mécanisme de “copier-coller” (Ctrl-C et Ctrl-V), où le contenu d'un trait est “collé” dans les classes. Cela vous permet de réutiliser du code sans avoir à créer de hiérarchies de classes compliquées.

Regardons un exemple simple d'utilisation des traits en PHP :

trait Honking
{
	public function honk()
	{
		echo 'Bip bip !';
	}
}

class Car
{
	use Honking;
}

class Truck
{
	use Honking;
}

$car = new Car;
$car->honk(); // Affiche 'Bip bip !'

$truck = new Truck;
$truck->honk(); // Affiche aussi 'Bip bip !'

Dans cet exemple, nous avons un trait nommé Honking qui contient une méthode honk(). Nous avons ensuite deux classes, Car et Truck, qui utilisent toutes deux le trait Honking. Résultat : les deux classes “possèdent” la méthode honk() et nous pouvons l'appeler sur les objets des deux classes.

Les traits permettent de partager du code entre classes facilement et efficacement. Ils n'entrent pas dans la hiérarchie d'héritage : $car instanceof Honking renverra donc false.

Exceptions

Les exceptions en POO nous permettent de traiter élégamment les erreurs et les situations inattendues dans notre code. Ce sont des objets qui portent une information sur une erreur ou une situation inhabituelle.

En PHP, nous disposons d'une classe intégrée Exception, qui sert de base à toutes les exceptions. Elle possède plusieurs méthodes qui nous permettent d'obtenir plus d'informations sur l'exception, comme le message d'erreur, le fichier et la ligne où l'erreur s'est produite, etc.

Lorsqu'une erreur survient dans le code, nous pouvons “lever” l'exception à l'aide du mot-clé throw.

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

Lorsque la fonction division() reçoit zéro comme second argument, elle lève une exception portant le message d'erreur 'Division par zéro !'. Pour que le programme ne plante pas lorsque l'exception est levée, nous l'attrapons dans un bloc try/catch :

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

Le code susceptible de lever une exception est enveloppé dans un bloc try. Si l'exception est levée, l'exécution du code passe au bloc catch, où nous pouvons la traiter (par exemple afficher un message d'erreur).

Après les blocs try et catch, nous pouvons ajouter un bloc facultatif finally, toujours exécuté, que l'exception ait été levée ou non (même si nous utilisons return, break ou continue dans le bloc try ou catch) :

try {
	echo division(10, 0);
} catch (Exception $e) {
	echo 'Exception attrapée : '. $e->getMessage();
} finally {
	// Code toujours exécuté, que l'exception ait été levée ou non
}

Nous pouvons aussi créer nos propres classes d'exceptions (une hiérarchie) qui héritent de la classe Exception. Prenons comme exemple une application bancaire simple permettant des dépôts et des retraits :

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('Fonds insuffisants sur le compte.');
		}

		if ($amount > $this->dailyLimit) {
			throw new ExceededLimitException('Limite de retrait quotidienne dépassée.');
		}

		$this->balance -= $amount;
		return $this->balance;
	}
}

Plusieurs blocs catch peuvent être indiqués pour un seul bloc try si vous attendez différents types d'exceptions.

$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 'Une erreur est survenue pendant l\'opération.';
}

Dans cet exemple, il est important de noter l'ordre des blocs catch. Comme toutes les exceptions héritent de BankingException, si nous avions placé ce bloc en premier, toutes les exceptions y seraient attrapées sans que le code n'atteigne les blocs catch suivants. Il est donc important de placer les exceptions les plus spécifiques (c'est-à-dire celles qui héritent des autres) plus haut dans l'ordre des blocs catch que leurs exceptions parentes.

Itérations

En PHP, vous pouvez parcourir des objets à l'aide de la boucle foreach, tout comme vous parcourez un tableau. Pour que cela fonctionne, l'objet doit implémenter une interface particulière.

La première possibilité est d'implémenter l'interface Iterator, qui possède les méthodes current() renvoyant la valeur courante, key() renvoyant la clé, next() passant à la valeur suivante, rewind() revenant au début et valid() vérifiant si nous sommes déjà à la fin.

L'autre possibilité est d'implémenter l'interface IteratorAggregate, qui n'a qu'une seule méthode, getIterator(). Celle-ci renvoie soit un objet auxiliaire qui assurera le parcours, soit un générateur, c'est-à-dire une fonction particulière qui utilise yield pour renvoyer successivement les clés et les valeurs :

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 "Âge : {$person->age} ans\n";
}

Bonnes pratiques

Une fois les principes de base de la programmation orientée objet acquis, il est essentiel de se concentrer sur les bonnes pratiques de la POO. Elles vous aideront à écrire un code qui n'est pas seulement fonctionnel, mais aussi lisible, compréhensible et facile à maintenir.

  1. Séparation des responsabilités : chaque classe devrait avoir une responsabilité clairement définie et ne traiter qu'une seule tâche principale. Si une classe fait trop de choses, il peut être approprié de la diviser en classes plus petites et spécialisées.
  2. Encapsulation : les données et les méthodes devraient être aussi cachées que possible et accessibles uniquement par une interface définie. Cela vous permet de changer l'implémentation interne d'une classe sans toucher au reste du code.
  3. Injection de dépendances : au lieu de créer les dépendances directement dans une classe, vous devriez les “injecter” depuis l'extérieur. Pour une compréhension approfondie de ce principe, nous recommandons les chapitres sur l'injection de dépendances.
version: 4.x