# Garder une extension compatible PHP 7.4 quand votre socle est passé à PHP 8.3

> Maintenir un plugin public à large compatibilité pendant que les projets internes tournent déjà sur PHP 8.3 impose une matrice de tests précise, sans dupliquer le code source.

- Auteur : Clément Hadrot
- Publié le : 2025-05-07
- Mis à jour le : 2025-05-07
- Catégorie : Tests
- URL : https://wpmoderne.dev.wordpress-developpement.fr/tests/extension-compatible-php-7-4-socle-php-8-3/

## L’essentiel

- Une seule base de code peut viser deux mondes de compatibilité PHP différents
- La matrice de CI, pas le code applicatif, porte la charge de vérifier les deux versions
- PHPUnit Polyfills absorbe les différences d'API entre versions de PHPUnit

Deux mondes cohabitent dans le même dépôt de code : celui des projets internes récents, tous passés sur PHP 8.3 depuis plusieurs mois, et celui d'un plugin distribué publiquement, dont une partie non négligeable des utilisateurs reste sur des hébergements mutualisés figés à PHP 7.4. Renoncer à cette compatibilité étendue couperait l'extension d'une part significative de sa base d'installation ; l'imposer sans discipline finit par produire du code truffé de conditions `version_compare()` illisibles.

La solution retenue ne consiste pas à écrire deux versions du plugin, mais à borner strictement le sous-ensemble du langage utilisé dans le code partagé, et à vérifier cette contrainte par une matrice de tests plutôt que par la seule vigilance des développeurs.

## Ce que le code source s'interdit d'utiliser

Certaines fonctionnalités introduites après PHP 7.4 restent hors de portée du code partagé : les énumérations natives (PHP 8.1), les propriétés en lecture seule (PHP 8.1), ou encore les types d'intersection (PHP 8.1). Ce périmètre est documenté dans le fichier `CONTRIBUTING.md` du dépôt, avec la liste précise des fonctionnalités autorisées et interdites, régulièrement mise à jour à chaque montée de version du socle interne.

## Une matrice de CI qui couvre les deux bornes

> L'essentiel à retenir : Une seule base de code peut viser deux mondes de compatibilité PHP différents ; La matrice de CI, pas le code applicatif, porte la charge de vérifier les deux versions ; PHPUnit Polyfills absorbe les différences d'API entre versions de PHPUnit

La configuration GitHub Actions exécute la suite PHPUnit sur deux versions de PHP, sans dupliquer aucun fichier de test :

```
strategy:
  matrix:
    php-version: ['7.4', '8.3']
    wordpress-version: ['6.4', '6.7']

steps:
  - uses: shivammathur/setup-php@v2
    with:
      php-version: ${{ matrix.php-version }}
  - run: composer install --no-progress
  - run: vendor/bin/phpunit
```

Chaque combinaison exécute exactement la même suite de tests contre exactement le même code source. Aucune branche conditionnelle dans les tests eux-mêmes ne distingue les deux versions : si le code fonctionne réellement sur les deux, les mêmes assertions passent des deux côtés.

### PHPUnit Polyfills pour absorber l'écart de version de PHPUnit

PHP 7.4 impose en pratique une version ancienne de PHPUnit, incompatible avec les versions récentes utilisées sur PHP 8.3. Le paquet `yoast/phpunit-polyfills` comble cet écart en fournissant des traits qui exposent une API stable de méthodes d'assertion, indépendamment de la version de PHPUnit réellement installée. Les tests eux-mêmes n'ont ainsi pas besoin de connaître la version de PHPUnit exécutée.

## PHPCompatibility en complément, pas en substitut

La matrice de tests vérifie que le comportement fonctionnel reste identique sur les deux versions, mais elle ne détecte pas systématiquement l'usage d'une syntaxe incompatible qui échouerait uniquement à l'installation sur un hébergement en PHP 7.4 réel. Une analyse statique complémentaire, exécutée en amont de la matrice, repère ces cas avant même de lancer les tests.

## Ce que cette approche ne résout pas

- Elle ne dispense pas de tester manuellement, de temps en temps, une installation réelle sur un hébergement mutualisé représentatif du parc visé.
- Elle ralentit la CI, puisque chaque changement déclenche désormais deux exécutions complètes au lieu d'une.
- Elle impose une discipline de revue stricte : tout code utilisant une fonctionnalité récente sans vérification préalable casse silencieusement la compatibilité descendante.

## Organiser la revue de code autour de cette contrainte

Une check-list courte, ajoutée au modèle de pull request du dépôt, rappelle systématiquement le périmètre autorisé avant toute fusion : aucune énumération native, aucune propriété en lecture seule, aucun type d'intersection. Cette liste reste volontairement courte, sous peine d'être ignorée par lassitude, et se limite aux fonctionnalités effectivement rencontrées comme source d'erreur lors des mois précédents.

Un relecteur qui repère une syntaxe hors périmètre lors d'une revue dispose d'un motif de refus clair et objectif, plutôt que d'une appréciation subjective sur la qualité du code proposé. Cette objectivité facilite également l'intégration de nouveaux contributeurs, qui découvrent la contrainte dès leur première pull request plutôt qu'après un incident de compatibilité en production.

> Une compatibilité large ne se maintient pas par bonne volonté, elle se maintient par une matrice qui échoue quand quelqu'un l'oublie.

## En résumé

Couvrir deux mondes de compatibilité PHP avec une seule base de code repose sur trois piliers distincts : un périmètre de langage documenté et restreint, une matrice de CI qui exécute la même suite sur les deux bornes de version, et une couche de compatibilité PHPUnit qui masque l'écart entre les versions du framework de test. Aucun de ces trois piliers ne fonctionne seul ; ensemble, ils évitent de dupliquer le code du plugin pour chaque version de PHP visée.
