---
title: 'PHP 8.4: Property Hooks, Lazy Objects, New DOM & Migration Guide | DevSense'
description: 'Notes de mise à niveau de PHP 8.4 (depuis la version 8.3) : Property Hooks, visibilité asymétrique, Lazy Objects, #[Deprecated], request_parse_body(), nouvelle API Dom\*.'
faq:
    - { question: "Qu'est-ce que les Property Hooks dans PHP 8.4 ?", answer: "Les property hooks permettent d'intercepter et de définir une logique personnalisée pour l'accès (get) et la modification (set) d'une propriété directement lors de sa déclaration, éliminant ainsi le besoin de getters et de setters répétitifs." }
    - { question: 'Une propriété avec des hooks peut-elle être passée par référence ?', answer: "Non, les propriétés avec des hooks ne peuvent pas être passées par référence car leurs valeurs sont lues ou écrites à travers des méthodes et peuvent ne pas avoir d'emplacement mémoire direct." }
    - { question: "Qu'est-ce que la visibilité asymétrique dans PHP 8.4 ?", answer: "La visibilité asymétrique permet de définir des niveaux de visibilité différents pour la lecture et l'écriture d'une propriété. Par exemple, une propriété peut être lue publiquement (public) mais modifiée uniquement de manière privée (private set)." }
    - { question: 'À quoi sert request_parse_body() ?', answer: "Elle permet d'analyser le corps des requêtes RFC1867 (multipart/form-data) pour des méthodes HTTP autres que POST, comme PUT ou PATCH." }
published: '2026-05-31'
---
# PHP 8.4 : Fonctionnalités majeures & Guide interactif

PHP 8.4 est une version majeure axée sur la productivité. Elle modifie profondément la façon dont nous exprimons les invariants aux frontières des propriétés (Property Hooks, visibilité asymétrique), introduit des fonctionnalités internes de niveau framework (Lazy Objects) et résout les problèmes historiques de l'extension DOM avec des classes DOM conformes aux standards du WHATWG.

Dans ce guide, nous allons explorer ces fonctionnalités à l'aide d'exemples simples, passer en revue les pièges courants et tester vos connaissances à la fin !

---

## Table des matières
* [Property Hooks (Hooks de propriétés)](#property-hooks)
* [Visibilité asymétrique des propriétés](#asymmetric-visibility)
* [Lazy Objects (Ghosts & Proxies)](#lazy-objects)
* [Dépréciations utilisateur avec `#[Deprecated]`](#deprecated-attribute)
* [Namespace DOM moderne conforme au WHATWG (`Dom\*`)](#dom-modern)
* [Analyse multipart pour requêtes non-POST : `request_parse_body()`](#request-parse-body)
* [Nouveaux utilitaires pour les tableaux & mathématiques](#new-functions)
* [Changements non rétrocompatibles & Dépréciations](#backward-incompatible)
* [🧠 Questions d'auto-évaluation](#self-check)

---

<a id="property-hooks"></a>
## Property Hooks (logique get/set sur les propriétés)

Pendant des années, les développeurs PHP ont dû écrire de longs getters et setters répétitifs uniquement pour valider ou formater des valeurs avant de les enregistrer. PHP 8.4 introduit les **Property Hooks** directement dans la déclaration de la propriété.

Il existe deux types de hooks :
* `set` : déclenché lors de l'écriture dans une propriété.
* `get` : déclenché lors de la lecture d'une propriété.

Voyons cela en action :

```php
// app/DTO/Person.php
final class Person
{
    // Un hook peut écrire dans une propriété sous-jacente en utilisant $this->propName
    public string $firstName {
        set => ucfirst(strtolower($value));
    }

    public string $lastName {
        set {
            if (strlen($value) < 2) {
                throw new InvalidArgumentException('Last name is too short!');
            }
            $this->lastName = $value;
        }
    }

    // Propriété calculée (virtuelle) sans espace de stockage sous-jacent !
    public string $fullName {
        get => $this->firstName . ' ' . $this->lastName;
    }
}
```

> [!NOTE]
> **Le saviez-vous ?**
> Les propriétés qui définissent uniquement un hook `get` sont virtuelles par défaut et n'occupent pas d'espace mémoire ni d'empreinte en base de données dans les objets.

### ⚠️ Erreurs courantes

**1. Tenter de passer une propriété avec des hooks par référence**
Puisque l'accès à una propriété avec des hooks appelle une fonction sous le capot, vous ne pouvez pas la passer par référence :

```php
// app/Services/ContactService.php
function cleanString(string &$str) {
    $str = trim($str);
}

$person = new Person();
cleanString($person->firstName); // ❌ Fatal Error: Cannot pass property Person::$firstName by reference
```

**2. Créer accidentellement une récursion infinie dans les hooks `set`**
Si vous affectez une valeur à `$this->propertyName` dans son propre hook en écrivant `$this->propertyName = $value`, le code compile mais si vous n'utilisez pas correctement la logique de stockage sous-jacente, vous risquez de provoquer une récursion infinie. Heureusement, les expressions courtes (`=>`) écrivent automatiquement dans le stockage sous-jacent.

---

<a id="asymmetric-visibility"></a>
## Visibilité asymétrique des propriétés (`public private(set)`)

Dans PHP 8.4, vous pouvez désormais définir des visibilités différentes pour la lecture et l'écriture des propriétés. Il s'agit d'une amélioration considérable pour les DTO en lecture seule et les racines d'agrégats (aggregate roots) dans la conception pilotée par le domaine (DDD).

```php
// app/Models/User.php
final class User
{
    // Lisible de partout, mais modifiable uniquement depuis cette classe
    public private(set) string $email;

    public protected(set) int $loginAttempts = 0;

    public function __construct(string $email)
    {
        $this->email = $email;
    }

    public function incrementAttempts(): void
    {
        $this->loginAttempts++;
    }
}
```

> [!NOTE]
> **Le saviez-vous ?**
> La visibilité asymétrique exige que l'opération d'écriture (`set`) soit **plus restrictive** que l'opération de lecture. Par exemple, `private public(set)` est invalide et lèvera une erreur de syntaxe.

---

<a id="lazy-objects"></a>
## Lazy Objects (Ghosts & Proxies via la Reflection)

PHP 8.4 introduit un support natif pour les **Lazy Objects** (objets paresseux). Cette nouveauté s'adresse principalement aux concepteurs de frameworks et d'ORM (comme Doctrine ou les relations Eloquent de Laravel) qui ont besoin de différer l'hydratation coûteuse de bases de données ou les appels d'API jusqu'à ce qu'une propriété soit accédée.

Auparavant, les bibliothèques devaient générer des classes proxy complexes à la volée. Désormais, la Reflection prend cela en charge de façon native :

```php
// app/Services/Container.php
$initializer = static function (LargeService $service) {
    $service->loadHeavyConfiguration();
};

$r = new ReflectionClass(LargeService::class);
// Crée une instance lazy ghost de LargeService
$lazyService = $r->newLazyGhost($initializer);
```

La fonction d'initialisation de l'objet n'est exécutée que lorsque vous tentez d'accéder à une propriété sur `$lazyService`.

---

<a id="deprecated-attribute"></a>
## Dépréciations utilisateur avec `#[Deprecated]`

Auparavant, pour déprécier des fonctions ou méthodes personnalisées, les développeurs devaient déclencher manuellement `E_USER_DEPRECATED`. Vous pouvez désormais utiliser l'attribut natif `#[\Deprecated]` :

```php
// app/Helpers/LegacyMath.php
class LegacyMath
{
    #[\Deprecated(message: "Use BCMath or native division instead", since: "1.4.0")]
    public static function divide(int $a, int $b): float
    {
        return $a / $b;
    }
}
```

Lorsqu'un développeur appelle cette méthode, PHP déclenche automatiquement un avertissement de dépréciation contenant votre message et la version.

---

<a id="dom-modern"></a>
## Namespace DOM moderne conforme au WHATWG (`Dom\*`)

Pendant longtemps, le `DOMDocument` de PHP a souffert d'une mauvaise conformité HTML5. PHP 8.4 introduit une API DOM moderne et conforme au WHATWG sous le namespace `Dom`.

```php
// app/Services/HtmlParser.php
// Analyse des structures HTML5 modernes sans astuces complexes
$dom = Dom\HTMLDocument::createFromString('<main><article>Modern HTML5!</article></main>');
$node = $dom->querySelector('article');

echo $node->textContent; // Sortie : Modern HTML5!
```

Les classes existantes comme `DOMDocument` restent disponibles pour la rétrocompatibilité, mais les développeurs sont encouragés à migrer vers `Dom\HTMLDocument` et `Dom\XMLDocument`.

---

<a id="request-parse-body"></a>
## Analyse multipart pour requêtes non-POST : `request_parse_body()`

Si vous avez déjà construit une API REST en PHP où les utilisateurs téléversent des fichiers via des requêtes `PUT` ou `PATCH`, vous connaissez le problème : `$_FILES` et `$_POST` ne sont renseignés que pour les requêtes `POST`. Les développeurs devaient analyser manuellement les flux de données.

PHP 8.4 propose `request_parse_body()`, qui analyse les requêtes multipart/form-data RFC1867 pour n'importe quel verbe HTTP :

```php
// public/index.php
if ($_SERVER['REQUEST_METHOD'] === 'PUT') {
    [$postData, $files] = request_parse_body();
    
    // $postData contient les champs du formulaire
    // $files contient les fichiers téléversés, mappés de façon identique à $_FILES
}
```

---

<a id="new-functions"></a>
## Nouveaux utilitaires pour les tableaux & mathématiques

PHP 8.4 intègre plusieurs fonctions utilitaires pour simplifier le développement quotidien :

### 1. `array_find()`, `array_find_key()`, `array_any()`, `array_all()`

```php
// app/Services/Validator.php
$users = [
    ['name' => 'Alice', 'role' => 'user'],
    ['name' => 'Bob', 'role' => 'admin'],
];

// Trouve le premier utilisateur administrateur
$admin = array_find($users, fn($u) => $u['role'] === 'admin');

// Vérifie si un utilisateur possède le rôle 'user'
$hasUsers = array_any($users, fn($u) => $u['role'] === 'user');
```

### 2. Trimming de chaînes multioctets (`mb_trim`)
La fonction native `trim()` n'est pas sécurisée pour les chaînes multioctets, laissant des espaces blancs résiduels en UTF-8. PHP 8.4 résout ce problème avec `mb_trim()`, `mb_ltrim()`, et `mb_rtrim()`.

---

<a id="backward-incompatible"></a>
## Changements non rétrocompatibles & Dépréciations

Avant de mettre à jour vos serveurs de production en PHP 8.4, inspectez votre base de code à la recherche des éléments suivants :

1. **Les paramètres implicitement nullables sont dépréciés**
   ```php
   // ❌ Déprécié dans PHP 8.4
   function save(string $name = null) {}

   // ✅ Approche correcte
   function save(?string $name = null) {}
   ```
2. **Comportement de `exit()` et `die()`**
   Ce sont désormais des fonctions au lieu de structures de langage. Cela signifie qu'elles sont soumises aux conversions de types standard et au mode strict (`strict_types`). Si vous passez un objet ou un tableau invalide, cela lèvera une `TypeError`.
3. **Migration des ressources vers des objets**
   Des extensions comme DBA, ODBC, et SOAP renvoient désormais des objets à la place de ressources. Si vous effectuez des vérifications avec `is_resource()` sur ces connexions, elles renverront désormais `false`.

---

<a id="self-check"></a>
## 🧠 Questions d'auto-évaluation

Voyons ce que nous avons appris ! Essayez de répondre à ces questions :
1. **Vrai ou Faux ?** Pouvez-vous définir à la fois un hook `get` et `set` pour une propriété virtuelle ?
2. Pourquoi `cleanString($person->firstName)` lève-t-elle une erreur fatale (Fatal Error) si `$firstName` possède des property hooks ?
3. Que se passe-t-il si vous définissez une propriété avec une visibilité `private public(set)` ?
4. Quelle fonction d'aide de PHP 8.4 devez-vous utiliser pour vérifier qu'au moins un élément d'un tableau satisfait un callback de validation donné ?

<details>
<summary><b>Afficher les réponses</b></summary>

1. **Faux.** Une propriété virtuelle n'a pas de stockage sous-jacent, vous ne pouvez donc pas définir de hook `set` qui écrit dedans (sauf si le hook `set` écrit dans une autre propriété de la classe).
2. Car les propriétés avec des hooks sont des valeurs calculées appelées via des routines ; elles ne possèdent pas de référence stable en mémoire, ce qui les rend incompatibles avec le passage par référence.
3. Cela lève une erreur de syntaxe. La visibilité en écriture (`set`) doit être aussi stricte ou plus stricte que la visibilité en lecture (`get`).
4. Vous devez utiliser `array_any()`.
</details>