Clone entity data into existing entities in Drupal 8 & 9

30 März 2018

Creating a duplicate of an entity

Creating a duplicate of an entity is easily done via the entity API method Entity::createDuplicate(). This is a convenient method if the goal is to clone an entity into a new entity, as all identifiers of the previous entity get unset when using this method.

  1. $nid = 5;
  2. $entity = \Drupal::service('entity_type.manager')->getStorage('node')->load($nid); // Use dependency injection instead if in class context.
  3. $duplicate = $entity->createDuplicate();
  4. $duplicate->save();

Cloning data into an existing entity

However partially or fully cloning data into an existing entity is less straight forward (and rightfully so). Still, the ability to do so can be useful

  • in custom migration scripts where we want to overwrite old entities without creating new ones,
  • in cases where we need to overwrite the old entity as other internal or external data may reference it and creating a new entity would break these references.

The second case could be an entity reference field referencing the old entity in question (this could be technically solved by reassigning the reference), but it could also be 3rd party software referencing the old entity, which would complicate things.

This article is going to demonstrate a couple of possible ways of cloning entity data into existing entities.

Cloning field data 1:1

  1. $source_nid = 5;
  2. $destination_nid = 6;
  3.  
  4. // Use dependency injection in class context instead.
  5. $source = \Drupal::service('entity_type.manager')->getStorage('node')->load($source_nid);
  6. $destination = \Drupal::service('entity_type.manager')->getStorage('node')->load($destination_nid);
  7.  
  8. foreach ($source->getFields() as $name => $field) {
  9. $destination->set($name, $field->getValue());
  10. }
  11. $destination->save();

Importing new data only

To import new field data without overwriting existing data, just check if the destination field is empty before cloning into it like so:

  1. foreach ($source->getFields() as $name => $field) {
  2. if ($destination->get($name)->isEmpty()) {
  3. $destination->set($name, $field->getValue());
  4. }
  5. }

Putting it all together

A nifty method that could be used to clone field data of entities into other existing entitites could look like this:

  1. /**
  2.  * @param \Drupal\Core\Entity\Entity $source
  3.  * @param \Drupal\Core\Entity\Entity $destination
  4.  * @param string $mode
  5.  * Can be 'keep', 'overwrite' and 'clone'.
  6.  * @param array $skip_fields
  7.  * An array of fields not to be cloned into the destination entity.
  8.  */
  9. public function cloneFields(Entity $source, Entity &$destination, $mode, $skip_fields = []) {
  10. foreach ($source->getFields() as $name => $field) {
  11.  
  12. // In this case clone only fields and leave out properties like title.
  13. if (strpos($name, 'field') === 0
  14.  
  15. // Leave out certain fields.
  16. && !in_array($name, $skip_fields)) {
  17.  
  18. switch ($mode) {
  19.  
  20. // Import only those fields from source that are empty in destination.
  21. case 'keep':
  22. default:
  23. if (!$destination->get($name)->isEmpty()) {
  24. continue 2;
  25. }
  26. break;
  27.  
  28. // Import field data from source overwriting all destination fields.
  29. // Do not empty fields in destination if they are empty in source.
  30. case 'overwrite':
  31. if ($source->get($name)->isEmpty()) {
  32. continue 2;
  33. }
  34. break;
  35.  
  36. // Import field data from source overwriting all destination fields.
  37. // Empty fields in destination if they are empty in source.
  38. case 'clone':
  39. break;
  40. }
  41. $destination->set($name, $field->getValue());
  42. }
  43. }
  44. $destination->save();
  45. }

There you go. Make sure to comment below in case of questions or if you know a better way of doing the above.

Comments

Thanks to this post, I was able to solve a problem that had no solution online: the ability to programmatically create a translation with all the fields of the original node. I scoured for solutions and found none, but thanks to your suggestion on iterating over fields, I was able to do it. Huge thank-you!

Here's the basic code that did it. I'm sure it needs tweaks as I'm skipping all non-fields except for path, but for now it's getting the job done!

        $node_trans = $node->addTranslation('en-au'); // sample using Australian English
        $node_trans->setTitle($node->getTitle());
        $node_trans_fields = $node->getTranslatableFields();
        foreach ($node_trans_fields as $name => $field) {
          if (substr($name, 0, 6) == 'field_' || in_array($name, ['body', 'path'])) {
            $node_trans->set($name, $field->getValue());
          }
        }
        try {
          $node->save();
        }
        catch (\Exception $error) {
          $add_status .= 'ERROR saving ';
        }

Thank you for this. I am new to Drupal and learning. Would this be possible to use this on a taxonomy term where one wishes to copy all the content to another existing term?

Neuen Kommentar hinzufügen

Der Inhalt dieses Feldes wird nicht öffentlich zugänglich angezeigt.

Restricted HTML

  • Erlaubte HTML-Tags: <a href hreflang target> <em> <strong> <cite> <blockquote cite> <pre> <ul type> <ol start type> <li> <dl> <dt> <dd> <h4 id> <h5 id> <h6 id>
  • Zeilenumbrüche und Absätze werden automatisch erzeugt.
  • Website- und E-Mail-Adressen werden automatisch in Links umgewandelt.

Angebot innerhalb von 24 Stunden

Ob ein großes kommerzielles System, oder eine kleine Business Seite, wir schicken ein Angebot ab innerhalb von 24 Stunden nachdem Sie diese Taste drücken: Angebot anfordern