Le problème : tout est ouvert
À la leçon 1, $solde était public. Donc n'importe où dans le code : $compte->solde = 999999;, on contourne deposer() et ses règles. C'est précisément l'encapsulation (vue dans le cours POO) qu'il faut appliquer ici, avec les mots-clés de visibilité de PHP.
public, private, protected
public: accessible de partout (l'interface de l'objet).private: accessible uniquement à l'intérieur de la classe. C'est le coffre-fort.protected: commeprivate, mais aussi accessible dans les classes filles (utile avec l'héritage, prochaine leçon).
On met les données en private par défaut, et on n'expose en public que les méthodes nécessaires.
Une classe Compte déclare private float $solde;. Du code extérieur à la classe tente $compte->solde = -9999;. Avant de dérouler : que se passe-t-il ? Et quelle est la différence entre private et protected ?
Voir la réponse
Accéder à une propriété private depuis l'extérieur de la classe déclenche une erreur fatale PHP : Fatal error: Uncaught Error: Cannot access private property Compte::$solde. private = accessible uniquement à l'intérieur de la classe qui la déclare. protected = accessible dans la classe ET ses classes filles (héritage), mais toujours pas depuis l'extérieur. public = accessible de partout. C'est l'encapsulation : on expose des méthodes contrôlées (getters/setters) plutôt que l'état brut.
class Compte {
private float $solde = 0; // verrouillé
public function deposer(float $m): void {
if ($m > 0) $this->solde += $m; // la règle vit ici
}
public function getSolde(): float { // accesseur en lecture seule
return $this->solde;
}
}
$c = new Compte();
$c->deposer(50);
echo $c->getSolde(); // 50
// $c->solde = 999999; // ❌ Fatal error: Cannot access private property
Tenter $c->solde depuis l'extérieur déclenche une erreur fatale : PHP refuse. La seule voie, c'est deposer(), qui impose ses règles. Un solde incohérent devient impossible.
Getters et setters
Pour exposer une donnée privée de façon contrôlée : un getter (lire) et, si besoin, un setter (écrire avec validation). Le setter est l'occasion de refuser une valeur invalide.
class Produit {
private float $prix = 0;
public function getPrix(): float { return $this->prix; }
public function setPrix(float $prix): void {
if ($prix < 0) {
throw new InvalidArgumentException("Prix négatif interdit");
}
$this->prix = $prix;
}
}
La capsule en vrai (exécute)
Un compte qui protège son solde. La voie normale marche ; la règle (pas de retrait à découvert) est imposée par la méthode.
<?php
class Compte {
private float $solde = 0;
public function deposer(float $m): void {
if ($m > 0) $this->solde += $m;
}
public function retirer(float $m): bool {
if ($m > 0 && $m <= $this->solde) {
$this->solde -= $m;
return true;
}
return false; // règle : pas de découvert
}
public function getSolde(): float { return $this->solde; }
}
$c = new Compte();
$c->deposer(100);
var_dump($c->retirer(30)); // bool(true) → solde 70
var_dump($c->retirer(99999)); // bool(false) → refusé
echo "Solde final : " . $c->getSolde() . " €\n";
?>
Les propriétés readonly : verrouiller après création
Parfois une valeur ne doit jamais changer après la création de l'objet : un identifiant de commande, une date de création, un montant figé. Avec une propriété public classique, n'importe qui peut la réécrire ; même avec un getter, l'objet pourrait se modifier lui-même par erreur. private protège de l'extérieur, mais pas d'une affectation accidentelle dans une autre méthode interne.
PHP 8.1 introduit le mot-clé readonly. Une propriété public readonly string $id ne peut être affectée qu'une seule fois (typiquement dans le constructeur). Toute tentative de modification ultérieure provoque une Error : Cannot modify readonly property. Cela vaut de l'extérieur comme de l'intérieur de la classe.
Deux précisions importantes :
readonlyexige que la propriété soit typée (ex.string,int,float).- Elle se marie parfaitement avec la promotion de constructeur :
public function __construct(public readonly string $id) {}déclare et fige la propriété en une seule ligne. C'est la base des value objects immuables.
<?php
class Commande {
public readonly string $id;
public readonly string $createdAt;
private float $montant;
public function __construct(string $id, float $montant) {
$this->id = $id;
$this->createdAt = date("Y-m-d");
$this->montant = $montant;
}
public function getMontant(): float {
return $this->montant;
}
}
$cmd = new Commande("ORD-001", 49.90);
echo $cmd->id . "\n"; // ORD-001 (lecture OK)
echo $cmd->createdAt . "\n"; // date du jour
echo $cmd->getMontant() . "\n"; // 49.9
// $cmd->id = "ORD-HACK"; // Error: Cannot modify readonly property
echo "Propriete readonly : lecture seule apres construction.\n";
?>
readonly plutôt que de compter sur la discipline du code. Le compilateur PHP vérifie la règle à ta place.
The problem: everything is open
In lesson 1, $solde was public. So anywhere in the code: $compte->solde = 999999; — bypassing deposer() and its rules. This is exactly the encapsulation (seen in the OOP course) we must apply here, with PHP's visibility keywords.
public, private, protected
public: accessible from everywhere (the object's interface).private: accessible only inside the class. The safe.protected: likeprivate, but also accessible in child classes (useful with inheritance, next lesson).
Keep data private by default, and expose as public only the methods you need.
A Compte class declares private float $solde;. Code outside the class tries $compte->solde = -9999;. Before scrolling down: what happens? And what is the difference between private and protected?
See the answer
Accessing a private property from outside the class triggers a PHP fatal error: Fatal error: Uncaught Error: Cannot access private property Compte::$solde. private = accessible only inside the class that declares it. protected = accessible in the class AND its child classes (inheritance), but still not from outside. public = accessible from everywhere. This is encapsulation: expose controlled methods (getters/setters) rather than raw state.
class Compte {
private float $solde = 0; // locked
public function deposer(float $m): void {
if ($m > 0) $this->solde += $m; // the rule lives here
}
public function getSolde(): float { // read-only accessor
return $this->solde;
}
}
$c = new Compte();
$c->deposer(50);
echo $c->getSolde(); // 50
// $c->solde = 999999; // ❌ Fatal error: Cannot access private property
Trying $c->solde from outside triggers a fatal error: PHP refuses. The only way is deposer(), which enforces its rules. An inconsistent balance becomes impossible.
Getters and setters
To expose a private value in a controlled way: a getter (read) and, if needed, a setter (write with validation). The setter is your chance to reject an invalid value.
class Produit {
private float $prix = 0;
public function getPrix(): float { return $this->prix; }
public function setPrix(float $prix): void {
if ($prix < 0) {
throw new InvalidArgumentException("Negative price not allowed");
}
$this->prix = $prix;
}
}
The capsule for real (run it)
An account protecting its balance. The normal path works; the rule (no overdraft) is enforced by the method.
<?php
class Compte {
private float $solde = 0;
public function deposer(float $m): void {
if ($m > 0) $this->solde += $m;
}
public function retirer(float $m): bool {
if ($m > 0 && $m <= $this->solde) {
$this->solde -= $m;
return true;
}
return false; // rule: no overdraft
}
public function getSolde(): float { return $this->solde; }
}
$c = new Compte();
$c->deposer(100);
var_dump($c->retirer(30)); // bool(true) → balance 70
var_dump($c->retirer(99999)); // bool(false) → refused
echo "Final balance: " . $c->getSolde() . " €\n";
?>
readonly properties: lock after creation
Sometimes a value must never change after the object is created: an order ID, a creation date, a fixed amount. With a plain public property, anyone can overwrite it; even with a getter, the object could modify itself by accident. private protects from outside, but not from an accidental assignment inside another method.
PHP 8.1 introduced the readonly keyword. A property declared public readonly string $id can only be assigned once (typically in the constructor). Any later modification attempt throws an Error: Cannot modify readonly property. This applies from outside the class as well as from within.
Two important details:
readonlyrequires the property to be typed (e.g.string,int,float).- It pairs perfectly with constructor promotion:
public function __construct(public readonly string $id) {}declares and locks the property in a single line. This is the foundation of immutable value objects.
<?php
class Commande {
public readonly string $id;
public readonly string $createdAt;
private float $montant;
public function __construct(string $id, float $montant) {
$this->id = $id;
$this->createdAt = date("Y-m-d");
$this->montant = $montant;
}
public function getMontant(): float {
return $this->montant;
}
}
$cmd = new Commande("ORD-001", 49.90);
echo $cmd->id . "\n"; // ORD-001 (read OK)
echo $cmd->createdAt . "\n"; // today's date
echo $cmd->getMontant() . "\n"; // 49.9
// $cmd->id = "ORD-HACK"; // Error: Cannot modify readonly property
echo "readonly property: read-only after construction.\n";
?>
readonly rather than relying on code discipline. PHP enforces the rule for you.
🎯 Pratique
S'entraîner (clique pour ouvrir) :
⚖️ Juge le code de l'IA
L'IA propose cette classe Panier pour un site marchand. Ton rôle de relecteur : l'accepter telle quelle ou la rejeter, et dire pourquoi.
class Panier {
public float $total = 0;
public function ajouter(float $prix): void {
$this->total += $prix;
}
}
$p = new Panier();
$p->ajouter(29.90);
$p->total = -500; // remise « surprise »
$total est public : n'importe qui peut écrire $p->total = -500 et contourner la seule règle qui compte (un total se construit par ajouter()). C'est exactement le problème de la leçon. Le réflexe pro : private float $total, un getTotal() en lecture seule, et la validation (prix > 0) dans ajouter(). La donnée redevient inviolable.🧠 Rappel libre
Sans remonter dans la leçon : que se passe-t-il si on écrit $c->solde = 999999 alors que $solde est private, et par quoi doit-on passer à la place ?
Fatal error: Cannot access private property : depuis l'extérieur de la classe, on ne peut ni lire ni écrire une propriété private. La seule voie est une méthode public de la classe (par exemple deposer() pour écrire, getSolde() pour lire), qui peut imposer ses règles avant de toucher à la donnée.🔧 Débugue le code
Symptôme : ce code devrait afficher le solde (100), mais il plante avec Error: Cannot access private property Compte::$solde. Répare-le dans l'éditeur, puis exécute.
<?php
class Compte {
private float $solde = 100;
}
$c = new Compte();
echo $c->solde;
$solde est private, donc inaccessible depuis l'extérieur de la classe ; y accéder par $c->solde lève une erreur fatale. La bonne approche (encapsulation) : ne pas rendre la propriété publique, mais ajouter une méthode publique qui expose la valeur de façon contrôlée : public function getSolde(): float { return $this->solde; }, puis appeler echo $c->getSolde();. On garde le contrôle sur l'accès au lieu d'ouvrir la propriété.private float $solde;. Du code hors de la classe exécute echo $compte->solde;. Que se passe-t-il ?Tes objets sont bien fermés. Comment créer un CompteEpargne qui réutilise tout Compte et ajoute un taux, sans copier-coller ? L'héritage.
Leçon 3 : Héritage →