backend / php-laravel
Programmation orientée objet en PHP
Explication
Ce que vous allez apprendre
- Créer une classe avec des propriétés typées, un constructeur et de la visibilité (
private,public) - Comprendre à quoi servent les propriétés
readonlyet la promotion de propriétés dans le constructeur - Distinguer une interface (contrat) d'un trait (code partagé horizontalement) et d'une classe abstraite
- Utiliser les enums natifs de PHP 8.1 à la place de constantes de classe dispersées
- Reconnaître les méthodes et propriétés statiques et savoir quand elles sont justifiées
Dans quel contexte ?
Une équipe backend modélise un système de comptes bancaires et de statuts de commande pour une plateforme e-commerce. Elle a besoin que chaque compte protège son solde contre des modifications directes non contrôlées (d'où private), que plusieurs types d'utilisateurs (admin, client) partagent un comportement commun de notification, et que le statut d'une commande ne puisse prendre qu'un nombre fini de valeurs valides. C'est exactement ce que permettent respectivement l'encapsulation, les traits/interfaces et les enums.
D'abord, protéger les données avec l'encapsulation
Une classe PHP regroupe des données (propriétés) et des comportements (méthodes). La visibilité private empêche tout code extérieur à la classe de modifier directement une propriété comme $solde : seules les méthodes de la classe elle-même (deposer(), retirer()) peuvent le faire, avec leurs propres règles de validation.
C'est la base de l'encapsulation : au lieu d'exposer une donnée brute modifiable n'importe comment, on expose des méthodes qui garantissent que les règles métier (jamais de solde négatif, par exemple) sont toujours respectées.
Prérequis
Cette leçon suppose une bonne maîtrise des fonctions et de la portée des variables (leçon 2). La POO ajoute une couche d'organisation par-dessus ces bases, elle ne les remplace pas.
Ensuite, une syntaxe qui élimine du code répétitif
Écrire un constructeur qui reçoit des paramètres puis les assigne un par un à des propriétés ($this->titulaire = $titulaire) est répétitif. PHP 8 permet la "promotion de propriétés" directement dans la signature du constructeur : private readonly string $titulaire déclare ET assigne la propriété en une seule ligne.
Le mot-clé readonly (PHP 8.1+) va plus loin : une fois la propriété assignée dans le constructeur, elle devient définitivement immuable. C'est idéal pour représenter des données qui ne devraient jamais changer après création, comme un identifiant ou une date de création.
Il reste une question : comment partager du comportement entre plusieurs classes ?
PHP ne permet pas l'héritage multiple (une classe ne peut hériter que d'une seule classe parente), mais propose trois outils complémentaires pour organiser le partage de comportement.
| Mécanisme | Rôle | Peut avoir du code ? |
|---|---|---|
| Interface | Définit un contrat (méthodes à implémenter) | Non |
| Trait | Partage du code réutilisable entre classes non liées | Oui |
| Classe abstraite | Base commune avec héritage classique | Oui (partiellement) |
Une interface comme Notifiable garantit que toute classe qui l'implémente possède une méthode notifier(), sans imposer comment elle est écrite. Un trait comme Horodatable fournit, lui, une implémentation concrète réutilisable dans plusieurs classes qui n'ont pas de lien de parenté entre elles.
Piège fréquent
Une classe abstraite ne peut être héritée que par UNE SEULE classe parente à la fois, alors qu'une classe peut implémenter PLUSIEURS interfaces et utiliser PLUSIEURS traits. Confondre les deux mène souvent à des architectures rigides difficiles à faire évoluer.
Maintenant, un outil moderne pour les valeurs finies
Avant PHP 8.1, on représentait souvent un statut limité (en attente, expédiée, livrée) avec des constantes de classe ou de simples chaînes, sans garantie qu'une valeur invalide ne se glisse quelque part. Les enums natifs (enum StatutCommande: string) résolvent ça : PHP garantit qu'une variable de ce type ne peut contenir qu'une des valeurs déclarées.
Astuce
Ajoute directement des méthodes à un enum, comme libelle() dans l'exemple, pour centraliser la logique d'affichage au même endroit que la définition des valeurs possibles — plutôt que de disperser des if/match dans toute l'application.
Maintenant que tu sais structurer du code avec des classes robustes, il reste une question essentielle : que se passe-t-il quand quelque chose tourne mal à l'exécution ? C'est le sujet de la prochaine leçon, sur la gestion des erreurs et des exceptions.
Commandes & code
Programmation orientée objet en PHP
<?php
declare(strict_types=1);
// Propriétés typées, constructeur, visibilité
class CompteBancaire {
private float $solde;
public function __construct(
private readonly string $titulaire, // readonly (PHP 8.1+) : immuable après construction
float $soldeInitial = 0.0
) {
$this->solde = $soldeInitial;
}
public function deposer(float $montant): void {
if ($montant <= 0) {
throw new InvalidArgumentException("Le montant doit être positif");
}
$this->solde += $montant;
}
public function retirer(float $montant): void {
if ($montant > $this->solde) {
throw new RuntimeException("Solde insuffisant");
}
$this->solde -= $montant;
}
public function getSolde(): float {
return $this->solde;
}
}
$compte = new CompteBancaire("Alice", 100.0);
$compte->deposer(50);
echo $compte->getSolde() . "
"; // 150
// Interfaces : contrat sans implémentation
interface Notifiable {
public function notifier(string $message): void;
}
// Traits : réutilisation de code horizontale (mixin)
trait Horodatable {
private ?DateTimeImmutable $creeLe = null;
public function marquerCree(): void {
$this->creeLe = new DateTimeImmutable();
}
public function getDateCreation(): ?DateTimeImmutable {
return $this->creeLe;
}
}
// Héritage + implémentation d'interface + trait
abstract class Utilisateur implements Notifiable {
use Horodatable;
public function __construct(protected string $email) {
$this->marquerCree();
}
// Méthode abstraite : chaque sous-classe doit la définir
abstract public function role(): string;
public function notifier(string $message): void {
echo "[{$this->role()}] Email à {$this->email}: $message
";
}
}
class Administrateur extends Utilisateur {
public function role(): string {
return "admin";
}
}
class Client extends Utilisateur {
public function role(): string {
return "client";
}
}
$admin = new Administrateur("admin@site.com");
$admin->notifier("Nouveau ticket ouvert");
// Enums natifs (PHP 8.1+) : typés et backés par une valeur
enum StatutCommande: string {
case EnAttente = 'en_attente';
case Expediee = 'expediee';
case Livree = 'livree';
public function libelle(): string {
return match ($this) {
self::EnAttente => "En attente",
self::Expediee => "Expédiée",
self::Livree => "Livrée",
};
}
}
echo StatutCommande::Expediee->libelle() . "
";
// Méthodes et propriétés statiques
class Compteur {
private static int $total = 0;
public static function incrementer(): int {
return ++self::$total;
}
}
Compteur::incrementer();
echo Compteur::incrementer() . "
"; // 2Résumé
readonly(8.1+) rend une propriété immuable après le constructeur, utile pour les value objects.- Les traits partagent du code entre classes sans héritage multiple.
- Les enums backés (
enum X: string) remplacent avantageusement les constantes de classe. - Promotion de propriétés dans le constructeur (
private readonly string $titulaire) évite le boilerplate.
Exercices pratiques
Mission : securiser un compte bancaire et unifier les notifications
Objectif : Utiliser encapsulation, traits, interfaces et enums pour corriger une classe CompteBancaire qui expose son solde directement.
Contexte
Une revue de code trouve que la classe CompteBancaire declare public float $solde; au lieu de private, ce qui permet a n'importe quel code exterieur d'ecrire $compte->solde = -500; sans passer par retirer() ni verifier les regles metier. Par ailleurs, deux classes Administrateur et Client dupliquent chacune une methode notifier() presque identique, et un champ $statut de commande est represente par de simples chaines ("en_attente", "expediee", "livree") sans aucune garantie qu'une valeur invalide ne s'y glisse.