2

I'm trying to write a drush command to resave all the nodes on my site.

I searched for a module and found the Resave All Nodes module, but its drush command isn't ready yet. So I decided to try to write it myself.

However, I can't get my nodes to re-save with $entity->save(), and I don't understand why.

<?php

namespace Drupal\resave_all_nodes\Commands;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\node\Entity\Node;
use Drush\Commands\DrushCommands;

/**
 * A Drush command class for Resave All Nodes module conversions.
 */
class ResaveAllNodesCommands extends DrushCommands
{

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  private $entityTypeManager;


  /**
   * Constructs a ResaveAllNodesCommands object.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager)
  {
    $this->entityTypeManager = $entity_type_manager;

    parent::__construct();
  }

  /**
   * Resave all nodes.
   *
   * @command resave-all-nodes:resave
   *
   * @usage drush resave-all-nodes:resave
   *   Resave all nodes on the site.
   *
   * @aliases ran
   */
  public function resaveAllNodes()
  {
    $my_node = Node::load(1);
    $my_node->save();
    \Drupal::logger('resave_all_nodes')->notice("node 1 saved!");
  }
}

When I run the command, the logs show the logger notices:

  • I see "node 1 saved!" in the logs
  • I have another log message in hook_entity_presave(), which also appears

But, when I go to /admin/content, the "date updated" for node 1 has not changed. Also, the content of the node is not resaved.

If I go to node/1/edit and re-save manually, the node is re-saved as I would expect (the updated date is updated, and the field values are updated).

So, why does node->save() silently fail (the update date and field values remain the same), when saving manually works?

I have a few custom modules and I disabled them and rebuilt the cache, but the problem remains.

5
  • Do you have any logic ignore PHP_SAPI, or some module ignore logic with PHP_SAPI?
    – Jonh
    Feb 18, 2022 at 4:58
  • @Jonh I don't think so. I just grepped my codebase for PHP_SAPI and the only non-core non-Symfony references are to webform module. Running my site locally in lando pulled down from platform.sh. Feb 18, 2022 at 5:44
  • 2
    But, when I go to /admin/content, the "date updated" for node 1 has not changed. You need to set the changed time: $my_node->setChangedTime(\Drupal::time()->getRequestTime()); If you still not see it, try to clear the cache.
    – 4uk4
    Feb 18, 2022 at 8:49
  • @4k4 Ok, that worked. So resaving a node in the UI is actually different than doing node->save(), in that node->save() doesn't make any changes unless you explicitly specify them? Feb 18, 2022 at 10:06
  • 2
    Entities don’t persist data if nothing has changed (for obvious performance reasons). Submitting an entity form updates the changed timestamp before save is called, so the persistence is triggered. You just need to do the same
    – Clive
    Feb 18, 2022 at 11:18

1 Answer 1

2

Thanks to @4k4 and @Clive I was able to get this working. This drush command saves all nodes. The code follows, but first some notes:

  • After calling batch_set(), for drush commands, you have to call drush_backend_batch_process() or the batch will never be run.
  • The Drush 9 Batch Processing Example module is a very helpful example.
  • In your batchOperation() command, if you typehint $context, you have to use \DrushBatchContext|array $context not just array because drush will crash hard. There are dozens of issues on drupal.org about this.

Ok, here's my code:

<?php

namespace Drupal\resave_all_nodes\Commands;

use Drupal\Core\Batch\BatchBuilder;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drush\Commands\DrushCommands;

/**
 * A Drush command class for Resave All Nodes module conversions.
 */
class ResaveAllNodesCommands extends DrushCommands
{
  use StringTranslationTrait;

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  private $entityTypeManager;


  /**
   * Constructs a ResaveAllNodesCommands object.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager)
  {
    $this->entityTypeManager = $entity_type_manager;

    parent::__construct();
  }

  /**
   * Resave all nodes.
   *
   * @command resave:nodes
   *
   * @usage drush resave:nodes
   *   Resave all nodes on the site.
   *
   * @aliases ran
   */
  public function resaveAllNodes()
  {
    // An array with lots of node IDs.
    $nids = $this->getNodeIds();
    // Chop array into sub-arrays (chunks) of specified size.
    $chunks = array_chunk($nids, 250);
    $num_chunks = count($chunks);

    // Now resave all nodes chunk by chunk.
    $batchBuilder = new BatchBuilder();
    for ($i = 0; $i < $num_chunks; $i++) {
      $batchBuilder->addOperation('\Drupal\resave_all_nodes\Batch\ResaveAllNodesBatch::batchOperation',
        [$chunks[$i]]
      );
    }

    $batchBuilder->setTitle(t('Resaving nodes'))
      ->setFinishCallBack('\Drupal\resave_all_nodes\Batch\ResaveAllNodesBatch::batchFinished');

    batch_set($batchBuilder->toArray());
    drush_backend_batch_process();
  }

  /**
   * Get an array node IDs.
   *
   * @return array|int
   *   An array of node IDs at best.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function getNodeIds() {
    $query = $this->entityTypeManager->getStorage('node')->getQuery();
    $nids = $query->execute();
    # To get node types.
    # $nids = $query->condition('type', $node_types, 'IN')->execute();

    return $nids;
  }
}

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.