← PHP 8.4 — ingénierie et préparation Zend

Module 3 / 8 · 90 min

Objets, interfaces, enums et visibilité

Placer les invariants dans des opérations et choisir un contrat explicite.

Lessons, exercises and questions in this path are in French. The interface follows the site language.

Your progress will appear here when JavaScript is active.

Progress and best score are stored only in this browser. No account is needed; clearing browser data removes them. Module times are estimates including exercises.

Comprendre le mécanisme

Une interface décrit les opérations attendues ; une classe implémente leur comportement. Les traits réutilisent du code sans créer à eux seuls une relation de sous-typage. Les enums modélisent un ensemble fermé de cas. readonly empêche certaines réaffectations de propriétés, sans rendre transitivement immuable l’objet référencé. PHP 8.4 permet une visibilité d’écriture plus restrictive que la lecture. Les attributs attachent des métadonnées ; leur présence ne lance pas automatiquement un validateur.

Raisonner et vérifier

Une propriété publique facilite la lecture mais peut exposer un invariant à des écritures arbitraires. Préférez une méthode métier qui vérifie les transitions autorisées. Un namespace évite les collisions de noms ; l’autoload résout la classe seulement lorsqu’elle est demandée. Déclarez les types des propriétés et des constantes de classe afin de rendre les contrats vérifiables.

L’interface définit une substitution utile

Une interface exprime les opérations qu’un consommateur peut demander sans connaître l’implémentation concrète. Pour un envoi d’email, le même contrat peut être réalisé par un transport réel et par un double de test qui enregistre les messages. Le test vérifie alors les données envoyées sans contacter un fournisseur. La conformité au type reste distincte d’une autorisation : implémenter un contrat ne donne aucun droit utilisateur sur une ressource.

Understand this common mistake

Une interface donne automatiquement une autorisation métier.

Elle décrit des capacités de code, pas les droits d’un acteur.

Redéfinir sans rompre la promesse

Une classe enfant doit rester utilisable là où le contrat parent est attendu. Les signatures suivent des règles de compatibilité, notamment sur les types d’entrée et de sortie. Une modification qui rend un cas auparavant accepté impossible peut casser cette substitution. Commencez la revue par les appels du consommateur et les garanties promises, puis vérifiez les variantes autorisées par PHP. Le simple partage d’un nom de méthode ne garantit pas cette compatibilité.

Understand this common mistake

Une méthode enfant peut rendre tout contrat incompatible.

La substitution impose des règles de variance.

Readonly et mutabilité intérieure

Une propriété readonly limite sa réaffectation selon les règles de son initialisation. Si elle référence un ArrayObject, elle ne transforme pas cet objet en collection immuable. Une méthode de l’objet peut encore ajouter un élément. Pour protéger un invariant, choisissez aussi le modèle de l’objet référencé : copie contrôlée, valeur immuable ou API empêchant les mutations interdites. Testez séparément réaffectation de propriété et modification de l’objet intérieur.

Understand this common mistake

readonly gèle récursivement tout graphe d’objets.

La restriction de réaffectation ne prouve pas cette immutabilité profonde.

Lecture et écriture ont des portées distinctes

La visibilité contrôle des accès de code, pas la confidentialité cryptographique. En PHP 8.4, une propriété publique avec private(set) peut être lue publiquement tout en réservant son écriture au contexte autorisé de la classe. Cette séparation permet une consultation simple sans ouvrir une modification arbitraire. Elle ne remplace pas les méthodes métier : un changement permis techniquement doit encore respecter les invariants et les droits de l’acteur concerné.

Understand this common mistake

private chiffre la valeur en mémoire.

La visibilité n’est pas une primitive cryptographique.

Composer des membres avec un trait

Un trait apporte des membres à une classe et facilite une réutilisation ciblée. Ce n’est pas une instance autonome qui possède sa propre identité métier. Examinez les dépendances implicites du trait, les noms de méthodes attendus et les conflits possibles lors de la composition. Une duplication évitée ne justifie pas une dépendance cachée à toute la classe ; un petit service explicite peut parfois exprimer plus clairement la responsabilité partagée.

Understand this common mistake

Un trait est une instance autonome obligatoire.

Le mécanisme compose du code dans une classe.

Typer les constantes de classe

Une constante de classe peut annoncer un type natif dans les versions PHP qui le supportent. Le type rend son contrat plus visible et permet de refuser une valeur incompatible. Il ne vérifie pas toutes les relations métier entre plusieurs constantes. Par exemple, un délai entier peut rester négatif alors que le domaine l’interdit. La déclaration doit donc accompagner une conception cohérente et des tests sur les bornes utiles.

Understand this common mistake

Typer une constante remplace tous les tests métier.

Le type apporte un contrat limité à sa valeur.

Utiliser un enum comme vocabulaire fermé

Un enum représente un ensemble explicite de cas. Il convient aux états connus d’un traitement et aide à repérer les branches manquantes. Une chaîne reçue du réseau n’est toutefois pas déjà un cas valide : la conversion doit gérer une valeur inconnue. Séparez reconnaissance du vocabulaire et transition autorisée. Un état existant peut encore être interdit après un autre état selon le cycle de vie de la réservation.

Understand this common mistake

Un enum valide automatiquement tout texte reçu du réseau.

La conversion d’entrée peut échouer et doit être traitée.

Construire un objet déjà cohérent

Le constructeur doit établir les données nécessaires avant les opérations publiques. Si une réservation exige un identifiant et une quantité positive, un objet partiellement rempli crée une période où les autres méthodes doivent deviner ce qui manque. Préférez une construction qui refuse le cas invalide ou un modèle explicitement séparé pour le brouillon. Vérifiez aussi les objets référencés : leur mutabilité peut ensuite remettre en cause l’invariant initial.

Understand this common mistake

Le nom constructeur prouve à lui seul un état valide.

Le corps doit contrôler les conditions du domaine.

Composition ou héritage selon le contrat

Deux classes partageant un code ressemblant ne forment pas automatiquement une relation de substitution. La composition permet de confier une tâche à un collaborateur sans exposer toutes ses capacités par héritage. Dessinez les appels réellement nécessaires avant de choisir la structure. Si une classe n’utilise qu’un calculateur de tarif, injecter ce calculateur peut être plus précis qu’hériter d’un service possédant aussi stockage et notifications.

Understand this common mistake

Tout partage de code exige une hiérarchie de classes.

D’autres formes d’assemblage existent.

Ce que final garantit réellement

Une méthode final interdit une redéfinition selon le contrat du langage. Cela stabilise un point d’extension et peut protéger une séquence d’opérations choisie par la conception. La restriction n’accorde aucun privilège métier et ne prouve pas l’absence de bug dans le corps. Testez les propriétés utiles de la séquence, par exemple une vérification avant écriture, plutôt que de considérer le mot-clé comme une validation de sécurité générale.

Understand this common mistake

final rend l’instance autorisée sur toute ressource.

Le contrat du langage ne décide pas des permissions.

Atelier guidé : lire, expliquer et vérifier le code

<?php
declare(strict_types=1);
final class Box {
    public function __construct(public readonly ArrayObject $items) {}
}
$box = new Box(new ArrayObject());
$box->items->append('A');
echo count($box->items);

Box reçoit un ArrayObject puis conserve sa référence dans une propriété readonly. L’appel append ne remplace pas la référence : il demande une mutation à l’objet déjà désigné. Le comptage affiche donc un élément. Pour distinguer les deux opérations, essayez dans un laboratoire une réaffectation de la propriété vers un nouvel ArrayObject, puis comparez le résultat avec la mutation autorisée de l’objet intérieur.

La leçon de conception est de préciser ce que vous voulez rendre stable. Une référence stable peut suffire pour un collaborateur, mais pas pour une collection de valeurs qui doit rester inchangée. Construisez une petite API de collection en lecture seule si tel est le besoin, puis vérifiez qu’aucune référence mutable n’est exposée indirectement par un autre accesseur.

PHP 8.4 : lecture publique et écriture private(set)

final class Attempt {
    public private(set) int $count = 0;
    public function increment(): void { ++$this->count; }
}
$a = new Attempt();
$a->increment();
echo $a->count;
$a->count = 7;

La visibilité asymétrique distingue la lecture d’une propriété et la capacité de l’affecter. Dans une déclaration public private(set), le code extérieur peut lire la valeur, mais seul le contexte privé autorisé peut l’écrire. Une méthode de la classe peut donc faire évoluer cet état tout en exposant sa lecture.

Suivez le programme dans l’ordre : la méthode interne incrémente, la lecture affiche un, puis l’affectation extérieure tente une opération interdite et lève Error. La valeur affichée avant l’erreur ne signifie pas que toutes les instructions ont réussi.

Ce mécanisme ne doit pas être confondu avec readonly, qui répond à un autre contrat d’écriture. Il peut servir à préserver des invariants en imposant une méthode de transition. Testez les deux accès avec PHP 8.4 ou une version compatible et gardez l’autorisation métier séparée : une méthode publique qui modifie un paiement doit encore être appelée dans un contexte autorisé.

Points de vigilance : PHP 8.4 permet une visibilité d’écriture distincte sur une propriété typée. La méthode de la classe peut écrire ; le code extérieur peut lire mais pas affecter cette propriété. private(set) limite le lieu d’écriture : il n’impose pas la règle d’initialisation unique de readonly. Le modificateur public concerne ici la lecture, pas l’écriture externe.

Reference documentation ↗

Worked example

<?php
declare(strict_types=1);
final class Stock {
    public const int MINIMUM = 0;
    public private(set) int $quantity = 0;
    public function add(int $amount): void {
        if ($amount <= 0) { throw new InvalidArgumentException(); }
        $this->quantity += $amount;
    }
}

quantity peut être lue de l’extérieur ; son écriture est réservée à la classe. La méthode add impose en plus la positivité, que la visibilité seule ne garantit pas.

Practical exercise

Ajoutez une opération reserve qui refuse une quantité insuffisante sans modifier l’état.

View a suggested solution

Validez la demande, comparez au stock puis soustrayez uniquement en cas de succès. Testez refus, égalité et succès. Pour un stock persistant partagé, ajoutez aussi une protection transactionnelle.

Verification checklist

  • Séparer visibilité et validation.
  • Ne pas confondre readonly et immutabilité profonde.
  • Tester chaque transition d’enum métier.

Knowledge check

Related lesson: Objets, interfaces, enums et visibilité ↗
Que garantit private(set) sur une propriété publique PHP 8.4 ?

Reference documentation ↗

Module validation

101 questions, local practice target of 75%. Suggested duration of 150 minutes, with no enforced deadline. Questions and choices are shuffled; corrections appear after submission. The quick lesson check and the final course exam remain separate exercises.

Start or resume the 101 questions →