Score:2

Cannot resave nodes with entity->save(), no particular error

cn flag

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.

br flag
Do you have any logic ignore PHP_SAPI, or some module ignore logic with PHP_SAPI?
cn flag
@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.
4uk4 avatar
cn flag
*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.
cn flag
@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?
cn flag
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
Score:2
cn flag

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;
  }
}
mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.