You are here

class TreeRebuilder in Entity Reference Hierarchy 3.x

Same name and namespace in other branches
  1. 8.2 src/Storage/TreeRebuilder.php \Drupal\entity_hierarchy\Storage\TreeRebuilder

Defines a class for rebuilding the tree.

Hierarchy

Expanded class hierarchy of TreeRebuilder

1 file declares its use of TreeRebuilder
TreeRebuilderUnitTest.php in tests/src/Unit/TreeRebuilderUnitTest.php
1 string reference to 'TreeRebuilder'
entity_hierarchy.services.yml in ./entity_hierarchy.services.yml
entity_hierarchy.services.yml
1 service uses TreeRebuilder
entity_hierarchy.tree_rebuilder in ./entity_hierarchy.services.yml
Drupal\entity_hierarchy\Storage\TreeRebuilder

File

src/Storage/TreeRebuilder.php, line 12

Namespace

Drupal\entity_hierarchy\Storage
View source
class TreeRebuilder {

  /**
   * Entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

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

  /**
   * Gets rebuild tasks suitable for usage with batch_set().
   *
   * @param string $field_name
   *   Field name to rebuild.
   * @param string $entity_type_id
   *   Entity Type to rebuild.
   *
   * @return array
   *   Batch definition.
   */
  public function getRebuildTasks($field_name, $entity_type_id) {
    $batch = [
      'title' => new TranslatableMarkup('Rebuilding tree for field @field, @entity_type_id ...', [
        '@field' => $field_name,
        '@entity_type_id' => $entity_type_id,
      ]),
      'operations' => [
        [
          [
            static::class,
            'removeTable',
          ],
          [
            $field_name,
            $entity_type_id,
          ],
        ],
      ],
      'finished' => [
        static::class,
        'batchFinished',
      ],
    ];
    $entityType = $this->entityTypeManager
      ->getDefinition($entity_type_id);
    $idKey = $entityType
      ->getKey('id');
    $query = $this->entityTypeManager
      ->getStorage($entity_type_id)
      ->getAggregateQuery()
      ->groupBy("{$field_name}.target_id")
      ->groupBy("{$field_name}.weight")
      ->groupBy($idKey)
      ->sort("{$field_name}.target_id")
      ->sort("{$field_name}.weight")
      ->exists($field_name)
      ->accessCheck(FALSE);
    $records = $query
      ->execute();
    $sorted = $this
      ->treeSort($field_name, $records, $idKey);
    foreach ($sorted as $entity_id => $entry) {
      $batch['operations'][] = [
        [
          static::class,
          'rebuildTree',
        ],
        [
          $field_name,
          $entity_type_id,
          $entity_id,
        ],
      ];
    }
    return $batch;
  }

  /**
   * Batch callback to remove table.
   *
   * @param string $field_name
   *   Field name.
   * @param string $entity_type_id
   *   Entity Type ID.
   */
  public static function removeTable($field_name, $entity_type_id) {
    \Drupal::database()
      ->schema()
      ->dropTable(\Drupal::service('entity_hierarchy.nested_set_storage_factory')
      ->getTableName($field_name, $entity_type_id, FALSE));
  }

  /**
   * Sort callback.
   *
   * @param array $a
   *   Item.
   * @param array $b
   *   Item.
   *
   * @return int
   *   Sort order.
   */
  protected function sortItems(array $a, array $b) {
    $a_path = (string) $a['materialized_path'];
    $b_path = (string) $b['materialized_path'];
    if ($a_path === $b_path) {
      return 0;
    }
    return $a_path < $b_path ? -1 : 1;
  }

  /**
   * Batch callback to rebuild the tree.
   *
   * @param string $field_name
   *   Field name.
   * @param string $entity_type_id
   *   Entity type ID.
   * @param mixed $entity_id
   *   Entity ID.
   * @param array $context
   *   Batch context.
   */

  //@codingStandardsIgnoreStart
  public static function rebuildTree($field_name, $entity_type_id, $entity_id, &$context) {

    //@codingStandardsIgnoreEnd
    $entity = \Drupal::entityTypeManager()
      ->getStorage($entity_type_id)
      ->load($entity_id);
    $entity
      ->get($field_name)
      ->postSave(TRUE);
    self::debug(sprintf('Rebuilt %s', $entity_id));
    $context['results'][] = $entity_id;
  }

  /**
   * Finished callback.
   *
   * @param bool $success
   *   TRUE if succeeded.
   * @param int $results
   *   Results.
   * @param array $operations
   *   Operations.
   */

  //@codingStandardsIgnoreStart
  public static function batchFinished($success, $results, $operations) {

    //@codingStandardsIgnoreEnd
    if ($success) {

      // Here we do something meaningful with the results.
      $message = new TranslatableMarkup('Finished rebuilding tree, @count items were processed.', [
        '@count' => count($results),
      ]);
      \Drupal::messenger()
        ->addMessage($message);
    }
    else {

      // An error occurred.
      // $operations contains the operations that remained unprocessed.
      $error_operation = reset($operations);
      $message = new TranslatableMarkup('An error occurred while processing %error_operation with arguments: @arguments', [
        '%error_operation' => implode('::', $error_operation[0]),
        '@arguments' => print_r($error_operation[1], TRUE),
      ]);
      \Drupal::messenger()
        ->addMessage($message, 'error');
    }
  }

  /**
   * Sorts tree.
   *
   * @param string $field_name
   *   Field name of parent field.
   * @param array $records
   *   Records to sort.
   * @param string $idKey
   *   Field name of ID.
   *
   * @return array
   *   Sorted records.
   */
  protected function treeSort($field_name, array $records, $idKey) {
    $items = [];
    $weights = [];
    $sets = [];
    foreach ($records as $ix => $item) {
      $parent = $item["{$field_name}_target_id"];
      $sets[$parent][] = $item[$idKey];
      $items[$item[$idKey]] = $parent;
    }

    // Add in root items.
    foreach (array_keys($sets) as $parent) {
      if (!isset($items[$parent])) {
        $items[$parent] = 0;
        $sets[0][] = $parent;
      }
    }
    $flipped_sets = array_map(function (array $items) {
      return array_flip($items);
    }, $sets);
    foreach ($items as $id => $parent) {
      $flipped = $flipped_sets[$parent];
      if (isset($weights[$id])) {

        // We've already done this one via a child.
        continue;
      }
      $weights[$id] = [
        $flipped[$id],
      ];
      if (!isset($weights[$parent]) && isset($items[$parent])) {
        $this
          ->buildThread($weights, $items, $parent, $items[$parent], $flipped_sets);
      }
      if (isset($weights[$parent])) {
        $weights[$id] = array_merge($weights[$parent], $weights[$id]);
      }
    }
    $sorted = array_map(function (array $item) {
      return [
        'materialized_path' => implode('.', array_map([
          Number::class,
          'intToAlphadecimal',
        ], $item)),
      ];
    }, $weights);

    // Sort.
    uasort($sorted, [
      $this,
      'sortItems',
    ]);

    // Remove root items.
    return array_diff_key($sorted, array_flip($sets[0]));
  }

  /**
   * Build thread for a given item ID and parent.
   *
   * @param array $weights
   *   Existing thread weights.
   * @param array $items
   *   All items.
   * @param int $id
   *   Item ID.
   * @param int $parent
   *   Parent ID.
   * @param array $flipped_sets
   *   Items grouped by parent ID, ordered by weight.
   */
  protected function buildThread(array &$weights, array $items, $id, $parent, array $flipped_sets) {
    $flipped = $flipped_sets[$parent];
    $weights[$id] = [
      $flipped[$id],
    ];
    if (isset($items[$parent])) {
      $next_parent = $items[$parent];
      $flipped = $flipped_sets[$next_parent];
      $weights[$parent] = [
        $flipped[$parent],
      ];
      if (!isset($weights[$next_parent]) && isset($items[$next_parent])) {
        $this
          ->buildThread($weights, $items, $next_parent, $items[$next_parent], $flipped_sets);
      }
      if (isset($weights[$next_parent])) {
        $weights[$parent] = array_merge($weights[$next_parent], $weights[$parent]);
      }
      $weights[$id] = array_merge($weights[$parent], $weights[$id]);
    }
  }

  /**
   * Outputs a debug message.
   *
   * @param string $message
   *   Message to output.
   */
  protected static function debug($message) {
    \Drupal::logger($message);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
TreeRebuilder::$entityTypeManager protected property Entity type manager.
TreeRebuilder::batchFinished public static function
TreeRebuilder::buildThread protected function Build thread for a given item ID and parent.
TreeRebuilder::debug protected static function Outputs a debug message.
TreeRebuilder::getRebuildTasks public function Gets rebuild tasks suitable for usage with batch_set().
TreeRebuilder::rebuildTree public static function
TreeRebuilder::removeTable public static function Batch callback to remove table.
TreeRebuilder::sortItems protected function Sort callback.
TreeRebuilder::treeSort protected function Sorts tree.
TreeRebuilder::__construct public function Constructs a new TreeRebuilder object.