You are here

public function TermStorage::loadTree in Drupal 9

Same name and namespace in other branches
  1. 8 core/modules/taxonomy/src/TermStorage.php \Drupal\taxonomy\TermStorage::loadTree()
  2. 10 core/modules/taxonomy/src/TermStorage.php \Drupal\taxonomy\TermStorage::loadTree()

Finds all terms in a given vocabulary ID.

Parameters

string $vid: Vocabulary ID to retrieve terms for.

int $parent: The term ID under which to generate the tree. If 0, generate the tree for the entire vocabulary.

int $max_depth: The number of levels of the tree to return. Leave NULL to return all levels.

bool $load_entities: If TRUE, a full entity load will occur on the term objects. Otherwise they are partial objects queried directly from the {taxonomy_term_data} table to save execution time and memory consumption when listing large numbers of terms. Defaults to FALSE.

Return value

object[]|\Drupal\taxonomy\TermInterface[] An array of term objects that are the children of the vocabulary $vid.

Overrides TermStorageInterface::loadTree

File

core/modules/taxonomy/src/TermStorage.php, line 221

Class

TermStorage
Defines a Controller class for taxonomy terms.

Namespace

Drupal\taxonomy

Code

public function loadTree($vid, $parent = 0, $max_depth = NULL, $load_entities = FALSE) {
  $cache_key = implode(':', func_get_args());
  if (!isset($this->trees[$cache_key])) {

    // We cache trees, so it's not CPU-intensive to call on a term and its
    // children, too.
    if (!isset($this->treeChildren[$vid])) {
      $this->treeChildren[$vid] = [];
      $this->treeParents[$vid] = [];
      $this->treeTerms[$vid] = [];
      $query = $this->database
        ->select($this
        ->getDataTable(), 't');
      $query
        ->join('taxonomy_term__parent', 'p', '[t].[tid] = [p].[entity_id]');
      $query
        ->addExpression('[parent_target_id]', 'parent');
      $result = $query
        ->addTag('taxonomy_term_access')
        ->fields('t')
        ->condition('t.vid', $vid)
        ->condition('t.default_langcode', 1)
        ->orderBy('t.weight')
        ->orderBy('t.name')
        ->execute();
      foreach ($result as $term) {
        $this->treeChildren[$vid][$term->parent][] = $term->tid;
        $this->treeParents[$vid][$term->tid][] = $term->parent;
        $this->treeTerms[$vid][$term->tid] = $term;
      }
    }

    // Load full entities, if necessary. The entity controller statically
    // caches the results.
    $term_entities = [];
    if ($load_entities) {
      $term_entities = $this
        ->loadMultiple(array_keys($this->treeTerms[$vid]));
    }
    $max_depth = !isset($max_depth) ? count($this->treeChildren[$vid]) : $max_depth;
    $tree = [];

    // Keeps track of the parents we have to process, the last entry is used
    // for the next processing step.
    $process_parents = [];
    $process_parents[] = $parent;

    // Loops over the parent terms and adds its children to the tree array.
    // Uses a loop instead of a recursion, because it's more efficient.
    while (count($process_parents)) {
      $parent = array_pop($process_parents);

      // The number of parents determines the current depth.
      $depth = count($process_parents);
      if ($max_depth > $depth && !empty($this->treeChildren[$vid][$parent])) {
        $has_children = FALSE;
        $child = current($this->treeChildren[$vid][$parent]);
        do {
          if (empty($child)) {
            break;
          }
          $term = $load_entities ? $term_entities[$child] : $this->treeTerms[$vid][$child];
          if (isset($this->treeParents[$vid][$load_entities ? $term
            ->id() : $term->tid])) {

            // Clone the term so that the depth attribute remains correct
            // in the event of multiple parents.
            $term = clone $term;
          }
          $term->depth = $depth;
          if (!$load_entities) {
            unset($term->parent);
          }
          $tid = $load_entities ? $term
            ->id() : $term->tid;
          $term->parents = $this->treeParents[$vid][$tid];
          $tree[] = $term;
          if (!empty($this->treeChildren[$vid][$tid])) {
            $has_children = TRUE;

            // We have to continue with this parent later.
            $process_parents[] = $parent;

            // Use the current term as parent for the next iteration.
            $process_parents[] = $tid;

            // Reset pointers for child lists because we step in there more
            // often with multi parents.
            reset($this->treeChildren[$vid][$tid]);

            // Move pointer so that we get the correct term the next time.
            next($this->treeChildren[$vid][$parent]);
            break;
          }
        } while ($child = next($this->treeChildren[$vid][$parent]));
        if (!$has_children) {

          // We processed all terms in this hierarchy-level, reset pointer
          // so that this function works the next time it gets called.
          reset($this->treeChildren[$vid][$parent]);
        }
      }
    }
    $this->trees[$cache_key] = $tree;
  }
  return $this->trees[$cache_key];
}