From a4bc626fae101bda791786e57e719917d7bd107b Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Thu, 29 Mar 2012 17:42:42 -0400 Subject: [PATCH] [Session] Fixes, cleanup, docs, tests --- .../Component/Session/SessionComponent.php | 54 ++++++++++++++++-- .../Session/Storage/VirtualSessionStorage.php | 4 +- .../Session/Serialize/PhpHandlerTest.php | 36 ++++++++++++ .../Session/SessionComponentTest.php | 52 ++++++++++++++++++ .../Tests/Mock/MemorySessionHandler.php | 39 +++++++++++++ .../Tests/Mock/NullMessageComponent.php | 55 +++++++++++++++++++ 6 files changed, 234 insertions(+), 6 deletions(-) create mode 100644 tests/Ratchet/Tests/Component/Session/Serialize/PhpHandlerTest.php create mode 100644 tests/Ratchet/Tests/Component/Session/SessionComponentTest.php create mode 100644 tests/Ratchet/Tests/Mock/MemorySessionHandler.php create mode 100644 tests/Ratchet/Tests/Mock/NullMessageComponent.php diff --git a/src/Ratchet/Component/Session/SessionComponent.php b/src/Ratchet/Component/Session/SessionComponent.php index 702b4ea..30654c2 100644 --- a/src/Ratchet/Component/Session/SessionComponent.php +++ b/src/Ratchet/Component/Session/SessionComponent.php @@ -7,29 +7,55 @@ use Ratchet\Component\Session\Serialize\HandlerInterface; use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler; +/** + * This component will allow access to session data from your website for each user connected + * Symfony HttpFoundation is required for this component to work + * Your website must also use Symfony HttpFoundation Sessions to read your sites session data + * If your are not using at least PHP 5.4 you must include a SessionHandlerInterface stub (is included in Symfony HttpFoundation, loaded w/ composer) + */ class SessionComponent implements MessageComponentInterface { + /** + * @var Ratchet\Component\MessageComponentInterface + */ protected $_app; - protected $_options; + /** + * Selected handler storage assigned by the developer + * @var SessionHandlerInterface + */ protected $_handler; + + /** + * Null storage handler if no previous session was found + * @var SessionHandlerInterface + */ protected $_null; + /** + * @var Ratchet\Component\Session\Serialize\HandlerInterface + */ protected $_serializer; + /** + * @param Ratchet\Component\MessageComponentInterface + * @param SessionHandlerInterface + * @param array + * @param Ratchet\Component\Session\Serialize\HandlerInterface + * @throws RuntimeException If unable to match serialization methods + */ public function __construct(MessageComponentInterface $app, \SessionHandlerInterface $handler, array $options = array(), HandlerInterface $serializer = null) { $this->_app = $app; $this->_handler = $handler; - $this->_options = array(); $this->_null = new NullSessionHandler; ini_set('session.auto_start', 0); ini_set('session.cache_limiter', ''); ini_set('session.use_cookies', 0); - $options = $this->setOptions($options); + $this->setOptions($options); if (null === $serializer) { - $serialClass = __NAMESPACE__ . '\\Serialize\\' . str_replace(' ', '', ucwords(str_replace('_', ' ', ini_get('session.serialize_handler')))) . 'Handler'; // awesome/terrible hack, eh? + $serialClass = __NAMESPACE__ . "\\Serialize\\{$this->toClassCase(ini_get('session.serialize_handler'))}Handler"; // awesome/terrible hack, eh? if (!class_exists($serialClass)) { throw new \RuntimeExcpetion('Unable to parse session serialize handler'); } @@ -44,7 +70,7 @@ class SessionComponent implements MessageComponentInterface { * {@inheritdoc} */ function onOpen(ConnectionInterface $conn) { - if (null === ($id = $conn->WebSocket->headers->getCookie($this->_options['name']))) { + if (null === ($id = $conn->WebSocket->headers->getCookie(ini_get('session.name')))) { $saveHandler = $this->_null; $id = ''; } else { @@ -53,6 +79,10 @@ class SessionComponent implements MessageComponentInterface { $conn->Session = new Session(new VirtualSessionStorage($saveHandler, $id, $this->_serializer)); + if (ini_get('session.auto_start')) { + $conn->Session->start(); + } + return $this->_app->onOpen($conn); } @@ -79,6 +109,12 @@ class SessionComponent implements MessageComponentInterface { return $this->_app->onError($conn, $e); } + /** + * Set all the php session. ini options + * © Symfony + * @param array + * @return array + */ protected function setOptions(array $options) { $all = array( 'auto_start', 'cache_limiter', 'cookie_domain', 'cookie_httponly', @@ -102,4 +138,12 @@ class SessionComponent implements MessageComponentInterface { return $options; } + + /** + * @param string Input to convert + * @return string + */ + protected function toClassCase($langDef) { + return str_replace(' ', '', ucwords(str_replace('_', ' ', $langDef))); + } } \ No newline at end of file diff --git a/src/Ratchet/Component/Session/Storage/VirtualSessionStorage.php b/src/Ratchet/Component/Session/Storage/VirtualSessionStorage.php index 9e3bdd4..795e4f4 100644 --- a/src/Ratchet/Component/Session/Storage/VirtualSessionStorage.php +++ b/src/Ratchet/Component/Session/Storage/VirtualSessionStorage.php @@ -11,7 +11,9 @@ class VirtualSessionStorage extends NativeSessionStorage { protected $_serializer; /** - * {@inheritdoc} + * @param SessionHandlerInterface + * @param string The ID of the session to retreive + * @param Ratchet\Component\Session\Serialize\HandlerInterface */ public function __construct(\SessionHandlerInterface $handler, $sessionId, HandlerInterface $serializer) { $this->setSaveHandler($handler); diff --git a/tests/Ratchet/Tests/Component/Session/Serialize/PhpHandlerTest.php b/tests/Ratchet/Tests/Component/Session/Serialize/PhpHandlerTest.php new file mode 100644 index 0000000..0528817 --- /dev/null +++ b/tests/Ratchet/Tests/Component/Session/Serialize/PhpHandlerTest.php @@ -0,0 +1,36 @@ +_handler = new PhpHandler; + } + + public function serializedProvider() { + return array( + array( + '_sf2_attributes|a:2:{s:5:"hello";s:5:"world";s:4:"last";i:1332872102;}_sf2_flashes|a:0:{}' + , array( + '_sf2_attributes' => array( + 'hello' => 'world' + , 'last' => 1332872102 + ) + , '_sf2_flashes' => array() + ) + ) + ); + } + + /** + * @dataProvider serializedProvider + */ + public function testUnserialize($in, $expected) { + $this->assertEquals($expected, $this->_handler->unserialize($in)); + } +} \ No newline at end of file diff --git a/tests/Ratchet/Tests/Component/Session/SessionComponentTest.php b/tests/Ratchet/Tests/Component/Session/SessionComponentTest.php new file mode 100644 index 0000000..87020c9 --- /dev/null +++ b/tests/Ratchet/Tests/Component/Session/SessionComponentTest.php @@ -0,0 +1,52 @@ +app = new SessionComponent + } + + /** + * @return bool + */ + public function checkSymfonyPresent() { + return class_exists('Symfony\\Component\\HttpFoundation\\Session\\Session'); + } + + public function classCaseProvider() { + return array( + array('php', 'Php') + , array('php_binary', 'PhpBinary') + ); + } + + /** + * @dataProvider classCaseProvider + */ + public function testToClassCase($in, $out) { + if (!interface_exists('SessionHandlerInterface')) { + return $this->markTestSkipped('SessionHandlerInterface not defined. Requires PHP 5.4 or Symfony HttpFoundation'); + } + + $ref = new \ReflectionClass('\\Ratchet\\Component\\Session\\SessionComponent'); + $method = $ref->getMethod('toClassCase'); + $method->setAccessible(true); + + $component = new SessionComponent(new NullMessageComponent, new MemorySessionHandler); + $this->assertEquals($out, $method->invokeArgs($component, array($in))); + } + +/* Put this in test methods that require Symfony + if (false === $this->checkSymfonyPresent()) { + return $this->markTestSkipped('Symfony HttpFoundation not loaded'); + } +*/ +} \ No newline at end of file diff --git a/tests/Ratchet/Tests/Mock/MemorySessionHandler.php b/tests/Ratchet/Tests/Mock/MemorySessionHandler.php new file mode 100644 index 0000000..87e9953 --- /dev/null +++ b/tests/Ratchet/Tests/Mock/MemorySessionHandler.php @@ -0,0 +1,39 @@ +_sessions[$session_id])) { + unset($this->_sessions[$session_id]); + } + + return true; + } + + public function gc($maxlifetime) { + return true; + } + + public function open($save_path, $session_id) { + if (!isset($this->_sessions[$session_id])) { + $this->_sessions[$session_id] = ''; + } + + return true; + } + + public function read($session_id) { + return $this->_sessions[$session_id]; + } + + public function write($session_id, $session_data) { + $this->_sessions[$session_id] = $session_data; + + return true; + } +} \ No newline at end of file diff --git a/tests/Ratchet/Tests/Mock/NullMessageComponent.php b/tests/Ratchet/Tests/Mock/NullMessageComponent.php new file mode 100644 index 0000000..0f93fde --- /dev/null +++ b/tests/Ratchet/Tests/Mock/NullMessageComponent.php @@ -0,0 +1,55 @@ +connections = new \SplObjectStorage; + $this->messageHistory = new \SplQueue; + $this->errorHistory = new \SplQueue; + } + + /** + * {@inheritdoc} + */ + function onOpen(ConnectionInterface $conn) { + $this->connections->attach($conn); + } + + /** + * {@inheritdoc} + */ + function onMessage(ConnectionInterface $from, $msg) { + $this->messageHistory->enqueue(array('from' => $from, 'msg' => $msg)); + } + + /** + * {@inheritdoc} + */ + function onClose(ConnectionInterface $conn) { + $this->connections->detach($conn); + } + + /** + * {@inheritdoc} + */ + function onError(ConnectionInterface $conn, \Exception $e) { + $this->errorHistory->enqueue(array('conn' => $conn, 'exception' => $e)); + } +} \ No newline at end of file