diff --git a/core/core.services.yml b/core/core.services.yml index cd647713cb..788cd96af8 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -1713,7 +1713,7 @@ services: - { name: event_subscriber } messenger: class: Drupal\Core\Messenger\Messenger - arguments: ['@session.flash_bag', '@page_cache_kill_switch'] + arguments: ['@session', '@page_cache_kill_switch'] tempstore.private: class: Drupal\Core\TempStore\PrivateTempStoreFactory arguments: ['@keyvalue.expirable', '@lock', '@current_user', '@request_stack', '%tempstore.expire%'] diff --git a/core/lib/Drupal/Core/Installer/InstallerServiceProvider.php b/core/lib/Drupal/Core/Installer/InstallerServiceProvider.php index dd4ae5f470..3af9d99a79 100644 --- a/core/lib/Drupal/Core/Installer/InstallerServiceProvider.php +++ b/core/lib/Drupal/Core/Installer/InstallerServiceProvider.php @@ -60,6 +60,13 @@ public function register(ContainerBuilder $container) { // we don't need to ship with a custom proxy class. ->setLazy(FALSE); + // Replace the session_manager with an implementation that does not use the + // database. + $container->getDefinition('session_manager') + ->setClass('Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage') + ->setArguments([]) + ->setMethodCalls([]); + // Use a performance optimised module extension list. $container->getDefinition('extension.list.module')->setClass('Drupal\Core\Installer\InstallerModuleExtensionList'); } diff --git a/core/lib/Drupal/Core/Messenger/Messenger.php b/core/lib/Drupal/Core/Messenger/Messenger.php index c0949438cc..90b582c3d1 100644 --- a/core/lib/Drupal/Core/Messenger/Messenger.php +++ b/core/lib/Drupal/Core/Messenger/Messenger.php @@ -5,7 +5,7 @@ use Drupal\Component\Render\MarkupInterface; use Drupal\Core\PageCache\ResponsePolicy\KillSwitch; use Drupal\Core\Render\Markup; -use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; +use Symfony\Component\HttpFoundation\Session\SessionInterface; /** * The messenger service. @@ -13,11 +13,11 @@ class Messenger implements MessengerInterface { /** - * The flash bag. + * The session. * - * @var \Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface + * @var \Symfony\Component\HttpFoundation\Session\SessionInterface */ - protected $flashBag; + protected $session; /** * The kill switch. @@ -27,19 +27,36 @@ class Messenger implements MessengerInterface { protected $killSwitch; /** + * A boolean to indicate if a message has been set. + * + * @var bool + */ + protected $messageSet = FALSE; + + /** * Messenger constructor. * - * @param \Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface $flash_bag - * The flash bag. + * @param \Symfony\Component\HttpFoundation\Session\SessionInterface $session + * The session. * @param \Drupal\Core\PageCache\ResponsePolicy\KillSwitch $killSwitch * The kill switch. */ - public function __construct(FlashBagInterface $flash_bag, KillSwitch $killSwitch) { - $this->flashBag = $flash_bag; + public function __construct(SessionInterface $session, KillSwitch $killSwitch) { + $this->session = $session; $this->killSwitch = $killSwitch; } /** + * Gets the flash bag. + * + * @return \Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface + * The flash bag. + */ + protected function getFlashBag() { + return $this->session->getFlashBag(); + } + + /** * {@inheritdoc} */ public function addError($message, $repeat = FALSE) { @@ -56,12 +73,13 @@ public function addMessage($message, $type = self::TYPE_STATUS, $repeat = FALSE) // Do not use strict type checking so that equivalent string and // MarkupInterface objects are detected. - if ($repeat || !in_array($message, $this->flashBag->peek($type))) { - $this->flashBag->add($type, $message); + if ($repeat || !in_array($message, $this->getFlashBag()->peek($type))) { + $this->getFlashBag()->add($type, $message); } // Mark this page as being uncacheable. $this->killSwitch->trigger(); + $this->messageSet = TRUE; return $this; } @@ -84,29 +102,54 @@ public function addWarning($message, $repeat = FALSE) { * {@inheritdoc} */ public function all() { - return $this->flashBag->peekAll(); + if ($this->noMessages()) { + return []; + } + return $this->getFlashBag()->peekAll(); } /** * {@inheritdoc} */ public function deleteAll() { - return $this->flashBag->clear(); + if ($this->noMessages()) { + return []; + } + return $this->getFlashBag()->clear(); } /** * {@inheritdoc} */ public function deleteByType($type) { + if ($this->noMessages()) { + return []; + } // Flash bag gets and clears flash messages from the stack. - return $this->flashBag->get($type); + return $this->getFlashBag()->get($type); } /** * {@inheritdoc} */ public function messagesByType($type) { - return $this->flashBag->peek($type); + if ($this->noMessages()) { + return []; + } + return $this->getFlashBag()->peek($type); + } + + /** + * Determines if there are no messages. + * + * If the session has not started and $this->messageSet there cannot be any + * messages. + * + * @return bool + * TRUE if there are no messages, FALSE if not. + */ + protected function noMessages() { + return !$this->session->isStarted() && !$this->messageSet; } } diff --git a/core/lib/Drupal/Core/Session/SessionManager.php b/core/lib/Drupal/Core/Session/SessionManager.php index 7981398678..b4f605484a 100644 --- a/core/lib/Drupal/Core/Session/SessionManager.php +++ b/core/lib/Drupal/Core/Session/SessionManager.php @@ -5,6 +5,7 @@ use Drupal\Component\Utility\Crypt; use Drupal\Core\Database\Connection; use Drupal\Core\DependencyInjection\DependencySerializationTrait; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; @@ -108,6 +109,11 @@ public function start() { } $request = $this->requestStack->getCurrentRequest(); + // If a session is started prior to the the request stack having the request + // create one from globals. + if (!$request) { + $request = Request::createFromGlobals(); + } $this->setOptions($this->sessionConfiguration->getOptions($request)); if ($this->sessionConfiguration->hasSession($request)) { diff --git a/core/modules/system/tests/modules/httpkernel_test/httpkernel_test.services.yml b/core/modules/system/tests/modules/httpkernel_test/httpkernel_test.services.yml index c4784de32b..dad2db4ce7 100644 --- a/core/modules/system/tests/modules/httpkernel_test/httpkernel_test.services.yml +++ b/core/modules/system/tests/modules/httpkernel_test/httpkernel_test.services.yml @@ -5,6 +5,6 @@ services: - { name: http_middleware } httpkernel_test.test_middleware2: class: Drupal\httpkernel_test\HttpKernel\TestMiddleware - arguments: ['test_argument'] + arguments: ['test_argument', '@messenger'] tags: - - { name: http_middleware, priority: 20 } + - { name: http_middleware, priority: 200 } diff --git a/core/modules/system/tests/modules/httpkernel_test/src/HttpKernel/TestMiddleware.php b/core/modules/system/tests/modules/httpkernel_test/src/HttpKernel/TestMiddleware.php index f9e9849ef3..fa6b350916 100644 --- a/core/modules/system/tests/modules/httpkernel_test/src/HttpKernel/TestMiddleware.php +++ b/core/modules/system/tests/modules/httpkernel_test/src/HttpKernel/TestMiddleware.php @@ -2,6 +2,7 @@ namespace Drupal\httpkernel_test\HttpKernel; +use Drupal\Core\Messenger\MessengerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\HttpKernelInterface; @@ -25,6 +26,13 @@ class TestMiddleware implements HttpKernelInterface { protected $optionalArgument; /** + * The messenger service. + * + * @var \Drupal\Core\Messenger\MessengerInterface + */ + protected $messenger; + + /** * Constructs a new TestMiddleware object. * * @param \Symfony\Component\HttpKernel\HttpKernelInterface $kernel @@ -32,9 +40,10 @@ class TestMiddleware implements HttpKernelInterface { * @param mixed $optional_argument * (optional) An optional argument. */ - public function __construct(HttpKernelInterface $kernel, $optional_argument = NULL) { + public function __construct(HttpKernelInterface $kernel, $optional_argument = NULL, MessengerInterface $messenger = NULL) { $this->kernel = $kernel; $this->optionalArgument = $optional_argument; + $this->messenger = $messenger; } /** @@ -48,6 +57,9 @@ public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = elseif (isset($this->optionalArgument)) { $request->attributes->set('_optional_argument', $this->optionalArgument); } + if ($this->messenger && $request->query->has('set_message')) { + $this->messenger->addStatus($request->query->get('set_message')); + } return $this->kernel->handle($request, $type, $catch); } diff --git a/core/modules/system/tests/modules/system_test/system_test.module b/core/modules/system/tests/modules/system_test/system_test.module index affc20ea42..bed7dfbdf7 100644 --- a/core/modules/system/tests/modules/system_test/system_test.module +++ b/core/modules/system/tests/modules/system_test/system_test.module @@ -135,6 +135,9 @@ function system_test_filetransfer_info() { */ function system_test_module_preinstall($module) { \Drupal::state()->set('system_test_preinstall_module', $module); + if (\Drupal::state()->get('system_test.verbose_module_hooks')) { + \Drupal::messenger()->addStatus(t('hook_module_preinstall fired for @module', ['@module' => $module])); + } } /** diff --git a/core/modules/system/tests/src/Functional/Bootstrap/DrupalMessengerServiceTest.php b/core/modules/system/tests/src/Functional/Bootstrap/DrupalMessengerServiceTest.php index 51404ef18d..0a99f440ca 100644 --- a/core/modules/system/tests/src/Functional/Bootstrap/DrupalMessengerServiceTest.php +++ b/core/modules/system/tests/src/Functional/Bootstrap/DrupalMessengerServiceTest.php @@ -17,7 +17,7 @@ class DrupalMessengerServiceTest extends BrowserTestBase { * * @var array */ - public static $modules = ['system_test']; + public static $modules = ['system_test', 'httpkernel_test']; /** * Tests Messenger service. @@ -44,6 +44,11 @@ public function testDrupalMessengerService() { // Ensure that strings that are not marked as safe are escaped. $this->assertEscaped('Thismarkup will be escaped.'); + + // Test that messages can be set very early by setting a message in + // the http_middleware layer. + $this->drupalGet('', ['query' => ['set_message' => 'super early message']]); + $this->assertSession()->responseContains('super early message'); } } diff --git a/core/modules/system/tests/src/Functional/Module/InstallUninstallTest.php b/core/modules/system/tests/src/Functional/Module/InstallUninstallTest.php index fd84c4db7e..825489fa84 100644 --- a/core/modules/system/tests/src/Functional/Module/InstallUninstallTest.php +++ b/core/modules/system/tests/src/Functional/Module/InstallUninstallTest.php @@ -134,6 +134,7 @@ public function testInstallUninstall() { // of modules, that each module's database tables now exist, and that // appropriate messages appear in the logs. foreach ($modules_to_install as $module_to_install) { + $this->assertText(t('hook_module_preinstall fired for @module', ['@module' => $module_to_install])); $this->assertText(t('hook_modules_installed fired for @module', ['@module' => $module_to_install])); $this->assertLogMessage('system', "%module module installed.", ['%module' => $module_to_install], RfcLogLevel::INFO); $this->assertInstallModuleUpdates($module_to_install);