Programmatically copy field data in Drupal 8

Written by John Ferris, Director of Front-End Engineering at Aten Design Group

The code to programmatically copy field data in Drupal 8 is pretty simple, but I wasn’t able to find any great examples for performing the operation at scale when I ran into the need myself. Hopefully the code sample below helps somebody save a few minutes of digging for solutions.

My use case was straightforward: After more than a year of continual development on a client site, new patterns emerged in how content editors utilized certain entity reference fields. It became apparent that similar relationships across different content types used separate fields with different machine names. This made it tricky to aggregate and filter content. It also led to overly complex implementations of any custom functionality based on these relationships. One thing was clear: we needed the fields to be consistent across content types.

Basically, I needed entity references stored in various older fields, let’s say field_old_one and field_old_two, to be copied into a single destination field we’ll call field_new.

Once I’d added our field_new field to all the appropriate content types via the field UI, I needed to migrate the references out of the old fields, and into the new fields. What’s the best way to do this when manual GUI changes would take too long? A hook_post_update_NAME implementation was my answer.

The code to programmatically copy field data in Drupal 8 isn’t the hard part. Assuming your source and destination field types are compatible, it’s just two quick lines:

It gets complicated when you need to do this on hundreds or thousands of entities. We don’t want to load, process, and save all those entities at once as that would likely bring our server down. We need to batch the operation. Implementations of hook_post_update_NAME act as implementations of callback_batch_operation — allowing us to execute a custom callback _my_module_copy_field_values across multiple HTTP requests and avoid PHP timeouts. First, we define that callback.

In this case, the $sandbox variable is passed by reference to each iteration of the post_update hook. It will loop over this function until $sandbox[‘#finished’] == 1 — or until the current node operation is equal to the total number of nodes.

In my case, I needed to run this operation on a few different fields across half a dozen content types each with their own mappings — I’ll just use article and postin my example below. To make it a little easier, I wrote the custom callback _my_module_copy_field_values to apply updates on a per content type and field basis. Then I called it from a few implementations of hook_post_update_NAME, one for each content type.

With your custom callback _my_module_copy_field_values defined, you simply call it in your hook_post_update_NAME implementations defined in your MY_MODULE.post_update.php file. Then when you run database updates, your work is done.

Executing your field level changes in hook_post_update_NAME versus hook_update ensures that any schema level changes (which should be in hook_update) are completed before your content level changes — just in case you’ve got some other updates going on or are working with a handful of other developers in the same codebase.

Do you have a simpler way to do this? Feel free to share in the comments.

--

--

Team of strategists, designers, and developers helping organizations build digital products to advance their work across the globe. www.atendesigngroup.com

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Aten Design Group

Team of strategists, designers, and developers helping organizations build digital products to advance their work across the globe. www.atendesigngroup.com