From e38df19faca55da9886007dcff318f2a87bdaeba Mon Sep 17 00:00:00 2001 From: Daniele Alessandri Date: Mon, 13 Jun 2016 22:47:24 +0200 Subject: [PATCH] Implement "crc16" client option for redis-cluster. By default Predis chooses automatically which hash generator to use: if a suitable version of ext-phpiredis is loaded (that is, a version that implements the phpiredis_utils_crc16() function) it uses the new Predis\Cluster\Hash\PhpiredisCRC16 generator, otherwise it falls back to the usual pure-PHP Predis\Cluster\Hash\CRC16 generator. --- src/Configuration/Option/CRC16.php | 74 ++++++++++ src/Configuration/Option/Cluster.php | 3 +- src/Configuration/Options.php | 1 + .../Predis/Configuration/Option/CRC16Test.php | 136 ++++++++++++++++++ .../Configuration/Option/ClusterTest.php | 9 +- 5 files changed, 221 insertions(+), 2 deletions(-) create mode 100644 src/Configuration/Option/CRC16.php create mode 100644 tests/Predis/Configuration/Option/CRC16Test.php diff --git a/src/Configuration/Option/CRC16.php b/src/Configuration/Option/CRC16.php new file mode 100644 index 00000000..bcd363a5 --- /dev/null +++ b/src/Configuration/Option/CRC16.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Configuration\Option; + +use Predis\Cluster\Hash; +use Predis\Configuration\OptionInterface; +use Predis\Configuration\OptionsInterface; + +/** + * Configures an hash generator used by the redis-cluster connection backend. + * + * @author Daniele Alessandri + */ +class CRC16 implements OptionInterface +{ + /** + * Returns an hash generator instance from a descriptive name. + * + * @param OptionsInterface $options Client options. + * @param string $description Identifier of a hash generator (`predis`, `phpiredis`) + * + * @return callable + */ + protected function getHashGeneratorByDescription(OptionsInterface $options, $description) + { + if ($description === 'predis') { + return new Hash\CRC16(); + } elseif ($description === 'phpiredis') { + return new Hash\PhpiredisCRC16(); + } else { + throw new \InvalidArgumentException( + 'String value for the crc16 option must be either `predis` or `phpiredis`' + ); + } + } + + /** + * {@inheritdoc} + */ + public function filter(OptionsInterface $options, $value) + { + if (is_callable($value)) { + $value = call_user_func($value, $options); + } + + if (is_string($value)) { + return $this->getHashGeneratorByDescription($options, $value); + } elseif ($value instanceof Hash\HashGeneratorInterface) { + return $value; + } else { + $class = get_class($this); + throw new \InvalidArgumentException("$class expects a valid hash generator"); + } + } + + /** + * {@inheritdoc} + */ + public function getDefault(OptionsInterface $options) + { + return function_exists('phpiredis_utils_crc16') + ? new Hash\PhpiredisCRC16() + : new Hash\CRC16(); + } +} diff --git a/src/Configuration/Option/Cluster.php b/src/Configuration/Option/Cluster.php index 79cd3503..f4467b74 100644 --- a/src/Configuration/Option/Cluster.php +++ b/src/Configuration/Option/Cluster.php @@ -11,6 +11,7 @@ namespace Predis\Configuration\Option; +use Predis\Cluster\RedisStrategy; use Predis\Configuration\OptionsInterface; use Predis\Connection\Cluster\PredisCluster; use Predis\Connection\Cluster\RedisCluster; @@ -38,7 +39,7 @@ class Cluster extends Aggregate $callback = $this->getDefault($options); } elseif ($description === 'redis') { $callback = function ($options) { - return new RedisCluster($options->connections); + return new RedisCluster($options->connections, new RedisStrategy($options->crc16)); }; } else { throw new \InvalidArgumentException( diff --git a/src/Configuration/Options.php b/src/Configuration/Options.php index 4cbaa285..f7ac4439 100644 --- a/src/Configuration/Options.php +++ b/src/Configuration/Options.php @@ -50,6 +50,7 @@ class Options implements OptionsInterface 'commands' => 'Predis\Configuration\Option\Commands', 'exceptions' => 'Predis\Configuration\Option\Exceptions', 'prefix' => 'Predis\Configuration\Option\Prefix', + 'crc16' => 'Predis\Configuration\Option\CRC16', ); } diff --git a/tests/Predis/Configuration/Option/CRC16Test.php b/tests/Predis/Configuration/Option/CRC16Test.php new file mode 100644 index 00000000..d81cb2ef --- /dev/null +++ b/tests/Predis/Configuration/Option/CRC16Test.php @@ -0,0 +1,136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Configuration\Option; + +use Predis\Cluster\Hash; +use PredisTestCase; + +/** + * + */ +class CRC16Test extends PredisTestCase +{ + /** + * @group disconnected + */ + public function testDefaultOptionValue() + { + $option = new CRC16(); + + $options = $this->getMock('Predis\Configuration\OptionsInterface'); + $hashGenerator = $option->getDefault($options); + + $this->assertInstanceOf('Predis\Cluster\Hash\HashGeneratorInterface', $hashGenerator); + + if (function_exists('phpiredis_utils_crc16')) { + $this->assertInstanceOf('Predis\Cluster\Hash\PhpiredisCRC16', $hashGenerator); + } else { + $this->assertInstanceOf('Predis\Cluster\Hash\CRC16', $hashGenerator); + } + } + + /** + * @group disconnected + */ + public function testAcceptsHashGeneratorInstance() + { + $option = new CRC16(); + + $options = $this->getMock('Predis\Configuration\OptionsInterface'); + $hashGenerator = $this->getMock('Predis\Cluster\Hash\HashGeneratorInterface'); + + $this->assertSame($hashGenerator, $option->filter($options, $hashGenerator)); + } + + /** + * @group disconnected + */ + public function testAcceptsCallableAsHashGeneratorInitializer() + { + $option = new CRC16(); + + $options = $this->getMock('Predis\Configuration\OptionsInterface'); + $hashGenerator = $this->getMock('Predis\Cluster\Hash\HashGeneratorInterface'); + + $callable = $this->getMock('stdClass', array('__invoke')); + $callable + ->expects($this->once()) + ->method('__invoke') + ->with($this->isInstanceOf('Predis\Configuration\OptionsInterface')) + ->will($this->returnValue($hashGenerator)); + + $this->assertSame($hashGenerator, $option->filter($options, $callable)); + } + + /** + * @group disconnected + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Predis\Configuration\Option\CRC16 expects a valid hash generator + */ + public function testThrowsExceptionOnInvalidReturnTypeOfHashGeneratorInitializer() + { + $option = new CRC16(); + + $options = $this->getMock('Predis\Configuration\OptionsInterface'); + $wrongValue = $this->getMock('stdClass'); + + $callable = $this->getMock('stdClass', array('__invoke')); + $callable + ->expects($this->once()) + ->method('__invoke') + ->with($this->isInstanceOf('Predis\Configuration\OptionsInterface')) + ->will($this->returnValue($wrongValue)); + + $option->filter($options, $callable); + } + + /** + * @group disconnected + */ + public function testAcceptsShortNameStringPredis() + { + $option = new CRC16(); + + $options = $this->getMock('Predis\Configuration\OptionsInterface'); + + $this->assertInstanceOf('Predis\Cluster\Hash\CRC16', $option->filter($options, 'predis')); + } + + /** + * @group disconnected + * @group ext-phpiredis + * @requires extension phpiredis + * @requires function phpiredis_utils_crc16 + */ + public function testAcceptsShortNameStringPhpiredis() + { + $option = new CRC16(); + + $options = $this->getMock('Predis\Configuration\OptionsInterface'); + + $this->assertInstanceOf('Predis\Cluster\Hash\PhpiredisCRC16', $option->filter($options, 'phpiredis')); + } + + /** + * @group disconnected + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage String value for the crc16 option must be either `predis` or `phpiredis` + */ + public function testThrowsExceptionOnInvalidShortNameString() + { + $option = new CRC16(); + + $options = $this->getMock('Predis\Configuration\OptionsInterface'); + + $option->filter($options, 'unknown'); + } +} diff --git a/tests/Predis/Configuration/Option/ClusterTest.php b/tests/Predis/Configuration/Option/ClusterTest.php index 847225f4..f7ec039e 100644 --- a/tests/Predis/Configuration/Option/ClusterTest.php +++ b/tests/Predis/Configuration/Option/ClusterTest.php @@ -102,12 +102,19 @@ class ClusterTest extends PredisTestCase $options = $this->getMock('Predis\Configuration\OptionsInterface'); $options - ->expects($this->once()) + ->expects($this->at(0)) ->method('__get') ->with('connections') ->will($this->returnValue( $this->getMock('Predis\Connection\FactoryInterface') )); + $options + ->expects($this->at(1)) + ->method('__get') + ->with('crc16') + ->will($this->returnValue( + $this->getMock('Predis\Cluster\Hash\HashGeneratorInterface') + )); $this->assertInstanceOf('Closure', $initializer = $option->filter($options, 'redis')); $this->assertInstanceOf('Predis\Connection\Cluster\RedisCluster', $initializer($parameters = array()));