From d11d91d47c41c71453729e7ba1ded078e1cf81c3 Mon Sep 17 00:00:00 2001 From: Demian Katz <demian.katz@villanova.edu> Date: Thu, 30 Jan 2020 14:34:06 -0500 Subject: [PATCH] Improve session handlers (VUFIND-1355). (#1506) - Move configuration setting to constructor via factory (instead of using setConfig in ManagerFactory) - Use camelCase for more parameters/variables - Simplify/condense code where possible - Add test coverage --- .../src/VuFind/Session/AbstractBase.php | 62 +++---- .../VuFind/Session/AbstractBaseFactory.php | 71 ++++++++ module/VuFind/src/VuFind/Session/Database.php | 30 ++-- module/VuFind/src/VuFind/Session/File.php | 92 +++++----- .../src/VuFind/Session/HandlerInterface.php | 11 -- .../src/VuFind/Session/ManagerFactory.php | 7 +- module/VuFind/src/VuFind/Session/Memcache.php | 65 ++++--- .../src/VuFind/Session/PluginManager.php | 10 +- module/VuFind/src/VuFind/Session/Redis.php | 67 +++----- .../src/VuFind/Session/RedisFactory.php | 95 +++++++++++ .../Unit/SessionHandlerTestCase.php | 108 ++++++++++++ .../src/VuFindTest/Session/DatabaseTest.php | 158 +++++++++++++++++ .../src/VuFindTest/Session/FileTest.php | 144 ++++++++++++++++ .../src/VuFindTest/Session/MemcacheTest.php | 159 ++++++++++++++++++ .../src/VuFindTest/Session/RedisTest.php | 136 +++++++++++++++ 15 files changed, 1021 insertions(+), 194 deletions(-) create mode 100644 module/VuFind/src/VuFind/Session/AbstractBaseFactory.php create mode 100644 module/VuFind/src/VuFind/Session/RedisFactory.php create mode 100644 module/VuFind/src/VuFindTest/Unit/SessionHandlerTestCase.php create mode 100644 module/VuFind/tests/unit-tests/src/VuFindTest/Session/DatabaseTest.php create mode 100644 module/VuFind/tests/unit-tests/src/VuFindTest/Session/FileTest.php create mode 100644 module/VuFind/tests/unit-tests/src/VuFindTest/Session/MemcacheTest.php create mode 100644 module/VuFind/tests/unit-tests/src/VuFindTest/Session/RedisTest.php diff --git a/module/VuFind/src/VuFind/Session/AbstractBase.php b/module/VuFind/src/VuFind/Session/AbstractBase.php index 1e5c24847ec..dac4afc1bc8 100644 --- a/module/VuFind/src/VuFind/Session/AbstractBase.php +++ b/module/VuFind/src/VuFind/Session/AbstractBase.php @@ -54,13 +54,6 @@ abstract class AbstractBase implements HandlerInterface */ protected $lifetime = 3600; - /** - * Session configuration settings - * - * @var Config - */ - protected $config = null; - /** * Whether writes are disabled, i.e. any changes to the session are not written * to the storage @@ -70,39 +63,36 @@ abstract class AbstractBase implements HandlerInterface protected $writesDisabled = false; /** - * Enable session writing (default) + * Constructor * - * @return void + * @param Config $config Session configuration ([Session] section of + * config.ini) */ - public function enableWrites() + public function __construct(Config $config = null) { - $this->writesDisabled = false; + if (isset($config->lifetime)) { + $this->lifetime = $config->lifetime; + } } /** - * Disable session writing, i.e. make it read-only + * Enable session writing (default) * * @return void */ - public function disableWrites() + public function enableWrites() { - $this->writesDisabled = true; + $this->writesDisabled = false; } /** - * Set configuration. - * - * @param Config $config Session configuration ([Session] section of - * config.ini) + * Disable session writing, i.e. make it read-only * * @return void */ - public function setConfig(Config $config) + public function disableWrites() { - if (isset($config->lifetime)) { - $this->lifetime = $config->lifetime; - } - $this->config = $config; + $this->writesDisabled = true; } /** @@ -140,16 +130,16 @@ abstract class AbstractBase implements HandlerInterface * mechanisms. If you override this method, be sure to still call * parent::destroy() in addition to any new behavior. * - * @param string $sess_id The session ID to destroy + * @param string $sessId The session ID to destroy * * @return bool */ - public function destroy($sess_id) + public function destroy($sessId) { $searchTable = $this->getTable('Search'); - $searchTable->destroySession($sess_id); + $searchTable->destroySession($sessId); $sessionTable = $this->getTable('ExternalSession'); - $sessionTable->destroySession($sess_id); + $sessionTable->destroySession($sessId); return true; } @@ -157,13 +147,13 @@ abstract class AbstractBase implements HandlerInterface * The garbage collector, this is executed when the session garbage collector * is executed and takes the max session lifetime as its only parameter. * - * @param int $sess_maxlifetime Maximum session lifetime. + * @param int $sessMaxLifetime Maximum session lifetime. * * @return bool * * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function gc($sess_maxlifetime) + public function gc($sessMaxLifetime) { // how often does this get called (if at all)? @@ -182,26 +172,26 @@ abstract class AbstractBase implements HandlerInterface /** * Write function that is called when session data is to be saved. * - * @param string $sess_id The current session ID - * @param string $data The session data to write + * @param string $sessId The current session ID + * @param string $data The session data to write * * @return bool */ - public function write($sess_id, $data) + public function write($sessId, $data) { if ($this->writesDisabled) { return true; } - return $this->saveSession($sess_id, $data); + return $this->saveSession($sessId, $data); } /** * A function that is called internally when session data is to be saved. * - * @param string $sess_id The current session ID - * @param string $data The session data to write + * @param string $sessId The current session ID + * @param string $data The session data to write * * @return bool */ - abstract protected function saveSession($sess_id, $data); + abstract protected function saveSession($sessId, $data); } diff --git a/module/VuFind/src/VuFind/Session/AbstractBaseFactory.php b/module/VuFind/src/VuFind/Session/AbstractBaseFactory.php new file mode 100644 index 00000000000..f01c67f9175 --- /dev/null +++ b/module/VuFind/src/VuFind/Session/AbstractBaseFactory.php @@ -0,0 +1,71 @@ +<?php +/** + * Generic factory for instantiating session handlers + * + * PHP version 7 + * + * Copyright (C) Villanova University 2019. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Session_Handlers + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ +namespace VuFind\Session; + +use Interop\Container\ContainerInterface; +use Zend\ServiceManager\Factory\FactoryInterface; + +/** + * Generic factory for instantiating session handlers + * + * @category VuFind + * @package Session_Handlers + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + * + * @codeCoverageIgnore + */ +class AbstractBaseFactory implements FactoryInterface +{ + /** + * Create an object + * + * @param ContainerInterface $container Service manager + * @param string $requestedName Service being created + * @param null|array $options Extra options (optional) + * + * @return object + * + * @throws ServiceNotFoundException if unable to resolve the service. + * @throws ServiceNotCreatedException if an exception is raised when + * creating a service. + * @throws ContainerException if any other error occurs + */ + public function __invoke(ContainerInterface $container, $requestedName, + array $options = null + ) { + if (!empty($options)) { + throw new \Exception('Unexpected options passed to factory.'); + } + + $config = $container->get(\VuFind\Config\PluginManager::class) + ->get('config'); + return new $requestedName($config->Session ?? null); + } +} diff --git a/module/VuFind/src/VuFind/Session/Database.php b/module/VuFind/src/VuFind/Session/Database.php index da9b7f3ea1f..8b36c02a189 100644 --- a/module/VuFind/src/VuFind/Session/Database.php +++ b/module/VuFind/src/VuFind/Session/Database.php @@ -44,18 +44,18 @@ class Database extends AbstractBase * Read function must return string value always to make save handler work as * expected. Return empty string if there is no data to read. * - * @param string $sess_id The session ID to read + * @param string $sessId The session ID to read * * @return string */ - public function read($sess_id) + public function read($sessId) { // Try to read the session, but destroy it if it has expired: try { return $this->getTable('Session') - ->readSession($sess_id, $this->lifetime); + ->readSession($sessId, $this->lifetime); } catch (SessionExpiredException $e) { - $this->destroy($sess_id); + $this->destroy($sessId); return ''; } } @@ -64,17 +64,17 @@ class Database extends AbstractBase * The destroy handler, this is executed when a session is destroyed with * session_destroy() and takes the session id as its only parameter. * - * @param string $sess_id The session ID to destroy + * @param string $sessId The session ID to destroy * * @return bool */ - public function destroy($sess_id) + public function destroy($sessId) { // Perform standard actions required by all session methods: - parent::destroy($sess_id); + parent::destroy($sessId); // Now do database-specific destruction: - $this->getTable('Session')->destroySession($sess_id); + $this->getTable('Session')->destroySession($sessId); return true; } @@ -83,27 +83,27 @@ class Database extends AbstractBase * The garbage collector, this is executed when the session garbage collector * is executed and takes the max session lifetime as its only parameter. * - * @param int $sess_maxlifetime Maximum session lifetime. + * @param int $sessMaxLifetime Maximum session lifetime. * * @return bool */ - public function gc($sess_maxlifetime) + public function gc($sessMaxLifetime) { - $this->getTable('Session')->garbageCollect($sess_maxlifetime); + $this->getTable('Session')->garbageCollect($sessMaxLifetime); return true; } /** * A function that is called internally when session data is to be saved. * - * @param string $sess_id The current session ID - * @param string $data The session data to write + * @param string $sessId The current session ID + * @param string $data The session data to write * * @return bool */ - protected function saveSession($sess_id, $data) + protected function saveSession($sessId, $data) { - $this->getTable('Session')->writeSession($sess_id, $data); + $this->getTable('Session')->writeSession($sessId, $data); return true; } } diff --git a/module/VuFind/src/VuFind/Session/File.php b/module/VuFind/src/VuFind/Session/File.php index a4773f693a0..110e4533065 100644 --- a/module/VuFind/src/VuFind/Session/File.php +++ b/module/VuFind/src/VuFind/Session/File.php @@ -27,6 +27,8 @@ */ namespace VuFind\Session; +use Zend\Config\Config; + /** * File-based session handler * @@ -39,84 +41,80 @@ namespace VuFind\Session; class File extends AbstractBase { /** - * Path to session file (boolean false until set) + * Path to session file * - * @var string|bool + * @var string */ - protected $path = false; + protected $path; /** - * Get the file path for writing sessions. + * Constructor * - * @throws \Exception - * @return string + * @param Config $config Session configuration ([Session] section of + * config.ini) */ - protected function getPath() + public function __construct(Config $config = null) { - if (!$this->path) { - // Set defaults if nothing set in config file. - if (isset($this->config->file_save_path)) { - $this->path = $this->config->file_save_path; - } else { - $tempdir = function_exists('sys_get_temp_dir') - ? sys_get_temp_dir() : DIRECTORY_SEPARATOR . 'tmp'; - $this->path = $tempdir . DIRECTORY_SEPARATOR . 'vufind_sessions'; - } + parent::__construct($config); - // Die if the session directory does not exist and cannot be created. - if (!file_exists($this->path) || !is_dir($this->path)) { - if (!mkdir($this->path)) { - throw new \Exception( - "Cannot access session save path: " . $this->path - ); - } - } + // Set defaults if nothing set in config file. + if (isset($config->file_save_path)) { + $this->path = $config->file_save_path; + } else { + $tempdir = function_exists('sys_get_temp_dir') + ? sys_get_temp_dir() : DIRECTORY_SEPARATOR . 'tmp'; + $this->path = $tempdir . DIRECTORY_SEPARATOR . 'vufind_sessions'; } - return $this->path; + // Die if the session directory does not exist and cannot be created. + if ((!file_exists($this->path) || !is_dir($this->path)) + && !mkdir($this->path) + ) { + throw new \Exception("Cannot access session save path: " . $this->path); + } } /** * Read function must return string value always to make save handler work as * expected. Return empty string if there is no data to read. * - * @param string $sess_id The session ID to read + * @param string $sessId The session ID to read * * @return string */ - public function read($sess_id) + public function read($sessId) { - $sess_file = $this->getPath() . '/sess_' . $sess_id; - if (!file_exists($sess_file)) { + $sessFile = $this->path . '/sess_' . $sessId; + if (!file_exists($sessFile)) { return ''; } // enforce lifetime of this session data - if (filemtime($sess_file) + $this->lifetime <= time()) { - $this->destroy($sess_id); + if (filemtime($sessFile) + $this->lifetime <= time()) { + $this->destroy($sessId); return ''; } - return (string)file_get_contents($sess_file); + return (string)file_get_contents($sessFile); } /** * The destroy handler, this is executed when a session is destroyed with * session_destroy() and takes the session id as its only parameter. * - * @param string $sess_id The session ID to destroy + * @param string $sessId The session ID to destroy * * @return bool */ - public function destroy($sess_id) + public function destroy($sessId) { // Perform standard actions required by all session methods: - parent::destroy($sess_id); + parent::destroy($sessId); // Perform file-specific cleanup: - $sess_file = $this->getPath() . '/sess_' . $sess_id; - if (file_exists($sess_file)) { - return unlink($sess_file); + $sessFile = $this->path . '/sess_' . $sessId; + if (file_exists($sessFile)) { + return unlink($sessFile); } return true; } @@ -131,7 +129,7 @@ class File extends AbstractBase */ public function gc($maxlifetime) { - foreach (glob($this->getPath() . "/sess_*") as $filename) { + foreach (glob($this->path . "/sess_*") as $filename) { if (filemtime($filename) + $maxlifetime < time()) { unlink($filename); } @@ -142,17 +140,17 @@ class File extends AbstractBase /** * A function that is called internally when session data is to be saved. * - * @param string $sess_id The current session ID - * @param string $data The session data to write + * @param string $sessId The current session ID + * @param string $data The session data to write * * @return bool */ - protected function saveSession($sess_id, $data) + protected function saveSession($sessId, $data) { - $sess_file = $this->getPath() . '/sess_' . $sess_id; - if ($fp = fopen($sess_file, "w")) { - $return = fwrite($fp, $data); - fclose($fp); + $sessFile = $this->path . '/sess_' . $sessId; + if ($handle = fopen($sessFile, "w")) { + $return = fwrite($handle, $data); + fclose($handle); if ($return !== false) { return true; } @@ -161,7 +159,7 @@ class File extends AbstractBase // It is tempting to throw an exception here, but this code is called // outside of the context of exception handling, so all we can do is // echo a message. - echo 'Cannot write session to ' . $sess_file . "\n"; + echo 'Cannot write session to ' . $sessFile . "\n"; return false; } } diff --git a/module/VuFind/src/VuFind/Session/HandlerInterface.php b/module/VuFind/src/VuFind/Session/HandlerInterface.php index bd69dc3452a..5aad643f31a 100644 --- a/module/VuFind/src/VuFind/Session/HandlerInterface.php +++ b/module/VuFind/src/VuFind/Session/HandlerInterface.php @@ -30,7 +30,6 @@ namespace VuFind\Session; use VuFind\Db\Table\DbTableAwareInterface; -use Zend\Config\Config; use Zend\Session\SaveHandler\SaveHandlerInterface; /** @@ -58,14 +57,4 @@ interface HandlerInterface extends SaveHandlerInterface, DbTableAwareInterface * @return void */ public function disableWrites(); - - /** - * Set configuration. - * - * @param Config $config Session configuration ([Session] section of - * config.ini) - * - * @return void - */ - public function setConfig(Config $config); } diff --git a/module/VuFind/src/VuFind/Session/ManagerFactory.php b/module/VuFind/src/VuFind/Session/ManagerFactory.php index 2995052263d..9b1a7d5e883 100644 --- a/module/VuFind/src/VuFind/Session/ManagerFactory.php +++ b/module/VuFind/src/VuFind/Session/ManagerFactory.php @@ -91,11 +91,8 @@ class ManagerFactory implements FactoryInterface throw new \Exception('Cannot initialize session; configuration missing'); } - $sessionPluginManager = $container - ->get(\VuFind\Session\PluginManager::class); - $sessionHandler = $sessionPluginManager->get($config->Session->type); - $sessionHandler->setConfig($config->Session); - return $sessionHandler; + return $container->get(\VuFind\Session\PluginManager::class) + ->get($config->Session->type); } /** diff --git a/module/VuFind/src/VuFind/Session/Memcache.php b/module/VuFind/src/VuFind/Session/Memcache.php index 97b81134c6e..b3b3785520d 100644 --- a/module/VuFind/src/VuFind/Session/Memcache.php +++ b/module/VuFind/src/VuFind/Session/Memcache.php @@ -30,6 +30,8 @@ */ namespace VuFind\Session; +use Zend\Config\Config; + /** * Memcache session handler * @@ -46,47 +48,44 @@ class Memcache extends AbstractBase * * @var \Memcache */ - protected $connection = false; + protected $connection; /** - * Get connection to Memcache + * Constructor * - * @throws \Exception - * @return \Memcache + * @param Config $config Session configuration ([Session] section of + * config.ini) + * @param \Memcache $client Optional Memcache client object */ - public function getConnection() + public function __construct(Config $config = null, \Memcache $client = null) { - if (!$this->connection) { - // Set defaults if nothing set in config file. - $host = isset($this->config->memcache_host) - ? $this->config->memcache_host : 'localhost'; - $port = isset($this->config->memcache_port) - ? $this->config->memcache_port : 11211; - $timeout = isset($this->config->memcache_connection_timeout) - ? $this->config->memcache_connection_timeout : 1; + parent::__construct($config); + + // Set defaults if nothing set in config file. + $host = $config->memcache_host ?? 'localhost'; + $port = $config->memcache_port ?? 11211; + $timeout = $config->memcache_connection_timeout ?? 1; - // Connect to Memcache: - $this->connection = new \Memcache(); - if (!$this->connection->connect($host, $port, $timeout)) { - throw new \Exception( - "Could not connect to Memcache (host = {$host}, port = {$port})." - ); - } + // Connect to Memcache: + $this->connection = $client ?? new \Memcache(); + if (!$this->connection->connect($host, $port, $timeout)) { + throw new \Exception( + "Could not connect to Memcache (host = {$host}, port = {$port})." + ); } - return $this->connection; } /** * Read function must return string value always to make save handler work as * expected. Return empty string if there is no data to read. * - * @param string $sess_id The session ID to read + * @param string $sessId The session ID to read * * @return string */ - public function read($sess_id) + public function read($sessId) { - $value = $this->getConnection()->get("vufind_sessions/{$sess_id}"); + $value = $this->connection->get("vufind_sessions/{$sessId}"); return empty($value) ? '' : $value; } @@ -94,31 +93,31 @@ class Memcache extends AbstractBase * The destroy handler, this is executed when a session is destroyed with * session_destroy() and takes the session id as its only parameter. * - * @param string $sess_id The session ID to destroy + * @param string $sessId The session ID to destroy * * @return bool */ - public function destroy($sess_id) + public function destroy($sessId) { // Perform standard actions required by all session methods: - parent::destroy($sess_id); + parent::destroy($sessId); // Perform Memcache-specific cleanup: - return $this->getConnection()->delete("vufind_sessions/{$sess_id}"); + return $this->connection->delete("vufind_sessions/{$sessId}"); } /** * A function that is called internally when session data is to be saved. * - * @param string $sess_id The current session ID - * @param string $data The session data to write + * @param string $sessId The current session ID + * @param string $data The session data to write * * @return bool */ - protected function saveSession($sess_id, $data) + protected function saveSession($sessId, $data) { - return $this->getConnection()->set( - "vufind_sessions/{$sess_id}", $data, 0, $this->lifetime + return $this->connection->set( + "vufind_sessions/{$sessId}", $data, 0, $this->lifetime ); } } diff --git a/module/VuFind/src/VuFind/Session/PluginManager.php b/module/VuFind/src/VuFind/Session/PluginManager.php index 6927865aa57..be84633a832 100644 --- a/module/VuFind/src/VuFind/Session/PluginManager.php +++ b/module/VuFind/src/VuFind/Session/PluginManager.php @@ -29,8 +29,6 @@ */ namespace VuFind\Session; -use Zend\ServiceManager\Factory\InvokableFactory; - /** * Session handler plugin manager * @@ -65,10 +63,10 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager * @var array */ protected $factories = [ - Database::class => InvokableFactory::class, - File::class => InvokableFactory::class, - Memcache::class => InvokableFactory::class, - Redis::class => InvokableFactory::class, + Database::class => AbstractBaseFactory::class, + File::class => AbstractBaseFactory::class, + Memcache::class => AbstractBaseFactory::class, + Redis::class => RedisFactory::class, ]; /** diff --git a/module/VuFind/src/VuFind/Session/Redis.php b/module/VuFind/src/VuFind/Session/Redis.php index dc8645e4f8b..603a13f9f9b 100644 --- a/module/VuFind/src/VuFind/Session/Redis.php +++ b/module/VuFind/src/VuFind/Session/Redis.php @@ -31,6 +31,8 @@ */ namespace VuFind\Session; +use Zend\Config\Config; + /** * Redis session handler * @@ -48,7 +50,7 @@ class Redis extends AbstractBase * * @var \Credis_Client */ - protected $connection = false; + protected $connection; /** * Redis version @@ -58,60 +60,45 @@ class Redis extends AbstractBase protected $redisVersion = 3; /** - * Get connection to Redis + * Constructor * - * @throws \Exception - * @return \Credis_Client + * @param \Credis_Client $connection Redis connection object + * @param Config $config Session configuration ([Session] section of + * config.ini) */ - protected function getConnection() + public function __construct(\Credis_Client $connection, Config $config = null) { - if (!$this->connection) { - // Set defaults if nothing set in config file. - $host = $this->config->redis_host ?? 'localhost'; - $port = $this->config->redis_port ?? 6379; - $timeout = $this->config->redis_connection_timeout ?? 0.5; - $auth = $this->config->redis_auth ?? false; - $redis_db = $this->config->redis_db ?? 0; - $this->redisVersion = (int)($this->config->redis_version ?? 3); - $standalone = (bool)($this->config->redis_standalone ?? true); - - // Create Credis client, the connection is established lazily - $this->connection = new \Credis_Client( - $host, $port, $timeout, '', $redis_db, $auth - ); - if ($standalone) { - $this->connection->forceStandalone(); - } - } - return $this->connection; + parent::__construct($config); + $this->redisVersion = (int)($config->redis_version ?? 3); + $this->connection = $connection; } /** * Read function must return string value always to make save handler work as * expected. Return empty string if there is no data to read. * - * @param string $sess_id The session ID to read + * @param string $sessId The session ID to read * * @return string */ - public function read($sess_id) + public function read($sessId) { - $session = $this->getConnection()->get("vufind_sessions/{$sess_id}"); + $session = $this->connection->get("vufind_sessions/{$sessId}"); return $session !== false ? $session : ''; } /** * Write function that is called when session data is to be saved. * - * @param string $sess_id The current session ID - * @param string $data The session data to write + * @param string $sessId The current session ID + * @param string $data The session data to write * * @return bool */ - protected function saveSession($sess_id, $data) + protected function saveSession($sessId, $data) { - return $this->getConnection()->setex( - "vufind_sessions/{$sess_id}", $this->lifetime, $data + return $this->connection->setex( + "vufind_sessions/{$sessId}", $this->lifetime, $data ); } @@ -119,21 +106,19 @@ class Redis extends AbstractBase * The destroy handler, this is executed when a session is destroyed with * session_destroy() and takes the session id as its only parameter. * - * @param string $sess_id The session ID to destroy + * @param string $sessId The session ID to destroy * * @return bool */ - public function destroy($sess_id) + public function destroy($sessId) { // Perform standard actions required by all session methods: - parent::destroy($sess_id); + parent::destroy($sessId); // Perform Redis-specific cleanup - if ($this->redisVersion >= 4) { - $return = $this->getConnection()->unlink("vufind_sessions/{$sess_id}"); - } else { - $return = $this->getConnection()->del("vufind_sessions/{$sess_id}"); - } - return ($return > 0) ? true : false; + $unlinkMethod = ($this->redisVersion >= 4) ? 'unlink' : 'del'; + $return = $this->connection->$unlinkMethod("vufind_sessions/{$sessId}"); + + return $return > 0; } } diff --git a/module/VuFind/src/VuFind/Session/RedisFactory.php b/module/VuFind/src/VuFind/Session/RedisFactory.php new file mode 100644 index 00000000000..49290a91ed8 --- /dev/null +++ b/module/VuFind/src/VuFind/Session/RedisFactory.php @@ -0,0 +1,95 @@ +<?php +/** + * Generic factory for instantiating session handlers + * + * PHP version 7 + * + * Copyright (C) Villanova University 2019. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Session_Handlers + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ +namespace VuFind\Session; + +use Interop\Container\ContainerInterface; +use Zend\ServiceManager\Factory\FactoryInterface; + +/** + * Generic factory for instantiating session handlers + * + * @category VuFind + * @package Session_Handlers + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + * + * @codeCoverageIgnore + */ +class RedisFactory implements FactoryInterface +{ + /** + * Create an object + * + * @param ContainerInterface $container Service manager + * @param string $requestedName Service being created + * @param null|array $options Extra options (optional) + * + * @return object + * + * @throws ServiceNotFoundException if unable to resolve the service. + * @throws ServiceNotCreatedException if an exception is raised when + * creating a service. + * @throws ContainerException if any other error occurs + */ + public function __invoke(ContainerInterface $container, $requestedName, + array $options = null + ) { + if (!empty($options)) { + throw new \Exception('Unexpected options passed to factory.'); + } + + $config = $container->get(\VuFind\Config\PluginManager::class) + ->get('config')->Session ?? null; + return new $requestedName($this->getConnection($config), $config); + } + + /** + * Given a configuration, build the client object. + * + * @param \Zend\Config\Config $config Session configuration + * + * @return \Credis_Client + */ + protected function getConnection(\Zend\Config\Config $config) + { + // Set defaults if nothing set in config file. + $host = $config->redis_host ?? 'localhost'; + $port = $config->redis_port ?? 6379; + $timeout = $config->redis_connection_timeout ?? 0.5; + $auth = $config->redis_auth ?? false; + $redisDb = $config->redis_db ?? 0; + + // Create Credis client, the connection is established lazily + $client = new \Credis_Client($host, $port, $timeout, '', $redisDb, $auth); + if ((bool)($config->redis_standalone ?? true)) { + $client->forceStandalone(); + } + return $client; + } +} diff --git a/module/VuFind/src/VuFindTest/Unit/SessionHandlerTestCase.php b/module/VuFind/src/VuFindTest/Unit/SessionHandlerTestCase.php new file mode 100644 index 00000000000..b4eb57adcbb --- /dev/null +++ b/module/VuFind/src/VuFindTest/Unit/SessionHandlerTestCase.php @@ -0,0 +1,108 @@ +<?php + +/** + * Abstract base class for session handler test cases. + * + * PHP version 7 + * + * Copyright (C) Villanova University 2019. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Tests + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ +namespace VuFindTest\Unit; + +use VuFind\Session\AbstractBase as SessionHandler; + +/** + * Abstract base class for session handler test cases. + * + * @category VuFind + * @package Tests + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ +abstract class SessionHandlerTestCase extends TestCase +{ + /** + * Mock database tables. + * + * @var \VuFind\Db\Table\PluginManager + */ + protected $tables = false; + + /** + * Get mock database plugin manager + * + * @return \VuFind\Db\Table\PluginManager + */ + protected function getTables() + { + if (!$this->tables) { + $this->tables = $this + ->getMockBuilder(\VuFind\Db\Table\PluginManager::class) + ->disableOriginalConstructor() + ->getMock(); + } + return $this->tables; + } + + /** + * Set up mock databases for a session handler. + * + * @param SessionHandler $handler Session handler + * + * @return void + */ + protected function injectMockDatabaseTables(SessionHandler $handler) + { + $handler->setDbTableManager($this->getTables()); + } + + /** + * Set up expectations for the standard abstract handler's destroy behavior. + * + * @param string $sessId Session ID that we expect will be destroyed. + * + * @return void + */ + protected function setUpDestroyExpectations($sessId) + { + $search = $this->getMockBuilder(\VuFind\Db\Table\Search::class) + ->disableOriginalConstructor() + ->getMock(); + $search->expects($this->once()) + ->method('destroySession') + ->with($this->equalTo($sessId)); + $external = $this->getMockBuilder(\VuFind\Db\Table\ExternalSession::class) + ->disableOriginalConstructor() + ->getMock(); + $external->expects($this->once()) + ->method('destroySession') + ->with($this->equalTo($sessId)); + $tables = $this->getTables(); + $tables->expects($this->at(0))->method('get') + ->with($this->equalTo('Search')) + ->will($this->returnValue($search)); + $tables->expects($this->at(1))->method('get') + ->with($this->equalTo('ExternalSession')) + ->will($this->returnValue($external)); + } +} diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/Session/DatabaseTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/Session/DatabaseTest.php new file mode 100644 index 00000000000..ae02e611566 --- /dev/null +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/Session/DatabaseTest.php @@ -0,0 +1,158 @@ +<?php +/** + * Database Session Handler Test Class + * + * PHP version 7 + * + * Copyright (C) Villanova University 2019. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Tests + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ +namespace VuFindTest\Session; + +use VuFind\Session\Database; + +/** + * Database Session Handler Test Class + * + * @category VuFind + * @package Tests + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ +class DatabaseTest extends \VuFindTest\Unit\SessionHandlerTestCase +{ + /** + * Test reading a session from the database. + * + * @return void + */ + public function testRead() + { + $handler = $this->getHandler(); + $session = $this->getMockSessionTable(); + $session->expects($this->once())->method('readSession') + ->with($this->equalTo('foo'), $this->equalTo(3600)) + ->will($this->returnValue('bar')); + $this->getTables()->expects($this->once())->method('get') + ->with($this->equalTo('Session')) + ->will($this->returnValue($session)); + $this->assertEquals('bar', $handler->read('foo')); + } + + /** + * Test reading a session from the database with a non-default lifetime config. + * + * @return void + */ + public function testReadWithNonDefaultLifetime() + { + $handler = $this->getHandler( + new \Zend\Config\Config(['lifetime' => 1000]) + ); + $session = $this->getMockSessionTable(); + $session->expects($this->once())->method('readSession') + ->with($this->equalTo('foo'), $this->equalTo(1000)) + ->will($this->returnValue('bar')); + $this->getTables()->expects($this->once())->method('get') + ->with($this->equalTo('Session')) + ->will($this->returnValue($session)); + $this->assertEquals('bar', $handler->read('foo')); + } + + /** + * Test garbage collection. + * + * @return void + */ + public function testGc() + { + $handler = $this->getHandler(); + $session = $this->getMockSessionTable(); + $session->expects($this->once())->method('garbageCollect') + ->with($this->equalTo(3600)); + $this->getTables()->expects($this->once())->method('get') + ->with($this->equalTo('Session')) + ->will($this->returnValue($session)); + $this->assertTrue($handler->gc(3600)); + } + + /** + * Test writing a session. + * + * @return void + */ + public function testWrite() + { + $handler = $this->getHandler(); + $session = $this->getMockSessionTable(); + $session->expects($this->once())->method('writeSession') + ->with($this->equalTo('foo'), $this->equalTo('stuff')); + $this->getTables()->expects($this->once())->method('get') + ->with($this->equalTo('Session')) + ->will($this->returnValue($session)); + $this->assertTrue($handler->write('foo', 'stuff')); + } + + /** + * Test destroying a session. + * + * @return void + */ + public function testDestroy() + { + $handler = $this->getHandler(); + $this->setUpDestroyExpectations('foo'); + $session = $this->getMockSessionTable(); + $session->expects($this->once())->method('destroySession') + ->with($this->equalTo('foo')); + $this->getTables()->expects($this->at(2))->method('get') + ->with($this->equalTo('Session')) + ->will($this->returnValue($session)); + $this->assertTrue($handler->destroy('foo')); + } + + /** + * Get the session handler to test. + * + * @param \Zend\Config\Config $config Optional configuration + * + * @return Database + */ + protected function getHandler($config = null) + { + $handler = new Database($config); + $this->injectMockDatabaseTables($handler); + return $handler; + } + + /** + * Get a mock session table. + * + * @return \VuFind\Db\Table\Session + */ + protected function getMockSessionTable() + { + return $this->getMockBuilder(\VuFind\Db\Table\Session::class) + ->disableOriginalConstructor() + ->getMock(); + } +} diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/Session/FileTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/Session/FileTest.php new file mode 100644 index 00000000000..1e51b0d01bf --- /dev/null +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/Session/FileTest.php @@ -0,0 +1,144 @@ +<?php +/** + * File Session Handler Test Class + * + * PHP version 7 + * + * Copyright (C) Villanova University 2019. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Tests + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ +namespace VuFindTest\Session; + +use VuFind\Session\File; + +/** + * File Session Handler Test Class + * + * @category VuFind + * @package Tests + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ +class FileTest extends \VuFindTest\Unit\SessionHandlerTestCase +{ + /** + * Path to session files + * + * @var string + */ + protected $path; + + /** + * Generic setup method + * + * @return void + */ + public function setUp() + { + $tempdir = function_exists('sys_get_temp_dir') + ? sys_get_temp_dir() : DIRECTORY_SEPARATOR . 'tmp'; + $this->path = $tempdir . DIRECTORY_SEPARATOR . 'vufindtest_sessions'; + } + + /** + * Generic teardown method + * + * @return void + */ + public function tearDown() + { + rmdir($this->path); + } + + /** + * Test the standard default session life cycle. + * + * @return void + */ + public function testWriteReadAndDestroy() + { + $handler = $this->getHandler(); + $this->assertTrue($handler->write('foo', 'bar')); + $this->assertEquals('bar', $handler->read('foo')); + $this->setUpDestroyExpectations('foo'); + $this->assertTrue($handler->destroy('foo')); + $this->assertEquals('', $handler->read('foo')); + } + + /** + * Test disabling writes. + * + * @return void + */ + public function testDisabledWrites() + { + $handler = $this->getHandler(); + $handler->disableWrites(); + $this->assertTrue($handler->write('foo', 'bar')); + $this->assertEquals('', $handler->read('foo')); + + // Now test re-enabling writes: + $handler->enableWrites(); + $this->assertTrue($handler->write('foo', 'bar')); + $this->assertEquals('bar', $handler->read('foo')); + + // Now clean up after ourselves: + $this->setUpDestroyExpectations('foo'); + $this->assertTrue($handler->destroy('foo')); + $this->assertEquals('', $handler->read('foo')); + } + + /** + * Test the session garbage collector. + * + * @return void + */ + public function testGarbageCollector() + { + $handler = $this->getHandler(); + $this->assertTrue($handler->write('foo', 'bar')); + $this->assertEquals('bar', $handler->read('foo')); + // Use a negative garbage collection age so we can purge everything + // without having to wait for time to pass in the test! + $this->assertTrue($handler->gc(-1)); + $this->assertEquals('', $handler->read('foo')); + } + + /** + * Get the session handler to test. + * + * @param \Zend\Config\Config $config Optional configuration + * + * @return Database + */ + protected function getHandler($config = null) + { + if (null === $config) { + $config = new \Zend\Config\Config( + ['file_save_path' => $this->path] + ); + } + $handler = new File($config); + $this->injectMockDatabaseTables($handler); + return $handler; + } +} diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/Session/MemcacheTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/Session/MemcacheTest.php new file mode 100644 index 00000000000..b996ced0894 --- /dev/null +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/Session/MemcacheTest.php @@ -0,0 +1,159 @@ +<?php +/** + * Memcache Session Handler Test Class + * + * PHP version 7 + * + * Copyright (C) Villanova University 2019. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Tests + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ +namespace VuFindTest\Session; + +use VuFind\Session\Memcache; + +/** + * Memcache Session Handler Test Class + * + * @category VuFind + * @package Tests + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ +class MemcacheTest extends \VuFindTest\Unit\SessionHandlerTestCase +{ + /** + * Test reading a session from the database. + * + * @return void + */ + public function testRead() + { + $memcache = $this->getMockBuilder(\Memcache::class) + ->setMethods(['connect', 'get']) + ->getMock(); + $memcache->expects($this->once())->method('connect') + ->will($this->returnValue(true)); + $memcache->expects($this->once())->method('get') + ->with($this->equalTo('vufind_sessions/foo')) + ->will($this->returnValue('bar')); + $handler = $this->getHandler(null, $memcache); + $this->assertEquals('bar', $handler->read('foo')); + } + + /** + * Test writing a session with default configs. + * + * @return void + */ + public function testWriteWithDefaults() + { + $memcache = $this->getMockBuilder(\Memcache::class) + ->setMethods(['connect', 'set']) + ->getMock(); + $memcache->expects($this->once())->method('connect') + ->with( + $this->equalTo('localhost'), + $this->equalTo(11211), + $this->equalTo(1) + )->will($this->returnValue(true)); + $memcache->expects($this->once())->method('set') + ->with( + $this->equalTo('vufind_sessions/foo'), + $this->equalTo('stuff'), + $this->equalTo(0), + $this->equalTo(3600) + )->will($this->returnValue(true)); + $handler = $this->getHandler(null, $memcache); + $this->assertTrue($handler->write('foo', 'stuff')); + } + + /** + * Test writing a session with non-default configs. + * + * @return void + */ + public function testWriteWithNonDefaults() + { + $memcache = $this->getMockBuilder(\Memcache::class) + ->setMethods(['connect', 'set']) + ->getMock(); + $memcache->expects($this->once())->method('connect') + ->with( + $this->equalTo('myhost'), + $this->equalTo(1234), + $this->equalTo(2) + )->will($this->returnValue(true)); + $memcache->expects($this->once())->method('set') + ->with( + $this->equalTo('vufind_sessions/foo'), + $this->equalTo('stuff'), + $this->equalTo(0), + $this->equalTo(1000) + )->will($this->returnValue(true)); + $config = new \Zend\Config\Config( + [ + 'lifetime' => 1000, + 'memcache_host' => 'myhost', + 'memcache_port' => 1234, + 'memcache_connection_timeout' => 2, + ] + ); + $handler = $this->getHandler($config, $memcache); + $this->assertTrue($handler->write('foo', 'stuff')); + } + + /** + * Test destroying a session. + * + * @return void + */ + public function testDestroy() + { + $memcache = $this->getMockBuilder(\Memcache::class) + ->setMethods(['connect', 'delete']) + ->getMock(); + $memcache->expects($this->once())->method('connect') + ->will($this->returnValue(true)); + $memcache->expects($this->once())->method('delete') + ->with($this->equalTo('vufind_sessions/foo')) + ->will($this->returnValue(true)); + $handler = $this->getHandler(null, $memcache); + $this->setUpDestroyExpectations('foo'); + + $this->assertTrue($handler->destroy('foo')); + } + + /** + * Get the session handler to test. + * + * @param \Zend\Config\Config $config Optional configuration + * @param \Memcache $client Optional client object + * + * @return Database + */ + protected function getHandler($config = null, $client = null) + { + $handler = new Memcache($config, $client); + $this->injectMockDatabaseTables($handler); + return $handler; + } +} diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/Session/RedisTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/Session/RedisTest.php new file mode 100644 index 00000000000..f3849a2aee1 --- /dev/null +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/Session/RedisTest.php @@ -0,0 +1,136 @@ +<?php +/** + * Redis Session Handler Test Class + * + * PHP version 7 + * + * Copyright (C) Villanova University 2019. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Tests + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ +namespace VuFindTest\Session; + +use VuFind\Session\Redis; + +/** + * Redis Session Handler Test Class + * + * @category VuFind + * @package Tests + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ +class RedisTest extends \VuFindTest\Unit\SessionHandlerTestCase +{ + /** + * Test reading a session from the database. + * + * @return void + */ + public function testRead() + { + $client = $this->getMockBuilder(\Credis_Client::class) + ->setMethods(['get']) + ->getMock(); + $client->expects($this->once())->method('get') + ->with($this->equalTo('vufind_sessions/foo')) + ->will($this->returnValue('bar')); + $handler = $this->getHandler($client); + $this->assertEquals('bar', $handler->read('foo')); + } + + /** + * Test writing a session with default configs. + * + * @return void + */ + public function testWrite() + { + $client = $this->getMockBuilder(\Credis_Client::class) + ->setMethods(['setex']) + ->getMock(); + $client->expects($this->once())->method('setex') + ->with( + $this->equalTo('vufind_sessions/foo'), + $this->equalTo(3600), + $this->equalTo('stuff') + ) + ->will($this->returnValue(true)); + $handler = $this->getHandler($client); + $this->assertTrue($handler->write('foo', 'stuff')); + } + + /** + * Test destroying a session with default (Redis version 3) support. + * + * @return void + */ + public function testDestroyDefault() + { + $client = $this->getMockBuilder(\Credis_Client::class) + ->setMethods(['del']) + ->getMock(); + $client->expects($this->once())->method('del') + ->with($this->equalTo('vufind_sessions/foo')) + ->will($this->returnValue(1)); + $handler = $this->getHandler($client); + $this->setUpDestroyExpectations('foo'); + + $this->assertTrue($handler->destroy('foo')); + } + + /** + * Test destroying a session with newer (Redis version 4+) support. + * + * @return void + */ + public function testDestroyNewRedis() + { + $client = $this->getMockBuilder(\Credis_Client::class) + ->setMethods(['unlink']) + ->getMock(); + $client->expects($this->once())->method('unlink') + ->with($this->equalTo('vufind_sessions/foo')) + ->will($this->returnValue(1)); + $config = new \Zend\Config\Config( + ['redis_version' => 4] + ); + $handler = $this->getHandler($client, $config); + $this->setUpDestroyExpectations('foo'); + + $this->assertTrue($handler->destroy('foo')); + } + + /** + * Get the session handler to test. + * + * @param \Credis_Client $client Client object + * @param \Zend\Config\Config $config Optional configuration + * + * @return Database + */ + protected function getHandler($client, $config = null) + { + $handler = new Redis($client, $config); + $this->injectMockDatabaseTables($handler); + return $handler; + } +} -- GitLab