
La migration d'un site Drupal 7 vers Drupal 9, 10 ou 11 implique plusieurs changements structurels importants. Parmi eux, le passage des File Entities vers le système de Media Entities constitue une évolution majeure dans la gestion des ressources numériques. Cette migration n'est pas seulement recommandée, elle représente une étape essentielle pour bénéficier pleinement de l'architecture média centralisée de Drupal.
Dans cet article, nous documentons une approche pratique pour transformer vos File Entities en Media Entities. Cette méthode a été éprouvée dans un contexte de migration réelle et peut être adaptée à différents scénarios de migration.
Si les outils standard de migration Drupal (Migrate UI, Migrate Plus, Migrate Tools) sont recommandés pour la majorité des contenus, la migration des File Entities vers Media Entities mérite une attention particulière.
Deux modules spécifiques pourraient potentiellement automatiser cette conversion :
Notre approche par script PHP offre plusieurs avantages significatifs :
Cette solution personnalisée permet de répondre précisément aux besoins spécifiques du projet tout en garantissant une transition fiable et contrôlée.
Avant toute chose, activer les modules core suivants :
Drupal créera automatiquement les types de médias de base : Image, Document, Audio, Vidéo, Vidéo distante.
Sur les différents types de contenus concernés, créer les champs media destinés à remplacer les anciens champs de type file.
Dans notre cas, nous avons créé :
field_media_image utilisé sur la quasi-totalité des contenus comme image principale.field_media_document pour les médias de type fichier téléchargeable (PDF, Word, etc.).Un script PHP a été écrit pour parcourir tous les nœuds existants et :
field_media_* correspondant sur le nœud.Note : L'API Media est stable depuis Drupal 9, le script est fonctionnel pour les migration de Drupal 7 vers Drupal 9,10 et 11.
<?php
/**
* Script de migration des fichiers (File entities) vers Media entities
*
* Ce script permet de migrer les entités File vers des entités Media.
* Compatible avec Drupal 9, 10 et 11.
*
* Utilisation :
* 1. Configurer $types_to_fields selon vos types de contenu
* 2. Vérifier que $dry_run est à true pour un test
* 3. Exécuter le script via drush scr real_media_migration.php
*
* @requires Drupal >= 9
* @requires Media module
*/
use Drupal\node\Entity\Node;
use Drupal\file\Entity\File;
use Drupal\media\Entity\Media;
// Configuration globale pour la performance et la sécurité
define('BATCH_SIZE', 50); // Nombre de nodes à traiter par lot
define('MAX_EXECUTION_TIME', 0); // 0 = illimité
set_time_limit(MAX_EXECUTION_TIME);
// Mode simulation pour tester sans modifier la base de données
$dry_run = true;
// Configuration : Types de contenu et leurs champs fichiers associés
// Format: 'type_de_contenu' => ['champ_fichier1', 'champ_fichier2']
$types_to_fields = [
'article' => ['field_fichier_pdf', 'field_image'],
'documents' => ['field_fichier_pdf', 'field_image'],
// Ajoutez vos types de contenu et champs ici
];
// Fonction utilitaire pour les logs avec horodatage
function log_message($message, $type = 'info') {
echo date('[Y-m-d H:i:s]') . " [$type] $message\n";
}
/**
* Détermine le type de média approprié selon le type MIME du fichier
* Retourne la configuration du bundle média ou null si non supporté
*/
function get_media_bundle($file) {
$mime = $file->getMimeType();
// Liste des types MIME supportés et leur configuration média associée
$supported_types = [
'image/' => ['bundle' => 'image', 'field_target' => 'field_media_image'],
'application/pdf' => ['bundle' => 'document', 'field_target' => 'field_media_document'],
];
foreach ($supported_types as $mime_prefix => $config) {
if (str_starts_with($mime, $mime_prefix)) {
return $config;
}
}
log_message("Type MIME non supporté : $mime", 'warning');
return null;
}
// Initialisation des compteurs pour les statistiques
$created_count = 0;
$skipped_count = 0;
$error_count = 0;
// PARTIE 1 : Migration des fichiers attachés aux nodes
log_message("Début de la migration des fichiers attachés");
// Parcours de chaque type de contenu et ses champs
foreach ($types_to_fields as $type => $fields) {
log_message("Traitement du type de contenu : $type");
foreach ($fields as $field_name) {
try {
// Recherche des nodes ayant des fichiers dans le champ spécifié
$nids = \Drupal::entityQuery('node')
->accessCheck(FALSE)
->condition('type', $type)
->condition($field_name, NULL, 'IS NOT NULL')
->execute();
log_message("- Champ $field_name : " . count($nids) . " nodes trouvés");
// Traitement de chaque node
$nodes = Node::loadMultiple($nids);
foreach ($nodes as $node) {
$file = $node->get($field_name)->entity;
if (!$file instanceof File) {
$skipped_count++;
log_message("Fichier ignoré : non trouvé dans $field_name pour le node " . $node->id(), 'warning');
continue;
}
$media_info = get_media_bundle($file);
if (!$media_info) {
$skipped_count++;
log_message("Fichier ignoré : type MIME non supporté pour " . $file->getFilename(), 'warning');
continue;
}
// Vérifier si un média existe déjà
$existing_media = \Drupal::entityQuery('media')
->accessCheck(FALSE)
->condition($media_info['field_target'], $file->id())
->execute();
if (!empty($existing_media)) {
$skipped_count++;
log_message("Media déjà existant pour le fichier " . $file->getFilename(), 'info');
continue;
}
$media = Media::create([
'bundle' => $media_info['bundle'],
'uid' => $node->getOwnerId(),
'status' => 1,
'name' => $file->getFilename(),
$media_info['field_target'] => [
'target_id' => $file->id(),
],
]);
if (!$dry_run) {
$media->save();
} else {
echo "[Dry-run] Media non sauvegardés.\n";
}
$node->set($media_info['field_target'], [
'target_id' => $media->id(),
]);
if (!$dry_run) {
$node->save();
} else {
echo "[Dry-run] node non sauvegardés.\n";
}
$created_count++;
}
} catch (\Exception $e) {
log_message("Erreur lors du traitement de $type/$field_name : " . $e->getMessage(), 'error');
$error_count++;
}
}
}
// PARTIE 2 : Migration des fichiers orphelins
log_message("Début de la migration des fichiers orphelins");
// Récupération des IDs de fichiers utilisés dans des nodes
$used_fids = \Drupal::database()->select('file_usage', 'fu')
->fields('fu', ['fid'])
->condition('type', 'node')
->execute()
->fetchCol();
// Récupération de tous les IDs de fichiers existants
$all_fids = \Drupal::database()->select('file_managed', 'fm')
->fields('fm', ['fid'])
->execute()
->fetchCol();
// Calcul des fichiers orphelins (présents dans file_managed mais pas utilisés)
$orphans = array_diff($all_fids, $used_fids);
log_message("Nombre de fichiers orphelins trouvés : " . count($orphans));
// Traitement de chaque fichier orphelin
foreach ($orphans as $fid) {
$file = File::load($fid);
if (!$file instanceof File) {
$skipped_count++;
log_message("Fichier orphelin ignoré : impossible de charger le fid $fid", 'warning');
continue;
}
$media_info = get_media_bundle($file);
if (!$media_info) {
$skipped_count++;
log_message("Fichier orphelin ignoré : type MIME non supporté pour " . $file->getFilename(), 'warning');
continue;
}
// Vérifier si un média existe déjà
$existing_media = \Drupal::entityQuery('media')
->accessCheck(FALSE)
->condition($media_info['field_target'], $file->id())
->execute();
if (!empty($existing_media)) {
$skipped_count++;
log_message("Media déjà existant pour le fichier orphelin " . $file->getFilename(), 'info');
continue;
}
$media = Media::create([
'bundle' => $media_info['bundle'],
'uid' => 1,
'status' => 1,
'name' => $file->getFilename(),
$media_info['field_target'] => [
'target_id' => $file->id(),
],
]);
if (!$dry_run) {
$media->save();
} else {
echo "[Dry-run] Media non sauvegardés.\n";
}
$created_count++;
}
// Affichage du résumé final avec statistiques
log_message("Migration terminée en " . (microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"]) . " secondes");
log_message("- $created_count médias créés");
log_message("- $skipped_count fichiers ignorés");
log_message("- $error_count erreurs rencontrées");
Le script peut être lancé via Drush :
drush src /chemin/complet/vers/real_media_migration.php
Note : Il est toujours recommandé de tester le script en mode
$dry_run = trueavant de l'exécuter. Et faites une sauvegarde de votre base de données!
Après la migration des File Entities vers des Media Entities, un problème subsiste : les nouveaux nœuds créés après la migration reçoivent automatiquement une image par défaut (selon la configuration du champ média), mais les nœuds existants n'en bénéficient pas.
Ce script identifie tous les nœuds sans image pour chaque type de contenu et leur assigne l'image par défaut appropriée.
<?php
use Drupal\node\Entity\Node;
/**
* Script pour assigner des images par défaut aux contenus existants.
*
* Contexte : Lors de la migration de Drupal 7 vers Drupal 11, les file entities
* ont été migrés vers des media entities. Cependant, les images par défaut qui
* sont normalement associées à la création de nouveaux nœuds n'ont pas été
* appliquées aux contenus déjà existants.
*/
// Tableau associatif définissant l'ID du média par défaut pour chaque type de contenu
$default_media_ids = [
'actualite' => 196,
'article' => 197,
'documents' => 198,
'notes_de_lectures' => 199,
'revue_de_presse' => 200,
];
// Nom du champ média à remplir dans les nœuds
$field_name = 'field_media_image';
// Parcours chaque type de contenu et son média par défaut associé
foreach ($default_media_ids as $bundle => $media_id) {
// Recherche tous les nœuds du type de contenu actuel qui n'ont pas d'image
$nids = \Drupal::entityQuery('node')
->accessCheck(FALSE) // Ignore les restrictions d'accès pour la requête
->condition('type', $bundle) // Filtre par type de contenu
->notExists($field_name) // Sélectionne uniquement les nœuds sans image
->execute();
// Affiche le nombre de nœuds trouvés pour ce type de contenu
echo "Traitement de {$bundle} : " . count($nids) . " nœuds sans image trouvés\n";
// Charge tous les nœuds trouvés
$nodes = Node::loadMultiple($nids);
// Pour chaque nœud, assigne l'image par défaut et sauvegarde
foreach ($nodes as $node) {
$node->set($field_name, ['target_id' => $media_id]); // Définit l'image par défaut
$node->save(); // Sauvegarde les modifications
echo " → Node ID: {$node->id()} ({$bundle}) mis à jour avec le média {$media_id}\n";
}
}
Remarque: Après l'exécution des scripts, pensez à mettre à jour les vues et les display modes concernés : remplacez les références aux anciens champs
field_imagepar les nouveaux champsfield_media_image.
La migration des File Entities vers les Media Entities représente un investissement stratégique pour la pérennité de votre site Drupal. Au-delà de la simple conformité avec l'architecture moderne de Drupal, cette transformation apporte plusieurs avantages concrets :
La méthode présentée ici peut être adaptée et étendue selon les besoins spécifiques de votre projet de migration.