Développement Web

Les nouveautés de syntaxe de PHP 8.4 pas à pas

14 دقائق للقراءة

Vous connaissez peut-être PHP par ses vieux réflexes : des variables non typées, des tableaux à tout faire, des fonctions qui acceptent n’importe quoi. PHP 8.4 propose une autre façon d’écrire — plus sûre, plus lisible, et comprise par les outils. Dans ce tutoriel, on prend la première brique du projet « Atelier », un gestionnaire de stock de pièces détachées, et on écrit la classe qui représente une pièce en exploitant les nouveautés du langage. À la fin, vous saurez tirer parti du typage strict, des property hooks, de la visibilité asymétrique et des fonctions de tableau récentes.

Article de référence : ce tutoriel fait partie du guide complet du PHP moderne. Pour la vue d’ensemble du parcours et du projet « Atelier », commencez par là.

Ce que vous allez apprendre

  • Activer et comprendre le typage strict avec declare(strict_types=1).
  • Déclarer des propriétés calculées avec les property hooks de PHP 8.4.
  • Protéger l’écriture d’une propriété avec la visibilité asymétrique private(set).
  • Chaîner un appel directement après new sans parenthèses.
  • Filtrer et interroger un tableau avec array_find(), array_any() et array_all().
  • Marquer du code obsolète avec l’attribut #[\Deprecated].

Ce que vous allez construire

Une classe Piece complète et une petite collection d’inventaire en mémoire. À la fin, un script qui crée quelques pièces, calcule leur valeur de stock et répond à des questions du type « toutes les pièces sont-elles en stock ? » — le tout en PHP 8.4 idiomatique, prêt à être enrichi dans les tutoriels suivants.

Prérequis

  • PHP 8.4 installé (vérifiez avec php --version). Les property hooks et la visibilité asymétrique n’existent pas avant 8.4.
  • Un éditeur avec coloration PHP (VS Code + l’extension PHP, ou PhpStorm).
  • Les bases de la syntaxe PHP : variables, fonctions, tableaux. Si vous savez écrire une fonction qui additionne deux nombres, vous êtes prêt.
  • ⏱️ Temps estimé : ~35 minutes.

Étape 1 — Activer le typage strict

Avant toute chose, il faut comprendre la différence entre typage faible et typage strict. Par défaut, PHP convertit les valeurs pour les faire correspondre au type attendu : passer la chaîne "3" à une fonction qui attend un int fonctionne, PHP convertit silencieusement. C’est commode, mais cela masque des bugs. Le typage strict désactive ces conversions implicites : on doit passer exactement le bon type, sinon une TypeError est levée.

On l’active fichier par fichier, avec une déclaration qui doit être la toute première instruction du fichier, juste après la balise d’ouverture :

<?php
declare(strict_types=1);

namespace Atelier;

function prixTotal(int $quantite, float $prixUnitaire): float
{
    return $quantite * $prixUnitaire;
}

echo prixTotal(4, 250.0);   // 1000
echo prixTotal("4", 250.0); // TypeError en mode strict

Avec strict_types=1, le deuxième appel échoue immédiatement avec une TypeError : « Argument #1 must be of type int, string given ». Sans cette déclaration, PHP aurait converti "4" en 4 et renvoyé 1000 sans broncher. Le mode strict transforme donc une erreur potentielle, qui aurait pu se propager loin dans le programme, en échec net et localisé. Prenez l’habitude de mettre cette ligne en tête de chaque fichier : c’est le réglage de base du PHP moderne.

Point d’étape — Créez un fichier test.php avec le code ci-dessus et lancez php test.php. Vous devez voir 1000 puis une TypeError sur le second appel. Si les deux appels affichent 1000, c’est que la ligne declare n’est pas exactement la première instruction du fichier.

Étape 2 — Modéliser la pièce avec promotion de constructeur

Passons à la classe Piece. Une pièce a une référence, un nom, une catégorie, un prix et une quantité. Plutôt que de déclarer chaque propriété puis de la recopier dans le constructeur, PHP 8 permet la promotion de constructeur : on déclare les propriétés directement dans la signature du constructeur, ce qui supprime la répétition.

<?php
declare(strict_types=1);

namespace Atelier;

final class Piece
{
    public function __construct(
        public string $reference,
        public string $nom,
        public float $prix,
        public int $quantite = 0,
    ) {}
}

$vis = new Piece('VIS-M6', 'Vis métaux M6', 250.0, 120);
echo $vis->reference; // VIS-M6

Chaque paramètre préfixé d’un modificateur de visibilité (public, private, protected) devient automatiquement une propriété de l’objet, initialisée avec la valeur reçue. Le mot-clé final empêche qu’on hérite de Piece ; pour un objet de données simple, c’est une bonne valeur par défaut qui clarifie l’intention. La pièce est créée en une ligne, et toutes ses propriétés sont typées.

Étape 3 — Rendre la référence immuable avec readonly et la visibilité asymétrique

La référence d’une pièce ne doit jamais changer après sa création : c’est son identité. PHP 8.1 a introduit readonly pour cela — une propriété en lecture seule, qu’on ne peut affecter qu’une fois, dans le constructeur. Mais on veut souvent un cas plus souple : une propriété qu’on peut lire de partout mais modifier seulement depuis l’intérieur de la classe. C’est exactement ce que résout la visibilité asymétrique, nouveauté de PHP 8.4.

<?php
declare(strict_types=1);

namespace Atelier;

final class Piece
{
    public function __construct(
        public readonly string $reference,   // figée pour toujours
        public string $nom,
        public float $prix,
        public private(set) int $quantite = 0, // lisible partout, modifiable en interne
    ) {}

    public function entrerStock(int $nombre): void
    {
        $this->quantite += $nombre;
    }
}

$vis = new Piece('VIS-M6', 'Vis métaux M6', 250.0, 120);
echo $vis->quantite;     // 120 — lecture OK
$vis->entrerStock(30);
echo $vis->quantite;     // 150 — modifié via une méthode
// $vis->quantite = 0;   // Error : écriture interdite depuis l'extérieur

La syntaxe public private(set) se lit « public en lecture, privé en écriture ». De l’extérieur, on peut afficher $vis->quantite, mais toute affectation directe déclenche une erreur ; seules les méthodes de la classe, comme entrerStock(), peuvent la modifier. On obtient l’encapsulation d’un getter/setter classique sans écrire une seule ligne de getter. Quant à reference, marquée readonly, elle est gravée dans le marbre dès la construction : aucune méthode, même interne, ne pourra la changer.

Point d’étape — Testez les trois dernières lignes une à une. La lecture de quantite doit afficher la valeur ; l’appel à entrerStock() doit l’augmenter ; et décommenter l’affectation directe doit produire une Error. Si l’affectation externe passe sans erreur, vérifiez que vous êtes bien en PHP 8.4 (php --version).

Étape 4 — Ajouter une propriété calculée avec un property hook

Souvent, une propriété n’a pas besoin d’être stockée : elle se déduit d’autres. « La pièce est-elle en stock ? » se calcule à partir de quantite. Avant PHP 8.4, on écrivait une méthode estEnStock(). Désormais, les property hooks permettent d’exposer cela comme une propriété, avec un bloc get qui calcule la valeur à la volée.

<?php
declare(strict_types=1);

namespace Atelier;

final class Piece
{
    public function __construct(
        public readonly string $reference,
        public string $nom,
        public float $prix,
        public private(set) int $quantite = 0,
    ) {}

    // Property hook en lecture seule : calculée, jamais stockée
    public bool $enStock {
        get => $this->quantite > 0;
    }

    // Property hook calculé : un libellé lisible (référence + nom)
    public string $libelle {
        get => "{$this->reference} — {$this->nom}";
    }
}

$vis = new Piece('VIS-M6', 'Vis métaux M6', 250.0, 0);
var_dump($vis->enStock); // bool(false)
echo $vis->libelle;      // VIS-M6 — Vis métaux M6

La propriété $enStock n’occupe aucune place en mémoire : à chaque lecture, son hook get évalue l’expression. Du point de vue de l’utilisateur de la classe, $vis->enStock se lit comme une propriété normale, mais sa valeur reflète toujours l’état courant de quantite. C’est plus lisible qu’un appel de méthode et, contrairement à un getter, c’est compris nativement par les IDE et l’analyse statique. Les hooks acceptent aussi un bloc set pour intercepter l’écriture (par exemple normaliser une chaîne) — utile, mais à réserver aux cas où la transformation est simple ; une logique complexe mérite une vraie méthode nommée.

Étape 5 — Instancier et chaîner sans parenthèses

Petit confort de syntaxe, mais qui revient sans cesse : PHP 8.4 autorise enfin l’accès à une propriété ou l’appel d’une méthode directement après new, sans entourer l’expression de parenthèses. Auparavant, pour appeler une méthode sur un objet fraîchement créé en une expression, il fallait écrire (new Piece(...))->libelle. C’est désormais inutile.

<?php
// Avant PHP 8.4 :
echo (new Piece('VIS-M6', 'Vis métaux M6', 250.0))->libelle;

// Depuis PHP 8.4 :
echo new Piece('VIS-M6', 'Vis métaux M6', 250.0)->libelle;

Le gain paraît cosmétique, mais il allège la lecture quand on enchaîne plusieurs appels, par exemple new RequeteBuilder()->where(...)->limit(10). Une paire de parenthèses en moins par instanciation chaînée, c’est du bruit visuel en moins. À noter : c’est une simplification de syntaxe, pas un changement de comportement — l’objet est créé exactement de la même façon.

Étape 6 — Interroger l’inventaire avec les nouvelles fonctions de tableau

PHP 8.4 ajoute quatre fonctions très attendues pour travailler sur les tableaux sans boucle manuelle : array_find() renvoie le premier élément qui satisfait une condition, array_find_key() sa clé, array_any() indique si au moins un élément la satisfait, et array_all() si tous la satisfont. Elles rendent le code déclaratif et lisible.

<?php
declare(strict_types=1);

use Atelier\Piece;
use Atelier\Categorie;

$inventaire = [
    new Piece('VIS-M6', 'Vis métaux M6', 250.0, 120),
    new Piece('HUI-10W', 'Huile moteur 10W40', 4500.0, 8),
    new Piece('FUS-5A', 'Fusible 5A', 100.0, 0),
];

// Première pièce en rupture de stock
$rupture = array_find($inventaire, fn(Piece $p) => $p->quantite === 0);
echo $rupture?->reference ?? 'aucune'; // FUS-5A

// Au moins une pièce en rupture ?
var_dump(array_any($inventaire, fn(Piece $p) => $p->quantite === 0)); // true

// Toutes les pièces ont-elles un prix positif ?
var_dump(array_all($inventaire, fn(Piece $p) => $p->prix > 0));       // true

Chaque fonction prend le tableau et une fonction de rappel (ici une fonction fléchée fn(...) => ...) qui renvoie un booléen. array_find() renvoie l’élément trouvé ou null ; on combine ce null possible avec l’opérateur nullsafe ?-> et l’opérateur de coalescence ?? pour afficher proprement « aucune » le cas échéant. Avant 8.4, il fallait écrire une boucle foreach avec un drapeau ou utiliser array_filter() puis reset() — plus verbeux et moins clair sur l’intention. Ces fonctions sont un petit pas, mais elles rapprochent PHP des langages où ces opérations sont natives.

Point d’étape — Rassemblez les classes Piece et Categorie et ce script dans un même dossier, puis lancez-le. Vous devez voir FUS-5A, puis bool(true) deux fois. Si vous obtenez « Call to undefined function array_find() », votre PHP est antérieur à 8.4.

Étape 7 — Signaler du code obsolète proprement

Quand on fait évoluer une base de code, on remplace des méthodes sans pouvoir les supprimer immédiatement. PHP 8.4 formalise cela avec l’attribut #[\Deprecated] : il marque une fonction ou une méthode comme dépréciée, et tout appel déclenche un avertissement à l’exécution, exactement comme les dépréciations internes de PHP.

<?php
declare(strict_types=1);

namespace Atelier;

final class Piece
{
    // ... constructeur et hooks ...

    #[\Deprecated(message: "Utilisez la propriété \$enStock", since: "1.2")]
    public function estEnStock(): bool
    {
        return $this->enStock;
    }
}

$p = new Piece('VIS-M6', 'Vis', 250.0, 5);
$p->estEnStock(); // Deprecated: Method Atelier\Piece::estEnStock() is deprecated since 1.2, Utilisez la propriété $enStock

Le message et la version (since) apparaissent dans le journal des dépréciations, ce qui aide les autres développeurs — et vous-même dans six mois — à migrer en douceur. C’est bien plus fiable qu’un commentaire // déprécié que personne ne lit. Combiné à l’analyse statique, l’attribut permet même de repérer les appels à du code déprécié avant l’exécution.

Étape 8 — Vérification finale

Réunissez la classe Piece dans son état final (readonly, visibilité asymétrique, hooks) et un script de démonstration qui crée trois pièces, en met une en rupture, puis affiche la valeur totale du stock. La valeur se calcule en une expression grâce à array_sum() et array_map() :

<?php
$valeurStock = array_sum(
    array_map(fn(Piece $p) => $p->prix * $p->quantite, $inventaire)
);
echo "Valeur du stock : " . number_format($valeurStock, 0, ',', ' ');

Si l’ensemble s’exécute sans erreur de type et affiche une valeur cohérente, votre première brique est solide. Vous avez écrit une classe métier complète en PHP 8.4 idiomatique : typée, immuable là où il faut, avec des propriétés calculées et des requêtes de collection lisibles.

Pièges fréquents

Symptôme / erreur Cause probable Correctif
declare() must be the first statement Une instruction (espace, BOM, balise) précède declare Mettre declare(strict_types=1); immédiatement après <?php
Cannot modify readonly property Tentative d’écriture sur une propriété readonly L’initialiser uniquement dans le constructeur, ou utiliser private(set)
Call to undefined function array_find() PHP antérieur à 8.4 Mettre à jour PHP, ou utiliser array_filter() en attendant
Le hook set boucle à l’infini Le hook réaffecte la propriété qu’il intercepte Dans un hook, on assigne $this->prop seulement dans un set qui reçoit la valeur, sans se rappeler lui-même
Aucun avertissement de dépréciation error_reporting n’inclut pas E_DEPRECATED Vérifier error_reporting(E_ALL) en développement

Récapitulatif

Vous venez de poser la première pierre du projet « Atelier » : une classe Piece écrite comme on écrit le PHP moderne. Le typage strict garantit la cohérence des données ; readonly et la visibilité asymétrique encadrent ce qui peut changer ; les property hooks exposent des valeurs calculées sans code répétitif ; les fonctions array_find et compagnie interrogent une collection de façon déclarative ; et #[\Deprecated] documente l’évolution du code. Ces réflexes ne sont pas propres à PHP 8.4 : ils définissent ce que veut dire « écrire du PHP propre » aujourd’hui.

Aide-mémoire

Élément Rôle
declare(strict_types=1); Active le typage strict (1re ligne du fichier)
public private(set) int $x Visibilité asymétrique : lecture publique, écriture privée
public readonly string $ref Propriété immuable après construction
public bool $y { get => ...; } Property hook : propriété calculée
new Piece(...)->methode() Chaînage après new sans parenthèses
array_find($a, $fn) Premier élément satisfaisant la condition (ou null)
array_any($a, $fn) / array_all($a, $fn) Au moins un / tous les éléments satisfont la condition
#[\Deprecated(message:..., since:...)] Marque une fonction/méthode comme dépréciée

À vous de jouer

Ajoutez à Piece une propriété calculée $valeurStock (prix × quantité) sous forme de property hook, puis écrivez une expression qui trouve, avec array_find, la première pièce dont la valeur de stock dépasse 10 000.

Voir une solution
// Dans la classe Piece :
public float $valeurStock {
    get => $this->prix * $this->quantite;
}

// Recherche :
$chere = array_find($inventaire, fn(Piece $p) => $p->valeurStock > 10000);
echo $chere?->libelle ?? 'aucune';

Tutoriels liés

Pour aller plus loin

Foire aux questions

Dois-je mettre declare(strict_types=1) dans tous les fichiers ?

Oui, dans chaque fichier qui contient du code exécutable. La déclaration est locale au fichier où elle figure : elle n’affecte pas les fichiers inclus. C’est une habitude à prendre dès la première ligne.

Les property hooks ralentissent-ils l’exécution ?

Le coût est négligeable pour un hook simple comme une comparaison ou une concaténation. Si un hook get effectue un calcul lourd à chaque lecture, mieux vaut le mémoïser ou en faire une méthode explicite, mais c’est un cas rare.

Quelle différence entre readonly et private(set) ?

readonly interdit toute modification après la première affectation, même depuis l’intérieur de la classe. private(set) autorise les modifications, mais seulement depuis l’intérieur de la classe. On choisit readonly pour une identité figée, private(set) pour un état qui évolue de façon contrôlée.

مشاركة