diff --git a/core/modules/forum/src/Form/Overview.php b/core/modules/forum/src/Form/Overview.php index 98688ab..a7a32b7 100644 --- a/core/modules/forum/src/Form/Overview.php +++ b/core/modules/forum/src/Form/Overview.php @@ -37,7 +37,7 @@ class Overview extends OverviewTerms { * The module handler service. */ public function __construct(EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler) { - parent::__construct($module_handler); + parent::__construct($module_handler, $entity_manager); $this->entityManager = $entity_manager; } diff --git a/core/modules/forum/src/ForumManager.php b/core/modules/forum/src/ForumManager.php index 442d794..ecc9a7f 100644 --- a/core/modules/forum/src/ForumManager.php +++ b/core/modules/forum/src/ForumManager.php @@ -463,7 +463,7 @@ public function resetCache() { * {@inheritdoc} */ public function getParents($tid) { - return taxonomy_term_load_parents_all($tid); + return $this->entityManager->getStorage('taxonomy_term')->loadParentsAll($tid); } /** diff --git a/core/modules/taxonomy/src/Form/OverviewTerms.php b/core/modules/taxonomy/src/Form/OverviewTerms.php index 9099835..fd2365c 100644 --- a/core/modules/taxonomy/src/Form/OverviewTerms.php +++ b/core/modules/taxonomy/src/Form/OverviewTerms.php @@ -7,6 +7,7 @@ namespace Drupal\taxonomy\Form; +use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Form\FormBase; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Form\FormStateInterface; @@ -26,13 +27,23 @@ class OverviewTerms extends FormBase { protected $moduleHandler; /** + * The term storage controller. + * + * @var \Drupal\taxonomy\TermStorageInterface + */ + protected $storageController; + + /** * Constructs an OverviewTerms object. * * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler service. + * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager + * The entity manager service. */ - public function __construct(ModuleHandlerInterface $module_handler) { + public function __construct(ModuleHandlerInterface $module_handler, EntityManagerInterface $entity_manager) { $this->moduleHandler = $module_handler; + $this->storageController = $entity_manager->getStorage('taxonomy_term'); } /** @@ -40,7 +51,8 @@ public function __construct(ModuleHandlerInterface $module_handler) { */ public static function create(ContainerInterface $container) { return new static( - $container->get('module_handler') + $container->get('module_handler'), + $container->get('entity.manager') ); } @@ -97,9 +109,7 @@ public function buildForm(array $form, FormStateInterface $form_state, Vocabular $delta = 0; $term_deltas = array(); - // @todo taxonomy_get_tree needs to be converted to a service and injected. - // Will be fixed in http://drupal.org/node/1976298. - $tree = taxonomy_get_tree($taxonomy_vocabulary->id(), 0, NULL, TRUE); + $tree = $this->storageController->loadTree($taxonomy_vocabulary->id(), 0, NULL, TRUE); $tree_index = 0; do { // In case this tree is completely empty. diff --git a/core/modules/taxonomy/src/TermStorage.php b/core/modules/taxonomy/src/TermStorage.php index 345921c..a1b460e 100644 --- a/core/modules/taxonomy/src/TermStorage.php +++ b/core/modules/taxonomy/src/TermStorage.php @@ -17,6 +17,55 @@ class TermStorage extends ContentEntityDatabaseStorage implements TermStorageInterface { /** + * Array of loaded parents keyed by child term ID. + * + * @var array + */ + protected $parents = array(); + + /** + * Array of all loaded term ancestry keyed by ancestor term ID. + * + * @var array + */ + protected $parentsAll = array(); + + /** + * Array of child terms keyed by parent term ID. + * + * @var array + */ + protected $children = array(); + + /** + * Array of term parents keyed by vocabulary ID and child term ID. + * + * @var array + */ + protected $treeParents = array(); + + /** + * Array of term ancestors keyed by vocabulary ID and parent term ID. + * + * @var array + */ + protected $treeChildren = array(); + + /** + * Array of terms in a tree keyed by vocabulary ID and term ID. + * + * @var array + */ + protected $treeTerms = array(); + + /** + * Array of loaded trees keyed by a cache id matching tree arguments. + * + * @var array + */ + protected $trees = array(); + + /** * {@inheritdoc} * * @param array $values @@ -48,12 +97,12 @@ protected function buildPropertyQuery(QueryInterface $entity_query, array $value */ public function resetCache(array $ids = NULL) { drupal_static_reset('taxonomy_term_count_nodes'); - drupal_static_reset('taxonomy_get_tree'); - drupal_static_reset('taxonomy_get_tree:parents'); - drupal_static_reset('taxonomy_get_tree:terms'); - drupal_static_reset('taxonomy_term_load_parents'); - drupal_static_reset('taxonomy_term_load_parents_all'); - drupal_static_reset('taxonomy_term_load_children'); + $this->parents = array(); + $this->parentsAll = array(); + $this->children = array(); + $this->treeChildren = array(); + $this->treeParents = array(); + $this->treeTerms = array(); parent::resetCache($ids); } @@ -86,50 +135,165 @@ public function updateTermHierarchy(EntityInterface $term) { * {@inheritdoc} */ public function loadParents($tid) { - $query = $this->database->select('taxonomy_term_field_data', 't'); - $query->join('taxonomy_term_hierarchy', 'h', 'h.parent = t.tid'); - $query->addField('t', 'tid'); - $query->condition('h.tid', $tid); - $query->condition('t.default_langcode', 1); - $query->addTag('term_access'); - $query->orderBy('t.weight'); - $query->orderBy('t.name'); - return $query->execute()->fetchCol(); + if (!isset($this->parents[$tid])) { + $parents = array(); + $query = $this->database->select('taxonomy_term_field_data', 't'); + $query->join('taxonomy_term_hierarchy', 'h', 'h.parent = t.tid'); + $query->addField('t', 'tid'); + $query->condition('h.tid', $tid); + $query->condition('t.default_langcode', 1); + $query->addTag('term_access'); + $query->orderBy('t.weight'); + $query->orderBy('t.name'); + if ($ids = $query->execute()->fetchCol()) { + $parents = $this->loadMultiple($ids); + } + $this->parents[$tid] = $parents; + } + return $this->parents[$tid]; + } + + /** + * {@inheritdoc} + */ + public function loadParentsAll($tid) { + if (!isset($this->parentsAll[$tid])) { + $parents = array(); + if ($term = $this->load($tid)) { + $parents[] = $term; + $n = 0; + while ($parent = $this->loadParents($parents[$n]->id())) { + $parents = array_merge($parents, $parent); + $n++; + } + } + + $this->parentsAll[$tid] = $parents; + } + return $this->parentsAll[$tid]; } /** * {@inheritdoc} */ public function loadChildren($tid, $vid = NULL) { - $query = $this->database->select('taxonomy_term_field_data', 't'); - $query->join('taxonomy_term_hierarchy', 'h', 'h.tid = t.tid'); - $query->addField('t', 'tid'); - $query->condition('h.parent', $tid); - if ($vid) { - $query->condition('t.vid', $vid); + if (!isset($this->children[$tid])) { + $children = array(); + $query = $this->database->select('taxonomy_term_field_data', 't'); + $query->join('taxonomy_term_hierarchy', 'h', 'h.tid = t.tid'); + $query->addField('t', 'tid'); + $query->condition('h.parent', $tid); + if ($vid) { + $query->condition('t.vid', $vid); + } + $query->condition('t.default_langcode', 1); + $query->addTag('term_access'); + $query->orderBy('t.weight'); + $query->orderBy('t.name'); + if ($ids = $query->execute()->fetchCol()) { + $children = $this->loadMultiple($ids); + } + $this->children[$tid] = $children; } - $query->condition('t.default_langcode', 1); - $query->addTag('term_access'); - $query->orderBy('t.weight'); - $query->orderBy('t.name'); - return $query->execute()->fetchCol(); + return $this->children[$tid]; } /** * {@inheritdoc} */ - public function loadTree($vid) { - $query = $this->database->select('taxonomy_term_field_data', 't'); - $query->join('taxonomy_term_hierarchy', 'h', 'h.tid = t.tid'); - return $query - ->addTag('term_access') - ->fields('t') - ->fields('h', array('parent')) - ->condition('t.vid', $vid) - ->condition('t.default_langcode', 1) - ->orderBy('t.weight') - ->orderBy('t.name') - ->execute(); + 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] = array(); + $this->treeParents[$vid] = array(); + $this->treeTerms[$vid] = array(); + $query = $this->database->select('taxonomy_term_field_data', 't'); + $query->join('taxonomy_term_hierarchy', 'h', 'h.tid = t.tid'); + $result = $query + ->addTag('term_access') + ->fields('t') + ->fields('h', array('parent')) + ->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 = array(); + if ($load_entities) { + $term_entities = $this->loadMultiple(array_keys($this->treeTerms[$vid])); + } + + $max_depth = (!isset($max_depth)) ? count($this->treeChildren[$vid]) : $max_depth; + $tree = array(); + + // Keeps track of the parents we have to process, the last entry is used + // for the next processing step. + $process_parents = array(); + $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; + 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]; } /** @@ -288,4 +452,28 @@ public function getNodeTerms($nids, $vocabs = array(), $langcode = NULL) { return $terms; } + /** + * {@inheritdoc} + */ + public function __sleep() { + $vars = parent::__sleep(); + // Do not serialize static cache. + unset($vars['parents'], $vars['parentsAll'], $vars['children'], $vars['treeChildren'], $vars['treeParents'], $vars['treeTerms']); + return $vars; + } + + /** + * {@inheritdoc} + */ + public function __wakeup() { + parent::__wakeup(); + // Initialize static caches. + $this->parents = array(); + $this->parentsAll = array(); + $this->children = array(); + $this->treeChildren = array(); + $this->treeParents = array(); + $this->treeTerms = array(); + } + } diff --git a/core/modules/taxonomy/src/TermStorageInterface.php b/core/modules/taxonomy/src/TermStorageInterface.php index 7f53683..60c981e 100644 --- a/core/modules/taxonomy/src/TermStorageInterface.php +++ b/core/modules/taxonomy/src/TermStorageInterface.php @@ -37,12 +37,23 @@ public function updateTermHierarchy(EntityInterface $term); * @param int $tid * Term ID to retrieve parents for. * - * @return array + * @return \Drupal\taxonomy\TermInterface[] * An array of term objects which are the parents of the term $tid. */ public function loadParents($tid); /** + * Finds all ancestors of a given term ID. + * + * @param int $tid + * Term ID to retrieve ancestors for. + * + * @return \Drupal\taxonomy\TermInterface[] + * An array of term objects which are the ancestors of the term $tid. + */ + public function loadParentsAll($tid); + + /** * Finds all children of a term ID. * * @param int $tid @@ -50,7 +61,7 @@ public function loadParents($tid); * @param string $vid * An optional vocabulary ID to restrict the child search. * - * @return array + * @return \Drupal\taxonomy\TermInterface[] * An array of term objects that are the children of the term $tid. */ public function loadChildren($tid, $vid = NULL); @@ -60,11 +71,22 @@ public function loadChildren($tid, $vid = NULL); * * @param string $vid * Vocabulary ID to retrieve terms for. + * @param int $parent + * The term ID under which to generate the tree. If 0, generate the tree + * for the entire vocabulary. + * @param int $max_depth + * The number of levels of the tree to return. Leave NULL to return all + * levels. + * @param 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 array + * @return \Drupal\taxonomy\TermInterface[] * An array of term objects that are the children of the vocabulary $vid. */ - public function loadTree($vid); + public function loadTree($vid, $parent = 0, $max_depth = NULL, $load_entities = FALSE); /** * Count the number of nodes in a given vocabulary ID. diff --git a/core/modules/taxonomy/src/Tests/TaxonomyTermIndentationTest.php b/core/modules/taxonomy/src/Tests/TaxonomyTermIndentationTest.php index c1abffc..1952b5d 100644 --- a/core/modules/taxonomy/src/Tests/TaxonomyTermIndentationTest.php +++ b/core/modules/taxonomy/src/Tests/TaxonomyTermIndentationTest.php @@ -66,7 +66,7 @@ function testTermIndentation() { $this->assertNoPattern('|
 
|'); // Check explicitly that term 2 has no parents. - drupal_static_reset(); + \Drupal::entityManager()->getStorage('taxonomy_term')->resetCache(); $parents = taxonomy_term_load_parents($term2->id()); $this->assertTrue(empty($parents), 'Term 2 has no parents now'); } diff --git a/core/modules/taxonomy/src/Tests/TermTest.php b/core/modules/taxonomy/src/Tests/TermTest.php index 4f7c58d..c6a8ccc 100644 --- a/core/modules/taxonomy/src/Tests/TermTest.php +++ b/core/modules/taxonomy/src/Tests/TermTest.php @@ -432,9 +432,7 @@ function testTermReorder() { // Fetch the created terms in the default alphabetical order, i.e. term1 // precedes term2 alphabetically, and term2 precedes term3. - drupal_static_reset('taxonomy_get_tree'); - drupal_static_reset('taxonomy_get_treeparent'); - drupal_static_reset('taxonomy_get_treeterms'); + \Drupal::entityManager()->getStorage('taxonomy_term')->resetCache(); list($term1, $term2, $term3) = taxonomy_get_tree($this->vocabulary->id(), 0, NULL, TRUE); $this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview'); @@ -459,9 +457,7 @@ function testTermReorder() { ); $this->drupalPostForm(NULL, $edit, t('Save')); - drupal_static_reset('taxonomy_get_tree'); - drupal_static_reset('taxonomy_get_treeparent'); - drupal_static_reset('taxonomy_get_treeterms'); + \Drupal::entityManager()->getStorage('taxonomy_term')->resetCache(); $terms = taxonomy_get_tree($this->vocabulary->id()); $this->assertEqual($terms[0]->tid, $term2->id(), 'Term 2 was moved above term 1.'); $this->assertEqual($terms[1]->parents, array($term2->id()), 'Term 3 was made a child of term 2.'); @@ -473,9 +469,6 @@ function testTermReorder() { // Ensure form redirected back to overview. $this->assertUrl('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview'); - drupal_static_reset('taxonomy_get_tree'); - drupal_static_reset('taxonomy_get_treeparent'); - drupal_static_reset('taxonomy_get_treeterms'); $terms = taxonomy_get_tree($this->vocabulary->id(), 0, NULL, TRUE); $this->assertEqual($terms[0]->id(), $term1->id(), 'Term 1 was moved to back above term 2.'); $this->assertEqual($terms[1]->id(), $term2->id(), 'Term 2 was moved to back below term 1.'); diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module index d58064d..59e0f85 100644 --- a/core/modules/taxonomy/taxonomy.module +++ b/core/modules/taxonomy/taxonomy.module @@ -386,41 +386,22 @@ function taxonomy_vocabulary_get_names() { * @return * An array of term objects which are the parents of the term $tid, or an * empty array if parents are not found. + * + * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0. + * Use \Drupal\taxonomy\TermStorageController::loadParents() */ function taxonomy_term_load_parents($tid) { - $parents = &drupal_static(__FUNCTION__, array()); - - if ($tid && !isset($parents[$tid])) { - $tids = \Drupal::entityManager()->getStorage('taxonomy_term')->loadParents($tid); - $parents[$tid] = entity_load_multiple('taxonomy_term', $tids); - } - - return isset($parents[$tid]) ? $parents[$tid] : array(); + return \Drupal::entityManager()->getStorage('taxonomy_term')->loadParents($tid); } /** * Find all ancestors of a given term ID. + * + * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0. + * Use \Drupal\taxonomy\TermStorageController::loadParentsAll() */ function taxonomy_term_load_parents_all($tid) { - $cache = &drupal_static(__FUNCTION__, array()); - - if (isset($cache[$tid])) { - return $cache[$tid]; - } - - $parents = array(); - if ($term = entity_load('taxonomy_term', $tid)) { - $parents[] = $term; - $n = 0; - while ($parent = taxonomy_term_load_parents($parents[$n]->id())) { - $parents = array_merge($parents, $parent); - $n++; - } - } - - $cache[$tid] = $parents; - - return $parents; + return \Drupal::entityManager()->getStorage('taxonomy_term')->loadParentsAll($tid); } /** @@ -432,16 +413,12 @@ function taxonomy_term_load_parents_all($tid) { * @return * An array of term objects that are the children of the term $tid, or an * empty array when no children exist. + * + * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0. + * Use \Drupal\taxonomy\TermStorageController::loadChildren() */ function taxonomy_term_load_children($tid) { - $children = &drupal_static(__FUNCTION__, array()); - - if ($tid && !isset($children[$tid])) { - $tids = \Drupal::entityManager()->getStorage('taxonomy_term')->loadChildren($tid); - $children[$tid] = entity_load_multiple('taxonomy_term', $tids); - } - - return isset($children[$tid]) ? $children[$tid] : array(); + return \Drupal::entityManager()->getStorage('taxonomy_term')->loadChildren($tid); } /** @@ -465,91 +442,12 @@ function taxonomy_term_load_children($tid) { * to have "depth" and "parents" attributes in addition to its normal ones. * Results are statically cached. Term objects will be partial or complete * depending on the $load_entities parameter. + * + * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0. + * Use \Drupal\taxonomy\TermStorageController::loadTree() */ function taxonomy_get_tree($vid, $parent = 0, $max_depth = NULL, $load_entities = FALSE) { - $children = &drupal_static(__FUNCTION__, array()); - $parents = &drupal_static(__FUNCTION__ . ':parents', array()); - $terms = &drupal_static(__FUNCTION__ . ':terms', array()); - - // We cache trees, so it's not CPU-intensive to call taxonomy_get_tree() on a - // term and its children, too. - if (!isset($children[$vid])) { - $children[$vid] = array(); - $parents[$vid] = array(); - $terms[$vid] = array(); - - $result = \Drupal::entityManager()->getStorage('taxonomy_term')->loadTree($vid); - - foreach ($result as $term) { - $children[$vid][$term->parent][] = $term->tid; - $parents[$vid][$term->tid][] = $term->parent; - $terms[$vid][$term->tid] = $term; - } - } - - // Load full entities, if necessary. The entity storage caches the results. - if ($load_entities) { - $term_entities = entity_load_multiple('taxonomy_term', array_keys($terms[$vid])); - } - - $max_depth = (!isset($max_depth)) ? count($children[$vid]) : $max_depth; - $tree = array(); - - // Keeps track of the parents we have to process, the last entry is used - // for the next processing step. - $process_parents = array(); - $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($children[$vid][$parent])) { - $has_children = FALSE; - $child = current($children[$vid][$parent]); - do { - if (empty($child)) { - break; - } - $term = $load_entities ? $term_entities[$child] : $terms[$vid][$child]; - if (isset($parents[$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; - unset($term->parent); - $tid = $load_entities ? $term->id() : $term->tid; - $term->parents = $parents[$vid][$tid]; - $tree[] = $term; - if (!empty($children[$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($children[$vid][$tid]); - // Move pointer so that we get the correct term the next time. - next($children[$vid][$parent]); - break; - } - } while ($child = next($children[$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($children[$vid][$parent]); - } - } - } - - return $tree; + return \Drupal::entityManager()->getStorage('taxonomy_term')->loadTree($vid, $parent, $max_depth, $load_entities); } /**