From a32a88cbc28eb7c824c1fc814c7b2ebd156f47cc Mon Sep 17 00:00:00 2001 From: Alex Pott Date: Sat, 14 Feb 2015 10:29:52 +0000 Subject: [PATCH] Issue #2372389 by znerol, almaudoh: Expose session handler in container --- core/core.services.yml | 23 +++- core/lib/Drupal/Core/CoreServiceProvider.php | 3 + .../Compiler/StackedSessionHandlerPass.php | 51 +++++++++ .../Drupal/Core/Session/SessionManager.php | 25 ++--- .../Core/Session/SessionManagerInterface.php | 19 ++++ .../StackSessionHandlerIntegrationTest.php | 52 +++++++++ .../session_test/session_test.routing.yml | 8 ++ .../session_test/session_test.services.yml | 11 ++ .../src/Controller/SessionTestController.php | 24 +++++ .../src/Session/TestSessionHandlerProxy.php | 100 ++++++++++++++++++ 10 files changed, 301 insertions(+), 15 deletions(-) create mode 100644 core/lib/Drupal/Core/DependencyInjection/Compiler/StackedSessionHandlerPass.php create mode 100644 core/modules/system/src/Tests/Session/StackSessionHandlerIntegrationTest.php create mode 100644 core/modules/system/tests/modules/session_test/src/Session/TestSessionHandlerProxy.php diff --git a/core/core.services.yml b/core/core.services.yml index c5c43fe0c7c..50808416da7 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -1107,11 +1107,28 @@ services: session.attribute_bag: class: Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag public: false - session_manager: - class: Drupal\Core\Session\SessionManager - arguments: ['@request_stack', '@database', '@session_manager.metadata_bag', '@session_configuration'] + session_handler: + alias: session_handler.storage + session_handler.storage: + class: Drupal\Core\Session\SessionHandler + arguments: ['@request_stack', '@database'] tags: - { name: backend_overridable } + session_handler.write_check: + class: Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler + tags: + - { name: session_handler_proxy, priority: 100 } + session_handler.write_safe: + class: Drupal\Core\Session\WriteSafeSessionHandler + tags: + - { name: session_handler_proxy, priority: 150 } + session_manager: + class: Drupal\Core\Session\SessionManager + arguments: ['@request_stack', '@database', '@session_manager.metadata_bag', '@session_configuration', '@session_handler'] + tags: + - { name: backend_overridable } + calls: + - [setWriteSafeHandler, ['@session_handler.write_safe']] session_manager.metadata_bag: class: Drupal\Core\Session\MetadataBag arguments: ['@settings'] diff --git a/core/lib/Drupal/Core/CoreServiceProvider.php b/core/lib/Drupal/Core/CoreServiceProvider.php index 6f434157759..75d8ec58261 100644 --- a/core/lib/Drupal/Core/CoreServiceProvider.php +++ b/core/lib/Drupal/Core/CoreServiceProvider.php @@ -14,6 +14,7 @@ use Drupal\Core\DependencyInjection\Compiler\RegisterLazyRouteEnhancers; use Drupal\Core\DependencyInjection\Compiler\RegisterLazyRouteFilters; use Drupal\Core\DependencyInjection\Compiler\DependencySerializationTraitPass; use Drupal\Core\DependencyInjection\Compiler\StackedKernelPass; +use Drupal\Core\DependencyInjection\Compiler\StackedSessionHandlerPass; use Drupal\Core\DependencyInjection\Compiler\RegisterStreamWrappersPass; use Drupal\Core\DependencyInjection\ServiceProviderInterface; use Drupal\Core\DependencyInjection\ContainerBuilder; @@ -63,6 +64,8 @@ class CoreServiceProvider implements ServiceProviderInterface { $container->addCompilerPass(new StackedKernelPass()); + $container->addCompilerPass(new StackedSessionHandlerPass()); + $container->addCompilerPass(new MainContentRenderersPass()); // Collect tagged handler services as method calls on consumer services. diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/StackedSessionHandlerPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/StackedSessionHandlerPass.php new file mode 100644 index 00000000000..32ef4076d74 --- /dev/null +++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/StackedSessionHandlerPass.php @@ -0,0 +1,51 @@ +hasDefinition('session_handler')) { + return; + } + + $session_handler_proxies = []; + $priorities = []; + + foreach ($container->findTaggedServiceIds('session_handler_proxy') as $id => $attributes) { + $priorities[$id] = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0; + $session_handler_proxies[$id] = $container->getDefinition($id); + } + + array_multisort($priorities, SORT_ASC, $session_handler_proxies); + + $decorated_id = 'session_handler.storage'; + foreach ($session_handler_proxies as $id => $decorator) { + // Prepend the inner session handler as first constructor argument. + $arguments = $decorator->getArguments(); + array_unshift($arguments, new Reference($decorated_id)); + $decorator->setArguments($arguments); + + $decorated_id = $id; + } + + $container->setAlias('session_handler', $decorated_id); + } + +} diff --git a/core/lib/Drupal/Core/Session/SessionManager.php b/core/lib/Drupal/Core/Session/SessionManager.php index 1bea6c364da..82498050ad1 100644 --- a/core/lib/Drupal/Core/Session/SessionManager.php +++ b/core/lib/Drupal/Core/Session/SessionManager.php @@ -10,7 +10,6 @@ namespace Drupal\Core\Session; use Drupal\Component\Utility\Crypt; use Drupal\Core\Database\Connection; use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler; use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; /** @@ -63,10 +62,8 @@ class SessionManager extends NativeSessionStorage implements SessionManagerInter /** * The write safe session handler. * - * @todo: The write safe session handler should be exposed in the - * container and this reference should be removed once all database queries + * @todo: This reference should be removed once all database queries * are removed from the session manager class. - * @see https://www.drupal.org/node/2372389 * * @var \Drupal\Core\Session\WriteSafeSessionHandlerInterface */ @@ -83,20 +80,17 @@ class SessionManager extends NativeSessionStorage implements SessionManagerInter * The session metadata bag. * @param \Drupal\Core\Session\SessionConfigurationInterface $session_configuration * The session configuration interface. + * @param \Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy|Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler|\SessionHandlerInterface|NULL $handler + * The object to register as a PHP session handler. + * @see \Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage::setSaveHandler() */ - public function __construct(RequestStack $request_stack, Connection $connection, MetadataBag $metadata_bag, SessionConfigurationInterface $session_configuration) { + public function __construct(RequestStack $request_stack, Connection $connection, MetadataBag $metadata_bag, SessionConfigurationInterface $session_configuration, $handler = NULL) { $options = array(); $this->sessionConfiguration = $session_configuration; $this->requestStack = $request_stack; $this->connection = $connection; - // Register the default session handler. - // @todo Extract session storage from session handler into a service. - $save_handler = new SessionHandler($this->requestStack, $this->connection); - $write_check_handler = new WriteCheckSessionHandler($save_handler); - $this->writeSafeHandler = new WriteSafeSessionHandler($write_check_handler); - - parent::__construct($options, $this->writeSafeHandler, $metadata_bag); + parent::__construct($options, $handler, $metadata_bag); // @todo When not using the Symfony Session object, the list of bags in the // NativeSessionStorage will remain uninitialized. This will lead to @@ -292,6 +286,13 @@ class SessionManager extends NativeSessionStorage implements SessionManagerInter return $this; } + /** + * {@inheritdoc} + */ + public function setWriteSafeHandler(WriteSafeSessionHandlerInterface $handler) { + $this->writeSafeHandler = $handler; + } + /** * Returns whether the current PHP process runs on CLI. * diff --git a/core/lib/Drupal/Core/Session/SessionManagerInterface.php b/core/lib/Drupal/Core/Session/SessionManagerInterface.php index c86fb7df647..fad62b787d0 100644 --- a/core/lib/Drupal/Core/Session/SessionManagerInterface.php +++ b/core/lib/Drupal/Core/Session/SessionManagerInterface.php @@ -27,6 +27,9 @@ interface SessionManagerInterface extends SessionStorageInterface { * * @return bool * FALSE if writing session data has been disabled. TRUE otherwise. + * + * @deprecated in Drupal 8.0.x, will be removed before Drupal 8.0.0 + * Use \Drupal\Core\Session\WriteSafeSessionHandler::isSessionWritable() */ public function isEnabled(); @@ -40,6 +43,9 @@ interface SessionManagerInterface extends SessionStorageInterface { * @see https://drupal.org/node/218104 * * @return $this + * + * @deprecated in Drupal 8.0.x, will be removed before Drupal 8.0.0 + * Use \Drupal\Core\Session\WriteSafeSessionHandler::setSessionWritable(FALSE) */ public function disable(); @@ -47,7 +53,20 @@ interface SessionManagerInterface extends SessionStorageInterface { * Re-enables saving of session data. * * @return $this + * + * @deprecated in Drupal 8.0.x, will be removed before Drupal 8.0.0 + * Use \Drupal\Core\Session\WriteSafeSessionHandler::setSessionWritable(True) */ public function enable(); + /** + * Sets the write safe session handler. + * + * @todo: This should be removed once all database queries are removed from + * the session manager class. + * + * @var \Drupal\Core\Session\WriteSafeSessionHandlerInterface + */ + public function setWriteSafeHandler(WriteSafeSessionHandlerInterface $handler); + } diff --git a/core/modules/system/src/Tests/Session/StackSessionHandlerIntegrationTest.php b/core/modules/system/src/Tests/Session/StackSessionHandlerIntegrationTest.php new file mode 100644 index 00000000000..65b55a61f74 --- /dev/null +++ b/core/modules/system/src/Tests/Session/StackSessionHandlerIntegrationTest.php @@ -0,0 +1,52 @@ +drupalGetAjax('session-test/trace-handler'); + $expect_trace = [ + ['BEGIN', 'test_argument', 'open'], + ['BEGIN', NULL, 'open'], + ['END', NULL, 'open'], + ['END', 'test_argument', 'open'], + ['BEGIN', 'test_argument', 'read', $this->sessionId], + ['BEGIN', NULL, 'read', $this->sessionId], + ['END', NULL, 'read', $this->sessionId], + ['END', 'test_argument', 'read', $this->sessionId], + ['BEGIN', 'test_argument', 'write', $this->sessionId], + ['BEGIN', NULL, 'write', $this->sessionId], + ['END', NULL, 'write', $this->sessionId], + ['END', 'test_argument', 'write', $this->sessionId], + ['BEGIN', 'test_argument', 'close'], + ['BEGIN', NULL, 'close'], + ['END', NULL, 'close'], + ['END', 'test_argument', 'close'], + ]; + $this->assertEqual($expect_trace, $actual_trace); + } + +} diff --git a/core/modules/system/tests/modules/session_test/session_test.routing.yml b/core/modules/system/tests/modules/session_test/session_test.routing.yml index e28d40d7731..8b9393e4249 100644 --- a/core/modules/system/tests/modules/session_test/session_test.routing.yml +++ b/core/modules/system/tests/modules/session_test/session_test.routing.yml @@ -75,3 +75,11 @@ session_test.form: _title: 'Test form' requirements: _access: 'TRUE' + +session_test.trace_handler: + path: '/session-test/trace-handler' + defaults: + _title: 'Returns the trace recorded by test proxy session handlers as JSON' + _controller: '\Drupal\session_test\Controller\SessionTestController::traceHandler' + requirements: + _access: 'TRUE' diff --git a/core/modules/system/tests/modules/session_test/session_test.services.yml b/core/modules/system/tests/modules/session_test/session_test.services.yml index 8ef2e204dec..324199d9968 100644 --- a/core/modules/system/tests/modules/session_test/session_test.services.yml +++ b/core/modules/system/tests/modules/session_test/session_test.services.yml @@ -3,3 +3,14 @@ services: class: Drupal\session_test\EventSubscriber\SessionTestSubscriber tags: - { name: event_subscriber } + session_test.session_handler.test_proxy: + class: Drupal\session_test\Session\TestSessionHandlerProxy + tags: + - { name: session_handler_proxy } + session_test.session_handler.test_proxy2: + class: Drupal\session_test\Session\TestSessionHandlerProxy + arguments: ['test_argument'] + tags: + - { name: session_handler_proxy, priority: 20 } + session_test.session_handler_proxy_trace: + class: ArrayObject diff --git a/core/modules/system/tests/modules/session_test/src/Controller/SessionTestController.php b/core/modules/system/tests/modules/session_test/src/Controller/SessionTestController.php index abaaa4c6d79..249b1ead487 100644 --- a/core/modules/system/tests/modules/session_test/src/Controller/SessionTestController.php +++ b/core/modules/system/tests/modules/session_test/src/Controller/SessionTestController.php @@ -9,6 +9,7 @@ namespace Drupal\session_test\Controller; use Drupal\Core\Controller\ControllerBase; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -125,4 +126,27 @@ class SessionTestController extends ControllerBase { return ['#markup' => $this->t('User is logged in.')]; } + /** + * Returns the trace recorded by test proxy session handlers as JSON. + * + * @return \Symfony\Component\HttpFoundation\JsonResponse + * The response. + */ + public function traceHandler() { + // Start a session if necessary, set a value and then save and close it. + \Drupal::service('session_manager')->start(); + if (empty($_SESSION['trace-handler'])) { + $_SESSION['trace-handler'] = 1; + } + else { + $_SESSION['trace-handler']++; + } + \Drupal::service('session_manager')->save(); + + // Collect traces and return them in JSON format. + $trace = \Drupal::service('session_test.session_handler_proxy_trace')->getArrayCopy(); + + return new JsonResponse($trace); + } + } diff --git a/core/modules/system/tests/modules/session_test/src/Session/TestSessionHandlerProxy.php b/core/modules/system/tests/modules/session_test/src/Session/TestSessionHandlerProxy.php new file mode 100644 index 00000000000..859ddb9ca06 --- /dev/null +++ b/core/modules/system/tests/modules/session_test/src/Session/TestSessionHandlerProxy.php @@ -0,0 +1,100 @@ +sessionHandler = $session_handler; + $this->optionalArgument = $optional_argument; + } + + /** + * {@inheritdoc} + */ + public function open($save_path, $name) { + $trace = \Drupal::service('session_test.session_handler_proxy_trace'); + $trace[] = ['BEGIN', $this->optionalArgument, __FUNCTION__]; + $result = $this->sessionHandler->open($save_path, $name); + $trace[] = ['END', $this->optionalArgument, __FUNCTION__]; + return $result; + } + + /** + * {@inheritdoc} + */ + public function close() { + $trace = \Drupal::service('session_test.session_handler_proxy_trace'); + $trace[] = ['BEGIN', $this->optionalArgument, __FUNCTION__]; + $result = $this->sessionHandler->close(); + $trace[] = ['END', $this->optionalArgument, __FUNCTION__]; + return $result; + } + + /** + * {@inheritdoc} + */ + public function read($session_id) { + $trace = \Drupal::service('session_test.session_handler_proxy_trace'); + $trace[] = ['BEGIN', $this->optionalArgument, __FUNCTION__, $session_id]; + $result = $this->sessionHandler->read($session_id); + $trace[] = ['END', $this->optionalArgument, __FUNCTION__, $session_id]; + return $result; + } + + /** + * {@inheritdoc} + */ + public function write($session_id, $session_data) { + $trace = \Drupal::service('session_test.session_handler_proxy_trace'); + $trace[] = ['BEGIN', $this->optionalArgument, __FUNCTION__, $session_id]; + $result = $this->sessionHandler->write($session_id, $session_data); + $trace[] = ['END', $this->optionalArgument, __FUNCTION__, $session_id]; + return $result; + } + + /** + * {@inheritdoc} + */ + public function destroy($session_id) { + return $this->sessionHandler->destroy($session_id); + } + + /** + * {@inheritdoc} + */ + public function gc($max_lifetime) { + return $this->sessionHandler->gc($max_lifetime); + } + +}