Converting File Entities To Media Entities in Drupal 8 and 9

So I have a personal Drupal  site which has is currently on Drupal 9.  It’s had a long history on Drupal dating back to Drupal 4. Like many Drupalists, my personal site is my background project as well as the place I experiment on things.  It’s also the first site I migrate to new versions of Drupal.

Round Tuit

The site had a photo content type with a file entity field and a couple of taxonomy reference fields.  It contains photographs I've taken.  In a modern D9 site this content type would be a media bundle.  But, since I migrated the D7 site to D8 early in D8’s life,  the media module wasn’t ready for prime time, and I left them as nodes instead of migrating them to media entities.  Since I got my hands on a Round Tuit, I decided to finally address this.

My goal is to create a media bundle which I'll call "photography", with the images and taxonomy attached.  In olden times I was lazy about adding alt tags to my images, so I want to populate empty alt tags with the image title (not perfect but better than nothing).   For now I'll attach those media entities back to the original node, with the goal of deleting the nodes once I'm comfortable that everything converted a-ok. 

So, I see 3 possible options to accomplish this:

  1. Drupal Contrib Modules
  2. MigrateAPI
  3. Write a custom module

Going through contrib I found the Migrate File Entities to Media Entities Module.  This module looks good for the average use case.  It creates fields and  migration scripts to do the migration. One feature on the module is duplicate checking.  The module uploads your file to a 3rd party service to generate a hash,  I don't need that for my photography, I know there are no duplicates.  If I were to use this, I'd also need to modify the migration YAML to add Taxonomy.  All this together made it so this was not the best solution for me.

So my choices how were either the MigrateAPI or write a module.  When the data source and data target are the same database I prefer writing a module, but I could've used migrate as well. Here's what I did:

The site has a Content Type called "photo"  The 4 fields I want to migrate are:

  • Title - a standard Drupal node title
  • field_photo - an image field containing the photo
  • field_photo_category : a taxonomy reference field
  • field_photo_tag : a taxonomy reference field

I created a Media Bundle called "photography" (photography sounds more serious than "photo".with corresponding fields:

  • Title
  • field_media_image
  • field_photo_category
  • field_photo_tag

I also created a field "field_photo_media" on the photo content type to store the reference to the newly created media entity.

Every site I build has a sitename_db_maint module which I use to for these little utilities that come up.  It's generally disabled and I'll enable it to run the utility, and disable it when I'm done.  I could've executed this through an update hook but for something like this I like to execute it explicitly by hitting a URL. If I was planning on a run with a larger number of nodes or something that could be run by a lesstechnical person, I'd add drush support and probaby batches. So I used drush generate to generate scaffolding for a controller, build the route etc.

Method convertNode2Media()  is where the work gets done.  The code does an entity query pulling all of the nodes in that content type.  I loop through the nodes, creating the media entity, assigning the fields, saving the media entity and assign the reference  to the new media entity back to the node and save that.  

You'll notice that if I'm missing an alt tag or title tag from the source, i'm assigning the node's title to these fields.

I added some simple logging and exception catching.  and the thing worked.  After some auditing, I deleted the photo nodes and content type.

Standard Disclaimer:  Test your code, backup you're database, don't run on live data.

namespace Drupal\seanreiser_db_maint\Controller;

use \Drupal\Core\Controller\ControllerBase;
use \Drupal\file\FileInterface;
use \Drupal\media\Entity\Media;
use \Drupal\node\Entity\Node;

 * Returns responses for Sean Reiser DB Maint routes.
class SeanReiserDbMaintControllerNode2Media extends ControllerBase {

   * Builds the response.
  public function build() {
  	$output = $this->convertNode2Media();

    $build['content'] = [
      '#type' => 'item',
      '#markup' => $this->t($output),

    return $build;

  private function convertNode2Media(){
	 $cnt = 0;
	 $nids = \Drupal::entityQuery('node')->condition('type','photo')->execute();
	 $nodes = Node::loadMultiple($nids);

	 foreach ($nodes as $node){

			 $categories = [];
			 foreach ($node->get('field_photo_category') as $category){
				$categories[] = $category->entity->id();

			 $tags = [];
			 foreach ($node->get('field_photo_tags') as $tag){
				$tags[] = $tag->entity->id();

			 $media_entity = Media::create([
			  	'bundle'            => 'photography',
			  	'uid'              	=> \Drupal::currentUser()->id(),
			  	'status' 			=> '1',
			  	'name'				=>  $node->get('title')->value,
			  	'field_media_image' => [
			  		'target_id' => $node->get('field_photo')->entity->id(),
			  		'alt' =>  !empty($file->alt) ? $file->alt : $node->get('title')->value,
			  		'title' =>  !empty($file->title) ? $file->title : $node->get('title')->value,

			  $media_entity->set('field_photo_category', $categories);
			  $media_entity->set('field_photo_tags', $tags);


			  $node->field_photo_media->entity = $media_entity;


				  'Node "@node" converted to Media Entity "@media" Count "@cnt"',
				  ['@node' => $node->id(), '@media' => $media_entity->id(), "@cnt" => $cnt]

			  $cnt ++;

		  }  catch (Exception $e) {


	  return $cnt.' nodes converted';






Laptop w/ Stickers