J'ai récemment franchi le pas et migré mon blog de WordPress vers Laravel. Je suis convaincu que mon expérience pourrait servir d'aide à d'autres qui souhaiteraient suivre la même démarche. Il ne s'agit pas d'une migration simple, car il est nécessaire de prendre en compte de nombreux éléments.
La principale difficulté réside dans la gestion des données et de la structure de la base WordPress, qui est assez complexe, et de celle qui doit recevoir ces données. Dans cet article, je vais décrire comment j'ai procédé après avoir décrypté la manière dont WordPress stocke les données des utilisateurs, des articles, des pages et des commentaires.
Les étapes clés de la migration
- Analyse de la structure de la base de données WordPress : il faut commencer par comprendre comment WordPress stocke et organise les données de vos utilisateurs, articles, pages et commentaires. Cette étape est cruciale pour une migration réussie.
- Création de la structure de base de données Laravel : concevoir une structure de base de données adaptée à Laravel, qui sera destinée à accueillir les données du blog.
- Extraction et transformation des données : exportation les données de WordPress et transformation pour qu'elles s'adaptent à la structure de Laravel. Cette étape peut être fastidieuse mais est essentielle pour assurer une migration sans erreur.
- Migration des données : importation des données transformées dans la base de données Laravel.
- Configuration de Laravel et intégration des données : mettreen place les éléments nécessaires pour que Laravel puisse exploiter ces données et les afficher correctement le blog.
Migrer un blog de WordPress vers Laravel est un processus complexe, mais réalisable avec un peu de planification et de patience. En suivant les étapes décrites ci-dessus et en prenant le temps de comprendre les spécificités de chaque plateforme, vous serez en mesure de mener votre migration à bien. N'hésitez pas à explorer d'autres sources d'information ou à demander de l'aide si vous vous sentez dépassé.
J'espère que cet article vous aidera à mener à bien votre projet de migration et vous souhaite bon courage dans cette aventure !
Analyse de la structure de la base de données WordPress
Pour réaliser une migration efficace de votre blog de WordPress vers Laravel, il est important de bien comprendre la manière dont WordPress stocke et organise les données de vos articles, pages et commentaires.
- Structure de base de données WordPress
La base de données WordPress est composée de plusieurs tables, chacune ayant une fonction spécifique. Les principales tables à prendre en compte pour votre migration sont :- wp_users : contient les informations des utilisateurs enregistrés.
- wp_terms : contient les termes qui peuvent être par exemple des noms de catégories.
- wp_posts : contient les articles, pages et autres types de contenu.
- wp_postmeta : stocke les métadonnées des articles, pages et autres types de contenu.
- wp_comments : stocke les commentaires publiés sur votre blog.
- wp_commentmeta : contient les métadonnées associées aux commentaires.
- Relations entre les tables
Il est essentiel de comprendre les relations existant entre les tables WordPress. Par exemple :- wp_posts et wp_postmeta : une relation 1-n (une entrée dans wp_posts peut avoir plusieurs entrées correspondantes dans wp_postmeta).
- wp_comments et wp_commentmeta : une relation 1-n (une entrée dans wp_comments peut avoir plusieurs entrées correspondantes dans wp_commentmeta).
- wp_term_relationship : une relation entre les termes et les articles.
- Identification des champs nécessaires
Pour une migration efficace, vous devez identifier les champs nécessaires pour reconstruire votre blog sur Laravel. Cela inclut :- Les titres, contenus et dates de vos articles et pages (wp_posts).
- Les informations sur les auteurs, catégories et tags (wp_posts et wp_postmeta).
- Les commentaires et leurs informations associées (wp_comments et wp_commentmeta).
- Compréhension du système de métadonnées
WordPress stocke beaucoup d'informations sous forme de métadonnées, dans les tables wp\_postmeta et wp\_commentmeta. Il est important de comprendre comment ces métadonnées sont organisées et comment les récupérer pour une migration efficace.
Une fois cette analyse effectuée, vous serez en mesure de mieux comprendre les informations à extraire de la base de données WordPress et comment les reformater pour être utilisées dans Laravel. Ne pas laisser passer les détails importants et les relations clés lors de cette étape est vital pour une migration réussie de votre blog.
Une application Laravel pour la migration
Pour faire cette migration on va évidemment utilliser Laravel. De base Laravel ne s'adresse qu'à une seule base de données. Pour accéder à deux bases il faut un peu changer la configuration. dans le fichier .env il faut déclarer les deux bases :
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=sillo1
DB_USERNAME=root
DB_PASSWORD=
SECOND_DB_CONNECTION=mysql
SECOND_DB_HOST=127.0.0.1
SECOND_DB_PORT=3306
SECOND_DB_DATABASE=wordpress
SECOND_DB_USERNAME=root
SECOND_DB_PASSWORD=
J'ai la base de destination que j'ai ici appelée sillo1 et la base de wordpress que j'ai simplement appelée wordpress.
Dans la configuration il faut aussi préciser cette deuxième base :
'connections' => [
'mysql' => [
'driver' => 'mysql',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
...
],
'wordpress' => [
'driver' => 'mysql',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('SECOND_DB_DATABASE', 'laravel'),
'username' => env('SECOND_DB_USERNAME', 'root'),
'password' => env('SECOND_DB_PASSWORD', ''),
...
],
Pour les modèle Eloquent j'ai utilisé des classiques User, Page, Post, Comment et Category. Pour accéder aux tables de Wordpress j'ai pris les mêmes noms en ajoutant un 1 : User1, Post1 et Comment1 (il n'y a pas de table pages dans Wordpress qui stocke tout dans ma table wp_posts).
Pour que ces modèles pour Wordpress fonctionnent il faut bien préciser la connexion et la table concernées, par exemple pour les utilisateurs :
class User1 extends Authenticatable
{
protected $connection = 'wordpress';
protected $table = 'wp_users';
Il faut ensuite mettre en place toutes les relation qui vont être utiles.
Un utilisateur a des commentaires :
public function comments()
{
return $this->hasMany(Comment1::class, 'user_id');
}
je décrirai plus en détails les autres dans la suite de cet article.
Les utilisateurs
Voici la table users de Wordpress :
Et voici la table de destination dans mon blog :
Je ne voulais récupérer que les utilisateurs qui avaient écrit des commentaires. Alors voilà le code utilisé pour la migration :
// Création des utilisateurs
$users = User1::has('comments')->get();
foreach ($users as $user) {
User::create([
'id' => $user->ID,
'name' => $user->user_login,
'email' => $user->user_email,
'password' => $user->user_pass,
'created_at' => $user->user_registered,
'updated_at' => $user->user_registered,
'valid' => true,
]);
}
Il faut bien prendre la précaution de compléter la propriété $fillable dans User :
protected $fillable = ['id', 'name', 'email', 'password', 'valid', 'created_at', 'updated_at'];
Il est important de bien avoir le même ID pour les relations.
Les catégories
Dans Wordpress les noms des catégories sont dans la tables wp_terms. Mais on trouve bien d'autres choses dans cette table. Pour différentier tout ça il y a la table wp_term_taxonomy qui nous dit à quoi ça correspond. Donc dans le modèle term on a besoin de la relation :
class Term extends Model
{
protected $connection = 'wordpress';
protected $table = 'wp_terms';
public function taxonomy()
{
return $this->hasOne(TermTaxonomy::class, 'term_id', 'term_id');
}
C'est le champ taxonomy de la table term_taxonomy qui nous dit s'il s'agit d'une catégorie. En découle le code pour la migration :
// Création des catégories
$terms = Term::whereHas('taxonomy', function ($query) {
$query->where('taxonomy', 'category');
})->get();
foreach ($terms as $term) {
Category::create([
'id' => $term->term_id,
'title' => $term->name,
'slug' => $term->slug
]);
}
Là aussi il ne faut pas oublier la propriété $fillable dans le modèle !
Les articles
Là l'affaire va un peu se compliquer au niveau du contenu :
- Wordpress utilise des url absolues pour tous les liens internes et pour les medias
- pour les URL des articles je n'ai pas la même base
- dans mon cas le code est encapsulé dans des balises spéciales générées par un plugin
J'ai donc dû créer des fonctions pour transformer les URL absolues en URL relatives et préciser l'emplacement de mes images (j'ai heureusement conserver l'organisation en années et mois de Wordpress dans mon projet). D'autre part j'ai aussi écrit une fonction pour changer le balisage pour mon code qui utilise prims js dans mon projet.
Voici les 3 fonctions que j'ai créées à cet usage :
private function replaceAbsoluteUrlsWithRelative(string $content)
{
$baseUrl = 'https://laravel.sillo.org/';
// Utilise une expression régulière pour remplacer les URLs absolues par des URLs relatives dans les attributs src et href
$pattern = '/(src|href)="' . preg_quote($baseUrl, '/') . 'wp-content\/uploads\/([^"]+)"/i';
$replacement = '$1="/storage/photos/$2"';
return preg_replace($pattern, $replacement, $content);
}
private function replaceAbsoluteUrlsWithRelativeBis(string $content)
{
$baseUrl = '(?:www\.)?laravel.sillo.org'; // Utilisation de (?:...) pour un groupe non capturant
$relativePath = '/posts';
// Utilise une expression régulière pour remplacer les URLs absolues par des URLs relatives
$pattern = '/<a\s+[^>]*href="https?:\/\/' . $baseUrl . '([^"]+)"/i';
$replacement = '<a href="' . $relativePath . '$1"';
return preg_replace($pattern, $replacement, $content);
}
private function replaceEnlighterSyntaxWithPrism(string $content)
{
// Utilise une expression régulière pour remplacer la syntaxe de l'enlighter de code par celle de Prism.js
$pattern = '/<pre class="EnlighterJSRAW" data-enlighter-language="([^"]+)">(.*?)<\/pre>/s';
$replacement = '<pre><code class="language-$1">$2</code></pre>';
return preg_replace($pattern, $replacement, $content);
}
Je ne vais pas entrer dans le détail de ce code, mais ça vous donne un exemple de réalisation.
Un autre problème est aussi d'aller chercher la catégorie pour chaque article. On peut dire que les créateurs de Wordpress n'ont pas fait dans la simplicité.Donc la relation pour aller chercher la catégorie d'un article est :
public function category()
{
return $this->hasOneThrough(Term::class, TermRelationship::class, 'object_id', 'term_id', 'ID', 'term_taxonomy_id');
}
Il faut aussi penser à l'image mise en avant dans un article, c'est optionnel dans Wordpress mais il y en a, donc il faut traiter le problème. Il faut savoir que ces images sont des attachements qui sont aussi mémorisés dans la table wp_posts. Pour savoir qui est à qui on a besoin de la table wp_postmeta. Avec cette relation dans le modèle POst :
public function featuredImage()
{
return $this->hasOne(PostMeta::class, 'post_id', 'ID')
->where('meta_key', '_thumbnail_id');
}
Et cette fonction dans le même modèle pour aller effectivement chercher cette image (du moins son nom) :
public function getFeaturedImageUrlAttribute()
{
$thumbnail_id = $this->featuredImage->meta_value ?? null;
if ($thumbnail_id) {
$attachment = Post1::find($thumbnail_id);
if ($attachment && $attachment->post_type === 'attachment') {
// Chemin relatif à partir de l'URL complète
$guid = $attachment->guid;
$path_parts = parse_url($guid, PHP_URL_PATH);
$path_parts = explode('/', $path_parts);
$year = $path_parts[count($path_parts) - 3];
$month = $path_parts[count($path_parts) - 2];
$name = $path_parts[count($path_parts) - 1];
$relative_path = $year . '/' . $month . '/' . $name;
return $relative_path;
}
}
return null;
}
Vive la simplicité !
Ainsi bien équipé on peut maintenant migrer tous les articles :
// Gestion des articles
$posts = Post1::with('category', 'featuredImage')->get();
foreach ($posts as $post) {
if($post->post_type == 'post') {
$featured_image_url = $post->featured_image_url;
// Remplacer les URLs absolues par des URLs relatives dans le contenu
$content = $this->replaceAbsoluteUrlsWithRelative($post->post_content);
// Remplacer la syntaxe de l'enlighter de code par celle de Prism.js
$content = $this->replaceEnlighterSyntaxWithPrism($content);
Post::create([
'id' => $post->ID,
'body' => $content,
'title' => $post->post_title,
'created_at' => $post->post_date,
'updated_at' => $post->post_modified,
'user_id' => $post->post_author,
'slug' => $post->post_name,
'active' => $post->post_status == 'publish' ? true : false,
'category_id' => $post->category->term_id,
'seo_title' => ' ',
'meta_description' => ' ',
'meta_keywords' => ' ',
'image' => $featured_image_url? $featured_image_url : null,
]);
}
}
Les pages
Pour les page on va bénéficier de tout ce qui a été mis en place pour les articles puisqu'elles se trouvent aussi dans l'obèse table wp_posts.
// Gestion des pages
$posts = Page1::all();
foreach ($posts as $post) {
if($post->post_type == 'page') {
$featured_image_url = $post->featured_image_url;
// Remplacer les URLs absolues par des URLs relatives dans le contenu
$content = $this->replaceAbsoluteUrlsWithRelative($post->post_content);
$content = $this->replaceAbsoluteUrlsWithRelativeBis($content);
// Remplacer la syntaxe de l'enlighter de code par celle de Prism.js
$content = $this->replaceEnlighterSyntaxWithPrism($content);
Page::create([
'id' => $post->ID,
'body' => $content,
'title' => $post->post_title,
'active' => true,
'created_at' => $post->post_date,
'updated_at' => $post->post_modified,
'slug' => $post->post_name,
'seo_title' => ' ',
'meta_description' => ' ',
'meta_keywords' => ' ',
]);
}
}
Et c'est bon !
Les commentaires
Pour terminer le travail on doit s'occuper des commentaires. Comme j'ai adopté le même principe que Wordpress pour la liaison parent->enfant ça m'a simplifié la vie. Il y a un champ pour mémoriser l'ID du parent, et pour un parent racine ce champ est tout simplement null.
$comments = Comment1::all();
foreach ($comments as $comment) {
if ($comment->user_id != 0) {
$parent_id = $comment->comment_parent == 0 ? null : $comment->comment_parent;
// On vérifie si l'utilisateur existe
if (!User::where('ID', $comment->user_id)->exists()) {
continue; // Ignorer le commentaire si l'utilisateur n'existe pas
}
// On vérifie si l'article existe
if (!Post::where('id', $comment->comment_post_ID)->exists()) {
continue; // Ignorer le commentaire si l'article n'existe pas
}
// On vérifie si le parent_id existe dans la table des commentaires
if ($parent_id !== null && !Comment::where('id', $parent_id)->exists()) {
$parent_id = null;
}
Comment::create([
'id' => $comment->comment_ID,
'body' => $comment->comment_content,
'created_at' => $comment->comment_date,
'updated_at' => $comment->comment_date,
'user_id' => $comment->user_id,
'post_id' => $comment->comment_post_ID,
'parent_id' => $parent_id,
]);
}
}
J'ai quand même vérifié l'existence effective de l'auteur, de l'article et du parent éventuel pour éviter des incohérences dans ma base.
Conclusion
Comme vous pouvez le constater à la lecture de cet article, ce genre de migration prend un peu de temps et nécessite une étude attentive des structures en présence. Je ne me suis consacré là qu'à la migration des données parce qu'il s'agit de la partie la plus délicate. Le reste ne présente pas vraiment de difficulté.
Par bestmomo
Aucun commentaire