Leçon 2/7 9 min

Visibilité et encapsulation

public, private, protected en PHP : encapsuler l'état d'un objet et n'exposer que les méthodes contrôlées.

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 : comme private, 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.

L'objet Compte garde sa propriété private solde derrière une frontière ; on n'agit dessus que par les méthodes public deposer et getSolde. Un accès direct est refusé. objet Compte private $solde = 120 méthodes public (les portes) deposer() getSolde() $c->solde = 999999 $c ->deposer(50) La donnée private est protégée ; on n'agit dessus que par des portes public.
La propriété private est à l'abri ; les méthodes public sont les seules portes, et elles imposent les règles.
Prédisez avant de lire

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 :

  • readonly exige 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";
?>
✦ Bonne pratique : pour toute donnée qui ne doit pas changer après création (identifiant, date de création, montant figé), déclare la propriété 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: like private, 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.

The Compte object keeps its private property solde behind a boundary; you act on it only through the public methods deposer and getSolde. A direct access is refused. Compte object private $solde = 120 public methods (the doors) deposer() getSolde() $c->solde = 999999 $c ->deposer(50) The private data is protected; you act on it only through public doors.
The private property is safe; the public methods are the only doors, and they enforce the rules.
Predict before reading

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:

  • readonly requires 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";
?>
✦ Best practice: for any data that must not change after creation (ID, creation date, fixed amount), declare the property 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
Accepter ou rejeter 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 »
Rejeter. $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
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 ?

PHP déclenche une 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;
La cause : $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é.
Une propriété private est accessible…
Pourquoi passer par un setter plutôt qu'une propriété public ?
Une classe déclare private float $solde;. Du code hors de la classe exécute echo $compte->solde;. Que se passe-t-il ?
Quelle est la différence entre private et protected, et pourquoi exposer des méthodes plutôt que l'état brut ?
Prochaine étape

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 →

Une erreur dans cette leçon, un passage flou, une question ? Écrivez-moi : chaque retour améliore ce cours.

Besoin d'un développeur pour votre projet ?

Réponse sous 24h · Sans engagement