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:

  1. Faire une copie de sauvegarde des 2 sites (au cas …);
  2. Faire une liste dans un tableur (Excel) de toutes les tables de l’ancienne version;
  3. Faire une exportation de la structure de la base de données de chacun des sites;
  4. 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
  5. Création de(s) script(s);
  6. migration des données Prestashop;
  7. Tests;
  8. 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.

PHPMyAdmin Print View

01_phpmyadmin_print_view

Geany Copier Coller

Ménage

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)

Structure Tableur

Nous continuons par copier-coller tous les noms de tables dans la colonne “Toutes les tables”

Tableur aevc 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:

  1. 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.
  2. 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”.
  3. 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).Comparaison de fichier MeldSi les deux structures de table sont pareil, ont peut donc faire une simple importation on met donc la table dans la colonne “Simple transfert”.

    File Compare same

    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”.

    File Compare AUTO INCREMENT

    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”.

    Nouvelle strucure

    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”.

    Nouveau Champ
    PHPMyAdmin ASC
    PHPMyAdmin DESC

    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”.

    Nouveau Champ

    PhpMyAdmin ASC sans défaut
    PhpMyAdmin DESC sans défaut

  4. 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.Nouvelle Key

Après environ une bonne heure de travail, nous devrions avoir un tableur qui ressemble à ceci:

Tableur Complet

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)

PhpMyAdmin Import

Puis dans l’interface PHPMyAdmin de la nouvelle version vous pouvez faire un “Import” en choisissant le fichier que vous venez de sauvegarder

19_mysql_import

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.

Oct, 24, 2016

4

SHARE THIS