Prestashop 1.5 & PHP 7
13/06/2020On a tous un projet conçu sous PHP 5 et qui encore en production. Dans mon cas, c'est une boutique Prestashop version 1.5.5 qui fait le boulot depuis presque 10 ans maintenant et qui merriterai de passer sous PHP 7. A l'aide d'un outil d'analyse statique et de quelques tests "humains", on va essayer de faire tourner la boutique sous ce nouvel environnement.
La version 1.5.5 de Prestashop commence vraiment à dater (2012) et il n'est pas prévu de basculer sur une version supérieure car beaucoup de codes (modules/overrides/simplifications) a été développé spécifiquement pour cette boutique. On ne présente plus PHP 7 qui a clairement révolutionné le langage, mais au dela des performances, c'est surtout la sécurité et la possibilité de le faire tourner sur une configuration serveur standard qui me motive.
Avant toute chose, je conseille la lecture de la documentation PHP qui propose une section spécifique concernant la migration de la version 5.6 à 7 et qui fait un tour d'horizon des nouvelles/anciennes fonctionnalités.
Dans un premier temps, je vais utiliser des outils d'analyses statiques qui vont parser le code pour voir s'il y a des références à des fonctions dépréciées ou a une syntaxe (devenue) invalide.
Ensuite je vais simplement faire tourner la boutique sous PHP 7, tester quelques scénarios clients (commandes, envoi de mail, création de fichier PDF...) et voir un peu ce qu'il s'y passe.
Analyses statique du code avec PhpCodeFixer
L'analyse statique d'un programme permet d'identifier tout un tas de problèmes dans le code sans executer le programme. Ce qui m'interesses particulièrement, ça va être de trouver toutes les références à des fonctions PHP dépréciées sans m'amuser à tester chacune des fonctionnalités de la boutique. De plus, PHP appartient à la famille des languages interprétés, il ne subit pas d'étape de compilation, on ne se rend compte qu'à l'execution que le code n'est pas "valide".
Il existe beaucoup d'outils dans l'univers PHP qui ont étés développés pour faire de l'analyse statique du code. Mon choix s'est porté sur PhpCodeFixer qui semble être un projet :
- Simple : ne cherche que les fonctions et structures de codes dépréciées ou encore les noms interdits.
- Multi-version : possibilité de tester de PHP 5.4 à 7.4
- Resultats clairs : une ligne, une erreur et possibilité d'exporter au format JSON.
- Maintenu : le projet semble régulièrement mis à jour et je pense qu'il évoluera au fil des versions de PHP.
Pour l'installation, il est possible de récupérer le binaire (.phar), de récupérer les sources (composer) ou comme moi de passer par l'excellente image Docker jakzal/phpqa qui est une toolbox très fournie pour PHP.
Ensuite, on lance l'analyse sur le dossier entier de Prestashop pour découvrir l'étendu des dégats (paramètre -e pour exclure certains dossiers et -s 10mb pour repousser la limite de taille des fichiers.
Il y'a presque 200 erreurs remontées, pour ségmenter et prioriser le travail, ma stratégie est de lancer l'analyse selon l'architecture de Prestashop :
- config : fichiers de configurations de la boutique.
- classes : toutes les classes qui constitue le coeur de Prestashop.
- controllers : tous les controlleurs pour le front et le back office.
- override : tous les overrides
- tools : autres projets php qui sert à prestashop (smarty, tcpdf, ....)
- modules : tous les modules.
- admin : thème et environnement d'execution de l'interface d'administration
Gros bémol, Prestashop se base sur le moteur smarty, qui certe semble compatible PHP 7 mais les fichiers de templates ne sont pas analysable alors qu'ils peuvent embarquer du code PHP.
Les erreurs
Je vais présenter les erreurs les plus communes ou les plus interessantes que j'ai rencontré.
Mysql
Evidemment, je m'attendais à avoir un paquet d'erreur dû à l'extension mysql (classes/db/MySQL.php) obsolète depuis PHP 7.0. C'est pas vraiment un problème car la boutique utilise le driver pdo (classes/db/DbPDO.php), ce code là ne sera jamais executé. Par contre je ne m'attendais pas à avoir autant de références à cette bibliothèque dans les modules et autres outils utilisés par Prestashop.
each()
La fonction each a été déprécié depuis PHP 7.2, elle est souvent associée à la fonction list dans un while pour faire une itération. On est là sur une vieille technique de parcours de tableau avant que le foreach ne devienne la norme.
Ces nombreux appels (près d'une vingtaine) sont facile à remplacer par un foreach, attention cependant à certains parcours qui modifient les données (utiliser alors le & pour un passage par référence) ou qui déplacent le pointeur sur le tableau.
mcrypt
La librairie mcrypt est utilisée dans le coeur de Prestashop (classe Rijndael.php) pour tout ce qui est opération chiffrement/déchiffrement et plus spécifiquement pour stoker le mot de passe des utilisateurs dans la base de données ou encore les cookies de sessions.
On remarque alors :
- Que la clé et que le vecteur d'initialisation sont des variables de la classe issues de la configuration de l'instance Prestashop.
- Que le résultat chiffré est encodé en base64 puis concaténé de la longueur du message sur 6 caractères.
- Que l'algorithme utilisé est du RIJNDAEL_128 (AES).
- Que le mode utilisé est ECB.
Je souhaite réécrire cette classe pour utiliser la librairie standard openssl. C'est la petite partie retro-ingénierie de ce billet par ce que j'ai du faire quelques tests avant de comprendre :
- Que le vecteur d'initialisation n'est pas utilisé dans le mode ECB, il sert à faire joli.
- Que contre-intuitivement, le RIJNDAEL_128 en fait c'est du AES 256.
- Qu'il faut remplir avec des zeros ce que l'on souhaite chiffré car cela fonctionne avec des blocs (16 pour cette algorithme là).
On retrouve également un ensemble de référence à mcrypt dans le AdminPerformancesController.php, en effet on peut basculer sur une implémentation via la classe locale BlowFish (ce qui regnénérerait les cookies), j'ai personnellement désactivé cette option qui n'a pas vocation à être utilisée.
Visiblement TCPDF utilise aussi mcrypt dans des fonctions internes (_RCA & _AES) qui ne sont pas utilisé dans la génération PDF.
create_function
Il y a de nombreuse références à la fonction create_function. Elle se base sur eval pour créer des fonctions dynamiques à partir d'une chaine de caractères et sont souvent utilisés pour des opérations sur des tableaux, expressions régulières ou opérations de tris.
C'est clairement une pratique à éviter et qui a pour le coup était dépréciée depuis PHP 7.3.
On peut donc réécrire le code en remplaçant par une fonction lambda (anonyme) ou tout autre callable.
Constructeurs PHP 4
On retrouve la trace de quelques classes utilisant la vieille syntaxe pour déclarer les constructeurs, rien de bien sorcier là dedans, il suffit de remplacer par le nouveau formalisme __construct().
Éxecution en environnement réél PHP 7.3
Conscient des limites de mon analyse statique, je m'attends à avoir quelques désagréments en situation réélle. Je prévois de réaliser quelques scénarios clients basiques : navigation, page produit, tunnel de paiement, mails, et back office.
DbQueryCore
Cette classe est centrale pour Prestashop et offre la couche d'abstraction base de données pour permettre de forger des requêtes.
Visiblement, il y a un problème de déclaration/initialisation avec le tableau $query et plus spécifiquement l'index from qui est initialisé comme une chaine de caractère alors quelle est utilisé plus tard comme un tableau et PHP 7 n'aime pas ça.
Count() sur un peu n'importe quoi
Depuis PHP 7 la fonction count retourne un avertissement lorsque elle est executé sur autre chose qu'un tableau.
À plusieurs endroits dans Prestashop (controlleurs/classes/modules), cette fonction est très souvent utilisée sur des variables non initialisées pour tester si un tableau possède des éléments.
Il est plus facile dans ce genre de cas de remplacer le count par !empty que de s'amuser à chercher à initialiser la variable. En effet, empty retourne vrai si le tableau est vide ou si la variable est NULL, 0, 0.0, "" (chaine vide).
Archive Tar & func_get_args
J'ai reperé une évolution sur la manière d'appelé la function func_get_args en faisant un tour dans la page de gestion des modules du back office Prestashop. Cette fonction est visiblement mal appelé dans la classe Archive_Tar (tools/tar/Archive_Tar.php), en PHP 7, il n'est plus nécessaire de mettre le & devant la fonction.
Conclusions
Après quelques jours de travail à corriger le code et à écrire cette article (car je fais les deux en même temps), je pense que les erreurs rencontrées en environnement réélles montre la limite de mon analyse statique. J'aurais pû la compléter avec d'autres outils bien plus complexe comme PHPStan ou phan ce qui m'aurait potentiellement permis de lever certaines erreurs supplémentaire.
Toutefois, je préfère utiliser ce temps pour mettre un place une couche de tests ciblés sur les principaux scénarios de la boutique. En tout cas, la boutique semble fonctionnelle sous PHP 7, je me laisse encore un peu de temps pour tester plus profondément mais il faudra bien à un moment pousser les corrections en production.