diff --git a/core/includes/common.inc b/core/includes/common.inc index 91aa48570af..cf4bbb6bb3e 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -1295,8 +1295,10 @@ function drupal_flush_all_caches() { // Reset all static caches. drupal_static_reset(); - // Wipe the PHP Storage caches. - PhpStorageFactory::get('service_container')->deleteAll(); + // Invalidate the container. + \Drupal::service('kernel')->invalidateContainer(); + + // Wipe the Twig PHP Storage cache. PhpStorageFactory::get('twig')->deleteAll(); // Rebuild module and theme data. diff --git a/core/includes/utility.inc b/core/includes/utility.inc index ff65dc8cf3f..4918719a55d 100644 --- a/core/includes/utility.inc +++ b/core/includes/utility.inc @@ -29,13 +29,17 @@ function drupal_rebuild(ClassLoader $class_loader, Request $request) { restore_error_handler(); restore_exception_handler(); - // Force kernel to rebuild container. - PhpStorageFactory::get('service_container')->deleteAll(); + // Force kernel to rebuild php cache. PhpStorageFactory::get('twig')->deleteAll(); // Bootstrap up to where caches exist and clear them. $kernel = new DrupalKernel('prod', $class_loader); $kernel->setSitePath(DrupalKernel::findSitePath($request)); + + // Invalidate the container. + $kernel->invalidateContainer(); + + // Prepare a NULL request. $kernel->prepareLegacyRequest($request); foreach (Cache::getBins() as $bin) { diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php index 54579354a7a..0f103e67cd3 100644 --- a/core/lib/Drupal/Core/DrupalKernel.php +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -126,6 +126,13 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { */ protected $allowDumping; + /** + * Whether the container needs to be rebuilt the next time it is initialized. + * + * @var bool + */ + protected $containerNeedsRebuild = FALSE; + /** * Whether the container needs to be dumped once booting is complete. * @@ -695,11 +702,13 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { } // If we haven't yet booted, we don't need to do anything: the new module - // list will take effect when boot() is called. If we have already booted, - // then rebuild the container in order to refresh the serviceProvider list - // and container. + // list will take effect when boot() is called. However we set a + // flag that the container needs a rebuild, so that a potentially cached + // container is not used. If we have already booted, then rebuild the + // container in order to refresh the serviceProvider list and container. + $this->containerNeedsRebuild = TRUE; if ($this->booted) { - $this->initializeContainer(TRUE); + $this->initializeContainer(); } } @@ -738,11 +747,9 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { /** * Initializes the service container. * - * @param bool $rebuild - * Force a container rebuild. * @return \Symfony\Component\DependencyInjection\ContainerInterface */ - protected function initializeContainer($rebuild = FALSE) { + protected function initializeContainer() { $this->containerNeedsDumping = FALSE; $session_started = FALSE; if (isset($this->container)) { @@ -764,7 +771,7 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { // If the module list hasn't already been set in updateModules and we are // not forcing a rebuild, then try and load the container from the disk. - if (empty($this->moduleList) && !$rebuild) { + if (empty($this->moduleList) && !$this->containerNeedsRebuild) { $fully_qualified_class_name = '\\' . $this->getClassNamespace() . '\\' . $this->getClassName(); // First, try to load from storage. @@ -781,6 +788,9 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { $container = $this->compileContainer(); } + // The container was rebuilt successfully. + $this->containerNeedsRebuild = FALSE; + $this->attachSynthetic($container); $this->container = $container; @@ -998,15 +1008,33 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { } /** - * Force a container rebuild. - * - * @return \Symfony\Component\DependencyInjection\ContainerInterface + * {@inheritdoc} */ public function rebuildContainer() { // Empty module properties and for them to be reloaded from scratch. $this->moduleList = NULL; $this->moduleData = array(); - return $this->initializeContainer(TRUE); + $this->containerNeedsRebuild = TRUE; + return $this->initializeContainer(); + } + + /** + * {@inheritdoc} + */ + public function invalidateContainer() { + // An invalidated container needs a rebuild. + $this->containerNeedsRebuild = TRUE; + + // If we have not yet booted, settings or bootstrap services might not yet + // be available. In that case the container will not be loaded from cache + // due to the above setting when the Kernel is booted. + if (!$this->booted) { + return; + } + + // Also wipe the PHP Storage caches, so that the container is rebuilt + // for the next request. + $this->storage()->deleteAll(); } /** diff --git a/core/lib/Drupal/Core/DrupalKernelInterface.php b/core/lib/Drupal/Core/DrupalKernelInterface.php index b17ed806523..892952a0698 100644 --- a/core/lib/Drupal/Core/DrupalKernelInterface.php +++ b/core/lib/Drupal/Core/DrupalKernelInterface.php @@ -96,6 +96,18 @@ interface DrupalKernelInterface extends HttpKernelInterface { */ public function updateModules(array $module_list, array $module_filenames = array()); + /** + * Force a container rebuild. + * + * @return \Symfony\Component\DependencyInjection\ContainerInterface + */ + public function rebuildContainer(); + + /** + * Invalidate the service container for the next request. + */ + public function invalidateContainer(); + /** * Prepare the kernel for handling a request without handling the request. * diff --git a/core/lib/Drupal/Core/Installer/InstallerKernel.php b/core/lib/Drupal/Core/Installer/InstallerKernel.php index 41e8cbe9cdc..cd3977cf3de 100644 --- a/core/lib/Drupal/Core/Installer/InstallerKernel.php +++ b/core/lib/Drupal/Core/Installer/InstallerKernel.php @@ -16,13 +16,11 @@ class InstallerKernel extends DrupalKernel { /** * {@inheritdoc} - * - * @param bool $rebuild - * Force a container rebuild. Unlike the parent method, this defaults to - * TRUE. */ - protected function initializeContainer($rebuild = TRUE) { - $container = parent::initializeContainer($rebuild); + protected function initializeContainer() { + // Always force a container rebuild. + $this->containerNeedsRebuild = TRUE; + $container = parent::initializeContainer(); return $container; } diff --git a/core/modules/language/src/ConfigurableLanguageManager.php b/core/modules/language/src/ConfigurableLanguageManager.php index a4ca11b6a76..d4dad5c0493 100644 --- a/core/modules/language/src/ConfigurableLanguageManager.php +++ b/core/modules/language/src/ConfigurableLanguageManager.php @@ -8,7 +8,6 @@ namespace Drupal\language; use Drupal\Core\Language\LanguageInterface; -use Drupal\Core\PhpStorage\PhpStorageFactory; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Language\Language; @@ -105,7 +104,7 @@ class ConfigurableLanguageManager extends LanguageManager implements Configurabl * {@inheritdoc} */ public static function rebuildServices() { - PhpStorageFactory::get('service_container')->deleteAll(); + \Drupal::service('kernel')->invalidateContainer(); } /** diff --git a/core/modules/language/src/EventSubscriber/ConfigSubscriber.php b/core/modules/language/src/EventSubscriber/ConfigSubscriber.php index ef3f65d5ef1..d3486aa16e9 100644 --- a/core/modules/language/src/EventSubscriber/ConfigSubscriber.php +++ b/core/modules/language/src/EventSubscriber/ConfigSubscriber.php @@ -9,9 +9,9 @@ namespace Drupal\language\EventSubscriber; use Drupal\Core\Language\LanguageDefault; use Drupal\Core\Language\LanguageManagerInterface; -use Drupal\Core\PhpStorage\PhpStorageFactory; use Drupal\Core\Config\ConfigCrudEvent; use Drupal\Core\Config\ConfigEvents; +use Drupal\language\ConfigurableLanguageManager; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** @@ -62,9 +62,8 @@ class ConfigSubscriber implements EventSubscriberInterface { $this->languageManager->reset(); language_negotiation_url_prefixes_update(); } - // Trigger a container rebuild on the next request by deleting compiled - // from PHP storage. - PhpStorageFactory::get('service_container')->deleteAll(); + // Trigger a container rebuild on the next request by invalidating it. + ConfigurableLanguageManager::rebuildServices(); } } diff --git a/core/modules/system/src/Tests/DrupalKernel/ContainerRebuildWebTest.php b/core/modules/system/src/Tests/DrupalKernel/ContainerRebuildWebTest.php index 734328d640a..36ed68f600e 100644 --- a/core/modules/system/src/Tests/DrupalKernel/ContainerRebuildWebTest.php +++ b/core/modules/system/src/Tests/DrupalKernel/ContainerRebuildWebTest.php @@ -35,4 +35,26 @@ class ContainerRebuildWebTest extends WebTestBase { $this->assertHeader('container_rebuild_indicator', 'new-identifier'); } + /** + * Tests container invalidation. + */ + public function testContainerInvalidation() { + + // Ensure that parameter is not set. + $this->drupalGet(''); + $this->assertHeader('container_rebuild_test_parameter', FALSE); + + // Ensure that after setting the parameter, without a container rebuild the + // parameter is still not set. + $this->writeSettings(['settings' => ['container_rebuild_test_parameter' => (object) ['value' => 'rebuild_me_please', 'required' => TRUE]]]); + + $this->drupalGet(''); + $this->assertHeader('container_rebuild_test_parameter', FALSE); + + // Ensure that after container invalidation the parameter is set. + \Drupal::service('kernel')->invalidateContainer(); + $this->drupalGet(''); + $this->assertHeader('container_rebuild_test_parameter', 'rebuild_me_please'); + } + } diff --git a/core/modules/system/tests/modules/service_provider_test/src/ServiceProviderTestServiceProvider.php b/core/modules/system/tests/modules/service_provider_test/src/ServiceProviderTestServiceProvider.php index 9e218839403..700c2ce3906 100644 --- a/core/modules/system/tests/modules/service_provider_test/src/ServiceProviderTestServiceProvider.php +++ b/core/modules/system/tests/modules/service_provider_test/src/ServiceProviderTestServiceProvider.php @@ -26,5 +26,9 @@ class ServiceProviderTestServiceProvider implements ServiceModifierInterface { if ($indicator = Settings::get('deployment_identifier')) { $container->setParameter('container_rebuild_indicator', $indicator); } + + if ($parameter = Settings::get('container_rebuild_test_parameter')) { + $container->setParameter('container_rebuild_test_parameter', $parameter); + } } } diff --git a/core/modules/system/tests/modules/service_provider_test/src/TestClass.php b/core/modules/system/tests/modules/service_provider_test/src/TestClass.php index cbbe773ea48..bec97d23004 100644 --- a/core/modules/system/tests/modules/service_provider_test/src/TestClass.php +++ b/core/modules/system/tests/modules/service_provider_test/src/TestClass.php @@ -51,6 +51,9 @@ class TestClass implements EventSubscriberInterface, DestructableInterface, Cont if ($this->container->hasParameter('container_rebuild_indicator')) { $event->getResponse()->headers->set('container_rebuild_indicator', $this->container->getParameter('container_rebuild_indicator')); } + if ($this->container->hasParameter('container_rebuild_test_parameter')) { + $event->getResponse()->headers->set('container_rebuild_test_parameter', $this->container->getParameter('container_rebuild_test_parameter')); + } } /**