PHP moderne : les fonctionnalités essentielles pour un code plus sûr

PHP a considérablement évolué depuis la version 7.0. Les fonctionnalités introduites dans les versions 8.x transforment le langage : typage strict, enums, propriétés readonly, match expressions, named arguments. Ces features ne sont pas de simples ajouts cosmétiques — elles préviennent des bugs, rendent le code auto-documenté et facilitent la maintenance à long terme. Ce guide couvre les fonctionnalités à adopter en priorité, avec des exemples concrets avant/après.

Avant / Après : du PHP legacy au PHP moderne

PHP legacy vs PHP moderne Trois comparaisons avant/après montrant l'évolution du code PHP : les constantes de classe deviennent des enums, le switch devient match, et les tableaux associatifs deviennent des readonly classes. PHP legacy PHP moderne class Status { const DRAFT = 'draft'; const PUBLISHED = 'published'; } enum Status: string { case Draft = 'draft'; case Published = 'published'; } Constantes → Enums (type-safe, validé, IDE-friendly) switch ($role) { case 'admin': $label = 'A'; break; // oubli = bug } $label = match ($role) { 'admin' => 'A', 'editor' => 'E', }; Switch → Match (strict, pas de fall-through, retourne une valeur) $user = ['name' => 'Jo', 'email' => 'jo@test.fr']; readonly class User { public string $name, $email; Tableaux → Readonly classes (typé, immuable, auto-complété)
Du PHP legacy au PHP moderne : constantes → enums, switch → match, tableaux → readonly classes.

declare(strict_types=1) : la base de tout

Sans strict types, PHP convertit silencieusement les types : passer un string "42" à une fonction qui attend un int fonctionne sans erreur. Avec declare(strict_types=1), PHP lance un TypeError — le bug est détecté immédiatement, pas en production.

declare(strict_types=1);

function calculateDiscount(float $price, int $percent): float
{
    return $price * (1 - $percent / 100);
}

calculateDiscount(99.99, "20"); // TypeError! string "20" au lieu de int
calculateDiscount(99.99, 20);   // OK → 79.99

La bonne pratique est d'ajouter declare(strict_types=1) en première ligne de chaque fichier PHP. Pour les projets existants, commencez par les nouveaux fichiers puis migrez progressivement avec l'aide de PHPStan.

Enums : remplacer les constantes de classe

Avant les enums (PHP 8.1), les statuts et les rôles étaient représentés par des constantes de classe ou des chaînes brutes — sans validation, sans auto-complétion et avec le risque de passer une valeur invalide.

// Backed Enum (avec valeur string pour la BDD)
enum OrderStatus: string
{
    case Pending = 'pending';
    case Paid = 'paid';
    case Shipped = 'shipped';
    case Cancelled = 'cancelled';
}

// Utilisation
function processOrder(OrderStatus $status): void
{
    // PHP garantit que $status est une valeur valide
}

processOrder(OrderStatus::Paid);              // OK
processOrder(OrderStatus::from('shipped'));   // OK (depuis la BDD)
processOrder(OrderStatus::from('invalid'));   // ValueError!

Les enums sont typés, refactorables, auto-complétés par l'IDE et compatibles avec match. Ils implémentent aussi des interfaces et peuvent contenir des méthodes.

Readonly : l'immuabilité par défaut

Readonly properties (PHP 8.1)

Une propriété readonly ne peut être initialisée qu'une seule fois, dans le constructeur. Toute tentative de modification ultérieure lance une erreur. C'est la garantie que vos objets ne sont pas modifiés accidentellement après construction.

class Product
{
    public function __construct(
        public readonly string $name,
        public readonly float $price,
        public readonly string $sku,
    ) {}
}

$product = new Product('Casque', 89.99, 'CSQ-001');
$product->price = 79.99; // Error! Cannot modify readonly property

Readonly classes (PHP 8.2)

Si toutes les propriétés d'une classe sont readonly, déclarez la classe entière comme readonly. Idéal pour les DTOs (Data Transfer Objects) et les value objects :

readonly class Address
{
    public function __construct(
        public string $street,
        public string $city,
        public string $postalCode,
        public string $country,
    ) {}
}

Match expression : le switch en mieux

La match expression (PHP 8.0) corrige les problèmes historiques du switch : comparaison stricte (=== au lieu de ==), pas de fall-through accidentel, retourne directement une valeur.

// Match + Enum = puissant
$label = match ($status) {
    OrderStatus::Pending   => 'En attente',
    OrderStatus::Paid      => 'Payé',
    OrderStatus::Shipped   => 'Expédié',
    OrderStatus::Cancelled => 'Annulé',
};
// Si un cas manque → UnhandledMatchError (bug détecté à l'exécution)

Match est exhaustif avec les enums : si vous ajoutez un nouveau cas à l'enum sans mettre à jour le match, PHP lance une erreur. Pas de bug silencieux.

Named arguments : lisibilité des appels

Les named arguments (PHP 8.0) permettent de nommer les paramètres lors de l'appel. C'est particulièrement utile pour les fonctions avec plusieurs paramètres optionnels :

// Sans named arguments — qu'est-ce que true et false veulent dire ?
htmlspecialchars($string, ENT_QUOTES, 'UTF-8', true);

// Avec named arguments — intention claire
htmlspecialchars($string, encoding: 'UTF-8', double_encode: true);

Les named arguments améliorent la lisibilité sans modifier la signature de la fonction. Ils sont aussi très utiles avec les constructeurs ayant beaucoup de paramètres.

Union types et intersection types

Union types (PHP 8.0)

Quand une fonction accepte plusieurs types, déclarez-le explicitement au lieu de laisser PHP deviner :

function findUser(int|string $id): ?User
{
    // Accepte un ID numérique ou un slug string
    return $id instanceof int
        ? $this->findById($id)
        : $this->findBySlug($id);
}

Intersection types (PHP 8.1)

Quand un paramètre doit satisfaire plusieurs interfaces simultanément :

function process(Countable&Iterator $collection): void
{
    // $collection doit implémenter les deux interfaces
}

Constructor promotion : moins de boilerplate

La promotion de propriétés dans le constructeur (PHP 8.0) élimine la déclaration redondante de propriétés + l'assignation dans le constructeur :

// Avant (PHP 7.x) — 15 lignes
class User {
    private string $name;
    private string $email;
    private int $age;

    public function __construct(string $name, string $email, int $age) {
        $this->name = $name;
        $this->email = $email;
        $this->age = $age;
    }
}

// Après (PHP 8.0+) — 5 lignes
class User {
    public function __construct(
        private readonly string $name,
        private readonly string $email,
        private readonly int $age,
    ) {}
}

Fibers : le parallélisme simplifié

Les Fibers (PHP 8.1) sont un mécanisme de bas niveau pour gérer la concurrence. Vous ne les utiliserez probablement pas directement, mais les frameworks comme Amphp et ReactPHP s'appuient dessus pour les opérations asynchrones (requêtes HTTP parallèles, connexions WebSocket, traitement de queues).

Pour les développeurs Symfony, le composant HttpClient utilise les Fibers en interne pour les requêtes HTTP concurrentes. Pour Laravel, les queues et les jobs bénéficient indirectement de cette technologie.

Attributes : remplacer les annotations DocBlock

Les Attributes (PHP 8.0) sont la version native des annotations qui étaient auparavant en commentaires DocBlock. Ils sont lisibles par le moteur PHP, validés à la compilation et supportés par les IDE :

// Avant — annotation en commentaire (pas validée par PHP)
/** @Route("/api/users", methods={"GET"}) */

// Après — Attribute natif (validé, auto-complété)
#[Route('/api/users', methods: ['GET'])]

// Utile aussi pour marquer les overrides
#[Override]
public function process(): void { ... }
// Si la méthode parente n'existe plus → erreur à la compilation

Adopter progressivement

Pas besoin de tout réécrire d'un coup. L'approche progressive recommandée :

  1. Aujourd'hui — declare(strict_types=1) sur tous les nouveaux fichiers. Ajouter les return types.
  2. Semaine 1 — Remplacer les constantes de statut par des enums. Remplacer les switch par match.
  3. Semaine 2 — Ajouter readonly sur les DTOs et value objects. Utiliser la constructor promotion.
  4. En continu — Monter le niveau PHPStan progressivement (5 → 6 → 7 → 8). Utiliser Rector pour automatiser les modernisations.

Ces fonctionnalités ne sont pas liées à un framework — elles fonctionnent aussi bien sur du PHP natif, Symfony, Laravel que sur des projets PrestaShop ou WordPress. Pour voir ces concepts appliqués sur un cas concret (PSR-4, strict_types, readonly classes, enums), j'ai mis en libre accès un générateur de plugin WordPress moderne.

Questions fréquentes

Faut-il activer strict_types sur tous les fichiers ?

Oui. declare(strict_types=1) empêche PHP de convertir silencieusement les types (string en int, etc.) et lance un TypeError à la place. C'est une pratique standard depuis PHP 7.0. Pour les projets existants, activez-le progressivement sur les nouveaux fichiers puis migrez l'existant avec l'aide de PHPStan.

Les enums remplacent-ils les constantes de classe ?

Pour les valeurs qui représentent un ensemble fini (statuts, rôles, types), oui. Les enums sont typés, validés à la compilation, auto-complétés par l'IDE et compatibles avec match/switch. Les constantes classiques restent utiles pour les valeurs de configuration (URL, limites, clés API) qui ne forment pas un ensemble fermé.

Readonly properties ou readonly classes ?

Les readonly properties (PHP 8.1) s'appliquent propriété par propriété — utile quand certaines propriétés doivent rester mutables. Les readonly classes (PHP 8.2) rendent toutes les propriétés readonly d'un coup — idéal pour les DTOs et value objects entièrement immuables. Commencez par les readonly properties, passez à readonly class quand toutes les propriétés sont readonly.

Match expression ou switch : quand utiliser lequel ?

Préférez match dans la plupart des cas : comparaison stricte (===), pas de fall-through accidentel, retourne une valeur directement. Le switch reste utile uniquement quand vous avez besoin du fall-through intentionnel entre cas ou quand vous exécutez du code complexe sans retourner de valeur.

Top