0

Unless there's a method I'm unaware of, node references are not related to their node's content type. I need to delete referenced nodes when the target ids are not of a certain content type. Here's my code so far.

// Get My content type nodes.
$nids = \Drupal::entityQuery('node')
  ->condition('type', 'my_content_type')
  ->execute();
/** @var \Drupal\node\NodeInterface[] $nodes */
$nodes = Node::loadMultiple($nids);
foreach ($nodes as $node) {
  // Select the related nodes from each node.
  $result = \Drupal::database()->select('node__my_reference_field', 'n')
    ->fields('n')
    ->condition('entity_id', $node->id())
    ->execute();

  // Determine what content type the related node is.
  while ($record = $result->fetchAssoc()) {
    $query = \Drupal::database()->select('node_field_data', 'nfd');
    $query->fields('nfd', ['type']);
    $query->condition('nid', $record['my_reference_field_target_id']);
    $type = $query->execute()->fetchField();

    if ($type != 'content_type_one' || $type != 'content_type_two') {
      \Drupal::database()->delete('node__my_reference_field')
        ->condition('entity_id', $node->id())
        ->condition('my_reference_field_target_id', $record['my_reference_field_target_id'])
        ->execute();
    }
  }
}

If this line

if ($type != 'content_type_one' || $type != 'content_type_two') {

were

if ($type == 'content_type_one' || $type == 'content_type_two') { 

the related nodes of content_type_one and content_type_two are deleted. Using != removes all the related nodes on the node. I only want to remove the related nodes that are not of content_type_one and content_type_two.

What am I missing?


@Jaypan here's the exact code that takes nine minutes to run.

function my_module_update_8008() {
  // Get the nodes.
  $nids = \Drupal::entityQuery('node')
    ->condition('type', 'my_type')
    ->execute();

  /** @var \Drupal\node\NodeInterface[] $nodes */
  $nodes = Node::loadMultiple($nids);

  foreach ($nodes as $node) {
    // Delete references on the nodes that are not news or story.
    foreach ($node->get('field_entity_reference')->referencedEntities() as $entity) {
      drush_print($node->id());
      if (!in_array($entity->bundle(), ['news', 'story'])) {
        $entity->delete();
      }
    }
  }
}

drush_print() is for debugging purposes. During the run of this code, a $node->id() prints every 1 or 2 seconds. As you well know, to run an update_N hook from the command line, use

drush php-eval 'module_load_include('install', 'my_module'); my_module_update_8008();';

Anyway, it's 160 nodes x ~20 node references for each node. So the drush_print() helps me understand where the time is going.

Thanks for your help. I didn't mean to sound ungrateful. I think referencedEntities() and $entity->bundle() and the like are great, but I find them too slow. Once I have the field table populated with only the records I need, drush cr gets the referenced nodes to print in the text fields on the admin. I'll post my query code when I get it working again.

Btw, it's getStorage() not loadStorage() with entityTypeManager(). Thanks again.

2 Answers 2

2

Your code can be made more concise using Drupal APIs. It's rare that DB queries are required in Drupal 8, as the system generally has APIs that handle it for you. Here's how I'd do what you're trying to do. I think the key you were looking for is the line with in_array().

// Load all 'some_content_type' nodes.
$properties = [
  'type' => 'some_content_type',
];
$nodes = \Drupal::entityTypeManager()->loadStorage('node')->loadByProperties($properties);

foreach ($nodes as $node) {
  // Loop through the entities referenced by the field.
  foreach ($node->get('my_reference_field')->referencedEntities() as $entity) {
    // Only act if the entity is not one of the given types.
    if (!in_array($entity->bundle(), ['type_1', 'type_2'])) {
      // Delete the entity.
      $entity->delete();
    }
  }
}
3
  • Very nice solution. Works perfectly. It's just what I was looking for. $entity->bundle(). So nice. I'm running this code on 160 nodes and each node has between 10 and 30 node references. It takes ~9 minutes to complete. A data query solution takes seconds to complete. The code is run on an update_N hook.
    – esod
    Commented May 13, 2020 at 18:05
  • Do you mean this solution took 9 min, or the original?
    – Jaypan
    Commented May 13, 2020 at 18:30
  • I updated the question with the exact code that's taking so long to run.
    – esod
    Commented May 13, 2020 at 20:33
0

I think the $entity->bundle()->delete() code deletes the entire node entity. I want to delete the reference on the node, not the referenced node entity.

For example, say the entity reference id is 4070 and the node referencing it is node id 196541. $entity->bundle()->delete() deletes node 4070 when I just want to delete the reference to node 4070 from the field on node 196541. That's where the ~9 minutes to run happened.

I don't want to delete the referenced nodes (yikes), I only want to delete the references to the node.

Using database queries:

/**
 * Delete nodes references that are not news or story.
 */
function my_module_update_8008() {
  // Get the nodes.
  $nids = \Drupal::entityQuery('node')
    ->condition('type', 'my_type')
    ->execute();
  /** @var \Drupal\node\NodeInterface[] $nodes */
  $nodes = Node::loadMultiple($nids);

  // Delete references on overview node that are not news or story.
  foreach ($nodes as $node) {
    $result = \Drupal::database()
      ->query("SELECT * FROM {node__field_entity_reference} WHERE entity_id=:entity_id ORDER BY field_entity_reference_target_id", [':entity_id' => $node->id()]);
    while ($record = $result->fetchAssoc()) {
      $type = \Drupal::database()
        ->query("SELECT type FROM node_field_data WHERE nid =:target_id", [':target_id' => $record['field_entity_reference_target_id']])
        ->fetchField();
      if ($type == 'news' || $type == 'story') {
        continue;
      }

      $params = [
        ':entity_id' => $node->id(),
        ':target_id' => $record['field_entity_reference_target_id'],
      ];
      \Drupal::database()
        ->query("DELETE FROM {node__field_entity_reference} WHERE entity_id=:entity_id AND field_entity_reference_target_id=:target_id", $params);
      \Drupal::database()
        ->query("DELETE FROM {node_revision__field_entity_reference} WHERE entity_id=:entity_id AND field_entity_reference_target_id=:target_id", $params);
    }
  }
}

This code:

  if ($type == 'news' || $type == 'story') {
    continue;
  }

skips the news and story types.

If I want to keep news and story node references and delete the other types, I can take out the continue;.

And then drush cr does its magic and the references are updated in the referenced field. Takes seconds and solves the problem.

1
  • "I don't want to delete the referenced nodes (yikes), I only want to delete the references to the node." You wrote that you wanted to delete the nodes in the original post.
    – Jaypan
    Commented May 15, 2020 at 21:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.