diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index de9f4e7e02ba2b42b80f564163b058dae5107161..b6f10263d1da2469394340cc85205ea19ab87844 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -627,13 +627,20 @@ function &drupal_static($name, $default_value = NULL, $reset = FALSE) { function drupal_static_reset($name = NULL) { switch ($name) { case 'taxonomy_vocabulary_get_names': - @trigger_error("Using drupal_static_reset() with 'taxonomy_vocabulary_get_names' as parameter is deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. There is no replacement for this usage. See https://www.drupal.org/node/3039041", E_USER_DEPRECATED); + @trigger_error("Calling drupal_static_reset() with 'taxonomy_vocabulary_get_names' as argument is deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. There is no replacement for this usage. See https://www.drupal.org/node/3039041", E_USER_DEPRECATED); break; case 'node_mark': @trigger_error("Calling drupal_static_reset() with 'node_mark' as argument is deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. There is no replacement for this usage. See https://www.drupal.org/node/3037203", E_USER_DEPRECATED); break; + case 'Drupal\book\BookManager::bookSubtreeData': + case 'Drupal\book\BookManager::bookTreeAllData': + case 'Drupal\book\BookManager::doBookTreeBuild': + @trigger_error("Calling drupal_static_reset() with '{$name}' as argument is deprecated in drupal:9.3.0 and is removed in drupal:10.0.0. Use \Drupal::service('book.memory_cache')->deleteAll() instead. See https://www.drupal.org/node/3039439", E_USER_DEPRECATED); + \Drupal::service('book.memory_cache')->deleteAll(); + break; + } drupal_static($name, NULL, TRUE); } diff --git a/core/modules/book/book.services.yml b/core/modules/book/book.services.yml index ab23275144ee42fca4e5a12e8c4c542e07258bd4..63bd10b6c63c6e5a8a5a5a9e4e987fb289651d9e 100644 --- a/core/modules/book/book.services.yml +++ b/core/modules/book/book.services.yml @@ -6,7 +6,7 @@ services: - { name: breadcrumb_builder, priority: 701 } book.manager: class: Drupal\book\BookManager - arguments: ['@entity_type.manager', '@string_translation', '@config.factory', '@book.outline_storage', '@renderer', '@language_manager', '@entity.repository'] + arguments: ['@entity_type.manager', '@string_translation', '@config.factory', '@book.outline_storage', '@renderer', '@language_manager', '@entity.repository', '@book.backend_chained_cache', '@book.memory_cache'] book.outline: class: Drupal\book\BookOutline arguments: ['@book.manager'] @@ -37,3 +37,14 @@ services: - { name: module_install.uninstall_validator } arguments: ['@book.outline_storage', '@entity_type.manager', '@string_translation'] lazy: true + book.memory_cache: + class: Drupal\Core\Cache\MemoryCache\MemoryCache + book.backend_chained_cache: + class: Drupal\Core\Cache\BackendChain + calls: + - [appendBackend, ['@book.memory_cache']] + - [appendBackend, ['@cache.data']] + tags: + # This tag ensures that Drupal's cache_tags.invalidator service + # invalidates also this cache data. + - { name: cache.bin } diff --git a/core/modules/book/src/BookManager.php b/core/modules/book/src/BookManager.php index b0cb5976300dc436140b4d18203161a3bcaf8000..0edcdf08440ebcacf3454bb10c63d17714473fd3 100644 --- a/core/modules/book/src/BookManager.php +++ b/core/modules/book/src/BookManager.php @@ -4,6 +4,7 @@ use Drupal\Component\Utility\Unicode; use Drupal\Core\Cache\Cache; +use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Entity\EntityRepositoryInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormStateInterface; @@ -85,6 +86,20 @@ class BookManager implements BookManagerInterface { */ protected $languageManager; + /** + * The book chained backend cache service. + * + * @var \Drupal\Core\Cache\CacheBackendInterface + */ + protected $backendChainedCache; + + /** + * The book memory cache service. + * + * @var \Drupal\Core\Cache\CacheBackendInterface + */ + protected $memoryCache; + /** * Constructs a BookManager object. * @@ -102,8 +117,12 @@ class BookManager implements BookManagerInterface { * The language manager. * @param \Drupal\Core\Entity\EntityRepositoryInterface|null $entity_repository * The entity repository service. + * @param \Drupal\Core\Cache\CacheBackendInterface $backend_chained_cache + * The book chained backend cache service. + * @param \Drupal\Core\Cache\CacheBackendInterface $memory_cache + * The book memory cache service. */ - public function __construct(EntityTypeManagerInterface $entity_type_manager, TranslationInterface $translation, ConfigFactoryInterface $config_factory, BookOutlineStorageInterface $book_outline_storage, RendererInterface $renderer, LanguageManagerInterface $language_manager = NULL, EntityRepositoryInterface $entity_repository = NULL) { + public function __construct(EntityTypeManagerInterface $entity_type_manager, TranslationInterface $translation, ConfigFactoryInterface $config_factory, BookOutlineStorageInterface $book_outline_storage, RendererInterface $renderer, LanguageManagerInterface $language_manager = NULL, EntityRepositoryInterface $entity_repository = NULL, CacheBackendInterface $backend_chained_cache = NULL, CacheBackendInterface $memory_cache = NULL) { $this->entityTypeManager = $entity_type_manager; $this->stringTranslation = $translation; $this->configFactory = $config_factory; @@ -119,6 +138,16 @@ public function __construct(EntityTypeManagerInterface $entity_type_manager, Tra $entity_repository = \Drupal::service('entity.repository'); } $this->entityRepository = $entity_repository; + if (!$backend_chained_cache) { + @trigger_error('Calling BookManager::__construct() without the $backend_chained_cache argument is deprecated in drupal:9.3.0 and the $backend_chained_cache argument will be required in drupal:10.0.0. See https://www.drupal.org/node/3039439', E_USER_DEPRECATED); + $backend_chained_cache = \Drupal::service('book.backend_chained_cache'); + } + $this->backendChainedCache = $backend_chained_cache; + if (!$memory_cache) { + @trigger_error('Calling BookManager::__construct() without the $memory_cache argument is deprecated in drupal:9.3.0 and the $memory_cache argument will be required in drupal:10.0.0. See https://www.drupal.org/node/3039439', E_USER_DEPRECATED); + $memory_cache = \Drupal::service('book.memory_cache'); + } + $this->memoryCache = $memory_cache; } /** @@ -510,34 +539,37 @@ public function deleteFromBook($nid) { * {@inheritdoc} */ public function bookTreeAllData($bid, $link = NULL, $max_depth = NULL) { - $tree = &drupal_static(__METHOD__, []); - - // Use $nid as a flag for whether the data being loaded is for the whole - // tree. + // Use $nid as flag for whether the data being loaded is for the whole tree. $nid = isset($link['nid']) ? $link['nid'] : 0; - // Generate a cache ID (cid) specific for this $bid, $link, language, and - // depth. $langcode = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId(); - $cid = implode(':', ['book-links', $bid, 'all', $nid, $langcode, (int) $max_depth]); - if (!isset($tree[$cid])) { - // If the tree data was not in the static cache, build $tree_parameters. - $tree_parameters = [ - 'min_depth' => 1, - 'max_depth' => $max_depth, - ]; - if ($nid) { - $active_trail = $this->getActiveTrailIds($bid, $link); - $tree_parameters['expanded'] = $active_trail; - $tree_parameters['active_trail'] = $active_trail; - $tree_parameters['active_trail'][] = $nid; - } + // Create a cache ID for the given $nid, $link, $langcode and $max_depth. + $cid = implode(':', ['book-links', $bid, $nid, $langcode, (int) $max_depth]); + + // Get it from cache, if available. + if ($cache = $this->memoryCache->get($cid)) { + return $cache->data; + } - // Build the tree using the parameters; the resulting tree will be cached. - $tree[$cid] = $this->bookTreeBuild($bid, $tree_parameters); + // If the tree data was not in the static cache, build $tree_parameters. + $tree_parameters = [ + 'min_depth' => 1, + 'max_depth' => $max_depth, + ]; + if ($nid) { + $active_trail = $this->getActiveTrailIds($bid, $link); + $tree_parameters['expanded'] = $active_trail; + $tree_parameters['active_trail'] = $active_trail; + $tree_parameters['active_trail'][] = $nid; } - return $tree[$cid]; + // Build the tree using the parameters. + $tree_build = $this->bookTreeBuild($bid, $tree_parameters); + + // Cache the tree build in memory. + $this->memoryCache->set($cid, $tree_build); + + return $tree_build; } /** @@ -701,46 +733,37 @@ protected function bookTreeBuild($bid, array $parameters = []) { * @see \Drupal\book\BookOutlineStorageInterface::getBookMenuTree() */ protected function doBookTreeBuild($bid, array $parameters = []) { - // Static cache of already built menu trees. - $trees = &drupal_static(__METHOD__, []); - // Build the cache id; sort parents to prevent duplicate storage and remove // default parameter values. if (isset($parameters['expanded'])) { sort($parameters['expanded']); } $langcode = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId(); - $tree_cid = implode(':', ['book-links', $bid, 'tree-data', $langcode, hash('sha256', serialize($parameters))]); + $cid = implode(':', ['book-links', $bid, 'tree-data', $langcode, hash('sha256', serialize($parameters))]); - // If we do not have this tree in the static cache, check {cache_data}. - if (!isset($trees[$tree_cid])) { - $cache = \Drupal::cache('data')->get($tree_cid); - if ($cache && $cache->data) { - $trees[$tree_cid] = $cache->data; - } + // Get it from cache, if available. + if ($cache = $this->backendChainedCache->get($cid)) { + return $cache->data; } - if (!isset($trees[$tree_cid])) { - $min_depth = (isset($parameters['min_depth']) ? $parameters['min_depth'] : 1); - $result = $this->bookOutlineStorage->getBookMenuTree($bid, $parameters, $min_depth, static::BOOK_MAX_DEPTH); + $min_depth = (isset($parameters['min_depth']) ? $parameters['min_depth'] : 1); + $result = $this->bookOutlineStorage->getBookMenuTree($bid, $parameters, $min_depth, static::BOOK_MAX_DEPTH); - // Build an ordered array of links using the query result object. - $links = []; - foreach ($result as $link) { - $link = (array) $link; - $links[$link['nid']] = $link; - } - $active_trail = (isset($parameters['active_trail']) ? $parameters['active_trail'] : []); - $data['tree'] = $this->buildBookOutlineData($links, $active_trail, $min_depth); - $data['node_links'] = []; - $this->bookTreeCollectNodeLinks($data['tree'], $data['node_links']); - - // Cache the data, if it is not already in the cache. - \Drupal::cache('data')->set($tree_cid, $data, Cache::PERMANENT, ['bid:' . $bid]); - $trees[$tree_cid] = $data; + // Build an ordered array of links using the query result object. + $links = []; + foreach ($result as $link) { + $link = (array) $link; + $links[$link['nid']] = $link; } + $active_trail = (isset($parameters['active_trail']) ? $parameters['active_trail'] : []); + $data['tree'] = $this->buildBookOutlineData($links, $active_trail, $min_depth); + $data['node_links'] = []; + $this->bookTreeCollectNodeLinks($data['tree'], $data['node_links']); + + // Cache tree data. + $this->backendChainedCache->set($cid, $data, Cache::PERMANENT, ['bid:' . $bid]); - return $trees[$tree_cid]; + return $data; } /** @@ -1135,50 +1158,29 @@ protected function buildBookOutlineRecursive(&$links, $parents, $depth) { * {@inheritdoc} */ public function bookSubtreeData($link) { - $tree = &drupal_static(__METHOD__, []); - // Generate a cache ID (cid) specific for this $link. - $cid = 'book-links:subtree-cid:' . $link['nid']; - - if (!isset($tree[$cid])) { - $tree_cid_cache = \Drupal::cache('data')->get($cid); + $cid = "book-links:subtree-data:{$link['nid']}"; + // Get it from cache, if available. + if ($cache = $this->backendChainedCache->get($cid)) { + return $cache->data; + } - if ($tree_cid_cache && $tree_cid_cache->data) { - // If the cache entry exists, it will just be the cid for the actual - // data. This avoids duplication of large amounts of data. - $cache = \Drupal::cache('data')->get($tree_cid_cache->data); + $result = $this->bookOutlineStorage->getBookSubtree($link, static::BOOK_MAX_DEPTH); - if ($cache && isset($cache->data)) { - $data = $cache->data; - } - } - - // If the subtree data was not in the cache, $data will be NULL. - if (!isset($data)) { - $result = $this->bookOutlineStorage->getBookSubtree($link, static::BOOK_MAX_DEPTH); - $links = []; - foreach ($result as $item) { - $links[] = $item; - } - $data['tree'] = $this->buildBookOutlineData($links, [], $link['depth']); - $data['node_links'] = []; - $this->bookTreeCollectNodeLinks($data['tree'], $data['node_links']); - // Compute the real cid for book subtree data. - $tree_cid = 'book-links:subtree-data:' . hash('sha256', serialize($data)); - // Cache the data, if it is not already in the cache. - if (!\Drupal::cache('data')->get($tree_cid)) { - \Drupal::cache('data')->set($tree_cid, $data, Cache::PERMANENT, ['bid:' . $link['bid']]); - } - // Cache the cid of the (shared) data using the book and item-specific - // cid. - \Drupal::cache('data')->set($cid, $tree_cid, Cache::PERMANENT, ['bid:' . $link['bid']]); - } - // Check access for the current user to each item in the tree. - $this->bookTreeCheckAccess($data['tree'], $data['node_links']); - $tree[$cid] = $data['tree']; + $links = []; + foreach ($result as $item) { + $links[] = $item; } + $data['tree'] = $this->buildBookOutlineData($links, [], $link['depth']); + $data['node_links'] = []; + $this->bookTreeCollectNodeLinks($data['tree'], $data['node_links']); + // Check access for the current user to each item in the tree. + $this->bookTreeCheckAccess($data['tree'], $data['node_links']); - return $tree[$cid]; + // Cache subtree data. + $this->backendChainedCache->set($cid, $data['tree'], Cache::PERMANENT, ['bid:' . $link['bid']]); + + return $data['tree']; } } diff --git a/core/modules/book/tests/src/Kernel/BookManagerDeprecationTest.php b/core/modules/book/tests/src/Kernel/BookManagerDeprecationTest.php new file mode 100644 index 0000000000000000000000000000000000000000..d1248c9ea02c906609b8a94243c6e836646e73c6 --- /dev/null +++ b/core/modules/book/tests/src/Kernel/BookManagerDeprecationTest.php @@ -0,0 +1,62 @@ +<?php + +namespace Drupal\Tests\book\Kernel; + +use Drupal\book\BookManager; +use Drupal\KernelTests\KernelTestBase; + +/** + * @coversDefaultClass \Drupal\book\BookManager + * @group legacy + */ +class BookManagerDeprecationTest extends KernelTestBase { + + /** + * {@inheritdoc} + */ + protected static $modules = ['book']; + + /** + * @param string $method + * Method to be tested. + * + * @dataProvider providerTestDrupalStaticResetDeprecation + * @see drupal_static_reset() + */ + public function testDrupalStaticResetDeprecation(string $method): void { + $this->expectDeprecation("Calling drupal_static_reset() with '{$method}' as argument is deprecated in drupal:9.3.0 and is removed in drupal:10.0.0. Use \Drupal::service('book.memory_cache')->deleteAll() instead. See https://www.drupal.org/node/3039439"); + drupal_static_reset($method); + } + + /** + * Provides test cases for ::testDrupalStaticResetDeprecation(). + * + * @return string[][] + * Test cases for ::testDrupalStaticResetDeprecation(). + */ + public function providerTestDrupalStaticResetDeprecation(): array { + return [ + ['Drupal\book\BookManager::bookSubtreeData'], + ['Drupal\book\BookManager::bookTreeAllData'], + ['Drupal\book\BookManager::doBookTreeBuild'], + ]; + } + + /** + * @covers ::__construct + */ + public function testOptionalParametersDeprecation(): void { + $this->expectDeprecation('Calling BookManager::__construct() without the $backend_chained_cache argument is deprecated in drupal:9.3.0 and the $backend_chained_cache argument will be required in drupal:10.0.0. See https://www.drupal.org/node/3039439'); + $this->expectDeprecation('Calling BookManager::__construct() without the $memory_cache argument is deprecated in drupal:9.3.0 and the $memory_cache argument will be required in drupal:10.0.0. See https://www.drupal.org/node/3039439'); + new BookManager( + $this->container->get('entity_type.manager'), + $this->container->get('string_translation'), + $this->container->get('config.factory'), + $this->container->get('book.outline_storage'), + $this->container->get('renderer'), + $this->container->get('language_manager'), + $this->container->get('entity.repository') + ); + } + +} diff --git a/core/modules/book/tests/src/Unit/BookManagerTest.php b/core/modules/book/tests/src/Unit/BookManagerTest.php index d452c6ec20125c83fd042972ba23f3d9b71652e9..8634985c7d38348d2df7081906fbafa5c8ece9b1 100644 --- a/core/modules/book/tests/src/Unit/BookManagerTest.php +++ b/core/modules/book/tests/src/Unit/BookManagerTest.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\book\Unit; use Drupal\book\BookManager; +use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Tests\UnitTestCase; @@ -79,7 +80,9 @@ protected function setUp(): void { $this->renderer = $this->createMock('\Drupal\Core\Render\RendererInterface'); $this->languageManager = $this->createMock('Drupal\Core\Language\LanguageManagerInterface'); $this->entityRepository = $this->createMock('Drupal\Core\Entity\EntityRepositoryInterface'); - $this->bookManager = new BookManager($this->entityTypeManager, $this->translation, $this->configFactory, $this->bookOutlineStorage, $this->renderer, $this->languageManager, $this->entityRepository); + // Used for both book manager cache services: backend chain and memory. + $cache = $this->createMock(CacheBackendInterface::class); + $this->bookManager = new BookManager($this->entityTypeManager, $this->translation, $this->configFactory, $this->bookOutlineStorage, $this->renderer, $this->languageManager, $this->entityRepository, $cache, $cache); } /** diff --git a/core/modules/taxonomy/tests/src/Kernel/TaxonomyDeprecationTest.php b/core/modules/taxonomy/tests/src/Kernel/TaxonomyDeprecationTest.php index e7eabda1307494c67cd87e7b6e97548635a70777..5080f4d8ffbee1a335c04bd163f0075ba46f85b3 100644 --- a/core/modules/taxonomy/tests/src/Kernel/TaxonomyDeprecationTest.php +++ b/core/modules/taxonomy/tests/src/Kernel/TaxonomyDeprecationTest.php @@ -60,7 +60,7 @@ public function testTaxonomyDeprecations(): void { $title = taxonomy_term_title($term1); $this->assertSame($term1->label(), $title); - $this->expectDeprecation("Using drupal_static_reset() with 'taxonomy_vocabulary_get_names' as parameter is deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. There is no replacement for this usage. See https://www.drupal.org/node/3039041"); + $this->expectDeprecation("Calling drupal_static_reset() with 'taxonomy_vocabulary_get_names' as argument is deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. There is no replacement for this usage. See https://www.drupal.org/node/3039041"); drupal_static_reset('taxonomy_vocabulary_get_names'); }