3

What I am trying to do is to save a copy of a node object after it is inserted into another Drupal database.

As it can be seen in the following piece of code, my idea is to change the database after a node is inserted. Make a copy of the node and save it. Then, return to the default database.

However, seems like entity storage manager is set to default database in node save function. Therefore, the node is getting saved inside the default database again.

//... mymodule_node_insert

$node = Node::load($id);
\Drupal\Core\Database\Database::setActiveConnection('external');
$db = \Drupal\Core\Database\Database::getConnection();

// save the node in the shared database
// Create node object with attached file.
$sharedNode = Node::create([
    'type'        => $node->getType();,
    'title'       => $node->getTitle();,
    'body_text'   => $node->get('body')->value,
]);
$sharedNode->save();

\Drupal\Core\Database\Database::setActiveConnection();

How can I make this work? Am I following a correct logic?

Any helps would be appreciated. Thanks

2 Answers 2

2

This seems similar to Database::setActiveConnection not working as expected?. Perhaps the Node object you've instantiated before switching the connection is still going to hold on to the old db settings as the source to save.

The DatabaseSwitcher class (found in the above posted question, just under the accepted answer by the original poster) may be an effective way for you to switch the container context in a way that will allow your already instantiated Node object to save in your external DB:

//... mymodule_node_insert

$node = Node::load($id);
$db_switcher = new DatabaseSwitcher();
$db_switcher->changeToDifferentDb('external');

// save the node in the shared database
// Create node object with attached file.
$sharedNode = Node::create([
    'type'        => $node->getType();,
    'title'       => $node->getTitle();,
    'body_text'   => $node->get('body')->value,
]);
$sharedNode->save();

$db_switcher->changeToDefaultDb(); 

Hope this works for you!

2
  • Thanks, great idea! I was really optimistic about this answer. Although, it does not work for nodes. After investing a bit time, I have found the reason. Nodes are getting saved through entity manager. So, just changing the database in the container is not enough. However, your answer showed me the path. Thanks
    – Hedeshy
    Jan 17, 2018 at 15:30
  • Now, the question is how can I change the entity.manager inside the container so this class works also for entities?
    – Hedeshy
    Jan 17, 2018 at 16:48
1

I too just came across this issue. All the examples on the web only show how to deal with SQL queries. Obviously when working with entities one would use Drupal's entity manager. The trick is to new up a new SqlContentEntityStorage instance

This is what I have done and it works

```

namespace Drupal\fintech_sync\Commands;

use Drupal\Core\Database\Database;
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
use Drupal\Core\Queue\QueueInterface;
use Drupal\Core\State\State;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drush\Commands\DrushCommands;
use Drupal\Core\Queue\QueueFactory;

/**
 * Provides drush commands for fintech_sync module.
 */
class NewCommand extends DrushCommands {

  use StringTranslationTrait;

  /**
   * The queue.
   *
   * @var \Drupal\Core\Queue\QueueInterface
   */
  private QueueInterface $queue;

  /**
   * The state.
   *
   * @var \Drupal\Core\State\State
   */
  private State $state;

  /**
   * SyncCommand constructor.
   */
  public function __construct(QueueFactory $queue) {
    $this->queue = $queue->get('fintech_sync_worker');
    parent::__construct();
  }

  /**
   * Sync dbs.
   *
   * @command fintech_sync:create-accounts
   */
  public function syncCreateAccounts($customLastRun = FALSE): void {
    $this->createAccounts();
    $this->io()->success(t('Creating accounts on queue.'));
  }

  /**
   * Sync dbs.
   *
   * @command fintech_sync:sync-accounts
   */
  public function syncSyncAccounts(): void {
     foreach (['588', '589'] as $accountId) {
      $this->doRemoteSave('account', $accountId);
    }

    $this->io()->success(t('Sync the accounts it remote databes.'));
  }

  public function doRemoteSave(string $entityType, int $id): void {

    /** @var \Drupal\Core\Entity\EntityStorageInterface */
    $entityManager = \Drupal::entityTypeManager();
    $entity = $entityManager->getStorage($entityType)->load($id);

    // Set the database connection key.
    Database::setActiveConnection('replica');

    $database = Database::getConnection();
    $entity_field_manager = \Drupal::service('entity_field.manager');
    $cache = \Drupal::service('cache.entity');
    $language_manager = \Drupal::service('language_manager');
    $memory_cache = \Drupal::service('entity.memory_cache');
    $entity_type_bundle_info = \Drupal::service('entity_type.bundle.info');

    $new = $entity->createDuplicate();
    $new->set('id', $entity->id());
    $new->set('uuid', $entity->uuid());
    $new->set('created', $entity->get('created')->value);
    $new->set('changed', $entity->get('changed')->value);

    $remoteStorage = new SqlContentEntityStorage(
      $entity->getEntityType(),
      $database,
      $entity_field_manager,
      $cache,
      $language_manager,
      $memory_cache,
      $entity_type_bundle_info,
      $entityManager
  );
    // If setting fields (id and uuid) that have a foreign keys it will fail with;
    // Integrity constraint violation error on a second save; so delete it first.
    $remoteStorage->delete([$entity]);
    $remoteStorage->save($new);
  }

}

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.