PHP 8.3 : ce qui change vraiment au quotidien

Abdoul Kadri ASSOUMANE MAMANE
Abdoul Kadri ASSOUMANE MAMANE Auteur
10 min de lecture
39 vues

Écouter l'article

PHP 8.3 : ce qui change vraiment au quotidien

PHP 8.3 est sorti le 23 novembre 2023. Cet article passe en revue ce qui change vraiment au quotidien pour un projet Laravel, et ce qu'il faut vérifier avant de migrer. Côté portfolio, le constat est simple et vérifiable : la CI GitHub Actions et le Dockerfile tournent en PHP 8.3, tandis que composer.json déclare php ^8.2. Aucune des fonctionnalités présentées ici n'apparaît à ce jour dans son code applicatif (il n'y a par exemple aucun #[\Override]) : ce sont des repères pour décider quoi adopter. Les extraits ont été exécutés sur PHP 8.3.6. (Depuis, PHP 8.4 puis PHP 8.5 sont sortis ; voir la section « Et maintenant ». La page php.net/supported-versions donne l'état à jour des versions supportées.)

Les constantes typées : il était temps

C'est sans doute la nouveauté la plus utile pour le système de types. Avant la 8.3, rien n'empêchait une classe enfant de redéfinir une constante avec un type complètement différent, ce qui produisait des bugs silencieux.

Maintenant on peut déclarer le type directement :

class Status
{
    public const string PENDING = 'pending';
    public const string ACTIVE = 'active';
    public const int MAX_RETRIES = 3;
}

Là où ça devient utile, c'est dans le cadre de l'héritage. Si une classe fille essaie de redéfinir MAX_RETRIES avec une string, PHP refuse : l'essai sur 8.3.6 donne « Type of Child::MAX_RETRIES must be compatible with Base::MAX_RETRIES of type int », une erreur fatale levée à la déclaration de la classe fille. Sur un gros projet avec de nombreuses classes qui héritent les unes des autres, c'est un garde-fou contre des régressions difficiles à voir.

Une remarque sur l'exemple : pour représenter des statuts comme PENDING et ACTIVE, un enum est aujourd'hui le choix naturel (depuis PHP 8.1). Il offre un type dédié, des cas fermés et des méthodes, ce qu'un groupe de constantes ne donne pas :

enum Status: string
{
    case Pending = 'pending';
    case Active = 'active';
}

Les constantes typées restent pertinentes pour les valeurs de configuration ou les bornes (MAX_RETRIES), et pour les classes de base dont on veut contraindre les surcharges.

L'attribut #[\Override] : un filet de sécurité discret

Dans une couche d'abstraction un peu épaisse, chaque refactoring ramène la même question : a-t-on bien surchargé la bonne méthode ? Un renommage dans la classe parente n'a-t-il pas cassé quelque chose en silence ?

L'attribut #[\Override] répond à ce problème :

class PaymentProcessor extends BaseProcessor
{
    #[\Override]
    public function handle(Order $order): Receipt
    {
        // Si BaseProcessor::handle() est renommée ou supprimée,
        // PHP lève une erreur fatale au chargement de la classe
    }
}

Si la méthode parente n'existe plus, PHP lève une erreur fatale au moment où la classe est chargée (message obtenu sur 8.3.6 : « C::handel() has #[\Override] attribute, but no matching parent method exists »). PHP n'ayant pas d'étape de compilation séparée comme Java, l'erreur n'apparaît donc pas à la construction du projet : elle survient dès que l'autoloader charge la classe, ce qui peut être tard si cette classe n'est exécutée que dans un cas rare. Pour la détecter plus tôt, il faut l'analyse statique (PHPStan, Psalm) ou une suite de tests qui charge toutes les classes. Le mécanisme est proche du @Override de Java et du mot-clé override de C#.

On peut l'ajouter sur toute méthode qui en redéfinit une. Le plus rentable est de commencer par les méthodes métier qu'on modifie souvent, là où un renommage dans le parent a le plus de risque de passer inaperçu.

json_validate() : enfin une fonction dédiée

Voici un schéma qu'on a tous écrit :

// L'ancien hack qu'on a tous fait
json_decode($payload);
if (json_last_error() !== JSON_ERROR_NONE) {
    throw new InvalidArgumentException('JSON invalide');
}

Le problème : on décode le JSON uniquement pour savoir s'il est valide. Sur de gros payloads reçus via une API, cela construit en mémoire une structure dont on n'a pas besoin.

PHP 8.3 introduit json_validate(), qui vérifie la syntaxe sans construire le résultat décodé :

if (!json_validate($payload)) {
    return response()->json(['error' => 'Payload malformé'], 422);
}

// On ne décode que si c'est valide
$data = json_decode($payload, true);

Une nuance : quand les données sont de toute façon nécessaires ensuite, json_validate() suivi de json_decode() parcourt le document deux fois. Dans ce cas, json_decode($payload, true, flags: JSON_THROW_ON_ERROR) avec un try/catch est plus simple. json_validate() est utile quand on veut seulement vérifier (ou rejeter tôt) sans exploiter le contenu.

L'accès dynamique aux constantes de classe

Celui-là paraît anecdotique, mais il simplifie certains cas de configuration. On peut maintenant accéder à une constante de classe via une variable :

class DatabaseDriver
{
    public const string MYSQL = 'mysql';
    public const string PGSQL = 'pgsql';
    public const string SQLITE = 'sqlite';
}

// Depuis un fichier de config ou une variable d'environnement
$driver = strtoupper(config('database.default'));
$value = DatabaseDriver::{$driver}; // Résolu dynamiquement

Avant, il fallait passer par constant() ou un tableau de correspondance intermédiaire. La syntaxe est plus lisible, mais il reste prudent de valider la valeur d'entrée : une constante inexistante lève une Error à l'exécution.

Les propriétés readonly et __clone()

Depuis PHP 8.2, les classes readonly posent un problème dès qu'on clone un objet qui contient un objet mutable. Une propriété readonly ne pouvait pas être réassignée dans __clone(), même pour dupliquer l'objet imbriqué : le clone et l'original partageaient donc la même instance, ce qui est dangereux.

PHP 8.3 autorise la réinitialisation d'une propriété readonly dans le contexte du clonage :

readonly class Invoice
{
    public function __construct(
        public string $reference,
        public DateTime $createdAt,
        public array $lines
    ) {}

    public function __clone()
    {
        // Autorisé en 8.3 (RFC « Readonly amendments ») ; refusé avant 8.3
        $this->createdAt = clone $this->createdAt;
    }
}

$lines = [];
$original = new Invoice('INV-001', new DateTime(), $lines);
$copie = clone $original;

// $copie->createdAt est maintenant un objet distinct

Dans cet exemple précis, il existe plus simple : utiliser DateTimeImmutable à la place de DateTime. Un objet immuable peut être partagé sans risque, le clonage profond devient inutile et __clone() disparaît. Le gain de 8.3 est surtout pour les objets imbriqués réellement mutables (une collection ou un objet tiers sans équivalent immuable).

Les objets immuables sont une bonne façon de représenter des événements ou des DTOs, et cette évolution rend les classes readonly plus utilisables dans des scénarios réalistes.

Le Randomizer enrichi

La classe Random\Randomizer, introduite en 8.2, gagne de nouvelles méthodes pratiques :

$rand = new Random\Randomizer();

// Générer un token à partir d'un alphabet personnalisé
$token = $rand->getBytesFromString(
    'ABCDEFGHJKMNPQRSTWXYZabcdefghjkmnpqrstwxyz23456789',
    16
);
// Résultat : quelque chose comme "kN7pWx3mR9fTqB2y"
// (sans caractères ambigus : 0/O, 1/l, I)

getBytesFromString() convient pour générer des codes d'invitation ou des identifiants lisibles, en évitant les caractères visuellement similaires. Sans argument, Randomizer utilise un moteur cryptographiquement sûr (Secure), ce qui convient à ce genre de codes. Un code facile à recopier est un petit détail qui améliore l'expérience.

Et côté performances ?

PHP 8.3 n'est pas une version centrée sur la performance, et mieux vaut ne pas citer de benchmarks génériques. Les gains éventuels dépendent du code, de la configuration (OPcache notamment) et des dépendances : le plus fiable est de mesurer avant et après sur l'application concernée, avec un outil de profilage ou des tests de charge reproductibles. Les réglages côté serveur sont décrits dans l'article sur les performances.

Ce qu'on peut retenir sans chiffre :

  • Temps de réponse : pas de changement spectaculaire à attendre sur une application web classique.
  • JIT : toujours limité en gains concrets sur du code web dominé par les entrées-sorties, plus visible sur du calcul intensif.

Les améliorations de performance ne justifient donc pas à elles seules la migration. Ce sont les garde-fous de typage (constantes typées, #[\Override]) qui font la différence pour la maintenabilité.

Comment aborder la migration

Une migration vers PHP 8.3 se passe en général bien, avec quelques points de vigilance. Voici une démarche simple.

1. Audit des dépendances

# Vérifier que toutes les dépendances supportent PHP 8.3
composer why-not php 8.3

Certains packages peu maintenus ne déclarent pas encore la compatibilité : il faut alors attendre une mise à jour, la contribuer ou trouver une alternative. Côté Laravel, selon le tableau de support de la documentation officielle (consulté en octobre 2026), Laravel 10 supporte PHP 8.1 à 8.3, Laravel 11 PHP 8.2 à 8.4, Laravel 12 PHP 8.2 à 8.5 et Laravel 13 PHP 8.3 à 8.5. Laravel 10, que le portfolio utilise encore, ne reçoit plus de correctifs de sécurité depuis février 2025 ; PHP 8.3 est donc le plafond de cette branche.

2. Exécution des tests

# Lancer la suite de tests complète avec la nouvelle version
php artisan test --parallel

Les changements de comportement sont rares mais réels. Par exemple, range() a été retouchée en 8.3 (gestion des valeurs flottantes et des cas limites) : un test qui s'appuyait sur l'ancien comportement peut casser. Ce genre de différence ne se détecte que si la couverture de tests est correcte.

3. Analyse statique

# PHPStan ou Psalm pour détecter les incompatibilités
./vendor/bin/phpstan analyse --level=max

4. Adoption progressive des nouvelles fonctionnalités

Inutile de tout migrer d'un coup. On ajoute les constantes typées et #[\Override] au fil de l'eau, quand on touche un fichier. Forcer un refactoring global juste pour adopter une nouvelle syntaxe est rarement une bonne idée.

Ce qu'il faut en retenir

PHP 8.3 est une version de consolidation des bases posées par 8.0, 8.1 et 8.2, et c'est plutôt une bonne chose. Les constantes typées et #[\Override] renforcent le système de types sans ajouter de complexité inutile. json_validate() comble un manque ancien.

Sur PHP 8.1 ou 8.2, la migration vers 8.3 vaut le coup, d'autant que ces versions sortent de leur période de support : d'après php.net/supported-versions (consulté en octobre 2026), PHP 8.2 reçoit des correctifs de sécurité jusqu'au 31 décembre 2026, et PHP 8.3 est en support de sécurité seul depuis le 31 décembre 2025, jusqu'au 31 décembre 2027. Vérifier les dépendances et lancer la suite de tests avant de passer en production reste la démarche de base. Une version antérieure à 8.0 appelle une montée de version planifiée.

Et maintenant : PHP 8.4 et 8.5

PHP 8.4 (21 novembre 2024) est la version suivante à regarder, suivie de PHP 8.5 (20 novembre 2025) ; ni l'une ni l'autre n'a été testée pour cet article, qui s'arrête à 8.3. Ses deux nouveautés les plus marquantes pour le code applicatif sont les property hooks et la visibilité asymétrique. Les property hooks permettent de définir directement sur une propriété le code exécuté à la lecture (get) et à l'écriture (set), sans écrire de getters et de setters à part :

class User
{
    public string $fullName {
        get => $this->firstName.' '.$this->lastName;
    }

    public function __construct(
        public string $firstName,
        public string $lastName,
    ) {}
}

La visibilité asymétrique, elle, permet de lire une propriété publiquement tout en restreignant son écriture, par exemple public private(set) int $id. Ces deux fonctionnalités remplacent une bonne partie du code répétitif des accesseurs. Pour un projet qui, comme le portfolio, déclare php ^8.2, leur adoption suppose d'abord de relever la version minimale de PHP, ce qui se décide après avoir vérifié l'hébergement et les dépendances. Avec Laravel 10, qui ne supporte pas PHP 8.4, cela suppose aussi de changer de version du framework (voir l'article sur GitHub Actions pour tester plusieurs versions de PHP en CI).

Partager cet article

Envie de ne rien rater ?

Recevez les nouveaux articles directement dans votre boîte mail. Pas de spam, uniquement du contenu technique.

Commentaires (0)

Laisser un commentaire

0 / 2000

Aucun commentaire pour le moment. Soyez le premier à réagir !

Lecture

Articles similaires

Envie d'en discuter ?

Une opportunité d'alternance, un projet ou une question technique : écrivez-moi, je réponds généralement sous 24 à 48 heures.