diff --git a/core/core.services.yml b/core/core.services.yml index 9c60943d25..c11dcebe4b 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -682,8 +682,6 @@ services: arguments: ['@config.factory'] request_stack: class: Symfony\Component\HttpFoundation\RequestStack - tags: - - { name: persist } current_route_match: class: Drupal\Core\Routing\CurrentRouteMatch arguments: ['@request_stack'] @@ -824,8 +822,6 @@ services: lazy: true router.request_context: class: Drupal\Core\Routing\RequestContext - tags: - - { name: persist } calls: - [fromRequestStack, ['@request_stack']] router.admin_context: diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php index a099327c99..0bc6f82e82 100644 --- a/core/lib/Drupal/Core/DrupalKernel.php +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -849,23 +849,6 @@ protected function getKernelParameters() { */ protected function initializeContainer() { $this->containerNeedsDumping = FALSE; - $session_started = FALSE; - if (isset($this->container)) { - // Save the id of the currently logged in user. - if ($this->container->initialized('current_user')) { - $current_user_id = $this->container->get('current_user')->id(); - } - - // If there is a session, close and save it. - if ($this->container->initialized('session')) { - $session = $this->container->get('session'); - if ($session->isStarted()) { - $session_started = TRUE; - $session->save(); - } - unset($session); - } - } // If we haven't booted yet but there is a container, then we're asked to // boot the container injected via setContainer(). @@ -919,35 +902,19 @@ protected function initializeContainer() { } $this->attachSynthetic($container); + $this->restoreServiceState($container); $this->container = $container; - if ($session_started) { - $this->container->get('session')->start(); - } + \Drupal::setContainer($this->container); - // The request stack is preserved across container rebuilds. Reinject the - // new session into the master request if one was present before. + // The request stack is preserved across container rebuilds. Allow other + // parts of the codebase to react on container initialization in subrequest. if (($request_stack = $this->container->get('request_stack', ContainerInterface::NULL_ON_INVALID_REFERENCE))) { if ($request = $request_stack->getMasterRequest()) { - $subrequest = TRUE; - if ($request->hasSession()) { - $request->setSession($this->container->get('session')); - } + $this->container->get('event_dispatcher')->dispatch(self::CONTAINER_INITIALIZE_SUBREQUEST_FINISHED); } } - if (!empty($current_user_id)) { - $this->container->get('current_user')->setInitialAccountId($current_user_id); - } - - \Drupal::setContainer($this->container); - - // Allow other parts of the codebase to react on container initialization in - // subrequest. - if (!empty($subrequest)) { - $this->container->get('event_dispatcher')->dispatch(self::CONTAINER_INITIALIZE_SUBREQUEST_FINISHED); - } - // If needs dumping flag was set, dump the container. if ($this->containerNeedsDumping && !$this->cacheDrupalContainer($container_definition)) { $this->container->get('logger.factory')->get('DrupalKernel')->error('Container cannot be saved to cache.'); @@ -1143,33 +1110,6 @@ protected function initializeRequestGlobals(Request $request) { $base_insecure_url = str_replace('https://', 'http://', $base_url); } - /** - * Returns service instances to persist from an old container to a new one. - */ - protected function getServicesToPersist(ContainerInterface $container) { - $persist = []; - foreach ($container->getParameter('persist_ids') as $id) { - // It's pointless to persist services not yet initialized. - if ($container->initialized($id)) { - $persist[$id] = $container->get($id); - } - } - return $persist; - } - - /** - * Moves persistent service instances into a new container. - */ - protected function persistServices(ContainerInterface $container, array $persist) { - foreach ($persist as $id => $object) { - // Do not override services already set() on the new container, for - // example 'service_container'. - if (!$container->initialized($id)) { - $container->set($id, $object); - } - } - } - /** * {@inheritdoc} */ @@ -1208,12 +1148,6 @@ public function invalidateContainer() { * @return \Symfony\Component\DependencyInjection\ContainerInterface */ protected function attachSynthetic(ContainerInterface $container) { - $persist = []; - if (isset($this->container)) { - $persist = $this->getServicesToPersist($this->container); - } - $this->persistServices($container, $persist); - // All namespaces must be registered before we attempt to use any service // from the container. $this->classLoaderAddMultiplePsr4($container->getParameter('container.namespaces')); @@ -1225,6 +1159,61 @@ protected function attachSynthetic(ContainerInterface $container) { return $container; } + /** + * Transfers state from services on the existing container to a new one. + * + * When rebuilding the container mid-request it is necessary to transfer the + * state of some services from the previous instance to the new one. This is + * required for services wrapping around PHP globals such as the request stack + * and the session. + * + * @param \Symfony\Component\DependencyInjection\ContainerInterface $container + * Container object + */ + protected function restoreServiceState(ContainerInterface $container) { + if (!isset($this->container)) { + return; + } + + // Restore request stack and request context. + $requests = []; + if ($this->container->initialized('request_stack')) { + $original_request_stack = $this->container->get('request_stack'); + while ($request = $original_request_stack->pop()) { + array_unshift($requests, $request); + } + + // Rebuild the request stack bottom up. + foreach ($requests as $request) { + $original_request_stack->push($request); + $container->get('request_stack')->push($request); + $container->get('router.request_context')->fromRequest($request); + } + } + + // If a session was started, close and save it and then start it again. + if ($this->container->initialized('session')) { + $session = $this->container->get('session'); + if ($session->isStarted()) { + $session->save(); + $container->get('session')->start(); + } + } + + // Refresh references to the session on all requests. + foreach ($requests as $request) { + if ($request->hasSession()) { + $request->setSession($container->get('session')); + } + } + + // Save the id of the currently logged in user. + if ($this->container->initialized('current_user')) { + $current_user_id = $this->container->get('current_user')->id(); + $container->get('current_user')->setInitialAccountId($current_user_id); + } + } + /** * Compiles a new service container. * @@ -1306,19 +1295,6 @@ protected function compileContainer() { } } - // Identify all services whose instances should be persisted when rebuilding - // the container during the lifetime of the kernel (e.g., during a kernel - // reboot). Include synthetic services, because by definition, they cannot - // be automatically reinstantiated. Also include services tagged to persist. - $persist_ids = []; - foreach ($container->getDefinitions() as $id => $definition) { - // It does not make sense to persist the container itself, exclude it. - if ($id !== 'service_container' && ($definition->isSynthetic() || $definition->getTag('persist'))) { - $persist_ids[] = $id; - } - } - $container->setParameter('persist_ids', $persist_ids); - $container->compile(); return $container; } diff --git a/core/lib/Drupal/Core/Installer/InstallerServiceProvider.php b/core/lib/Drupal/Core/Installer/InstallerServiceProvider.php index dd4ae5f470..4c25e45fca 100644 --- a/core/lib/Drupal/Core/Installer/InstallerServiceProvider.php +++ b/core/lib/Drupal/Core/Installer/InstallerServiceProvider.php @@ -72,16 +72,6 @@ public function alter(ContainerBuilder $container) { $twig_config = $container->getParameter('twig.config'); $twig_config['cache'] = FALSE; $container->setParameter('twig.config', $twig_config); - - // No service may persist when the early installer kernel is rebooted into - // the production environment. - // @todo The DrupalKernel reboot performed by drupal_install_system() is - // actually not a "regular" reboot (like ModuleInstaller::install()), so - // services are not actually persisted. - foreach ($container->findTaggedServiceIds('persist') as $id => $tags) { - $definition = $container->getDefinition($id); - $definition->clearTag('persist'); - } } } diff --git a/core/tests/Drupal/KernelTests/Core/DrupalKernel/DrupalKernelTest.php b/core/tests/Drupal/KernelTests/Core/DrupalKernel/DrupalKernelTest.php index b5cb96b8cc..d194168591 100644 --- a/core/tests/Drupal/KernelTests/Core/DrupalKernel/DrupalKernelTest.php +++ b/core/tests/Drupal/KernelTests/Core/DrupalKernel/DrupalKernelTest.php @@ -133,11 +133,6 @@ public function testCompileDIC() { 'pathname' => drupal_get_filename('module', 'service_provider_test'), 'filename' => NULL, ]); - - // Check that the container itself is not among the persist IDs because it - // does not make sense to persist the container itself. - $persist_ids = $container->getParameter('persist_ids'); - $this->assertSame(FALSE, array_search('service_container', $persist_ids)); } /** diff --git a/core/tests/Drupal/KernelTests/KernelTestBase.php b/core/tests/Drupal/KernelTests/KernelTestBase.php index dad4c97ea1..672c3fe99e 100644 --- a/core/tests/Drupal/KernelTests/KernelTestBase.php +++ b/core/tests/Drupal/KernelTests/KernelTestBase.php @@ -179,6 +179,13 @@ */ protected $configImporter; + /** + * The key_value service that must persist between container rebuilds. + * + * @var \Drupal\Core\KeyValueStore\KeyValueMemoryFactory + */ + protected $keyValue; + /** * The app root. * @@ -530,12 +537,18 @@ public function register(ContainerBuilder $container) { $container ->register('cache_factory', 'Drupal\Core\Cache\MemoryBackendFactory'); $container - ->register('keyvalue.memory', 'Drupal\Core\KeyValueStore\KeyValueMemoryFactory') - // Must persist container rebuilds, or all data would vanish otherwise. - ->addTag('persist'); + ->register('keyvalue.memory', 'Drupal\Core\KeyValueStore\KeyValueMemoryFactory'); $container ->setAlias('keyvalue', 'keyvalue.memory'); + // Must persist container rebuilds, or all data would vanish otherwise. + if (isset($this->keyValue)) { + $container->set('keyvalue.memory', $this->keyValue); + } + else { + $this->keyValue = $container->get('keyvalue.memory'); + } + // Set the default language on the minimal container. $container->setParameter('language.default_values', Language::$defaultValues);