PrestaShop et PHP 8.2 à 8.4 : les avertissements deprecated et les modules qui cassent

Chaque renouvellement d’hébergement ramène la même interrogation : est-ce que la boutique tiendra sur PHP 8.3, sur PHP 8.4 ? Ça ne se devine pas. J’irai lire la réponse dans les sources de PrestaShop, qui annoncent d’elles-mêmes entre quelles bornes de version la boutique accepte de tourner, le reste se jouant du côté de vos modules (pour lesquels personne n’a jamais rien déclaré).

Des boutiques en 1.7.8, des 8.2, j’y suis passé, et le trio d’avertissements se répète d’un chantier à l’autre : propriétés dynamiques du côté des modules, paramètres typés avec null en valeur par défaut, deux ou trois fonctions évacuées du langage. Rien d’insurmontable. Reste à les repérer avant votre client.

Ruptures de compatibilité entre les versions de PHP et les branches de PrestaShop

Ce que votre version de PrestaShop accepte vraiment

Ces bornes, PrestaShop les grave chez son installateur, dans un fichier, avec deux constantes : une version minimale, une version maximale. Franchissez l’intervalle et l’installateur cale au démarrage, l’assistant de montée de version s’appuyant d’ailleurs sur la même déclaration. Voilà ma référence (bien plus que ce que contraint le fichier composer, limité au plancher).

Le verdict est net. En 1.7.8, le plafond tombe à PHP 7.4. Pour la branche 8.x tout entière, 8.2 incluse, il tombe à PHP 8.1. Seule la 9.0 grimpe jusqu’à PHP 8.4. Une 8.2 posée sur PHP 8.3, je la mets donc hors périmètre supporté, quand bien même votre boutique semblerait tenir les premiers jours. Choisir l’inverse, c’est-à-dire camper sur une branche de PHP que plus aucun correctif ne vient nourrir, revient à déplacer l’ennui vers la sécurité, là où les failles PHP les plus courantes demeurent ouvertes puisque le langage n’est plus corrigé. Colonne après colonne, la documentation officielle raconte la même histoire dans son tableau de compatibilité, alors gardez-le ouvert le jour où votre hébergeur vous soutiendra l’inverse. Le fichier plus bas recopie ces bornes telles que les trois branches les écrivent.

; Bornes de version PHP declarees par PrestaShop lui-meme, dans
; install-dev/install_version.php. Ce sont elles que l'installateur
; et l'assistant de mise a jour verifient.

[PrestaShop 1.7.8.11]
php_minimum = 7.1.3
php_maximum = 7.4

[PrestaShop 8.2.3]
php_minimum = 7.2.5
php_maximum = 8.1

[PrestaShop 9.0.0]
php_minimum = 8.1
php_maximum = 8.4

; Autrement dit : PHP 8.2, 8.3 et 8.4 ne sont couverts par aucune
; branche 8.x. La documentation officielle le confirme dans son
; tableau de compatibilite, ou la colonne >= 8.2 vaut No pour 8.0~8.2.

Ce que chaque version de PHP a déprécié

Avec PHP 8.2, poser sur un objet une propriété jamais déclarée bascule du côté des dépréciations, sauf si la classe arbore l’attribut qui l’y autorise. La même version range au placard l’interpolation dont les accolades suivent un dollar, les renvois relatifs self ou parent transmis comme des chaînes, et deux fonctions chargées de convertir les jeux de caractères (que je croise encore à tout bout de champ dans du vieux code).

Du côté de PHP 8.3, c’est l’invocation à vide des deux fonctions qui donnent le nom de classe, courante ou parente, plus celle qui configure les assertions. PHP 8.4 franchit une marche autrement gênante, celle qui oblige désormais tout paramètre typé prenant null par défaut à porter noir sur blanc un type nullable. Cette écriture-là se niche absolument partout, dans le vieux code de PrestaShop autant que chez vos modules, et je la croise sans arrêt. La 8.4 déprécie encore le niveau fatal utilisateur donné à la fonction de déclenchement d’erreur, et sort carrément du langage la constante de rapport strict. Isolez-en une, elle ne cassera aucune page toute seule. Chacune, par contre, annonce la suppression qui vient à la version d’après.

Repérer les modules à risque avant de basculer

Nulle part un module n’indique la version de PHP sur laquelle il tourne, et pour s’en faire une idée, je ne connais pas de méthode honnête en dehors de la lecture du code. Mon script, plus bas, traverse un dossier, éclate chaque fichier en jetons PHP, puis lève quatre motifs : paramètres implicitement nullables, fonctions de nom de classe invoquées à vide, fonctions dépréciées, interpolation dépréciée. Lancez-le d’abord sur le dossier des modules, ensuite sur le thème.

Pris isolément, cet outil n’établit rien, et il ne retouche aucun fichier. Tout ce qu’il vous donne, c’est un relevé de lignes à rouvrir. Mon calibrage, je l’ai fait en le lâchant sur le cœur : du côté de la 8.2.3, pour le seul dossier des classes, une centaine de paramètres implicitement nullables remontent, avec trois appels à ces fonctions de conversion maintenant dépréciées. Du côté de la 9.0.0, ce dossier n’en héberge plus aucun, les conversions dépréciées résistent, et traîne toujours un appel vers la fonction de formatage de date que PHP 8.1 avait déjà écartée. Même la mouture la plus fraîche du cœur garde donc ses casseroles. Un relevé vide, oubliez l’idée (ça n’arrivera jamais) et occupez-vous plutôt de savoir ce qu’il contient avant de basculer.

<?php
/**
 * scan-php-compat.php : reperage des ecritures depreciees par PHP 8.2 a 8.4
 * dans un dossier de modules, de themes ou d'overrides PrestaShop.
 *
 * Usage : php scan-php-compat.php modules
 *         php scan-php-compat.php override
 *
 * Le script ne modifie rien. Il signale des emplacements a relire,
 * pas des erreurs certaines : a chaque ligne remontee correspond une
 * verification manuelle.
 */

$racine = $argv[1] ?? 'modules';

if (!is_dir($racine)) {
    fwrite(STDERR, "Dossier introuvable : $racine\n");
    exit(1);
}

// Fonctions depreciees, avec la version de PHP qui les a marquees.
$fonctionsDepreciees = [
    'utf8_encode' => '8.2',
    'utf8_decode' => '8.2',
    'assert_options' => '8.3',
    'strftime' => '8.1',
    'gmstrftime' => '8.1',
    'date_sunrise' => '8.1',
    'date_sunset' => '8.1',
];

$compteur = [];

$iterateur = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator($racine, FilesystemIterator::SKIP_DOTS)
);

foreach ($iterateur as $fichier) {
    if ($fichier->getExtension() !== 'php') {
        continue;
    }

    $chemin = $fichier->getPathname();
    $source = file_get_contents($chemin);
    if ($source === false) {
        continue;
    }

    $jetons = @token_get_all($source);
    $nombre = count($jetons);

    for ($i = 0; $i < $nombre; ++$i) {
        $jeton = $jetons[$i];

        if (!is_array($jeton)) {
            continue;
        }

        $ligne = $jeton[2];

        // 1. Interpolation "${var}" : depreciee en 8.2.
        if ($jeton[0] === T_DOLLAR_OPEN_CURLY_BRACES) {
            signaler($compteur, $chemin, $ligne, '8.2', 'interpolation ${...} depreciee');
            continue;
        }

        // 2. Appel a une fonction depreciee, hors appel de methode.
        if ($jeton[0] === T_STRING) {
            $nom = strtolower($jeton[1]);
            $precedent = precedentSignificatif($jetons, $i);
            $estMethode = is_array($precedent)
                && in_array($precedent[0], [T_OBJECT_OPERATOR, T_DOUBLE_COLON, T_FUNCTION], true);

            if (isset($fonctionsDepreciees[$nom]) && !$estMethode) {
                signaler($compteur, $chemin, $ligne, $fonctionsDepreciees[$nom], $nom . '() depreciee');
            }

            // 3. get_class() / get_parent_class() sans argument : depreciees en 8.3.
            if (in_array($nom, ['get_class', 'get_parent_class'], true) && !$estMethode) {
                $suivant = suivantSignificatif($jetons, $i);
                $apres = suivantSignificatif($jetons, $i + 1);
                if ($suivant === '(' && $apres === ')') {
                    signaler($compteur, $chemin, $ligne, '8.3', $nom . '() sans argument');
                }
            }
        }

        // 4. Parametre implicitement nullable : depreciee en 8.4.
        if ($jeton[0] === T_FUNCTION) {
            foreach (parametresImplicitementNullables($jetons, $i) as $signalement) {
                signaler($compteur, $chemin, $signalement[0], '8.4', 'parametre ' . $signalement[1] . ' implicitement nullable');
            }
        }
    }
}

ksort($compteur);
foreach ($compteur as $cle => $occurrences) {
    echo $cle . ' : ' . $occurrences . ' occurrence(s)' . PHP_EOL;
}

function signaler(array &$compteur, string $chemin, int $ligne, string $version, string $message): void
{
    echo sprintf("PHP %s | %s:%d | %s\n", $version, $chemin, $ligne, $message);
    $cle = 'PHP ' . $version;
    $compteur[$cle] = ($compteur[$cle] ?? 0) + 1;
}

/**
 * @return array|string|null
 */
function precedentSignificatif(array $jetons, int $i)
{
    for ($j = $i - 1; $j >= 0; --$j) {
        if (is_array($jetons[$j]) && in_array($jetons[$j][0], [T_WHITESPACE, T_COMMENT, T_DOC_COMMENT], true)) {
            continue;
        }

        return $jetons[$j];
    }

    return null;
}

/**
 * @return array|string|null
 */
function suivantSignificatif(array $jetons, int $i)
{
    $nombre = count($jetons);
    for ($j = $i + 1; $j < $nombre; ++$j) {
        if (is_array($jetons[$j]) && in_array($jetons[$j][0], [T_WHITESPACE, T_COMMENT, T_DOC_COMMENT], true)) {
            continue;
        }

        return $jetons[$j];
    }

    return null;
}

/**
 * Parcourt la liste des parametres d'une fonction et retourne ceux
 * qui portent un type non nullable suivi de = null.
 *
 * @return array<int, array{0: int, 1: string}>
 */
function parametresImplicitementNullables(array $jetons, int $depart): array
{
    $nombre = count($jetons);
    $i = $depart;

    // On se place sur la parenthese ouvrante de la liste de parametres.
    while ($i < $nombre && $jetons[$i] !== '(') {
        if ($jetons[$i] === '{' || $jetons[$i] === ';') {
            return [];
        }
        ++$i;
    }

    if ($i >= $nombre) {
        return [];
    }

    $profondeur = 0;
    $type = '';
    $nomVariable = '';
    $ligne = 0;
    $nullable = false;
    $resultats = [];

    for (; $i < $nombre; ++$i) {
        $jeton = $jetons[$i];

        if ($jeton === '(') {
            ++$profondeur;
            continue;
        }

        if ($jeton === ')') {
            --$profondeur;
            if ($profondeur === 0) {
                break;
            }
            continue;
        }

        if ($profondeur !== 1) {
            continue;
        }

        if ($jeton === ',') {
            $type = '';
            $nomVariable = '';
            $nullable = false;
            continue;
        }

        if ($jeton === '?') {
            $nullable = true;
            continue;
        }

        if ($jeton === '=') {
            $valeur = suivantSignificatif($jetons, $i);
            $estNull = is_array($valeur) && strtolower($valeur[1]) === 'null';
            if ($estNull && $type !== '' && !$nullable && stripos($type, 'null') === false && stripos($type, 'mixed') === false) {
                $resultats[] = [$ligne, $nomVariable];
            }
            continue;
        }

        if (!is_array($jeton)) {
            continue;
        }

        if ($jeton[0] === T_VARIABLE) {
            $nomVariable = $jeton[1];
            $ligne = $jeton[2];
            continue;
        }

        if (in_array($jeton[0], [T_STRING, T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED, T_ARRAY, T_CALLABLE], true)) {
            if ($nomVariable === '') {
                $type .= $jeton[1];
                $ligne = $jeton[2];
            }
        }
    }

    return $resultats;
}

Capturer les dépréciations au lieu de les deviner

Un scan statique garde des angles morts : le code généré lui échappe, les appels dynamiques aussi, les chemins conditionnels pareil. D’où la mesure à l’exécution que j’ajoute par-dessus, c’est-à-dire mettre la boutique sous la version visée et noter tous les avertissements au passage. PrestaShop met deux constantes à votre disposition pour ça, une première pour le mode développement, une seconde qui tranche la présence ou non des dépréciations au niveau de rapport.

La version introduit ici une nuance. Les deux constantes viennent gardées en 8.x comme en 9.x, alors écrivez-les dans votre propre fichier de configuration, sans redouter le moindre conflit. En 1.7.8, la seconde se trouve déclarée sans garde côté cœur, et la redéclarer ailleurs vous ramène un avertissement signalant qu’elle existe déjà. Là, modifiez le fichier du cœur lui-même, ou bien servez-vous du rapport d’erreurs de PHP. Pour information, cette broutille me coûte une demi-heure à chaque fois.

Masquer proprement en production

Une fois sorti du mode développement, PrestaShop pousse l’affichage des erreurs sur off, et s’arrête là. Au niveau de rapport il ne touche pas, à la destination du journal non plus. Ces deux-là vous reviennent, et c’est exactement ce qui sépare un problème étouffé d’un problème gardé à l’œil. Une dépréciation devenue invisible, c’est une dette, et elle tournera à l’erreur fatale une version plus tard.

Tout ça se pose dans votre propre fichier de configuration (le cœur l’ouvre avant le sien, et vos réglages franchissent donc les montées de version). Affichage éteint, écriture dirigée vers un fichier à part, et du niveau de rapport vous ne retranchez que les dépréciations, avertissements et erreurs demeurant visibles. Sur la recette, prenez le contrepied, laissez tout remonter. Les deux variantes se trouvent dans le fichier plus bas, la première active et la seconde commentée, de quoi basculer entre elles sans avoir à retaper une ligne.

<?php
// config/defines_custom.inc.php
// Ce fichier est inclus par config/config.inc.php AVANT config/defines.inc.php :
// c'est le seul endroit ou vous pouvez fixer ces constantes sans modifier
// un fichier du core, qu'une mise a jour ecraserait.

// --- Environnement de recette : on veut voir les deprecations ---
// define('_PS_MODE_DEV_', true);
// define('_PS_DISPLAY_COMPATIBILITY_WARNING_', true);

// --- Environnement de production ---
define('_PS_MODE_DEV_', false);

// Hors mode dev, PrestaShop se contente de passer display_errors a off.
// Le niveau de rapport et la destination du journal restent a votre charge.
@ini_set('display_errors', 'off');
@ini_set('log_errors', 'on');
@ini_set('error_log', dirname(__DIR__) . '/var/logs/php_errors.log');

// On journalise tout sauf les deprecations, qui n'apprennent rien
// au visiteur et saturent le fichier.
@error_reporting(E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED);

// Variante utile en recette : garder _PS_MODE_DEV_ a true pour le profileur
// et les messages Symfony, mais taire uniquement les deprecations.
// define('_PS_MODE_DEV_', true);
// define('_PS_DISPLAY_COMPATIBILITY_WARNING_', false);

Corriger une propriété dynamique

Le message ne cache rien : la création d’une propriété dynamique est dépréciée. Vous le verrez surgir aussitôt qu’une classe affecte quelque chose à une propriété jamais déclarée chez elle. Dans les modules, j’en croise à la pelle, souvenir du temps où le moteur laissait passer sans broncher.

Déclarez la propriété. Deux mots à écrire, la classe y gagne en lisibilité, et le coût est nul. Il existe bien un attribut pour autoriser les propriétés dynamiques, seulement je le garde aux morceaux de code que je suis incapable de réécrire dans l’immédiat, avec deux réserves en tête. La première, il descend jusqu’aux classes filles, et masque donc le problème bien au-delà de ce qu’on souhaitait. La seconde, sa syntaxe démarre à PHP 8 : inutilisable pour un module encore attendu sur PHP 7, où son ajout rendrait même le fichier inanalysable. Vous distribuez votre module chez plusieurs clients aux hébergements disparates ? La déclaration explicite reste alors votre seule option raisonnable.

<?php
// Avant : la propriete est creee a la volee, PHP 8.2 emet un E_DEPRECATED
// Creation of dynamic property ... is deprecated.
class MonModuleHelper
{
    public function __construct()
    {
        $this->idBoutique = (int) Context::getContext()->shop->id;
    }
}

// Apres, solution a privilegier : declarer la propriete.
class MonModuleHelperCorrige
{
    /** @var int */
    public $idBoutique;

    public function __construct()
    {
        $this->idBoutique = (int) Context::getContext()->shop->id;
    }
}

// Apres, solution de depannage : autoriser explicitement les proprietes
// dynamiques sur la classe. L'attribut s'applique aussi aux classes filles.
// A reserver au code que vous ne pouvez pas reecrire tout de suite,
// et seulement sur PHP 8.0 et plus (la syntaxe #[...] n'existe pas avant).
#[\AllowDynamicProperties]
class MonModuleHelperTolere
{
    public function __construct()
    {
        $this->idBoutique = (int) Context::getContext()->shop->id;
    }
}

Corriger un paramètre implicitement nullable

Parmi tout ce que PHP 8.4 a déprécié, voilà ce qui revient le plus sous mes yeux dans le code PrestaShop. Prenez un paramètre typé par une classe, avec null pour valeur par défaut : jusque-là, le moteur élargissait son type en douce afin d’accepter null. Il le signale désormais. Marquez ce type comme nullable, point d’interrogation ou union avec null, au choix.

Attention particulière sur un cas de figure : ce paramètre optionnel placé juste devant un paramètre obligatoire. Ici, le point d’interrogation ne vous sauvera pas, un optionnel glissé avant un requis se faisant déprécier à son tour. Supprimez donc la valeur par défaut et gardez uniquement le type nullable, sachant que la signature bouge et qu’il vous faudra repasser sur chacun des appels en place. Classe surchargée par un override ? Sa signature à lui doit épouser celle du parent, sous peine d’erreur fatale. De toute la liste, voilà le seul endroit où une correction à moitié faite peut mettre votre boutique par terre. Et quand la surcharge se contente de ne plus s’appliquer, sans lever quoi que ce soit, allez chercher du côté des causes d’une surcharge inopérante.

<?php
// Avant : le type n'est pas nullable, la valeur par defaut l'est.
// PHP 8.4 emet « Implicitly marking parameter $context as nullable is deprecated ».
class LijeExempleAvant
{
    public static function getProduits($idCategorie, Context $context = null)
    {
        $context = $context ?: Context::getContext();

        return (int) $context->shop->id;
    }
}

// Apres : le type devient explicitement nullable.
class LijeExempleApres
{
    public static function getProduits($idCategorie, ?Context $context = null)
    {
        $context = $context ?: Context::getContext();

        return (int) $context->shop->id;
    }
}

// Cas particulier : un parametre optionnel place avant un parametre
// obligatoire est lui aussi deprecie. Il faut alors retirer la valeur
// par defaut et garder uniquement le type nullable.
class LijeExempleOrdre
{
    public static function calculer(int $idProduit, ?Context $context, int $idLang): int
    {
        $context = $context ?: Context::getContext();

        return $idProduit + $idLang + (int) $context->shop->id;
    }
}

Sauvegarde, cache et retour arrière

Abordez un changement de version PHP avec les mêmes précautions qu’une montée de version : la base et les fichiers dans une sauvegarde, l’archive rouverte pour vérifier qu’il n’y manque rien, et une porte de sortie repérée avant le premier geste. Offrez-vous ces cinq minutes. Une archive tronquée, ça se découvre pile à l’instant où elle devrait servir.

Le grand oublié, de ce que je constate, c’est le cache. Le cache Symfony compilé, les templates Smarty compilés et l’index de classes sortent tous de la version précédente, et un vidage demandé au back-office ne règle pas toujours l’affaire, ce dernier pouvant très bien refuser de se charger. Du coup, débarrassez-vous-en depuis la ligne de commande, et relancez ensuite le service PHP de façon à chasser le bytecode qu’OPcache retient encore en mémoire. Un cache figé dans son état d’avant figure d’ailleurs parmi les causes d’une erreur 500 survenue juste après la bascule, à égalité avec un module incompatible chargé trop tôt. Rendez enfin ses droits à votre dossier de travail (autrement PrestaShop n’arrivera plus à reconstruire ce qu’il a perdu, et vous récoltez une page blanche au pire moment).

#!/bin/bash
# Apres un changement de version PHP, le cache compile reste ecrit
# pour l'ancienne version : il faut le supprimer, pas seulement le vider
# depuis le back-office.
set -eu

RACINE="/var/www/ma-boutique"
UTILISATEUR_WEB="www-data"

# 1. Cache Symfony, cache Smarty compile et index de classes.
rm -rf "${RACINE}/var/cache/prod" "${RACINE}/var/cache/dev"
rm -rf "${RACINE}/var/cache"/*/class_index.php
find "${RACINE}/var/cache" -maxdepth 1 -type d -name 'smarty' -exec rm -rf {} +

# 2. OPcache conserve le bytecode de l'ancienne version : un rechargement
# du service PHP-FPM est le moyen le plus sur de le purger.
# systemctl reload php8.3-fpm

# 3. Les droits doivent rester coherents, sinon PrestaShop ne pourra pas
# reecrire ce qu'il vient de perdre.
chown -R "${UTILISATEUR_WEB}:${UTILISATEUR_WEB}" "${RACINE}/var"

echo "Caches supprimes. La premiere page sera lente, c'est normal."
#!/bin/bash
# Sauvegarde et retour arriere autour d'un changement de version PHP.
# A adapter : identifiants, chemins, nom de base.
set -euo pipefail

BASE="ma_boutique"
UTILISATEUR="ma_boutique"
RACINE="/var/www/ma-boutique"
HORODATAGE=$(date +%Y%m%d-%H%M)
DEPOT="/var/backups/prestashop"

mkdir -p "$DEPOT"

# 1. Base de donnees. --single-transaction evite de verrouiller les tables
# InnoDB pendant l'export. --routines et --triggers embarquent le reste.
mysqldump --single-transaction --routines --triggers \
  -u "$UTILISATEUR" -p "$BASE" | gzip > "${DEPOT}/${BASE}-${HORODATAGE}.sql.gz"

# 2. Fichiers. On exclut les caches, qui se regenerent.
tar czf "${DEPOT}/fichiers-${HORODATAGE}.tar.gz" \
  --exclude="var/cache" --exclude="img/tmp" \
  -C "$(dirname "$RACINE")" "$(basename "$RACINE")"

# 3. Verification : une archive illisible n'est pas une sauvegarde.
gzip -t "${DEPOT}/${BASE}-${HORODATAGE}.sql.gz"
tar tzf "${DEPOT}/fichiers-${HORODATAGE}.tar.gz" > /dev/null
echo "Sauvegarde verifiee : ${HORODATAGE}"

# 4. Restauration, si la bascule tourne mal. A n'executer qu'en connaissance
# de cause : la base cible est ecrasee.
# gunzip -c "${DEPOT}/${BASE}-${HORODATAGE}.sql.gz" | mysql -u "$UTILISATEUR" -p "$BASE"
# rm -rf "${RACINE}/var/cache"/*

Ce que je vérifie ensuite

Premier passage, je refais le tunnel d’achat en entier, depuis le panier jusqu’à l’écran de confirmation, paiement en mode test. Vient ensuite la facture que je sors, le flux produits dont je déclenche l’export, la fiche que j’ouvre puis j’enregistre dans l’administration. Ces quatre passages-là concentrent les erreurs de typage, et ils les révèlent vite, pour la bonne raison qu’on y brasse des dates, des décimaux et du XML. Déroulez-les au moins une fois chez vous.

Après quoi, laissez vivre la boutique et ouvrez vos journaux. Pas une seule fois. Plusieurs jours d’affilée. Une dépréciation tapie sur un chemin peu fréquenté ne pointera pas le nez le premier soir. Avertissements encore nombreux ? Attaquez d’abord les modules du parcours d’achat, le reste attendra (conservez la liste, surtout ne la jetez pas). Voilà comment je procède dans mes interventions sur mesure, et le blog héberge d’autres notes techniques de la même eau.

#!/bin/bash
# Ou lire les deprecations, selon la couche qui les emet.

# 1. Journal PHP du serveur. Le chemin depend de l'hebergeur : demandez-le
# a PHP lui-meme plutot que de le deviner.
php -i | grep -E '^(error_log|log_errors|display_errors|error_reporting)'

# 2. Les dernieres deprecations, regroupees par message pour voir
# lesquelles reviennent le plus souvent.
JOURNAL=$(php -r 'echo ini_get("error_log");')
[ -n "$JOURNAL" ] && [ -f "$JOURNAL" ] && \
  grep -i 'deprecated' "$JOURNAL" | tail -n 500 \
  | sed -E 's/^\[[^]]+\] //' | sort | uniq -c | sort -rn | head -20

# 3. Journal Symfony de PrestaShop, en environnement de production.
tail -n 100 var/logs/prod.log 2>/dev/null | grep -i 'deprecat'

# 4. Journal du serveur web, pour les erreurs fatales qui n'ont pas eu
# le temps d'atteindre PHP.
tail -n 100 /var/log/apache2/error.log 2>/dev/null | grep -iE 'fatal|deprecat'

Questions fréquentes

PrestaShop 8.2 fonctionne-t-il sur PHP 8.3 ?

Pas à l’intérieur du périmètre supporté. En 8.2.3, l’installateur annonce dans son fichier un maximum fixé à 8.1, pendant que la documentation officielle, dans son tableau de compatibilité, refuse PHP 8.2 et les suivantes sur l’ensemble de la branche 8.0 à 8.2. Cette borne, l’installateur comme l’assistant de montée de version la font respecter.

Peut-être que ça tiendra un moment chez vous, jusqu’au jour où ça lâchera sur un chemin bien précis (le plus souvent en fabriquant un document ou en triturant une date). PHP 8.1 disparaît de chez votre hébergeur ? Surtout, ne forcez rien. Programmez plutôt le saut en 9, qui va de PHP 8.1 à 8.4.

Comment repérer les modules qui ne passeront pas ?

Aucun module n’annonce sa compatibilité PHP, vous en serez donc réduit à lire le code. Les motifs dépréciés, un scan par jetons les attrape : paramètres typés à valeur par défaut nulle, fonctions de nom de classe appelées sans aucun argument, fonctions que le langage a retirées, interpolation avec accolades. Au bout du compte, des emplacements à relire, et rien d’autre.

Deuxième filet : montez une copie de la boutique sous la version visée, dépréciations allumées au rapport d’erreurs, puis déroulez vos parcours critiques, journal ouvert à côté. Analyse statique et exécution réelle ne ramènent pas le même gibier. Il vous faut les deux.

Que fait exactement l’attribut qui autorise les propriétés dynamiques ?

Posé sur une classe, il annonce au moteur qu’elle a le droit de recevoir des propriétés que personne n’a déclarées, et l’avertissement de dépréciation s’éteint. Du dépannage, en somme, bien commode quand plus personne ne maintient le module et que le temps presse.

Deux limites à garder en tête. D’abord, l’attribut file jusqu’aux classes filles, et la tolérance s’étend bien au-delà du prévu et risque de camoufler vos fautes de frappe sur les noms de propriétés. Ensuite, sa syntaxe démarre à PHP 8, donc un module encore attendu sur PHP 7 ne pourra pas y recourir. Je préfère de loin la déclaration explicite de la propriété.

Comment masquer les dépréciations sans perdre l’information ?

En dissociant l’affichage du journal. PrestaShop, une fois quitté le mode développement, éteint ce qui s’affiche et vous abandonne le reste, soit le niveau de rapport et l’endroit où atterrit le journal. Ces deux-là, posez-les dans votre propre fichier de configuration, que le cœur ouvre avant le sien et que les montées de version épargnent.

Voici le réglage : affichage sur off, journalisation sur on vers un fichier à part, et un niveau de rapport d’où vous n’écartez rien d’autre que les dépréciations. En 8.x comme en 9.x, une garde protège la constante chargée d’inclure ou non les avertissements de compatibilité, de sorte qu’on la redéfinit proprement. En 1.7.8, pas de garde, et il ne vous reste qu’à jouer du rapport d’erreurs de PHP.

Que faire du cache après un changement de version PHP ?

Supprimez-le pour de bon, je n’ai jamais vu un simple vidage par l’interface suffire. Tout ce qu’a produit l’ancienne version du langage y dort, le cache Symfony compilé, les gabarits Smarty compilés, l’index de classes, et rien ne garantit que le back-office réponde encore pour déclencher la purge.

En ligne de commande, effacez les dossiers et redonnez ses droits au dossier de travail, après quoi un rechargement du service PHP videra le bytecode qu’OPcache garde en mémoire. La page suivante traînera (bon signe, la recompilation a eu lieu). Un doute subsiste chez vous ? Envoyez-moi le détail par le formulaire de contact.

Commentaires

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Un projet ? Parlons-en.

Devis gratuit et sans engagement. Je réponds dans les douze heures.