
Migration des données Prestashop 1.4 ou 1.5 vers 1.6 ou 1.7
Mise en garde: Ce guide de Migration des données Prestashop s’adresse à des gens avec un niveau technique assez élevé et une bonne compréhension de Prestashop et son modèle de données. Cette technique est loin d’être la moins compliquée, nous la privilégions, car elle offre un grand contrôle et une souplesse lors de l’importation des données. Si vous désirez une solution moins technique, plus efficace et automatisée, nous vous conseillons d’utiliser les services de Cart2Cart: https://www.shopping-cart-migration.com/.
Voici une mise en situation qui nous arrive assez souvent, un client nous appelle il a un Prestashop qui roule une ancienne version (1.4 ou 1.5) et il est prêt à passer à une nouvelle version et il veut en profiter pour changer le visuel (thème) et ajouter des fonctionnalités.
Nous commençons par répliquer son site sur notre environnement de développement, nous procédons à la migration des données par le module “1-Click Upgrade” ou manuellement, nous installons le nouveau thème, nous appliquons les modifications, nous créons les nouveaux modules et quelques semaines/mois plus tard nous avons une nouvelle version parfaite de la boutique et nous devons maintenant mettre cette version en ligne. Pendant le développement de la nouvelle version, l’ancienne version roule en parallèle et le défi est maintenant de faire un “Merge” des 2 versions soit en intégrant le style actuel sur l’ancienne version ou en important les données de l’ancienne version vers la nouvelle.
Sans entrer dans l’analyse de notre décision, comme nous savons ce que nous voulons importer versus l’incertitude de toutes les modifications apportées nous y allons avec la deuxième solution soit migrer les données de l’ancienne version vers la nouvelle et ça là que ça se complique. Il y a plusieurs solutions proposées telles que des modules qui exporte en CSV les données qu’on peut ensuite importer dans la nouvelle version , nous n’aimons pas cette solution pour les raisons suivantes il est difficile d’exporter seulement les données manquantes, ces “export” donnent souvent le résultat et non la structure des données (relation entre 2 tables) de plus il y a souvent des modifications au modèle de données entre 2 version majeure de Prestashop et ces “exports”, ne reflète pas nécessairement ces modifications.
Puisque nous avons dû développer notre technique au fils du temps, nous croyons pertinent de partager notre solution avec vous.
Voici un sommaire des étapes:
- Faire une copie de sauvegarde des 2 sites (au cas …);
- Faire une liste dans un tableur (Excel) de toutes les tables de l’ancienne version;
- Faire une exportation de la structure de la base de données de chacun des sites;
- Pour chacune des tables dans le modèle de données de l’ancienne version, faire le processus suivant:
– Décider si nous désirons importer la table;
– Vérifier si la table peut être importée directement ou si elle a besoin d’un script - Création de(s) script(s);
- migration des données Prestashop;
- Tests;
- Si tout est concluant passage de DEV à PROD (hors de ce tutoriel).
1. Faire une copie de sauvegarde des 2 sites
Cette étape me semble assez évidente et comme il y a une multitude de techniques pour faire des copies de sauvegarde, nous n’allons pas faire un plan détaillé, nous allons nous contenter de vous rappelé que c’est la base et fort certainement l’étape la plus importante quand on fait n’importe quelle migration.
2. Faire une liste dans un tableur (Excel) de toutes les tables de l’ancienne version
Pour cette étape nous allons simplement faire un “Print View” de la structure de la Base de données PHPMyAdmin, que nous allons copier-coller dans un fichier texte pour ensuite faire un ménage (nous utilisons des éditeurs de texte avec des “REGEX” comme Geanny, Notepad2, NotePad++ pour faire du “search and replace”), mais ça peut très bien ce faire manuellement en effaçant toutes les données qui ne sont pas des noms de tables pour créer une liste de nom de table.
Ensuite, nous créons un nouveau tableur (Excel) dans lequel nous créons les 4 colonnes suivantes: “Toutes les tables”, “Simple transfert”, “Besoin d’un script” et “Ne pas transférer”. (télécharger le modèle)
Nous continuons par copier-coller tous les noms de tables dans la colonne “Toutes les tables”
3. Faire une exportation de la structure de la base de données de chacun des sites
Pour cette étape nous allons utiliser phpmyadmin pour faire un “export” de la structure de toutes les tables dans un fichier texte pour chacune des bases de données (ancienne version et la nouvelle), pour nous faciliter la vie nous les appelons simplement 15.sql et 16.sql.
4. Faire le tri de chacune des tables
Cette étape peut sembler terrifiante lorsqu’on voit les 250+ tables de Prestashop, mais elle se fait généralement assez bien.
Pour chacune des tables de la liste, faire les étapes suivantes:
- Choisir si nous importons ou pas la table, c’est certain que cette décision est quand même relative à chaque projet dans le cas présent nous désirons importer les données qui sont liées aux utilisateurs, aux produits, aux commandes, aux factures, aux paniers, aux paiements, au log, etc. Et nous ne désirons pas importer les configurations, les modules, les thèmes, etc. Donc en gros on regarde le nom de la table si on voit ps_order c’est la table des commandes et si on voit ps_configurations on se doute qu’on ne veut pas l’importer.
- Regarder la nomenclature de la table, Prestashop nomme ses tables avec des relations dans un certain ordre qui peut vite nous dire si on désire l’importer ou pas. Par exemple la table “ps_category” contient les catégories et la table “ps_category_lang” contient les traductions pour les catégories donc, si on importe “ps_category” il faudra importer “ps_category_lang” bref si le nom des tables commence par la même nomenclature en général il faudra prendre la même décision d’importer ou pas que la table parent. Dans notre exemple, nous désirons importer “ps_category” il faudra impérativement importer “ps_category_group”, “ps_category_lang”, “ps_category_product”, “ps_category_shop”.
- Maintenant que nous savons si nous désirons ou pas importer la table, maintenant ça se “corse” si nous ne désirons pas importer la table nous n’avons qu’à copier-coller le nom de la table dans la colonne “Ne pas transférer” et si nous désirons l’importer il faut maintenant déterminer si nous pouvons faire un simple “import” ou si le modèle de données a changé entre les deux versions et si tel est le cas est-ce que nous avons besoin de manipuler les données pour l’import. Pour ce faire nous avons recours à plusieurs techniques/outils: La première est de faire un “file compare” entre nos deux fichiers “export” de l’étape 3 (faire une exportation de la structure de la base de données de chacun des sites), dans notre cas nous utilisons le logiciel “Meld” qui est gratuit et open source, par contre nous ne sommes pas certain de sa disponibilité sous d’autres environnements que Linux (Oui chez Varmédia INC, nous sommes fier d’avoir pris le tournant 100% open source/Linux pour nos stations de travaille depuis plus de 3 ans).
Si les deux structures de table sont pareil, ont peut donc faire une simple importation on met donc la table dans la colonne “Simple transfert”.
Si les 2 tables ont la même structure, mais un “AUTO INCREMENT” différent, c’est simplement qu’il y a une différence au niveau des données enregistrées nous pouvons donc faire une simple importation, on met le nom de la table dans la colonne “Simple transfert”.
Si les 2 tables ont la même structure, mais elles ont une différence au niveau structurel comme ont peut apercevoir sur l’image ci-dessous la champ “name” est passé de 32 à 64 caractères de long ( 1.5: “`name` varchar(32) DEFAULT NULL,” VS 1.6: “`name` varchar(64) DEFAULT NULL,”), dans ce cas il s’agit certainement que l’équipe de développement de Prestashop se soit rendu compte que certain nom de “tab” faisaient plus de 32 caractères et ils ont changer la structure pour l’accepter. Comme on voit lors d’une importation des données “CREATE TABLE IF NOT EXISTS” qui en gros veut dire que si la table existe la structure ne sera pas modifiée lors de l’importation, nous pouvons donc faire une simple importation, on met le nom de la table dans la colonne “Simple transfert”.
S’il y a un champ ajouté à la nouvelle table, il faut aller voir les données dans la table de la nouvelle version pour voir si les données sont tous à la valeur par défaut:Dans l’exemple suivant en classant les données de la colonne “pack_stock_type” de façon croisant et décroissant et que nous ne voyons que des 3 (valeur par défaut) c’est que toutes les données importées ont la valeur par défaut, nous pouvons donc faire une simple importation, on met le nom de la table dans la colonne “Simple transfert”.
Dans l’exemple suivant en classant les données de la colonne “id_lang” de façon croisant et décroissant nous verrons que le “id_lang” est à 1 mais qu’il n’y a pas de “Default value” il faudra donc faire une certaine manipulation lors de l’importation, nous allons donc mettre le nom de la table dans la colonne “Besoin d’un script”.
- Si on voit une différence au niveau des “KEY” c’est la même logique qu’une modification structurelle dans l’importation de la structure de la table on voit “CREATE TABLE IF NOT EXISTS” qui en gros veut dire que si la table existe la structure ne sera pas modifiée lors de l’importation, nous pouvons donc faire une simple importation et on met le nom de la table dans la colonne “Simple transfert”.Si la nouvelle clé est une “Unique Key”, il y a une infime possibilité d’avoir des erreurs si tel est le cas nous verrons l’erreur dans l’importation et nous pourrons corriger manuellement l’erreur.
Après environ une bonne heure de travail, nous devrions avoir un tableur qui ressemble à ceci:
Qui sera notre référence pour les étapes 5 et 6.
5. Création de(s) Script(s)
Pour les tables qui sont dans la colonne “Simple transfert”, vous pouvez effectuer le transfert à l’aide de PHPMyAdmin en commençant par accéder l’interface PHPMyAdmin de l’ancienne version de Prestashop, vous choisissez la base de données puis vous allez dans l’onglet “Export”. À partir de cette interface, vous devez choisir “Custom” dans “Export Method”, ensuite vous devez prendre soin de choisir toutes les tables qui sont dans la colonne “Simple transfert” (et exclusivement celles-ci)
Puis dans l’interface PHPMyAdmin de la nouvelle version vous pouvez faire un “Import” en choisissant le fichier que vous venez de sauvegarder
Pour les tables qui sont dans la colonne “Simple transfert”, vous pouvez aussi vous faire un script qui utilisera MySQLDUMP pour faire le transfert, nous fournissons ici un exemple de script, par contre, n’allons pas décrire ce qu’il fait (hors du contexte du présent article).
mysqldump -h "HÔTE ANCIEN SITE" -u "UTILISATEUR" -p"MOT DE PASSE" "BASE DE DONNÉES ANCIEN SITE" "TABLE 1" "TABLE 2" "TABLE ..." | mysql -h "HÔTE NOUVEAU SITE"  -u "UTILISATEUR" -p"MOT DE PASSE" "BASE DE DONNÉES NOUVEAU SITE"
Pour les tables dans la colonne “Besoin d’un script” nous allons créer un script PHP qui sera utilisé pour ajuster les données au passage , nous fournissons ici un exemple de script, par contre, n’allons pas décrire ce qu’il fait (hors du contexte du présent article). L’idée générale est de créer 2 connexions aux bases de données pour ensuite transférer table par table, rangée par rangée en ajustant les données au passage. Garder en tête que ce code ne sera exécuter qu’une seule fois. (télécharger en txt)
<?php //Avoid unwanted run if( (!isset($_SERVER['argv'][1])) && (!isset($_GET['cronk'])) ) exit(); //For cache update ini_set('max_execution_time', 9999999); ini_set('memory_limit','1024M'); function convertIpToString($ip) { $long = 4294967295 - ($ip - 1); return long2ip(-$long); } function clean_data_recursive_pre_insert($data){ if(is_array($data)){ foreach($data as $k=>$v) $data[$k] = clean_data_recursive_pre_insert($v); }else{ $data = clean_data_pre_insert($data); } return $data; } function clean_data_pre_insert($data){ $data = trim($data); $data = addslashes($data); return $data; } function retrieve_columns($tbl, $db){ $cols = array(); $rows = $db->query('SELECT * FROM `'. $tbl .'` LIMIT 0,1;'); $row = array_shift($rows); foreach($row as $col=>$val){ $cols[$col] = $col; } return $cols; } function getDateUdp($data){ return $data['date_add']; } function getIdProd($data){ global $olddb; $q = 'SELECT `id_product` FROM `ps_image` WHERE `id_image`="'. $data['id_image'] .'";'; $id_products = $olddb->query($q); return $id_products[0]['id_product']; } require "lib/meekrodb.php"; $errors = array(); $settings =array(); $settings['olddb_host'] = 'HÖTE ANCIEN SITE'; $settings['olddb_name'] = 'BD ANCIEN SITE'; $settings['olddb_user'] = 'UTILISATEUR BD ANCIEN SITE'; $settings['olddb_pass'] = 'MOT DE PASSE BD ANCIEN SITE'; $settings['newdb_host'] = 'HÖTE NOUVEAU SITE'; $settings['newdb_name'] = 'BD NOUVEAU SITE'; $settings['newdb_user'] = 'UTILISATEUR BD NOUVEAU SITE'; $settings['newdb_pass'] = 'MOT DE PASSE BD NOUVEAU SITE'; $olddb = new MeekroDB($settings['olddb_host'], $settings['olddb_user'], $settings['olddb_pass'], $settings['olddb_name']); $newdb = new MeekroDB($settings['newdb_host'], $settings['newdb_user'], $settings['newdb_pass'], $settings['newdb_name']); $tables = array(); $tables[] = array( 'table'=>'ps_customer_message', 'id'=>'id_customer_message', 'fields'=>array('id_customer_thread' => 'id_customer_thread', 'id_employee' => 'id_employee', 'message' => 'message', 'file_name' => 'file_name', 'ip_address' => 'ip_address', 'user_agent' => 'user_agent', 'private' => 'private', 'read' => 'read', 'date_add' => 'date_add', 'user_subject' => 'user_subject', 'ordernumber' => 'ordernumber', 'user_product_name' => 'user_product_name', 'date_add2'=>array('date_upd', 'getDateUdp')), 'truncate'=>0 ); $tables[] = array( 'table'=>'ps_product_tag', 'id'=>'id_tag', 'fields'=>array('id_product'=>'id_product', 'id_tag'=>'id_tag', 'id_lang'=>array('id_lang', 1)), 'truncate'=>1 ); $tables[] = array( 'table'=>'ps_image_shop', 'id'=>'id_image', 'fields'=>array('id_image'=>'id_image', 'id_shop'=>'id_shop', 'cover'=>'cover', 'id_product'=>array('id_product', 'getIdProd')), 'truncate'=>1 ); /** * EX: table all field $tables[] = array( 'table'=>'ps_image_shop', 'id'=>'id_image', 'fields'=>array(), 'truncate'=>1 ); */ foreach($tables as $table){ $errors = array(); $oldrows = $olddb->query('SELECT * FROM `'. $table['table'] .'`;'); $queries = array(); if( (isset($table['truncate'])) && ($table['truncate'] == 1) ){ $newdb->query('TRUNCATE TABLE `'. $table['table'] .'`;'); } if(empty($table['fields'])) $table['fields'] = retrieve_columns($table['table'], $olddb); foreach($oldrows as $oldrow){ $q = 'SELECT `'. $table['id'] .'` FROM `'. $table['table'] .'` WHERE `'. $table['id'] .'` = \''. $oldrow[$table['id']] .'\';'; $newrows = $newdb->query($q); $oldrow = clean_data_recursive_pre_insert($oldrow); $query = ''; foreach($table['fields'] as $of=>$nf){ if(isset($oldrow[$of]) || is_array($nf)){ if(is_array($nf)){ $f = $nf[0]; if(function_exists( $nf[1])){ if(isset($oldrow[$of])) $val = call_user_func($nf[1], $oldrow[$of]); else $val = call_user_func($nf[1], $oldrow); }else{ $val = $nf[1]; } }else{ $f = $nf; $val = $oldrow[$of]; } if($f != $table['id']) $query .= ' `'. $f.'`=\''. $val .'\','; } } $query = rtrim($query, ','); if($query != ''){ if(!empty($newrows)){ //UPDATE $query = 'UPDATE `'. $table['table'] .'` SET'. $query .' WHERE `'. $table['id'] .'` = \''. $oldrow[$table['id']] .'\';'; }else{ //INSERT $query = 'INSERT INTO `'. $table['table'] .'` SET'. $query .', `'. $table['id'] .'` = \''. $oldrow[$table['id']] .'\';'; } $queries[] = $query; } } foreach($queries as $q){ if(!$newdb->query($q)) $errors[] = $q; } if(count($errors) > 0) echo "<h3 style=\"color:#FF0000\">Errors:[". $table['table'] ."]</h3><pre>". print_r($errors, true) ."</pre><br><br>\n\n"; else echo "<h3>OK:[". $table['table'] ."]</h3><br><br>\n\n"; } ?>
Maintenant que nous avons fait le transfert des données, il reste un détail important c’est celui de faire le transfert des images/fichiers envoyés. Donc en gros ce que nous voulons faire c’est de copier les fichiers inclus dans les répertoires “img”, “download” et “upload”, pour ce faire il s’agit de télécharger tous les fichiers sur votre ordinateur à l’aide de votre client FTP préféré puis les téléverser sur le nouveau site. Pour ma part je préfère utiliser un programme en Shell qui s’appelle LFTP qui permet de faire des miroirs de serveur FTP, comme c’est télécharger directement sur le serveur et qu’il ne télécharge que les nouveaux fichiers ça économise beaucoup de temps voici un exemple de commande pour faire un miroir du répertoire img:
lftp -u "$FTPUSER","$FTPPSW" -e "set ftp:ssl-allow no;set ftp:list-options -a; mirror -n -x error_log /public_html/img /home/site/public_html/img; quit;" $FTPHOST
Remplacer
“$FTPUSER”: Utilisateur FTP
“$FTPPSW”: Mot de passe FTP
$FTPHOST: Hôte du FTP
Maintenant si vous avez accès au Shell (SSH) du serveur et que vous voulez automatiser le tout voici un exemple de “BASH script”, nous fournissons ici un exemple de script, par contre, n’allons pas décrire ce qu’il fait (hors du contexte du présent article). (télécharger en txt)
#Decalaration de variable (MYSQL) REMDBHOST='HOTE BD ANCIEN SITE' REMDBNAME='BD ANCIEN SITE' REMDBUSER='UTILISATEUR BD ANCIEN SITE' REMDBPSW='MOT DE PASSE BD ANCIEN SITE' REMTBL2XFER=( "TABLE 1" "TABLE 2" "TABLE 3" "TABLE..." ) REMFILENAME=REMOTE_$(date +"%Y_%m_%d_%I_%M_%p").sql DBHOST='HOTE NOUVEAU SITE' DBNAME='BD NOUVEAU SITE' DBUSER='UTILISATEUR BD NOUVEAU SITE' DBPSW='MOT DE PASSE BD NOUVEAU SITE' #Decalaration de variable (FTP) FTPHOST='HOTE FTP ANCIEN SITE' FTPUSER='UTILISATEUR FTP ANCIEN SITE' FTPPSW='MOT DE PASSE FTP ANCIEN SITE' #Chemin vers repertoire IMG FTPIMGFOLDER='/public_html/img' SITEIMGFOLDER='/home/site/public_html/img' #Chemin vers repertoire Download FTPDLFOLDER='/public_html/download' SITEDLFOLDER='/home/site/public_html/download' #Chemin vers repertoire UPLOAD FTPUPFOLDER='/public_html/upload' SITEUPFOLDER='/home/site/public_html/upload' echo ---------------------- echo REMOTE DB BACKUP echo ---------------------- mysqldump -h $REMDBHOST -u $REMDBUSER -p"$REMDBPSW" $REMDBNAME > REMOTE_BCK_$(date +"%Y_%m_%d_%I_%M_%p").sql echo ---------------------- echo REMOTE DB BACKUP DONE echo ---------------------- echo ---------------------- echo LOCAL DB BACKUP echo ---------------------- mysqldump -h $DBHOST -u $DBUSER -p"$DBPSW" $DBNAME > LOCAL_BCK_$(date +"%Y_%m_%d_%I_%M_%p").sql echo ---------------------- echo LOCAL DB BACKUP DONE echo ---------------------- echo ---------------------- echo SIMPLE MYSQL LOOP echo ---------------------- for ((i=0; i<${#REMTBL2XFER[*]}; i++)); do echo -- TABLE: ${REMTBL2XFER[i]} mysqldump --complete-insert --skip-triggers -h $REMDBHOST -u $REMDBUSER -p"$REMDBPSW" $REMDBNAME  ${REMTBL2XFER[i]} > $REMFILENAME sed -i 's/CREATE TABLE/CREATE TABLE IF NOT EXISTS/g' $REMFILENAME sed -i 's/DROP TABLE IF EXISTS/TRUNCATE TABLE/g' $REMFILENAME mysql -h $DBHOST -u $DBUSER -p"$DBPSW" $DBNAME < $REMFILENAME done echo ---------------------- echo SIMPLE MYSQL LOOP DONE echo ---------------------- echo ---------------------- echo PHP SCRIPT IMPORT echo ---------------------- /usr/bin/php5 /home/fomdi/web/fomdi.com/public_html/mep/data_xfer.php cronkey echo ---------------------- echo PHP SCRIPT IMPORT DONE echo ---------------------- echo ---------------------- echo IMG SYNC echo ---------------------- lftp -u "$FTPUSER","$FTPPSW" -e "set ftp:ssl-allow no;set ftp:list-options -a; mirror -n -x error_log $FTPIMGFOLDER $SITEIMGFOLDER; quit;" $FTPHOST echo ---------------------- echo IMG SYNC DONE echo ---------------------- echo ---------------------- echo DOWNLOAD SYNC echo ---------------------- lftp -u "$FTPUSER","$FTPPSW" -e "set ftp:ssl-allow no;set ftp:list-options -a; mirror -n -x error_log $FTPDLFOLDER $SITEDLFOLDER; quit;" $FTPHOST echo ---------------------- echo DOWNLOAD SYNC DONE echo ---------------------- echo ---------------------- echo UPLOAD SYNC echo ---------------------- lftp -u "$FTPUSER","$FTPPSW" -e "set ftp:ssl-allow no;set ftp:list-options -a; mirror -n -x error_log $FTPUPFOLDER $SITEUPFOLDER; quit;" $FTPHOST echo ---------------------- echo UPLOAD SYNC DONE</pre> echo ---------------------- <pre>
6. Migrations des données Prestashop
Cette étape consiste à exécuter les tâches/scripts que nous avons créés à l’étape précédente pour effectuer l’importation de données en restant vigilants si des erreurs surviennent, il faudra les corriger et recommencer jusqu’au résultat parfait.
7. Tests
Cette étape est trop souvent mise de coté par les développeurs, mais elle est cruciale, il s’agit d’aller sur le site et tester que toutes nos données sont importer, on veux tout tester les factures, les commandes, les factures les transactions, etc.
8. Si tout est concluant passage de DEV à PROD (hors de ce tutoriel).
Chez Varmédia, lorsque nous procédons à une migration nous faisons toujours cette migration vers notre environnement de Développement, quand tout est testé nous procédons avec la mise en Production de ces données. Il s’agit de mettre le site Live.
Le but de cet article n’est pas de vous donner un “One for all, step by step guide”, mais bien une vue d’ensemble sur une logique comment vous pouvez procéder de façon flexible et automatisée pour effectuer une migration de donnée. Nous vous invitons à laisser vos commentaires ci-dessous, à nous joindre si vous voulez plus d’information à propos de nos services de migrations de données et à visiter le site de Cart2Cart pour des services de migrations en ligne.
Super, merci pour cette approche très pro. et surtout de la partager !
5 janvier 2017 at 4 h 04 min
Gabriel,
Merci pour ce commentaire, je suis heureux de savoir que tu l’apprécie!
Bonne continuation.
5 janvier 2017 at 13 h 26 min
Hello,
Je suis content de voir comment font les autres entreprises pour gérer des migrations, car sincèrement c’est un véritable casse-tête.
La méthode que vous proposez est tout à fait applicable, mais c’est vrai que les risques de bugs ou oubli par méconnaissance de certaines relations de la base, peuvent provoquer des problèmes cachés.
Moi aussi je me suis posé la question et j’essaie de faire au plus simple pour que ça soit “gérable” en interne et que ça me limite le nombre de bugs / risques d’erreurs.
Ma manière de faire est beaucoup plus basique, j’évite de gérer le “merge” car cela demande énormément de manipulations : https://www.webbax.ch/2015/07/22/technique-universelle-de-migration-pour-prestashop/
Merci de nous avoir partagé votre expérience !
9 février 2017 at 2 h 06 min
Merci d’avoir partagé votre article, j’aime l’idée et je vais essayer de l’appliquer sur certain projet!
9 février 2017 at 8 h 00 min