diff --git a/core/lib/Drupal/Core/Cache/CacheFactory.php b/core/lib/Drupal/Core/Cache/CacheFactory.php index 5adfb30718..d06f330234 100644 --- a/core/lib/Drupal/Core/Cache/CacheFactory.php +++ b/core/lib/Drupal/Core/Cache/CacheFactory.php @@ -65,7 +65,7 @@ public function __construct(Settings $settings, array $default_bin_backends = [] public function get($bin) { $cache_settings = $this->settings->get('cache'); // First, look for a cache bin specific setting. - if (isset($cache_settings['bins'][$bin])) { + if (isset($cache_settings['bins'][$bin]) && $this->container->has($cache_settings['bins'][$bin])) { $service_name = $cache_settings['bins'][$bin]; } // Second, use the default backend specified by the cache bin. @@ -73,10 +73,10 @@ public function get($bin) { $service_name = $this->defaultBinBackends[$bin]; } // Third, use configured default backend. - elseif (isset($cache_settings['default'])) { + elseif (isset($cache_settings['default']) && $this->container->has($cache_settings['default'])) { $service_name = $cache_settings['default']; } - else { + if (!isset($service_name) || !$this->container->has($service_name)) { // Fall back to the database backend if nothing else is configured. $service_name = 'cache.backend.database'; } diff --git a/core/lib/Drupal/Core/Cache/ChainedFastBackendFactory.php b/core/lib/Drupal/Core/Cache/ChainedFastBackendFactory.php index 789b7274aa..e721485a6b 100644 --- a/core/lib/Drupal/Core/Cache/ChainedFastBackendFactory.php +++ b/core/lib/Drupal/Core/Cache/ChainedFastBackendFactory.php @@ -76,6 +76,7 @@ public function __construct(Settings $settings = NULL, $consistent_service_name * The cache backend object associated with the specified bin. */ public function get($bin) { + $this->consistentServiceName = ($this->consistentServiceName && $this->container->has($this->consistentServiceName)) ? $this->consistentServiceName : 'cache.backend.database'; // Use the chained backend only if there is a fast backend available; // otherwise, just return the consistent backend directly. if (isset($this->fastServiceName)) { diff --git a/core/modules/system/system.install b/core/modules/system/system.install index 88d30e5387..88b3116400 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -352,6 +352,25 @@ function system_requirements($phase) { } } + if ($phase === 'runtime') { + // Check that the required cache backends exist. + $cache_default_bin_backends = Drupal::getContainer()->getParameter('cache_default_bin_backends'); + if (!empty($cache_default_bin_backends)) { + foreach ($cache_default_bin_backends as $bin => $backend) { + if (!Drupal::getContainer()->has($backend)) { + $requirements['cache_backend_bin_' . $bin] = [ + 'title' => t('Cache Backend'), + 'value' => t('Missing'), + 'severity' => REQUIREMENT_WARNING, + 'description' => new TranslatableMarkup( + 'The configured cache backend %backend is not available for the bin %bin.', + ['%backend' => $backend, '%bin' => $bin]), + ]; + } + } + } + } + if ($phase != 'update') { // Test whether we have a good source of random bytes. $requirements['php_random_bytes'] = [ diff --git a/core/modules/system/tests/modules/cache_test/cache_test.services.yml b/core/modules/system/tests/modules/cache_test/cache_test.services.yml new file mode 100644 index 0000000000..577c6333b8 --- /dev/null +++ b/core/modules/system/tests/modules/cache_test/cache_test.services.yml @@ -0,0 +1,25 @@ +services: + cache.cache_test_missing: + class: Drupal\Core\Cache\CacheBackendInterface + tags: + - { name: cache.bin, default_backend: cache.backend.missing } + factory: [ '@cache_factory', 'get' ] + arguments: [ cache_test_missing ] + cache.cache_test_lost: + class: Drupal\Core\Cache\CacheBackendInterface + tags: + - { name: cache.bin, default_backend: cache.backend.lost } + factory: [ '@cache_factory', 'get' ] + arguments: [ cache_test_lost ] + cache.cache_test_memory: + class: Drupal\Core\Cache\CacheBackendInterface + tags: + - { name: cache.bin, default_backend: cache.backend.memory } + factory: [ '@cache_factory', 'get' ] + arguments: [ cache_test_memory ] + cache.cache_test_no_default: + class: Drupal\Core\Cache\CacheBackendInterface + tags: + - { name: cache.bin } + factory: [ '@cache_factory', 'get' ] + arguments: [ cache_test_no_default ] diff --git a/core/modules/system/tests/src/Functional/System/MissingCacheBackendTest.php b/core/modules/system/tests/src/Functional/System/MissingCacheBackendTest.php new file mode 100644 index 0000000000..8fcbb7496f --- /dev/null +++ b/core/modules/system/tests/src/Functional/System/MissingCacheBackendTest.php @@ -0,0 +1,49 @@ +drupalLogin($this->createUser(['administer site configuration'])); + } + + /** + * Tests warnings correctly appear on status page. + */ + public function testStatusPage() { + $assert_session = $this->assertSession(); + $this->drupalGet('admin/reports/status'); + + // A couple services are configured with non-existent backends. + $assert_session->pageTextContains('The configured cache backend cache.backend.missing is not available for the bin cache_test_missing'); + $assert_session->pageTextContains('The configured cache backend cache.backend.lost is not available for the bin cache_test_lost'); + // A couple services have valid configuration and should have no warning. + $assert_session->pageTextNotContains('cache_test_memory'); + $assert_session->pageTextNotContains('cache_test_no_default'); + } + +} diff --git a/core/tests/Drupal/Tests/Core/Cache/CacheFactoryTest.php b/core/tests/Drupal/Tests/Core/Cache/CacheFactoryTest.php index 2876d7177b..26d5e0feab 100644 --- a/core/tests/Drupal/Tests/Core/Cache/CacheFactoryTest.php +++ b/core/tests/Drupal/Tests/Core/Cache/CacheFactoryTest.php @@ -145,4 +145,66 @@ public function testCacheFactoryWithSpecifiedPerBinBackend() { $this->assertSame($render_bin, $actual_bin); } + /** + * Tests cache factory that specified bin service does not exist. + * + * @covers ::__construct + * @covers ::get + */ + public function testCacheFactoryBinBackendServiceExist() { + $settings = new Settings([ + 'cache' => [ + 'bins' => [ + 'render' => 'cache.backend.custom', + ], + ], + ]); + $cache_factory = new CacheFactory($settings); + + $container = new ContainerBuilder(); + $cache_factory->setContainer($container); + + $builtin_default_backend_factory = $this->createMock('\Drupal\Core\Cache\CacheFactoryInterface'); + $container->set('cache.backend.database', $builtin_default_backend_factory); + + $render_bin = $this->createMock('\Drupal\Core\Cache\CacheBackendInterface'); + $builtin_default_backend_factory->expects($this->once()) + ->method('get') + ->with('render') + ->will($this->returnValue($render_bin)); + + $actual_bin = $cache_factory->get('render'); + $this->assertSame($render_bin, $actual_bin); + } + + /** + * Tests cache factory that specified default bin service does not exist. + * + * @covers ::__construct + * @covers ::get + */ + public function testCacheFactoryBinBackendDefaultServiceExist() { + $settings = new Settings([ + 'cache' => [ + 'default' => 'cache.backend.custom', + ], + ]); + $cache_factory = new CacheFactory($settings); + + $container = new ContainerBuilder(); + $cache_factory->setContainer($container); + + $builtin_default_backend_factory = $this->createMock('\Drupal\Core\Cache\CacheFactoryInterface'); + $container->set('cache.backend.database', $builtin_default_backend_factory); + + $render_bin = $this->createMock('\Drupal\Core\Cache\CacheBackendInterface'); + $builtin_default_backend_factory->expects($this->once()) + ->method('get') + ->with('render') + ->will($this->returnValue($render_bin)); + + $actual_bin = $cache_factory->get('render'); + $this->assertSame($render_bin, $actual_bin); + } + } diff --git a/core/tests/Drupal/Tests/Core/Cache/ChainedFastBackendFactoryTest.php b/core/tests/Drupal/Tests/Core/Cache/ChainedFastBackendFactoryTest.php new file mode 100644 index 0000000000..f7373099fa --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Cache/ChainedFastBackendFactoryTest.php @@ -0,0 +1,190 @@ +setContainer($container); + + $builtin_default_backend_factory = $this->createMock('\Drupal\Core\Cache\CacheFactoryInterface'); + $container->set('cache.backend.database', $builtin_default_backend_factory); + $builtin_fast_factory = $this->createMock('\Drupal\Core\Cache\CacheFactoryInterface'); + $container->set('cache.backend.apcu', $builtin_fast_factory); + + $render_bin = $this->createMock('\Drupal\Core\Cache\CacheBackendInterface'); + $builtin_default_backend_factory->expects($this->once()) + ->method('get') + ->with('render') + ->will($this->returnValue($render_bin)); + $builtin_fast_factory->expects($this->once()) + ->method('get') + ->with('render') + ->will($this->returnValue($render_bin)); + + $actual_bin = $chained_fast_backend_factory->get('render'); + $this->assertInstanceOf('Drupal\Core\Cache\ChainedFastBackend', $actual_bin); + } + + /** + * Tests that chained fast backend is bypassed during installation. + * + * @covers ::__construct + * @covers ::get + */ + public function testChainedFastBackendFactoryDuringInstallation() { + $GLOBALS['install_state']['installation_finished'] = FALSE; + $settings = new Settings([]); + $chained_fast_backend_factory = new ChainedFastBackendFactory($settings); + + $container = new ContainerBuilder(); + $chained_fast_backend_factory->setContainer($container); + + $builtin_default_backend_factory = $this->createMock('\Drupal\Core\Cache\CacheFactoryInterface'); + $container->set('cache.backend.database', $builtin_default_backend_factory); + $builtin_fast_factory = $this->createMock('\Drupal\Core\Cache\CacheFactoryInterface'); + $container->set('cache.backend.apcu', $builtin_fast_factory); + + $render_bin = $this->createMock('\Drupal\Core\Cache\CacheBackendInterface'); + $builtin_default_backend_factory->expects($this->once()) + ->method('get') + ->with('render') + ->will($this->returnValue($render_bin)); + + $actual_bin = $chained_fast_backend_factory->get('render'); + $this->assertSame($render_bin, $actual_bin); + unset($GLOBALS['install_state']); + } + + /** + * Tests that it falls back to customized default service. + * + * @covers ::__construct + * @covers ::get + */ + public function testChainedFastBackendFactoryWithCustomizedDefaultBackend() { + $settings = new Settings([ + 'cache' => [ + 'default' => 'cache.backend.custom', + ], + ]); + $chained_fast_backend_factory = new ChainedFastBackendFactory($settings); + + $container = new ContainerBuilder(); + $chained_fast_backend_factory->setContainer($container); + + $custom_default_backend_factory = $this->createMock('\Drupal\Core\Cache\CacheFactoryInterface'); + $container->set('cache.backend.custom', $custom_default_backend_factory); + $builtin_fast_factory = $this->createMock('\Drupal\Core\Cache\CacheFactoryInterface'); + $container->set('cache.backend.apcu', $builtin_fast_factory); + + $render_bin = $this->createMock('\Drupal\Core\Cache\CacheBackendInterface'); + $custom_default_backend_factory->expects($this->once()) + ->method('get') + ->with('render') + ->will($this->returnValue($render_bin)); + $builtin_fast_factory->expects($this->once()) + ->method('get') + ->with('render') + ->will($this->returnValue($render_bin)); + + $actual_bin = $chained_fast_backend_factory->get('render'); + $this->assertInstanceOf('Drupal\Core\Cache\ChainedFastBackend', $actual_bin); + } + + /** + * Tests that it uses the correct default bin backend. + * + * @covers ::__construct + * @covers ::get + */ + public function testChainedFastBackendFactoryWithDefaultBinBackend() { + // Ensure the default bin backends are used before the configured default. + $settings = new Settings([ + 'cache' => [ + 'default' => 'cache.backend.unused', + ], + ]); + + $consistent_service_name = 'cache.backend.custom'; + + $chained_fast_backend_factory = new ChainedFastBackendFactory($settings, $consistent_service_name); + + $container = new ContainerBuilder(); + $chained_fast_backend_factory->setContainer($container); + + $custom_default_backend_factory = $this->createMock('\Drupal\Core\Cache\CacheFactoryInterface'); + $container->set('cache.backend.custom', $custom_default_backend_factory); + $builtin_fast_factory = $this->createMock('\Drupal\Core\Cache\CacheFactoryInterface'); + $container->set('cache.backend.apcu', $builtin_fast_factory); + + $render_bin = $this->createMock('\Drupal\Core\Cache\CacheBackendInterface'); + $custom_default_backend_factory->expects($this->once()) + ->method('get') + ->with('render') + ->will($this->returnValue($render_bin)); + $builtin_fast_factory->expects($this->once()) + ->method('get') + ->with('render') + ->will($this->returnValue($render_bin)); + + $actual_bin = $chained_fast_backend_factory->get('render'); + $this->assertInstanceOf('Drupal\Core\Cache\ChainedFastBackend', $actual_bin); + } + + /** + * Tests when default bin service does not exist. + * + * @covers ::__construct + * @covers ::get + */ + public function testChainedFastBackendFactoryBinBackendDefaultServiceExist() { + $settings = new Settings([ + 'cache' => [ + 'default' => 'cache.backend.custom', + ], + ]); + $chained_fast_backend_factory = new ChainedFastBackendFactory($settings); + + $container = new ContainerBuilder(); + $chained_fast_backend_factory->setContainer($container); + + $builtin_default_backend_factory = $this->createMock('\Drupal\Core\Cache\CacheFactoryInterface'); + $container->set('cache.backend.database', $builtin_default_backend_factory); + $builtin_fast_factory = $this->createMock('\Drupal\Core\Cache\CacheFactoryInterface'); + $container->set('cache.backend.apcu', $builtin_fast_factory); + + $render_bin = $this->createMock('\Drupal\Core\Cache\CacheBackendInterface'); + $builtin_default_backend_factory->expects($this->once()) + ->method('get') + ->with('render') + ->will($this->returnValue($render_bin)); + $builtin_fast_factory->expects($this->once()) + ->method('get') + ->with('render') + ->will($this->returnValue($render_bin)); + + $actual_bin = $chained_fast_backend_factory->get('render'); + $this->assertInstanceOf('Drupal\Core\Cache\ChainedFastBackend', $actual_bin); + } + +}