Browse Source

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.
Daniele Alessandri 8 years ago
parent
commit
e38df19fac

+ 74 - 0
src/Configuration/Option/CRC16.php

@@ -0,0 +1,74 @@
+<?php
+
+/*
+ * This file is part of the Predis package.
+ *
+ * (c) Daniele Alessandri <suppakilla@gmail.com>
+ *
+ * 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 <suppakilla@gmail.com>
+ */
+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();
+    }
+}

+ 2 - 1
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(

+ 1 - 0
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',
         );
     }
 

+ 136 - 0
tests/Predis/Configuration/Option/CRC16Test.php

@@ -0,0 +1,136 @@
+<?php
+
+/*
+ * This file is part of the Predis package.
+ *
+ * (c) Daniele Alessandri <suppakilla@gmail.com>
+ *
+ * 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');
+    }
+}

+ 8 - 1
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()));