Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Le langage en 2026

Est-ce un langage que vous auriez envie d’écrire pendant quelques annĂ©es ? Aucun benchmark ne rĂ©pond Ă  cela ; vous y rĂ©pondez en lisant du code. PHP est aujourd’hui un langage orientĂ© objet, avec ramasse-miettes, aux types dĂ©clarĂ©s et appliquĂ©s Ă  l’exĂ©cution aux frontiĂšres des fonctions et des propriĂ©tĂ©s, dotĂ© d’énumĂ©rations, d’immutabilitĂ©, de closures, d’attributs et d’un gestionnaire de paquets. Un exemple suffit pour le juger, et chaque construction plus jeune que 2022 porte la version qui l’a introduite, pour que vous sachiez ce qu’un projet donnĂ© peut utiliser.

Un exemple complet

<?php
declare(strict_types=1);

enum Status: string
{
    case Draft = 'draft';
    case Published = 'published';
    case Archived = 'archived';

    public function isVisible(): bool
    {
        return $this === self::Published;
    }
}

final readonly class Article
{
    public function __construct(
        public string $title,
        public Status $status,
        public ?\DateTimeImmutable $publishedAt = null,
    ) {
    }

    public function publish(\DateTimeImmutable $at): static
    {
        return new static($this->title, Status::Published, $at);
    }
}

function summary(Article ...$articles): string
{
    $visible = array_filter($articles, fn (Article $a): bool => $a->status->isVisible());
    $titles = array_map(fn (Article $a): string => $a->title, $visible);

    return match (count($titles)) {
        0 => 'nothing published',
        1 => $titles[array_key_first($titles)],
        default => implode(', ', $titles),
    };
}

$draft = new Article(title: 'Benchmarks', status: Status::Draft);
$live = $draft->publish(new \DateTimeImmutable('2026-09-01'));

echo summary($draft, $live), PHP_EOL;   // Benchmarks
echo $draft->status->value, PHP_EOL;    // draft

Lisez-le comme vous liriez une pull request. declare(strict_types=1) passe le fichier en typage strict : une chaĂźne passĂ©e lĂ  oĂč un int est dĂ©clarĂ© lĂšve une TypeError au lieu d’ĂȘtre convertie. L’interrupteur agit fichier par fichier, et un projet l’active dans chacun. L’enum est une vraie Ă©numĂ©ration, un ensemble fermĂ© d’objets singletons qui peuvent porter des mĂ©thodes et une valeur sous-jacente. La classe readonly (PHP 8.2) rend chaque propriĂ©tĂ© immuable aprĂšs construction, et son constructeur dĂ©clare et affecte ces propriĂ©tĂ©s en un seul endroit.

Le reste de la syntaxe, vous le connaissez dĂ©jĂ  par d’autres langages : les arguments nommĂ©s Ă  l’appel, ? pour les types nullables, static comme type de retour, fn pour les closures d’une ligne, et un match qui compare strictement, ne tombe jamais dans la branche suivante et lĂšve une exception quand aucune branche ne correspond.

Ce que l’exemple ne montre pas compte tout autant. Chaque frontiĂšre y est typĂ©e, alors que le langage ne l’exige pas ; la lecture d’une variable non dĂ©finie est un avertissement en PHP 8, et les analyseurs statiques en font une erreur. Le signe $ sur les variables et la flĂšche -> pour l’accĂšs aux membres sont les deux morceaux de syntaxe qui paraissent Ă©trangers Ă  tout le monde, et au bout d’une heure on ne les voit plus.

Les ajouts récents

Les deux derniĂšres versions ont changĂ© la façon d’écrire le code, et un projet sur PHP 8.4 ou plus rĂ©cent utilisera ce qu’elles ont apportĂ©.

// PHP 8.4
final class Money
{
    public function __construct(
        public private(set) int $cents,
        public string $currency,
    ) {
    }

    public string $formatted {
        get => number_format($this->cents / 100, 2) . ' ' . $this->currency;
    }
}

$price = new Money(1999, 'EUR');
echo $price->formatted, PHP_EOL;     // 19.99 EUR
$price->cents = 5;                   // Error: Cannot modify private(set) property Money::$cents

public private(set) est la visibilitĂ© asymĂ©trique (PHP 8.4) : la propriĂ©tĂ© se lit de partout et ne s’écrit que depuis l’intĂ©rieur de la classe, ce qui supprime la plupart des getters qu’un dĂ©veloppeur Java ou C# s’attend Ă  Ă©crire. Le bloc get sous $formatted est un hook de propriĂ©tĂ© (PHP 8.4), une logique attachĂ©e Ă  une propriĂ©tĂ© sans changer ses sites d’appel, et cela supprime la plupart des autres. L’opĂ©rateur pipe (PHP 8.5) enchaĂźne les fonctions de gauche Ă  droite :

// PHP 8.5
$slug = '  Hello, World  '
    |> trim(...)
    |> strtolower(...)
    |> (fn (string $s): string => preg_replace('/[^a-z0-9]+/', '-', $s))
    |> (fn (string $s): string => trim($s, '-'));

echo $slug, PHP_EOL;   // hello-world

La forme trim(...) est un callable de premiĂšre classe (PHP 8.1), une rĂ©fĂ©rence Ă  une fonction sous forme de valeur, avec l’aritĂ© vĂ©rifiĂ©e par le moteur. Ajoutez les attributs, des mĂ©tadonnĂ©es structurĂ©es lues par rĂ©flexion et utilisĂ©es par tous les frameworks pour le routage, la validation et le mapping, et vous avez les constructions que vous croiserez le plus dans une base de code dĂ©marrĂ©e aprĂšs 2024.

Deux listings de code cÎte à cÎte sur un bureau, vus de dessus. Celui de gauche est sur du papier jauni, dense, avec quelques taches de café et des soulignements ondulés. Celui de droite est sur une feuille blanche propre, plus court, avec une indentation nette et quelques annotations de type surlignées. Un petit éléphant lit la feuille de droite, un stylo à la main

Ce que le systĂšme de types fait et ne fait pas

Les types sont dĂ©clarĂ©s sur les paramĂštres, les valeurs de retour, les propriĂ©tĂ©s et les constantes de classe, et le moteur les applique Ă  l’exĂ©cution. Les types union (int|string), les types intersection (Countable&Traversable), les types nullables, never, mixed et les Ă©numĂ©rations font tous partie du langage. Une erreur de type est une exception, pas un avertissement, et en mode strict il n’y a aucune coercition implicite entre scalaires, Ă  l’exception de l’élargissement d’un int en float.

Ce qui manque, autant l’entendre de moi maintenant que le dĂ©couvrir la troisiĂšme semaine. Il n’y a pas de gĂ©nĂ©riques dans le langage. Un list<Order> ne peut pas s’exprimer dans une signature ; le paramĂštre est array. L’écosystĂšme rĂ©pond par une syntaxe de docblock que les deux analyseurs statiques, PHPStan et Psalm, comprennent et appliquent. @param list<Order> $orders est vĂ©rifiĂ© au moment de l’analyse, dans l’éditeur et en intĂ©gration continue, avec les types template, les types conditionnels et les types de forme, plus riches que tout ce que le langage lui-mĂȘme sait exprimer ; un projet PHP typĂ© exĂ©cute donc un analyseur Ă  son niveau le plus strict Ă  chaque build et traite sa sortie comme des erreurs de compilation. Que ce soit un substitut acceptable Ă  des gĂ©nĂ©riques dans le langage, c’est Ă  vous d’en juger, selon vos propres habitudes. C’est un substitut, et vous devriez le peser comme tel.

L’autre absence vient du runtime plutĂŽt que du langage : pas de threads en userland ni de boucle d’évĂ©nements intĂ©grĂ©e. Concurrence dĂ©crit ce qui existe Ă  la place.

La bibliothĂšque standard

strpos voisine avec str_replace, array_key_exists avec in_array, et certaines fonctions prennent l’aiguille en premier quand d’autres prennent la botte de foin. Cette incohĂ©rence est historique, elle est rĂ©elle, et elle ne disparaĂźtra pas. Depuis PHP 8.0, les nouvelles fonctions suivent un seul schĂ©ma de nommage (str_contains, array_is_list, array_find), les fonctions internes lĂšvent TypeError et ValueError sur une entrĂ©e invalide au lieu de retourner false, et tout Ă©diteur dotĂ© d’un serveur de langage PHP complĂšte les noms ; c’est ainsi qu’une Ă©quipe vit avec le reste. La seconde verrue, ce sont les chaĂźnes, qui sont des suites d’octets : strlen('Ă©') vaut 2, et le traitement du texte passe par la famille mb_. VoilĂ  les deux que vous rencontrerez en premier.

La limite : le systĂšme de types de PHP est appliquĂ© Ă  l’exĂ©cution et aux frontiĂšres des fonctions et des propriĂ©tĂ©s, pas Ă  l’intĂ©rieur des expressions ni Ă  travers les conteneurs gĂ©nĂ©riques. Une Ă©quipe qui veut des garanties Ă  la compilation sur ses collections les obtient d’un analyseur statique, pas du langage.

Ce que vous pouvez vĂ©rifier vous-mĂȘme

Installez PHP 8.5 (votre gestionnaire de paquets, ou l’image Docker officielle php:8.5-cli), collez le premier exemple dans un fichier et lancez-le avec php file.php. Puis cassez-le : passez une chaĂźne lĂ  oĂč l’énumĂ©ration Status est attendue, retirez strict_types, Ă©crivez un match sur l’énumĂ©ration en oubliant un cas, puis appelez-le avec ce cas. Les messages d’erreur sont la partie d’un langage avec laquelle on vit, et cinq minutes suffisent pour les juger. Pour le systĂšme de types, lancez PHPStan ou Psalm Ă  son niveau le plus strict sur n’importe quel projet PHP open source d’une taille qui vous parle, et lisez les vingt premiers rĂ©sultats ; ils vous montrent ce que l’analyseur attrape et que le moteur laisse passer.