Guide de migration de version PHP (baserCMS)
Recueil de corrections pour les fonctionnalités dépréciées et les changements cassants rencontrés lors de la mise à niveau de PHP. Organisé par version et conçu pour évoluer. Les problèmes liés au framework CakePHP lui-même sont traités dans le skill séparé cakephp-migration.
Contenu : PHP 8.2 / 8.4 / 8.5. Lors de la prise en charge d'une nouvelle version, ajouter une nouvelle section « ## PHP X.Y ». Remarque : PHP 8.5 est la version mineure précédant la version 9.0. Toutes les fonctionnalités dépréciées dans la version 9.0 seront supprimées ; il est donc conseillé de corriger rapidement les avertissements de dépréciation apparus dans la version 8.5.
Principes généraux
- Distinguer les « erreurs fatales » des « avertissements de dépréciation ». En mode débogage, les avertissements de dépréciation s'affichent en bas de l'écran et ressemblent à des « erreurs », mais ceux qui apparaissent dans
logs/debug.logavecdebug:sont des avertissements (l'exécution continue). Les vraies erreurs sont les Fatal/Exception. - Séparer les régressions des échecs existants. Utiliser
git diff HEAD -- <fichier>pour vérifier si le fichier concerné fait partie de vos propres modifications. - Si vous modifiez la signature d'une classe parente, mettez également à jour les classes filles et les interfaces.
- L'analyse statique avec Rector ou autre est efficace pour une détection exhaustive de toutes les dépréciations.
Pour les méthodes d'exécution des tests unitaires, la vérification de la syntaxe et l'analyse des échecs, reportez-vous au skill
basercms-unittest.php -l(vérification de la syntaxe) signale également les avertissements de dépréciation comme les nullables implicites.
PHP 8.2
Propriétés dynamiques dépréciées
L'affectation à une propriété non déclarée est dépréciée. Message : Creation of dynamic property X::$prop is deprecated.
- Solution A (recommandée) : Déclarer la propriété dans la classe.
/** @var ThemeFoldersServiceInterface */ protected $ThemeFoldersService; protected $_templateTypes = [];- Les cas typiques sont les affectations dans le constructeur comme
$this->Xxx = $this->getService(...).
- Les cas typiques sont les affectations dans le constructeur comme
- Solution B : Pour les classes de base qui se propagent à de nombreuses classes filles (par exemple, la classe de base de test
BcTestCasea plus de 400 sous-classes), ajouter#[\[AllowDynamicProperties]\)].L'attribut est hérité par les classes filles, une seule modification suffit donc.#[\(AllowDynamicProperties] class BcTestCase extends TestCase { ... }
PHP 8.4
1. Paramètres nullable implicites dépréciés
Type $x = null est un nullable implicite et est déprécié. Utiliser explicitement ?Type.
// Avant
function foo(int $contentId = null, BcAbstractDetector $agent = null)
// Après
function foo(?int $contentId = null, ?BcAbstractDetector $agent = null)
- Détection (expression régulière) : correspond aux paramètres sans
?devant le type et ayant$name = null. Exemple :([A-Za-z_\\\\][A-Za-z0-9_\\\\]*)\s+(\$[A-Za-z0-9_]+)\s*=\s*null(sans?juste avant). - Lors de la modification de la signature d'une classe parente, mettez également à jour les classes filles et les interfaces.
- Avertissement :
Implicitly marking parameter $x as nullable is deprecated, the explicit nullable type must be used instead.
2. Paramètre $escape de fgetcsv() déprécié
L'omission du paramètre $escape est dépréciée. Le spécifier explicitement.
// Avant
$head = fgetcsv($fp, 10240);
// Après
$head = fgetcsv($fp, 10240, ',', '"', '\\\\');
- Les implémentations personnalisées (par exemple, des fonctions qui utilisent
fgets+ expressions régulières pour analyser le CSV) n'utilisent pasfgetcsvet ne sont donc pas concernées. Ne corriger que les endroits qui appellent réellementfgetcsv().
3. Appel à ReflectionProperty::setValue() avec un seul argument déprécié
Pour les propriétés non statiques, passer l'objet cible.
// Avant
$property->setValue($value);
// Après
$property->setValue($object, $value);
- Avertissement :
Calling ReflectionProperty::setValue() with a single argument is deprecated. - Se cache souvent dans les utilitaires de test (helpers de génération de requêtes ou de manipulation de propriétés privées).
- Pour les propriétés statiques, passer
nullen premier argument :setValue(null, $value). Cela fonctionne sur toutes les versions 8.1 à 8.5 (ne casse pas la compatibilité 8.1).
4. Passage de null aux arguments string des fonctions internes déprécié (en réalité depuis 8.1+)
Passer null à un argument qui n'est pas ?string (comme parse_url(null) / version_compare($a, null) / str_replace(..., null)) produit l'avertissement Passing null to parameter #N ($x) of type string is deprecated. Utiliser un cast (string) pour une conversion explicite.
// Avant → Après
parse_url($url); → parse_url((string)$url);
version_compare($current, $required); → version_compare((string)$current, (string)$required);
- Cette dépréciation a été introduite dans PHP 8.1, mais elle devient particulièrement visible lors de la migration vers 8.4/8.5, d'où son inclusion ici.
- Comme elle n'apparaît qu'à l'exécution lorsque la valeur est null, un grep statique ne suffit pas. Compiler tous les
Passing null to parameterdans les logs de test pour identifier la source.
PHP 8.5 (sortie le 20 novembre 2025)
Dernière version mineure avant la 9.0. La plupart des éléments ci-dessous génèrent des avertissements mais le code continue de fonctionner ; ils seront supprimés dans la 9.0, il faut donc les traiter. Classés par ordre d'impact sur l'application/framework. ※ Seul le point « 0. » est une erreur fatale (changement cassant), à traiter en priorité.
⚠️ Attention lors du maintien de la compatibilité PHP 8.1 (
composer.jsonavec"php": ">=8.1"). Corriger les dépréciations de la 8.5 avec les remplacements recommandés peut casser la compatibilité 8.1 si le remplacement n'existe pas en 8.1. Les éléments marqués ❌ sont à reporter jusqu'à ce que la compatibilité 8.1 soit abandonnée (l'avertissement en 8.5 est acceptable). La compatibilité 8.1 n'est pas cassée (seulement des avertissements).
- ❌
str_increment()(8.3+) → rester sur$s++en 8.1- ❌
http_get_last_response_headers()(8.4+) → rester sur$http_response_headeren 8.1- ❌ PDO avec espace de noms (
Pdo\Mysql::ATTR_*etc., 8.4+) → rester surPDO::MYSQL_ATTR_*en 8.1Les autres éléments (casts non standard, offset
null→'', fonctions de libération automatique, guillemets obliques →shell_exec,case :,__serialize) peuvent être appliqués en toute sécurité en 8.1.
0. [Changement cassant, fatal] Référencement de noms de classes réservés (Array etc.) avec use → Fatal
En 8.5, des noms comme Array sont réservés comme noms de classes spéciaux. use ...\Array; ou class Array provoque une erreur Fatal (pas un avertissement). Si cela se produit pendant la collecte des tests (autoloading), toute la suite de tests devient inutilisable, d'où la priorité.
Fatal error: Cannot use BcMail\Test\TestCase\Model\Array as Array because 'Array' is a special class name
- Cas baserCMS :
MailMessagesTableTest.phpcontenait des vestiges de l'époque CakePHP2 :use ...\Array;/use ...\ClassRegistry;(importations mortes de classes inexistantes), inoffensives jusqu'à la 8.4, devenues fatales en 8.5. Supprimer ces lignesusepour résoudre le problème (les références étaient dans du codemarkTestIncompletenon exécuté). - Détection :
grep -rnE "use [A-Za-z0-9_\\\\]+\\(Array|Object|Resource|Enum|Mixed|Never|Void|Null|False|True|Iterable|Numeric)\s*;" --include="*.php". - Attention : Comme c'est fatal,
php -lsur un seul fichier peut le détecter. Mais un grep sur tous les fichiers avant l'exécution des tests évite que la suite ne plante dès la phase de collecte.
1. Noms de casts non standard dépréciés (impact fort)
(boolean) (integer) (double) (binary) sont dépréciés. Les remplacer par le nom standard.
// Avant → Après
(boolean) → (bool)
(integer) → (int)
(double) → (float)
(binary) → (string)
- Détection :
grep -rnE "\((boolean|integer|double|binary)\)" --include="*.php". - Fréquent dans le code ancien. Remplacement mécanique possible.
2. Utilisation de null comme offset de tableau dépréciée
$arr[null] / array_key_exists(null, $arr) etc. est déprécié (null était converti implicitement en ""). Utiliser explicitement la chaîne vide "".
// Avant
$arr[null] = $v; array_key_exists(null, $arr);
// Après
$arr[''] = $v; array_key_exists('', $arr);
- Attention aux endroits où une variable sert de clé (
$arr[$key]où$keypeut être null). - Difficile à détecter par grep statique (n'apparaît qu'à l'exécution quand la valeur est null). Exécuter tous les tests en 8.5 et utiliser
grep "null as an array offset" phpunit.logpour identifier la source (on line N of <file>). - Stratégies de correction : ① Utiliser
$key ?? ''pour l'affectation/la lecture (équivalent à la conversion implicite précédente ; assurez-vous que set et get utilisent la même clé), ② Ajouter un garde$key !== null &&avantisset()/lecture. Récupérer les retours null commegetData('batch')avec?? ''est également efficace. - Cas baserCMS (308 occurrences) :
BcFileUploader(set/get deuploadingFiles[$bcUploadId]avec?? ''),BcUploadBehavior(oldEntity[$alias][$entity->_bc_upload_id]avec?? ''),MailMessagesTable($dists[$mailField->group_valid]),SearchIndexes/Contents/PermissionsController(getData('batch') ?? ''),BcTextHelper::arrayValue/BcUtil::decodeContent($key !== null && isset(...)).
3. Fonctions de libération de ressources auto-libérantes dépréciées
Ces fonctions manipulent désormais des objets (plus des ressources), libérés automatiquement par le GC. Les appels explicites à close/free sont dépréciés. Il suffit de supprimer l'appel.
curl_close()/curl_share_close()(CurlHandle / CurlShareHandle)finfo_close()(finfo)imagedestroy()(GdImage) ← souvent utilisé dans les utilitaires de traitement d'imagexml_parser_free()(XMLParser)- Détection :
grep -rnE "\b(curl_close|curl_share_close|finfo_close|imagedestroy|xml_parser_free)\s*\(" --include="*.php". - Cas baserCMS : 4 occurrences de
imagedestroy()dansImageresizer.phpetBcFileUploader.php. On peut simplement supprimer, mais pour préserver la libération mémoire immédiate dans une boucle, remplacer parunset($img)(GdImage est un objet, donc retirer la référence permet au GC de libérer la mémoire. Sûr en >=8.1).
3-2. ReflectionProperty/Method::setAccessible() déprécié (le plus grand nombre d'occurrences)
setAccessible() est no-op depuis PHP 8.1 (on peut accéder directement aux propriétés/méthodes private/protected avec getValue/setValue/invoke). Déprécié en 8.5. Il suffit de supprimer la ligne d'appel (sûr en >=8.1).
// Avant
$property = new ReflectionProperty($obj, 'foo');
$property->setAccessible(true); // ← supprimer cette ligne
$property->setValue($obj, $v);
- Cas baserCMS : 33 occurrences dans src/ et tests (
BcUtil/BcTestCase/BaserCorePlugin/PreviewController/BcDatabaseServiceetc.). Comme elles se trouvent dans des chemins chauds (classes de base de test), le nombre d'occurrences à l'exécution est démesuré (environ 6 300 sur l'ensemble des tests, soit la majorité des dépréciations 8.5). Les corriger en premier réduit considérablement les logs. - Suppression groupée (uniquement pour les instructions seules) :
sed -i -E "/^[[:space:]]*\$[A-Za-z0-9_]+->setAccessible\(true\);[[:space:]]*$/d" <fichier>. - Attention à la détection : Si le motif grep commence par
-, comme->setAccessible, grep peut l'interpréter comme une option et retourner 0 résultat. Utilisergrep -rn "setAccessible"(éviter le début par-), ou utilisergrep -e.
4. Incrémentation de chaînes non numériques dépréciée
$str++ (sur une chaîne non numérique) est déprécié. Utiliser str_increment(). ※ str_increment() nécessite 8.3+. Tant que la compatibilité 8.1 est maintenue, conserver $str++.
// Avant
$code++; // $code vaut 'AZ', etc.
// Après
$code = str_increment($code);
5. __sleep() / __wakeup() soft-dépréciés
Utiliser __serialize() / __unserialize() (si la compatibilité PHP 7 n'est pas nécessaire).
6. $http_response_header déprécié
La superglobale $http_response_header est dépréciée. Utiliser http_get_last_response_headers(). ※ http_get_last_response_headers() nécessite 8.4+. Rester sur $http_response_header en attendant.
7. Opérateur backtick déprécié
`commande` (alias de shell_exec()) est déprécié. Utiliser shell_exec() directement.
8. Terminaison d'une clause case par ; dépréciée
case 1; est déprécié. Utiliser case 1:.
- Détection :
grep -rnE "case\s+.+;\s*$" --include="*.php"(vérification visuelle nécessaire car risque de faux positifs).
8-2. array_key_exists(null, ...) déprécié (déclenché par une forme incorrecte de serialize)
Le passage de null en premier argument à array_key_exists() est déprécié (utiliser une chaîne vide). Si cela se produit dans le framework (CakePHP JsonView::_dataToSerialize), la cause est une forme incorrecte de l'option serialize de votre propre code. Mélanger une liste de noms de variables avec des paires 'clé' => valeur comme dans setOption('serialize', ['foo', 'message' => $message]) fait que $message (si null) entre dans le tableau serialisé sous 'message' => null, déclenchant array_key_exists(null, ...).
- Correction : L'option serialize doit être une simple liste de noms de variables de vue.
['foo', 'message' => $message]→['foo', 'message'](les valeurs sont déjà définies avec$this->set()). Cas baserCMS : actions index/view dansMailMessagesController. - Méthode pour identifier la source quand elle est dans le framework : Instrumenter temporairement le vendor (
if ($key === null) file_put_contents(..., var_export($serialize,true).getTraceAsString())), exécuter les tests du plugin concerné, identifier le contrôleur concerné en tête de la stack trace, puis restaurer le vendor.
8-3. Passage d'un objet comme tableau de support à ArrayObject déprécié
new ArrayObject($obj) avec un objet (stdClass de json_decode() etc.) est déprécié. Caster en tableau.
- Correction :
new ArrayObject(json_decode($body)->devices)→new ArrayObject((array)json_decode($body)->devices). Cas baserCMS :SitesControllerTest.
9. Autres (uniquement si applicable)
- Constantes/méthodes spécifiques aux drivers PDO migrées vers des espaces de noms (
PDO::MYSQL_ATTR_*→Pdo\Mysql::ATTR_*,PDO::sqliteCreateFunction()→Pdo\Sqlite::createFunction()etc.). Souvent interne au framework, mais si vous utilisez des constantes de driver DB personnalisées, adaptez-vous. Le schéma DSN"uri:"est également déprécié. ※ Les classes PDO avec espace de noms nécessitent 8.4+. Rester surPDO::MYSQL_ATTR_*en attendant. openssl_pkey_derive()avec$key_length,finfo_buffer()avec$contextsont dépréciés (ignorés).- Le paramètre INI
register_argc_argven dehors de l'interface CLI est déprécié (utiliser$_GET/$_SERVER['QUERY_STRING']). - Certains bindings de closures statiques sont dépréciés.
- Changement de message d'erreur pour
clone null(impact sur les tests) : jusqu'à la 8.4,__clone method called on non-object; en 8.5,clone(): Argument #1 ($object) must be of type object, null given. Les tests qui vérifient les cas d'erreur declonesur null avec une chaîne exacte doivent mettre à jour leur valeur attendue (cas baserCMS :UploaderCategoriesControllerTestdans les teststest_copyde Admin/Api).
⚠️ Pour les erreurs en chaîne lors de l'exécution complète des tests, commencer par réexécuter pour isoler : Dans cette base de code, il arrive parfois qu'un test précédent contamine l'état global (configuration des plugins Configure, etc.) par intermittence, ce qui fait échouer la quasi-totalité des tests suivants avec
PluginCollection::create(): $config ... null givenlors du bootstrap de l'Application. Si une suite de tests de plugin indépendant (ex.plugins/baser-core/tests/TestCase) réussit et qu'une réexécution est propre, le problème n'est pas lié au code mais est un flaky. Vérifier systématiquement avec l'isolation et une réexécution.
Méthodologie d'investigation (checklist)
- Utiliser
php -l(vérification syntaxique) oulogs/debug.logpour identifier le fichier et la ligne de l'avertissement de dépréciation. - Classer selon le motif de la section correspondante et corriger. Pour les remplacements mécaniques (casts non standard, etc.), utiliser grep pour une détection groupée.
- Revérifier avec
php -l(vérifier que l'avertissement a disparu). - Tester individuellement le test concerné avec
--filter→ terminer par une réexécution de tous les tests.
Connexe : Les problèmes liés au framework (valeurs de retour d'événements, doublons d'Association, ResultSet, FormProtection, etc.) sont traités dans le skill cakephp-migration.