diff --git a/src/Kdyby/Redis/ConnectionPool.php b/src/Kdyby/Redis/ConnectionPool.php new file mode 100644 index 0000000..7fe6b41 --- /dev/null +++ b/src/Kdyby/Redis/ConnectionPool.php @@ -0,0 +1,74 @@ + + */ +class ConnectionPool extends Nette\Object +{ + + /** + * @var IRedisDriver[] + */ + private $connections = array(); + + + /** + * Add new connection to pool + * @param string $host + * @param int $port + * @param IRedisDriver $connection + * @throws ConnectionAlreadyInPoolException + */ + public function addConnection($host, $port, IRedisDriver $connection) + { + $key = $this->getKey($host, $port); + + if (isset($this->connections[$key])) { + throw new ConnectionAlreadyInPoolException; + } + + $this->connections[$key] = $connection; + } + + + + /** + * Get connection from pool + * @param string $host + * @param int $port + * @return Driver\PhpRedisDriver + */ + public function getConnection($host, $port) + { + $key = $this->getKey($host, $port); + + if (!isset($this->connections[$key])) { + return NULL; + } + + return $this->connections[$key]; + } + + + + private function getKey($host, $port) + { + return strtolower($host) . ':' . $port; + } + +} diff --git a/src/Kdyby/Redis/Driver/PhpRedisDriver.php b/src/Kdyby/Redis/Driver/PhpRedisDriver.php index 4c0f4e3..a791a05 100644 --- a/src/Kdyby/Redis/Driver/PhpRedisDriver.php +++ b/src/Kdyby/Redis/Driver/PhpRedisDriver.php @@ -21,6 +21,12 @@ class PhpRedisDriver extends \Redis implements Kdyby\Redis\IRedisDriver { + /** + * @var integer + */ + private $database = 0; + + /** * {@inheritdoc} */ @@ -38,7 +44,13 @@ public function connect($host, $port = NULL, $timeout = 0) public function select($database) { $args = func_get_args(); - return call_user_func_array('parent::select', $args); + $result = call_user_func_array('parent::select', $args); + + if ($result === TRUE) { + $this->database = (int) $database; + } + + return $result; } @@ -63,4 +75,14 @@ public function evalsha($scriptSha, $argsArray = array(), $numKeys = 0) return call_user_func_array('parent::evalsha', $args); } + + + /** + * {@inheritdoc} + */ + public function getDatabase() + { + return $this->database; + } + } diff --git a/src/Kdyby/Redis/IRedisDriver.php b/src/Kdyby/Redis/IRedisDriver.php index 6900c60..86817eb 100644 --- a/src/Kdyby/Redis/IRedisDriver.php +++ b/src/Kdyby/Redis/IRedisDriver.php @@ -195,6 +195,13 @@ function select($database); */ function close(); + /** + * Get actual selected database + * + * @return int + */ + function getDatabase(); + /** * The last error message (if any) * diff --git a/src/Kdyby/Redis/RedisClient.php b/src/Kdyby/Redis/RedisClient.php index a753d47..2e9826c 100644 --- a/src/Kdyby/Redis/RedisClient.php +++ b/src/Kdyby/Redis/RedisClient.php @@ -165,7 +165,7 @@ class RedisClient extends Nette\Object implements \ArrayAccess /** * @var Driver\PhpRedisDriver */ - private $driver; + protected $driver; /** * @var bool @@ -183,7 +183,7 @@ class RedisClient extends Nette\Object implements \ArrayAccess private $panel; /** - * @var string + * @var int */ private $host; @@ -261,6 +261,36 @@ public function __destruct() + /** + * @return string + */ + protected function getHost() + { + return $this->host; + } + + + + /** + * @return int + */ + protected function getPort() + { + return $this->port; + } + + + + /** + * @return int + */ + protected function getDatabase() + { + return $this->database; + } + + + /** * @return \Kdyby\Redis\IRedisDriver */ @@ -279,6 +309,7 @@ public function connect() } if ($this->driver->isConnected()) { + $this->isConnected = TRUE; return; } diff --git a/src/Kdyby/Redis/RedisSharedClient.php b/src/Kdyby/Redis/RedisSharedClient.php new file mode 100644 index 0000000..0444ac0 --- /dev/null +++ b/src/Kdyby/Redis/RedisSharedClient.php @@ -0,0 +1,97 @@ + + */ +class RedisSharedClient extends RedisClient +{ + + /** + * @var ConnectionPool + */ + private $connectionPool; + + + /** + * @param ConnectionPool $connectionPool + * @param string $host + * @param int $port + * @param int $database + * @param int $timeout + * @param string $auth + * @param bool $persistent + * @throws MissingExtensionException + */ + public function __construct(ConnectionPool $connectionPool, $host = '127.0.0.1', $port = NULL, $database = 0, $timeout = 10, $auth = NULL, $persistent = FALSE) + { + parent::__construct($host, $port, $database, $timeout, $auth, $persistent); + $this->connectionPool = $connectionPool; + } + + + + public function connect() + { + if (!$this->driver) { + $driver = $this->connectionPool->getConnection($this->getHost(), $this->getPort()); + + if ($driver === NULL) { + $driver = new Driver\PhpRedisDriver(); + $this->connectionPool->addConnection($this->getHost(), $this->getPort(), $driver); + } + + $this->driver = $driver; + } + + parent::connect(); + + $this->synchronizeClientDatabase(); + } + + + + /** + * {@inheritdoc} + */ + public function send($cmd, array $args = array()) + { + if ($this->driver) { + if (!$this->driver->isConnected()) { + $this->connect(); + } + $this->synchronizeClientDatabase(); + } + + return parent::send($cmd, $args); + } + + + + /** + * @throws RedisClientException + */ + private function synchronizeClientDatabase() + { + if ($this->driver->getDatabase() != $this->getDatabase()) { + if (call_user_func_array(array($this->driver, 'select'), [$this->getDatabase()]) === FALSE) { + throw new RedisClientException('Can\'t set client database on driver'); + } + } + } + +} diff --git a/src/Kdyby/Redis/exceptions.php b/src/Kdyby/Redis/exceptions.php index f3b4679..cb7a149 100644 --- a/src/Kdyby/Redis/exceptions.php +++ b/src/Kdyby/Redis/exceptions.php @@ -92,6 +92,16 @@ class TransactionException extends RedisClientException implements Exception +/** + * @author Filip Procházka + */ +class ConnectionAlreadyInPoolException extends \RuntimeException implements Exception +{ + +} + + + /** * @author Filip Procházka */ diff --git a/tests/KdybyTests/Redis/RedisSharedClient.phpt b/tests/KdybyTests/Redis/RedisSharedClient.phpt new file mode 100644 index 0000000..f29d713 --- /dev/null +++ b/tests/KdybyTests/Redis/RedisSharedClient.phpt @@ -0,0 +1,121 @@ + + * @package Kdyby\Redis + */ + +namespace KdybyTests\Redis; + +use Kdyby\Redis\ConnectionPool; +use Kdyby\Redis\RedisSharedClient; +use Kdyby\Redis\RedisClientException; +use Kdyby\Redis\Driver\PhpRedisDriver; +use Nette; +use Tester; +use Tester\Assert; + +require_once __DIR__ . '/../bootstrap.php'; + + + +/** + * @author Filip Procházka + */ +class RedisSharedClientTest extends AbstractRedisTestCase +{ + + /** + * @var ConnectionPool + */ + private $pool; + + /** + * @var RedisSharedClient + */ + private $client1; + + /** + * @var RedisSharedClient + */ + private $client2; + + + + public function setUp() + { + parent::setUp(); + $this->pool = new ConnectionPool; + $this->client1 = new RedisSharedClient($this->pool, '127.0.0.1', NULL, 0); + $this->client2 = new RedisSharedClient($this->pool, '127.0.0.1', NULL, 1); + + try { + $this->client1->flushDb(); + $this->client2->flushDb(); + + } catch (RedisClientException $e) { + Tester\Assert::fail($e->getMessage()); + } + + } + + + + public function testConnectionPool_GetExistingConnection() + { + Assert::same($this->pool->getConnection('127.0.0.1', NULL), $this->client1->getDriver()); + Assert::same($this->pool->getConnection('127.0.0.1', NULL), $this->client2->getDriver()); + } + + + + public function testConnectionPool_GetNonexistingConnection() + { + Assert::same(NULL, $this->pool->getConnection('127.0.0.1', 1234)); + } + + + + public function testConnectionPool_AddExistingConnection() + { + $sharedDriver = new PhpRedisDriver; + Assert::exception(function () use ($sharedDriver) { + $this->pool->addConnection('127.0.0.1', NULL, $sharedDriver); + + }, 'Kdyby\Redis\ConnectionAlreadyInPoolException'); + } + + + + public function testSharedClients() + { + $driver = $this->client1->getDriver(); + + Assert::same(FALSE, $this->client1->test); + Assert::same(FALSE, $this->client2->test); + + $this->client1->test = 'xx'; + Assert::same(0, $driver->getDatabase()); + + Assert::same('xx', $this->client1->test); + Assert::same(FALSE, $this->client2->test); + + $this->client2->test = 'yy'; + Assert::same(1, $driver->getDatabase()); + + Assert::same('xx', $this->client1->test); + Assert::same('yy', $this->client2->test); + + $this->client1->test = 'zz'; + Assert::same(0, $driver->getDatabase()); + + Assert::same('zz', $this->client1->test); + Assert::same('yy', $this->client2->test); + } + +} + +\run(new RedisSharedClientTest());