« Cannot use object of type Product as array » : les fiches produit PrestaShop tombent en 500 par intermittence

par Luca Cavallini Cas d'assistance
« Cannot use object of type Product as array » : les fiches produit PrestaShop tombent en 500 par intermittence

Le symptôme : de vrais clients qui tombent sur une page d'erreur, par intermittence

Une boutique PrestaShop 1.7 avec un thème sur mesure enregistrait environ soixante-dix erreurs 500 par jour, à peu près une toutes les 15 minutes, toutes sur des fiches produit et sur des requêtes d'ajout au panier. Pas des robots : des adresses IP italiennes, des navigateurs ordinaires (Safari, Chrome). Chacune de ces erreurs, c'est un vrai client qui regardait un produit, ou s'apprêtait à l'acheter, et qui a reçu une page blanche à la place.

Le log affichait toujours la même erreur fatale :

PHP Fatal error: Uncaught Error:
  Cannot use object of type Product as array
  in .../product-flags.tpl.php
  on line 44

La ligne fautive, dans le fichier compilé par Smarty, renvoyait à $product.flags dans le gabarit source product-flags.tpl, le fragment qui affiche les badges « promo », « pack », « nouveau » sous le nom du produit. Cela n'arrivait pas sur toutes les fiches produit, ni toujours sur les mêmes produits.

Ce que cela semblait être, et n'était pas

Avant d'arriver à la vraie cause, trois suspects raisonnables ont été vérifiés et écartés un par un.

  • Un module accroché à la fiche produit. Le soupçon naturel, avec un thème sur mesure, est qu'un module tiers écrase la variable product transmise à Smarty. Tous les modules accrochés aux hooks concernés (chat, avis, blocs de produits associés, suivi) ont été vérifiés un par un : aucun n'assignait product ou products d'une façon qui puisse expliquer l'erreur.
  • L'override personnalisé du contrôleur produit. La boutique avait un override du contrôleur de la fiche produit, écrit des années plus tôt. Vérifié ligne par ligne : il ne touche qu'à l'association de catégorie, pas à la variable qui déclenche l'erreur.
  • « Ce sont des produits aux données corrompues. » Les produits concernés, vérifiés un à un, contenaient des données ordinaires : rien de cassé dans les champs, rien de manquant. Le point commun, une fois regardés ensemble, était d'avoir au moins un badge actif (une promotion, un pack), pas une valeur mal formée.

Aucune des trois pistes n'expliquait pourquoi l'erreur était intermittente : elle survenait parfois sur un produit et pas sur un autre structurellement identique, et sur un même produit elle apparaissait parfois, et parfois non.

La vraie cause (partielle, et il faut le dire honnêtement)

Dans PrestaShop, la variable $product qui arrive dans les gabarits est normalement un ProductLazyArray : une classe qui implémente ArrayAccess, si bien que $product['flags'] en PHP et $product.flags en Smarty fonctionnent tous deux, parce que l'objet sait se comporter comme un tableau sans en être un. Dans certains contextes, pourtant, c'est un objet Product brut qui arrive à la place, le véritable ObjectModel, qui n'implémente pas ArrayAccess. Accéder à un champ d'un tel objet avec la syntaxe des tableaux ne lève pas un simple avertissement récupérable : cela lève une Error fatale, que PrestaShop ne peut pas intercepter, qui arrête l'exécution et renvoie un 500 au visiteur.

Il faut le dire avec la même honnêteté que celle avec laquelle le cas a été traité sur la boutique réelle : qui transmet exactement l'objet Product brut au lieu du ProductLazyArray n'a jamais été identifié dans le temps disponible. Les hooks les plus évidents ont été écartés, mais le point d'injection précis reste à isoler, très probablement dans un endroit moins visible du code du thème ou dans un contexte de rendu particulier. C'est aussi pour cela que le correctif ci-dessous a été appliqué délibérément comme une mesure d'atténuation, et non comme une correction de la cause racine.

Le correctif : rendre le gabarit tolérant, sans deviner qui se trompe

Quand la cause exacte ne peut pas être isolée dans le temps disponible, et que l'effet est un site qui renvoie des 500 aux clients, la priorité change : d'abord supprimer l'urgence (le 500 disparaît), puis, sans la pression, continuer à chercher qui injecte la mauvaise donnée. Le gabarit a été modifié pour ne lire les badges que lorsque $product est réellement accessible comme un tableau, et pour rester silencieux (badges vides, et non une page cassée) dans le cas contraire :

{assign var='flags'
  value=null}
{if isset($product) &&
    ($product|is_array ||
     $product|is_a:
       'ArrayAccess')}
    {assign var='flags'
      value=$product.flags}
{/if}
<ul class="product-flags">
  {if $flags}
    {foreach from=$flags
      item=flag}
      ...
    {/foreach}
  {/if}
</ul>

Cela couvre les deux cas légitimes : un simple tableau PHP (is_array) et un objet de type ProductLazyArray (is_a:'ArrayAccess'). Quand l'objet brut arrive, qui n'est ni l'un ni l'autre, le bloc reste simplement vide au lieu de faire tomber toute la page.

Une vérification à faire avant, pas après. Utiliser un modificateur Smarty comme |is_array ou |is_a suppose que le thème autorise l'appel de fonctions PHP depuis les gabarits. Sur beaucoup d'installations c'est le cas par défaut, mais pas sur toutes : si une politique de sécurité Smarty l'interdit, exactement la même modification casserait la compilation de toutes les fiches produit, et pas seulement le cas qu'elle devait corriger. Avant d'écrire le modificateur, il vaut la peine de vérifier si le thème en utilise déjà un équivalent quelque part (dans ce cas, |count apparaissait déjà dix-sept fois dans le même thème) : s'il est déjà là, la voie est libre.

Comment vérifier que le correctif tient

  • Ne supprimez que le fichier compilé de ce gabarit précis, trouvé avec find dans var/cache/prod/smarty/compile/, jamais tout le dossier de cache : c'est le seul moyen de forcer la recompilation de ce seul fichier sans payer le coût d'une reconstruction de tout le cache Smarty de la boutique.
  • Rechargez les fiches produit qui échouaient et vérifiez deux choses à la fois : qu'elles renvoient 200, et que les badges (promotion, réduction) sont toujours visibles là où ils doivent l'être. Un gabarit rendu « tolérant » de la mauvaise façon peut cesser de lever des erreurs tout en masquant aussi les bonnes données, pas seulement les mauvaises.
  • Comptez les occurrences de l'erreur dans le log avant et après, sans vous arrêter à la première vérification : grep -c "Cannot use object of type Product as array" error.log quelques heures plus tard, pas quelques minutes, parce que l'erreur apparaissait à des intervalles de plusieurs dizaines de minutes, et un test trop court peut ressembler à un succès par pure chance.
  • Gardez un rappel pour la cause racine. La mesure d'atténuation supprime l'urgence, elle ne clôt pas le dossier : il vaut la peine de vérifier plus tard si le même schéma ($quelquechose.cle dans un fragment qui attend un tableau mais reçoit parfois un objet) se répète ailleurs dans le même thème, parce qu'un point d'injection défaillant peut facilement toucher plus d'un gabarit.

La boutique reste volontairement anonyme : c'est le mécanisme qui compte. Si un thème PrestaShop lève Cannot use object of type Product as array sur un fragment qui lit $product.quelquechose, le bon soupçon n'est pas « ce produit est cassé » : c'est qu'à cet endroit précis du rendu, parfois, c'est un vrai Product qui arrive au lieu du ProductLazyArray qu'attend le gabarit.

Laisser un commentaire

Les commentaires sont modérés avant publication.

← Blog

Chargement...
Retour en haut