Introduction à la programmation orientée objet
Le terme “POO” désigne la programmation orientée objet, qui est une manière 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 qu'une séquence d'instructions et de fonctions.
En POO, un “objet” est une unité qui contient des données et des fonctions qui travaillent avec ces données. Les objets sont créés à partir de “classes”, que l'on peut comprendre comme des plans ou des modèles pour les objets. Lorsque nous avons une classe, nous pouvons créer son “instance”, qui est un objet concret créé selon cette classe.
Voyons comment nous pouvons créer une classe simple en PHP. Lors de la définition d'une classe, nous utilisons le mot-clé “class”, suivi du nom de la classe, puis des accolades qui entourent les fonctions (appelées “méthodes”) et les variables de la classe (appelées “propriétés”) :
class Voiture
{
function klaxonner()
{
echo 'Bip bip!';
}
}
Dans cet exemple, nous avons créé une classe nommée Voiture
avec une fonction (ou “méthode”) appelée
klaxonner
.
Chaque classe ne devrait traiter qu'une seule tâche principale. Si une classe fait trop de choses, il peut être judicieux de la diviser en classes plus petites et spécialisées.
Les classes sont généralement stockées dans des fichiers séparés pour que le code soit organisé et facile à naviguer. Le
nom du fichier doit correspondre au nom de la classe, donc pour la classe Voiture
, le nom du fichier serait
Voiture.php
.
Lors de la dénomination des classes, il est bon de suivre la convention “PascalCase”, ce qui signifie que chaque mot du nom commence par une majuscule et qu'il n'y a pas de traits de soulignement ou d'autres séparateurs entre eux. Les méthodes et les propriétés utilisent la convention “camelCase”, ce qui signifie qu'elles commencent par une lettre minuscule.
Certaines méthodes en PHP ont des rôles spéciaux et sont préfixées par __
(deux traits de soulignement).
L'une des méthodes spéciales les plus importantes est le “constructeur”, qui est désigné par __construct
. Le
constructeur est une méthode qui est automatiquement appelée lorsque vous créez une nouvelle instance de la classe.
Nous utilisons souvent le constructeur pour définir l'état initial de l'objet. Par exemple, lorsque vous créez un objet représentant une personne, vous pouvez utiliser le constructeur pour définir son âge, son nom ou d'autres propriétés.
Voyons comment utiliser un constructeur en PHP :
class Personne
{
private $age;
function __construct($age)
{
$this->age = $age;
}
function quelAgeAsTu()
{
return $this->age;
}
}
$personne = new Personne(25);
echo $personne->quelAgeAsTu(); // Affiche : 25
Dans cet exemple, la classe Personne
a une propriété (variable) $age
et un constructeur qui
définit cette propriété. La méthode quelAgeAsTu()
permet ensuite d'accéder à l'âge de la personne.
La pseudo-variable $this
est utilisée à l'intérieur de la classe pour accéder aux propriétés et méthodes de
l'objet.
Le mot-clé new
est utilisé pour créer une nouvelle instance de la classe. Dans l'exemple ci-dessus, nous avons
créé une nouvelle personne âgée de 25 ans.
Vous pouvez également définir des valeurs par défaut pour les paramètres du constructeur si elles ne sont pas spécifiées lors de la création de l'objet. Par exemple :
class Personne
{
private $age;
function __construct($age = 20)
{
$this->age = $age;
}
function quelAgeAsTu()
{
return $this->age;
}
}
$personne = new Personne; // si aucun argument n'est passé, les parenthèses peuvent être omises
echo $personne->quelAgeAsTu(); // Affiche : 20
Dans cet exemple, si vous ne spécifiez pas l'âge lors de la création de l'objet Personne
, la valeur par défaut
20 sera utilisée.
Il est agréable de constater que la définition de la propriété avec son initialisation via le constructeur peut être ainsi raccourcie et simplifiée :
class Personne
{
function __construct(
private $age = 20,
) {
}
}
Pour être complet, en plus des constructeurs, les objets peuvent également avoir des destructeurs (méthode
__destruct
), qui sont appelés avant que l'objet ne soit libéré de la mémoire.
Espaces de noms
Les espaces de noms (ou “namespaces” en anglais) nous permettent d'organiser et de regrouper des classes, fonctions et constantes liées, tout en évitant les conflits de noms. Vous pouvez les imaginer comme des dossiers sur votre ordinateur, où chaque dossier contient des fichiers appartenant à un projet ou à un thème spécifique.
Les espaces de noms sont particulièrement utiles dans les grands projets ou lorsque vous utilisez des bibliothèques tierces, où des conflits de noms de classes pourraient survenir.
Imaginez que vous ayez une classe nommée Voiture
dans votre projet et que vous souhaitiez la placer dans un
espace de noms appelé Transport
. Vous le feriez comme ceci :
namespace Transport;
class Voiture
{
function klaxonner()
{
echo 'Bip bip!';
}
}
Si vous souhaitez utiliser la classe Voiture
dans un autre fichier, vous devez spécifier de quel espace de noms
la classe provient :
$voiture = new Transport\Voiture;
Pour simplifier, vous pouvez indiquer au début du fichier quelle classe de l'espace de noms donné vous souhaitez utiliser, ce qui permet de créer des instances sans avoir à spécifier le chemin complet :
use Transport\Voiture;
$voiture = new Voiture;
Héritage
L'héritage est un outil de la programmation orientée objet qui permet de créer de nouvelles classes basées sur des classes existantes, d'hériter de leurs propriétés et méthodes, et de les étendre ou de les redéfinir selon les besoins. L'héritage permet d'assurer la réutilisabilité du code et une hiérarchie de classes.
En termes simples, si nous avons une classe et que nous voulons en créer une autre, dérivée de celle-ci, mais avec quelques modifications, nous pouvons faire “hériter” la nouvelle classe de la classe d'origine.
En PHP, l'héritage est réalisé à l'aide du mot-clé extends
.
Notre classe Personne
stocke des informations sur l'âge. Nous pouvons avoir une autre classe
Etudiant
, qui étend Personne
et ajoute des informations sur le domaine d'études.
Regardons un exemple :
class Personne
{
private $age;
function __construct($age)
{
$this->age = $age;
}
function afficherInformations()
{
echo "Âge : {$this->age} ans\n";
}
}
class Etudiant extends Personne
{
private $domaine;
function __construct($age, $domaine)
{
parent::__construct($age);
$this->domaine = $domaine;
}
function afficherInformations()
{
parent::afficherInformations();
echo "Domaine d'études : {$this->domaine} \n";
}
}
$etudiant = new Etudiant(20, 'Informatique');
$etudiant->afficherInformations();
Comment fonctionne ce code ?
- Nous avons utilisé le mot-clé
extends
pour étendre la classePersonne
, ce qui signifie que la classeEtudiant
hérite de toutes les méthodes et propriétés dePersonne
. - Le mot-clé
parent::
nous permet d'appeler des méthodes de la classe parente. Dans ce cas, nous avons appelé le constructeur de la classePersonne
avant d'ajouter notre propre fonctionnalité à la classeEtudiant
. Et de même, la méthodeafficherInformations()
du parent avant d'afficher les informations sur l'étudiant.
L'héritage est destiné aux situations où il existe une relation “est un” entre les classes. Par exemple, un
Etudiant
est une Personne
. Un chat est un animal. Cela nous donne la possibilité, dans les cas où le
code attend un objet (par exemple, “Personne”), d'utiliser à la place un objet hérité (par exemple, “Etudiant”).
Il est important de noter que l'objectif principal de l'héritage n'est pas d'éviter la duplication de code. Au contraire, une utilisation incorrecte de l'héritage peut conduire à un code complexe et difficile à maintenir. Si la relation “est un” n'existe pas entre les classes, nous devrions envisager la composition au lieu de l'héritage.
Notez que les méthodes afficherInformations()
dans les classes Personne
et Etudiant
affichent des informations légèrement différentes. Et nous pouvons ajouter d'autres classes (par exemple,
Employe
), qui fourniront d'autres implémentations de cette méthode. La capacité des objets de différentes classes
à réagir à la même méthode de différentes manières s'appelle le polymorphisme :
$personnes = [
new Personne(30),
new Etudiant(20, 'Informatique'),
new Employe(45, 'Directeur'),
];
foreach ($personnes as $personne) {
$personne->afficherInformations();
}
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 son instance dans notre classe. Cela nous permet de combiner les fonctionnalités et les propriétés de plusieurs classes sans avoir à créer de structures d'héritage complexes.
Regardons un exemple. Nous avons une classe Moteur
et une classe Voiture
. Au lieu de dire “Une
Voiture est un Moteur”, nous disons “Une Voiture a un Moteur”, ce qui est une relation typique de composition.
class Moteur
{
function demarrer()
{
echo 'Le moteur tourne.';
}
}
class Voiture
{
private $moteur;
function __construct()
{
$this->moteur = new Moteur;
}
function demarrerVoiture()
{
$this->moteur->demarrer();
echo 'La voiture est prête à rouler !';
}
}
$voiture = new Voiture;
$voiture->demarrerVoiture();
Ici, Voiture
n'a pas toutes les propriétés et méthodes de Moteur
, mais elle y a accès via la
propriété $moteur
.
L'avantage de la composition est une plus grande flexibilité dans la conception et une meilleure possibilité de modifications futures.
Visibilité
En PHP, vous pouvez définir la “visibilité” pour les propriétés, méthodes et constantes d'une classe. La visibilité détermine d'où vous pouvez accéder à ces éléments.
- Public : Si un élément est marqué comme
public
, cela signifie que vous pouvez y accéder de n'importe où, même en dehors de la classe. - Protected : Un élément marqué
protected
n'est accessible qu'à l'intérieur de la classe donnée et de tous ses descendants (classes qui héritent de cette classe). - Private : Si un élément est
private
, vous ne pouvez y accéder que depuis l'intérieur de la classe où il a été défini.
Si vous ne spécifiez pas de visibilité, PHP la définit automatiquement sur public
.
Regardons un exemple de code :
class ExempleVisibilite
{
public $proprietePublique = 'Publique';
protected $proprieteProtegee = 'Protégée';
private $proprietePrivee = 'Privée';
public function afficherProprietes()
{
echo $this->proprietePublique; // Fonctionne
echo $this->proprieteProtegee; // Fonctionne
echo $this->proprietePrivee; // Fonctionne
}
}
$objet = new ExempleVisibilite;
$objet->afficherProprietes();
echo $objet->proprietePublique; // Fonctionne
// echo $objet->proprieteProtegee; // Génère une erreur
// echo $objet->proprietePrivee; // Génère une erreur
Continuons avec l'héritage de classe :
class ClasseEnfant extends ExempleVisibilite
{
public function afficherProprietes()
{
echo $this->proprietePublique; // Fonctionne
echo $this->proprieteProtegee; // Fonctionne
// echo $this->proprietePrivee; // Génère une erreur
}
}
Dans ce cas, la méthode afficherProprietes()
de la classe ClasseEnfant
peut accéder aux
propriétés publiques et protégées, mais ne peut pas accéder aux propriétés privées de la classe parente.
Les données et les méthodes doivent être autant que possible cachées et accessibles uniquement via une interface définie. Cela vous permet de modifier l'implémentation interne de la classe sans affecter le reste du code.
Le mot-clé final
En PHP, nous pouvons utiliser le mot-clé final
si nous voulons empêcher une classe, une méthode ou une
constante d'être héritée ou redéfinie. Lorsque nous marquons une classe comme final
, elle ne peut pas être
étendue. Lorsque nous marquons une méthode comme final
, elle ne peut pas être redéfinie dans une classe
enfant.
Savoir qu'une certaine classe ou méthode ne sera pas modifiée ultérieurement nous permet d'effectuer des modifications plus facilement, sans avoir à nous soucier des conflits potentiels. Par exemple, nous pouvons ajouter une nouvelle méthode sans craindre qu'un de ses descendants ait déjà une méthode du même nom, ce qui entraînerait une collision. Ou nous pouvons modifier les paramètres d'une méthode, car là encore, il n'y a aucun risque de provoquer une incohérence avec une méthode redéfinie dans un descendant.
final class ClasseFinale
{
}
// Le code suivant provoquera une erreur, car nous ne pouvons pas hériter d'une classe finale.
class EnfantClasseFinale extends ClasseFinale
{
}
Dans cet exemple, la tentative d'hériter de la classe finale ClasseFinale
provoquera une erreur.
Propriétés et méthodes statiques
Lorsque 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 spécifique de cette classe. Cela signifie que vous n'avez pas besoin de créer une instance de la classe pour y accéder. Au lieu de cela, vous les appelez ou y accédez directement via le nom de la classe.
Gardez à l'esprit que, puisque les éléments statiques appartiennent à 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 confus plein d'embûches, c'est pourquoi vous ne devriez jamais les utiliser et nous ne montrerons pas d'exemple d'utilisation ici. En revanche, les méthodes statiques sont utiles. Exemple d'utilisation :
class Calculatrice
{
public static function addition($a, $b)
{
return $a + $b;
}
public static function soustraction($a, $b)
{
return $a - $b;
}
}
// Utilisation de la méthode statique sans créer d'instance de la classe
echo Calculatrice::addition(5, 3); // Résultat : 8
echo Calculatrice::soustraction(5, 3); // Résultat : 2
Dans cet exemple, nous avons créé une classe Calculatrice
avec deux méthodes statiques. Nous pouvons appeler
ces méthodes directement sans créer d'instance de la classe en utilisant 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 spécifique de la classe.
Constantes de classe
Au sein des classes, nous avons la possibilité de définir des constantes. Les constantes sont des valeurs qui ne changeront jamais pendant l'exécution du programme. Contrairement aux variables, la valeur d'une constante reste toujours la même.
class Voiture
{
public const NombreDeRoues = 4;
public function afficherNombreDeRoues(): int
{
echo self::NombreDeRoues;
}
}
echo Voiture::NombreDeRoues; // Sortie : 4
Dans cet exemple, nous avons une classe Voiture
avec la constante NombreDeRoues
. Lorsque nous voulons
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'objet
Les interfaces d'objet fonctionnent comme des “contrats” pour les classes. Si une classe doit implémenter une interface d'objet, elle doit contenir toutes les méthodes définies par cette interface. C'est un excellent moyen de s'assurer que certaines classes respectent le même “contrat” ou la même structure.
En PHP, une interface est définie avec le mot-clé interface
. Toutes les méthodes définies dans une interface
sont publiques (public
). Lorsqu'une classe implémente une interface, elle utilise le mot-clé
implements
.
interface Animal
{
function emettreSon();
}
class Chat implements Animal
{
public function emettreSon()
{
echo 'Miaou';
}
}
$chat = new Chat;
$chat->emettreSon();
Si une classe implémente une interface mais que toutes les méthodes attendues n'y sont pas définies, PHP générera une erreur.
Une classe peut implémenter plusieurs interfaces à la fois, ce qui la différencie de l'héritage, où une classe ne peut hériter que d'une seule classe :
interface Gardien
{
function garderMaison();
}
class Chien implements Animal, Gardien
{
public function emettreSon()
{
echo 'Wouf';
}
public function garderMaison()
{
echo 'Le chien garde attentivement la maison';
}
}
Classes abstraites
Les classes abstraites servent de modèles de base pour d'autres classes, mais vous ne pouvez pas créer leurs instances directement. Elles contiennent une combinaison 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 des définitions pour toutes les méthodes abstraites du parent.
Pour définir une classe abstraite, nous utilisons le mot-clé abstract
.
abstract class ClasseAbstraite
{
public function methodeOrdinaire()
{
echo 'Ceci est une méthode ordinaire';
}
abstract public function methodeAbstraite();
}
class Enfant extends ClasseAbstraite
{
public function methodeAbstraite()
{
echo 'Ceci est l\'implémentation de la méthode abstraite';
}
}
$instance = new Enfant;
$instance->methodeOrdinaire();
$instance->methodeAbstraite();
Dans cet exemple, nous avons une classe abstraite avec une méthode ordinaire et une méthode abstraite. Ensuite, nous avons
une classe Enfant
qui hérite de ClasseAbstraite
et fournit une implémentation pour la méthode
abstraite.
Quelle est la différence entre les interfaces et les classes abstraites ? Les classes abstraites peuvent contenir à la fois des méthodes abstraites et concrètes, tandis que les interfaces définissent uniquement les méthodes qu'une classe doit implémenter, mais ne fournissent aucune 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 de type
En programmation, il est très important d'être sûr que les données avec lesquelles nous travaillons sont du bon type. En PHP, nous avons des outils qui nous assurent cela. La vérification que les données ont le bon type s'appelle le “contrôle de type”.
Les types que nous pouvons rencontrer en PHP :
- Types de base : Incluent
int
(entiers),float
(nombres décimaux),bool
(valeurs booléennes),string
(chaînes de caractères),array
(tableaux) etnull
. - Classes : Si nous voulons qu'une valeur soit une instance d'une classe spécifique.
- Interfaces : Définit un ensemble de méthodes qu'une classe doit implémenter. Une valeur qui satisfait une interface doit avoir ces méthodes.
- Types mixtes : Nous pouvons spécifier qu'une variable peut avoir plusieurs types autorisés.
- Void : Ce type spécial indique qu'une fonction ou une méthode ne retourne aucune valeur.
Voyons comment modifier le code pour inclure les types :
class Personne
{
private int $age;
public function __construct(int $age)
{
$this->age = $age;
}
public function afficherAge(): void
{
echo "Cette personne a {$this->age} ans.";
}
}
/**
* Fonction qui accepte un objet de la classe Personne et affiche l'âge de la personne.
*/
function afficherAgePersonne(Personne $personne): void
{
$personne->afficherAge();
}
De cette manière, nous nous sommes assurés que notre code attend et travaille avec des données du bon type, ce qui nous aide à prévenir les erreurs potentielles.
Certains types ne peuvent pas être écrits directement en PHP. Dans ce cas, ils sont indiqués dans un commentaire phpDoc, qui
est un format standard pour documenter le code PHP commençant par /**
et se terminant par */
. Il permet
d'ajouter des descriptions de classes, de méthodes, etc. Et aussi d'indiquer des types complexes à l'aide d'annotations telles
que @var
, @param
et @return
. Ces types sont ensuite utilisés par les outils d'analyse
statique de code, mais PHP lui-même ne les vérifie pas.
class Liste
{
/** @var array<Personne> cette notation indique qu'il s'agit d'un tableau d'objets Personne */
private array $personnes = [];
public function ajouterPersonne(Personne $personne): void
{
$this->personnes[] = $personne;
}
}
Comparaison et identité
En PHP, vous pouvez comparer des objets de deux manières :
- 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. - Identité
===
: Vérifie s'il s'agit de la même instance d'objet.
class Voiture
{
public string $marque;
public function __construct(string $marque)
{
$this->marque = $marque;
}
}
$voiture1 = new Voiture('Skoda');
$voiture2 = new Voiture('Skoda');
$voiture3 = $voiture1;
var_dump($voiture1 == $voiture2); // true, car ils ont la même valeur
var_dump($voiture1 === $voiture2); // false, car ce ne sont pas la même instance
var_dump($voiture1 === $voiture3); // true, car $voiture3 est la même instance que $voiture1
L'opérateur instanceof
L'opérateur instanceof
permet de déterminer si un objet donné est une instance d'une certaine classe, d'un
descendant de cette classe, ou s'il implémente une certaine interface.
Imaginons que nous ayons une classe Personne
et une autre classe Etudiant
, qui est un descendant de
la classe Personne
:
class Personne
{
private int $age;
public function __construct(int $age)
{
$this->age = $age;
}
}
class Etudiant extends Personne
{
private string $domaine;
public function __construct(int $age, string $domaine)
{
parent::__construct($age);
$this->domaine = $domaine;
}
}
$etudiant = new Etudiant(20, 'Informatique');
// Vérification si $etudiant est une instance de la classe Etudiant
var_dump($etudiant instanceof Etudiant); // Sortie : bool(true)
// Vérification si $etudiant est une instance de la classe Personne (car Etudiant est un descendant de Personne)
var_dump($etudiant instanceof Personne); // Sortie : bool(true)
Il ressort des sorties que l'objet $etudiant
est considéré simultanément comme une instance des deux
classes – Etudiant
et Personne
.
Interfaces fluides
L'“interface fluide” (en anglais “Fluent Interface”) est une technique en POO qui permet d'enchaîner des méthodes ensemble 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 retourne une référence à l'objet actuel. Nous
y parvenons 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 de l'objet.
Montrons à quoi peut ressembler une interface fluide avec un exemple d'envoi d'e-mails :
public function envoyerMessage()
{
$email = new Email;
$email->setFrom('expediteur@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 nous retournent l'objet actuel ($email
), ce qui nous permet d'enchaîner une autre méthode après
elle. Enfin, nous appelons la méthode send()
, qui envoie réellement l'e-mail.
Grâce aux interfaces fluides, nous pouvons écrire du code intuitif et facile à lire.
Copie avec clone
En PHP, nous pouvons créer une copie d'un objet à l'aide de l'opérateur clone
. De cette façon, nous obtenons
une nouvelle instance avec un contenu identique.
Si nous devons modifier certaines propriétés d'un objet lors de sa copie, nous pouvons définir une méthode spéciale
__clone()
dans la classe. Cette méthode est automatiquement appelée lorsque l'objet est cloné.
class Mouton
{
public string $nom;
public function __construct(string $nom)
{
$this->nom = $nom;
}
public function __clone()
{
$this->nom = 'Clone ' . $this->nom;
}
}
$original = new Mouton('Dolly');
echo $original->nom . "\n"; // Affiche : Dolly
$klon = clone $original;
echo $klon->nom . "\n"; // Affiche : Clone Dolly
Dans cet exemple, nous avons une classe Mouton
avec une propriété $nom
. Lorsque nous clonons une
instance de cette classe, la méthode __clone()
s'assure que le nom du mouton cloné reçoit le préfixe
“Clone”.
Traits
Les traits en PHP sont un outil qui permet de partager des méthodes, des propriétés et des constantes entre les classes et d'éviter la duplication de code. Vous pouvez les imaginer comme un mécanisme de “copier-coller” (Ctrl-C et Ctrl-V), où le contenu du trait est “inséré” dans les classes. Cela vous permet de réutiliser du code sans avoir à créer des hiérarchies de classes complexes.
Montrons un exemple simple d'utilisation des traits en PHP :
trait KlaxonnerTrait
{
public function klaxonner()
{
echo 'Bip bip!';
}
}
class Voiture
{
use KlaxonnerTrait;
}
class Camion
{
use KlaxonnerTrait;
}
$voiture = new Voiture;
$voiture->klaxonner(); // Affiche 'Bip bip!'
$camion = new Camion;
$camion->klaxonner(); // Affiche aussi 'Bip bip!'
Dans cet exemple, nous avons un trait nommé KlaxonnerTrait
, qui contient une méthode klaxonner()
.
Ensuite, nous avons deux classes : Voiture
et Camion
, qui utilisent toutes deux le trait
KlaxonnerTrait
. Grâce à cela, les deux classes “ont” la méthode klaxonner()
, et nous pouvons
l'appeler sur les objets des deux classes.
Les traits vous permettent de partager facilement et efficacement du code entre les classes. Cependant, ils n'entrent pas dans
la hiérarchie d'héritage, c'est-à-dire que $voiture instanceof KlaxonnerTrait
retournera false
.
Exceptions
Les exceptions en POO nous permettent de gérer élégamment les erreurs et les situations inattendues dans notre code. Ce sont des objets qui transportent des informations sur l'erreur ou la situation inhabituelle.
En PHP, nous avons 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, telles que le message d'erreur, le fichier
et la ligne où l'erreur s'est produite, etc.
Lorsqu'une erreur se produit dans le code, nous pouvons “lever” une 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 deuxième argument, elle lève une exception avec le message
d'erreur 'Division par zéro !'
. Pour éviter que le programme ne plante lorsqu'une exception est levée, nous la
capturons dans un bloc try/catch
:
try {
echo division(10, 0);
} catch (Exception $e) {
echo 'Exception capturée : '. $e->getMessage();
}
Le code qui peut lever une exception est encapsulé dans un bloc try
. Si une exception est levée, l'exécution du
code passe au bloc catch
, où nous pouvons traiter l'exception (par exemple, afficher un message d'erreur).
Après les blocs try
et catch
, nous pouvons ajouter un bloc finally
facultatif, qui
s'exécutera toujours, qu'une exception ait été levée ou non (même si nous utilisons une instruction return
,
break
ou continue
dans le bloc try
ou catch
) :
try {
echo division(10, 0);
} catch (Exception $e) {
echo 'Exception capturée : '. $e->getMessage();
} finally {
// Code qui s'exécute toujours, qu'une exception ait été levée ou non
}
Nous pouvons également créer nos propres classes (hiérarchie) d'exceptions qui héritent de la classe Exception. À titre d'exemple, imaginons une application bancaire simple qui permet d'effectuer des dépôts et des retraits :
class ExceptionBancaire extends Exception {}
class ExceptionFondsInsuffisants extends ExceptionBancaire {}
class ExceptionLimiteDepassee extends ExceptionBancaire {}
class CompteBancaire
{
private int $solde = 0;
private int $limiteJournaliere = 1000;
public function deposer(int $montant): int
{
$this->solde += $montant;
return $this->solde;
}
public function retirer(int $montant): int
{
if ($montant > $this->solde) {
throw new ExceptionFondsInsuffisants('Fonds insuffisants sur le compte.');
}
if ($montant > $this->limiteJournaliere) {
throw new ExceptionLimiteDepassee('La limite quotidienne de retrait a été dépassée.');
}
$this->solde -= $montant;
return $this->solde;
}
}
Pour un seul bloc try
, plusieurs blocs catch
peuvent être spécifiés si vous attendez différents
types d'exceptions.
$compte = new CompteBancaire;
$compte->deposer(500);
try {
$compte->retirer(1500);
} catch (ExceptionLimiteDepassee $e) {
echo $e->getMessage();
} catch (ExceptionFondsInsuffisants $e) {
echo $e->getMessage();
} catch (ExceptionBancaire $e) {
echo 'Une erreur s\'est produite lors de l\'exécution de l\'opération.';
}
Dans cet exemple, il est important de noter l'ordre des blocs catch
. Étant donné que toutes les exceptions
héritent de ExceptionBancaire
, si nous avions ce bloc en premier, toutes les exceptions y seraient capturées sans
que le code n'atteigne les blocs catch
suivants. Il est donc important de placer les exceptions plus spécifiques
(c'est-à-dire celles qui héritent d'autres) dans un bloc catch
plus haut dans l'ordre que leurs exceptions
parentes.
Itération
En PHP, vous pouvez parcourir des objets à l'aide d'une boucle foreach
, de la même manière que vous parcourez
des tableaux. Pour que cela fonctionne, l'objet doit implémenter une interface spéciale.
La première option est d'implémenter l'interface Iterator
, qui possède les méthodes current()
retournant la valeur actuelle, key()
retournant la clé, next()
passant à la valeur suivante,
rewind()
revenant au début et valid()
vérifiant si nous ne sommes pas encore à la fin.
La deuxième option est d'implémenter l'interface IteratorAggregate
, qui n'a qu'une seule méthode
getIterator()
. Celle-ci retourne soit un objet de substitution qui assurera le parcours, soit peut représenter un
générateur, qui est une fonction spéciale dans laquelle yield
est utilisé pour retourner progressivement les
clés et les valeurs :
class Personne
{
public function __construct(
public int $age,
) {
}
}
class Liste implements IteratorAggregate
{
private array $personnes = [];
public function ajouterPersonne(Personne $personne): void
{
$this->personnes[] = $personne;
}
public function getIterator(): Generator
{
foreach ($this->personnes as $personne) {
yield $personne;
}
}
}
$liste = new Liste;
$liste->ajouterPersonne(new Personne(30));
$liste->ajouterPersonne(new Personne(25));
foreach ($liste as $personne) {
echo "Âge : {$personne->age} ans \n";
}
Bonnes pratiques
Une fois que vous maîtrisez les principes de base de la programmation orientée objet, il est important de se concentrer sur les bonnes pratiques en POO. Celles-ci vous aideront à écrire du code qui est non seulement fonctionnel, mais aussi lisible, compréhensible et facile à maintenir.
- Séparation des préoccupations (Separation of Concerns) : Chaque classe doit avoir une responsabilité clairement définie et ne doit traiter qu'une seule tâche principale. Si une classe fait trop de choses, il peut être judicieux de la diviser en classes plus petites et spécialisées.
- Encapsulation (Encapsulation) : Les données et les méthodes doivent être autant que possible cachées et accessibles uniquement via une interface définie. Cela vous permet de modifier l'implémentation interne de la classe sans affecter le reste du code.
- Injection de dépendances (Dependency Injection) : Au lieu de créer des dépendances directement dans la classe, vous devriez les “injecter” de l'extérieur. Pour une compréhension plus approfondie de ce principe, nous recommandons les chapitres sur l'injection de dépendances.