Browse Source

Implement new logic to load command classes.

By default Predis now uses a convention-over-configuration approach
by looking for a command class in the Predis\Command\Redis namespace
if it is not already defined in the commands class map.

This change allow us to decrease the time needed to load Predis on
each request since we removed 99% of the mappings in the commands
class map. Classes defined in the internal class map still take the
precedence over this mechanism, so users can still define their own
command classes to handle each command.
Daniele Alessandri 8 years ago
parent
commit
822f02b8eb

+ 8 - 4
CHANGELOG.md

@@ -1,18 +1,22 @@
 v2.0.0 (201x-xx-xx)
 ================================================================================
 
+- Classes for Redis commands have been moved into the new `Predis\Command\Redis`
+  namespace and each class name mirrors the respective Redis command ID.
+
 - The concept of server profiles is gone, the library now uses a single command
   factory to create instances of commands classes. The `profile` option has been
   replaced by the `commands` option accepting `Predis\Command\FactoryInterface`
   to customize the underlying command factory. The default command factory class
   used by Predis is `Predis\Command\RedisFactory` and it still allows developers
-  to define or override commands with their own implementations.
+  to define or override commands with their own implementations. In addition to
+  that, `Predis\Command\RedisFactory` relies on a convention-over-configuration
+  approach by looking for a suitable class with the same name as the command ID
+  in the `Predis\Command\Redis` when the internal class map does not contain a
+  class associated.
 
 - Changed the signature for the constructor of `Predis\Command\RawCommand`.
 
-- Classes for Redis commands have been moved into the new `Predis\Command\Redis`
-  namespace and each class name mirrors the respective Redis command ID.
-
 
 v1.1.0 (2016-06-02)
 ================================================================================

+ 10 - 25
src/Command/Factory.php

@@ -24,31 +24,15 @@ use Predis\Command\Processor\ProcessorInterface;
  */
 abstract class Factory implements FactoryInterface
 {
-    private $commands;
-    private $processor;
-
-    /**
-     *
-     */
-    public function __construct()
-    {
-        $this->commands = $this->getSupportedCommands();
-    }
-
-    /**
-     * Returns all the commands supported by this factory mapped to their actual
-     * PHP classes.
-     *
-     * @return array
-     */
-    abstract protected function getSupportedCommands();
+    protected $commands = array();
+    protected $processor;
 
     /**
      * {@inheritdoc}
      */
     public function supportsCommand($commandID)
     {
-        return isset($this->commands[strtoupper($commandID)]);
+        return $this->getCommandClass($commandID) !== null;
     }
 
     /**
@@ -84,13 +68,12 @@ abstract class Factory implements FactoryInterface
      */
     public function createCommand($commandID, array $arguments = array())
     {
-        $commandID = strtoupper($commandID);
+        if (!$commandClass = $this->getCommandClass($commandID)) {
+            $commandID = strtoupper($commandID);
 
-        if (!isset($this->commands[$commandID])) {
             throw new ClientException("Command '$commandID' is not a registered Redis command.");
         }
 
-        $commandClass = $this->commands[$commandID];
         $command = new $commandClass();
         $command->setArguments($arguments);
 
@@ -111,10 +94,12 @@ abstract class Factory implements FactoryInterface
      */
     public function defineCommand($commandID, $class)
     {
-        $reflection = new \ReflectionClass($class);
+        if ($class !== null) {
+            $reflection = new \ReflectionClass($class);
 
-        if (!$reflection->isSubclassOf('Predis\Command\CommandInterface')) {
-            throw new \InvalidArgumentException("The class '$class' is not a valid command class.");
+            if (!$reflection->isSubclassOf('Predis\Command\CommandInterface')) {
+                throw new \InvalidArgumentException("The class '$class' is not a valid command class.");
+            }
         }
 
         $this->commands[strtoupper($commandID)] = $class;

+ 0 - 9
src/Command/FactoryInterface.php

@@ -11,8 +11,6 @@
 
 namespace Predis\Command;
 
-use Predis\Command\CommandInterface;
-
 /**
  * Command factory interface.
  *
@@ -23,13 +21,6 @@ use Predis\Command\CommandInterface;
  */
 interface FactoryInterface
 {
-    /**
-     * Returns a string representing the current command factory.
-     *
-     * @return string
-     */
-    public function getVersion();
-
     /**
      * Checks if the command factory supports the specified command.
      *

+ 16 - 248
src/Command/RedisFactory.php

@@ -19,263 +19,31 @@ namespace Predis\Command;
 class RedisFactory extends Factory
 {
     /**
-     * {@inheritdoc}
+     *
      */
-    public function getVersion()
+    public function __construct()
     {
-        return '3.2';
+        $this->commands = array(
+            'ECHO' => 'Predis\Command\Redis\ECHO_',
+            'EVAL' => 'Predis\Command\Redis\EVAL_',
+        );
     }
 
     /**
      * {@inheritdoc}
      */
-    public function getSupportedCommands()
+    public function getCommandClass($commandID)
     {
-        return array(
-            /* ---------------- Redis 1.2 ---------------- */
-
-            /* commands operating on the key space */
-            'EXISTS' => 'Predis\Command\Redis\EXISTS',
-            'DEL' => 'Predis\Command\Redis\DEL',
-            'TYPE' => 'Predis\Command\Redis\TYPE',
-            'KEYS' => 'Predis\Command\Redis\KEYS',
-            'RANDOMKEY' => 'Predis\Command\Redis\RANDOMKEY',
-            'RENAME' => 'Predis\Command\Redis\RENAME',
-            'RENAMENX' => 'Predis\Command\Redis\RENAMENX',
-            'EXPIRE' => 'Predis\Command\Redis\EXPIRE',
-            'EXPIREAT' => 'Predis\Command\Redis\EXPIREAT',
-            'TTL' => 'Predis\Command\Redis\TTL',
-            'MOVE' => 'Predis\Command\Redis\MOVE',
-            'SORT' => 'Predis\Command\Redis\SORT',
-            'DUMP' => 'Predis\Command\Redis\DUMP',
-            'RESTORE' => 'Predis\Command\Redis\RESTORE',
-
-            /* commands operating on string values */
-            'SET' => 'Predis\Command\Redis\SET',
-            'SETNX' => 'Predis\Command\Redis\SETNX',
-            'MSET' => 'Predis\Command\Redis\MSET',
-            'MSETNX' => 'Predis\Command\Redis\MSETNX',
-            'GET' => 'Predis\Command\Redis\GET',
-            'MGET' => 'Predis\Command\Redis\MGET',
-            'GETSET' => 'Predis\Command\Redis\GETSET',
-            'INCR' => 'Predis\Command\Redis\INCR',
-            'INCRBY' => 'Predis\Command\Redis\INCRBY',
-            'DECR' => 'Predis\Command\Redis\DECR',
-            'DECRBY' => 'Predis\Command\Redis\DECRBY',
-
-            /* commands operating on lists */
-            'RPUSH' => 'Predis\Command\Redis\RPUSH',
-            'LPUSH' => 'Predis\Command\Redis\LPUSH',
-            'LLEN' => 'Predis\Command\Redis\LLEN',
-            'LRANGE' => 'Predis\Command\Redis\LRANGE',
-            'LTRIM' => 'Predis\Command\Redis\LTRIM',
-            'LINDEX' => 'Predis\Command\Redis\LINDEX',
-            'LSET' => 'Predis\Command\Redis\LSET',
-            'LREM' => 'Predis\Command\Redis\LREM',
-            'LPOP' => 'Predis\Command\Redis\LPOP',
-            'RPOP' => 'Predis\Command\Redis\RPOP',
-            'RPOPLPUSH' => 'Predis\Command\Redis\RPOPLPUSH',
-
-            /* commands operating on sets */
-            'SADD' => 'Predis\Command\Redis\SADD',
-            'SREM' => 'Predis\Command\Redis\SREM',
-            'SPOP' => 'Predis\Command\Redis\SPOP',
-            'SMOVE' => 'Predis\Command\Redis\SMOVE',
-            'SCARD' => 'Predis\Command\Redis\SCARD',
-            'SISMEMBER' => 'Predis\Command\Redis\SISMEMBER',
-            'SINTER' => 'Predis\Command\Redis\SINTER',
-            'SINTERSTORE' => 'Predis\Command\Redis\SINTERSTORE',
-            'SUNION' => 'Predis\Command\Redis\SUNION',
-            'SUNIONSTORE' => 'Predis\Command\Redis\SUNIONSTORE',
-            'SDIFF' => 'Predis\Command\Redis\SDIFF',
-            'SDIFFSTORE' => 'Predis\Command\Redis\SDIFFSTORE',
-            'SMEMBERS' => 'Predis\Command\Redis\SMEMBERS',
-            'SRANDMEMBER' => 'Predis\Command\Redis\SRANDMEMBER',
-
-            /* commands operating on sorted sets */
-            'ZADD' => 'Predis\Command\Redis\ZADD',
-            'ZINCRBY' => 'Predis\Command\Redis\ZINCRBY',
-            'ZREM' => 'Predis\Command\Redis\ZREM',
-            'ZRANGE' => 'Predis\Command\Redis\ZRANGE',
-            'ZREVRANGE' => 'Predis\Command\Redis\ZREVRANGE',
-            'ZRANGEBYSCORE' => 'Predis\Command\Redis\ZRANGEBYSCORE',
-            'ZCARD' => 'Predis\Command\Redis\ZCARD',
-            'ZSCORE' => 'Predis\Command\Redis\ZSCORE',
-            'ZREMRANGEBYSCORE' => 'Predis\Command\Redis\ZREMRANGEBYSCORE',
-
-            /* connection related commands */
-            'PING' => 'Predis\Command\Redis\PING',
-            'AUTH' => 'Predis\Command\Redis\AUTH',
-            'SELECT' => 'Predis\Command\Redis\SELECT',
-            'ECHO' => 'Predis\Command\Redis\ECHO_',
-            'QUIT' => 'Predis\Command\Redis\QUIT',
-
-            /* remote server control commands */
-            'INFO' => 'Predis\Command\Redis\INFO',
-            'SLAVEOF' => 'Predis\Command\Redis\SLAVEOF',
-            'MONITOR' => 'Predis\Command\Redis\MONITOR',
-            'DBSIZE' => 'Predis\Command\Redis\DBSIZE',
-            'FLUSHDB' => 'Predis\Command\Redis\FLUSHDB',
-            'FLUSHALL' => 'Predis\Command\Redis\FLUSHALL',
-            'SAVE' => 'Predis\Command\Redis\SAVE',
-            'BGSAVE' => 'Predis\Command\Redis\BGSAVE',
-            'LASTSAVE' => 'Predis\Command\Redis\LASTSAVE',
-            'SHUTDOWN' => 'Predis\Command\Redis\SHUTDOWN',
-            'BGREWRITEAOF' => 'Predis\Command\Redis\BGREWRITEAOF',
-
-            /* ---------------- Redis 2.0 ---------------- */
-
-            /* commands operating on string values */
-            'SETEX' => 'Predis\Command\Redis\SETEX',
-            'APPEND' => 'Predis\Command\Redis\APPEND',
-            'SUBSTR' => 'Predis\Command\Redis\SUBSTR',
-
-            /* commands operating on lists */
-            'BLPOP' => 'Predis\Command\Redis\BLPOP',
-            'BRPOP' => 'Predis\Command\Redis\BRPOP',
-
-            /* commands operating on sorted sets */
-            'ZUNIONSTORE' => 'Predis\Command\Redis\ZUNIONSTORE',
-            'ZINTERSTORE' => 'Predis\Command\Redis\ZINTERSTORE',
-            'ZCOUNT' => 'Predis\Command\Redis\ZCOUNT',
-            'ZRANK' => 'Predis\Command\Redis\ZRANK',
-            'ZREVRANK' => 'Predis\Command\Redis\ZREVRANK',
-            'ZREMRANGEBYRANK' => 'Predis\Command\Redis\ZREMRANGEBYRANK',
-
-            /* commands operating on hashes */
-            'HSET' => 'Predis\Command\Redis\HSET',
-            'HSETNX' => 'Predis\Command\Redis\HSETNX',
-            'HMSET' => 'Predis\Command\Redis\HMSET',
-            'HINCRBY' => 'Predis\Command\Redis\HINCRBY',
-            'HGET' => 'Predis\Command\Redis\HGET',
-            'HMGET' => 'Predis\Command\Redis\HMGET',
-            'HDEL' => 'Predis\Command\Redis\HDEL',
-            'HEXISTS' => 'Predis\Command\Redis\HEXISTS',
-            'HLEN' => 'Predis\Command\Redis\HLEN',
-            'HKEYS' => 'Predis\Command\Redis\HKEYS',
-            'HVALS' => 'Predis\Command\Redis\HVALS',
-            'HGETALL' => 'Predis\Command\Redis\HGETALL',
-
-            /* transactions */
-            'MULTI' => 'Predis\Command\Redis\MULTI',
-            'EXEC' => 'Predis\Command\Redis\EXEC',
-            'DISCARD' => 'Predis\Command\Redis\DISCARD',
-
-            /* publish - subscribe */
-            'SUBSCRIBE' => 'Predis\Command\Redis\SUBSCRIBE',
-            'UNSUBSCRIBE' => 'Predis\Command\Redis\UNSUBSCRIBE',
-            'PSUBSCRIBE' => 'Predis\Command\Redis\PSUBSCRIBE',
-            'PUNSUBSCRIBE' => 'Predis\Command\Redis\PUNSUBSCRIBE',
-            'PUBLISH' => 'Predis\Command\Redis\PUBLISH',
-
-            /* remote server control commands */
-            'CONFIG' => 'Predis\Command\Redis\CONFIG',
-
-            /* ---------------- Redis 2.2 ---------------- */
-
-            /* commands operating on the key space */
-            'PERSIST' => 'Predis\Command\Redis\PERSIST',
-
-            /* commands operating on string values */
-            'STRLEN' => 'Predis\Command\Redis\STRLEN',
-            'SETRANGE' => 'Predis\Command\Redis\SETRANGE',
-            'GETRANGE' => 'Predis\Command\Redis\GETRANGE',
-            'SETBIT' => 'Predis\Command\Redis\SETBIT',
-            'GETBIT' => 'Predis\Command\Redis\GETBIT',
-
-            /* commands operating on lists */
-            'RPUSHX' => 'Predis\Command\Redis\RPUSHX',
-            'LPUSHX' => 'Predis\Command\Redis\LPUSHX',
-            'LINSERT' => 'Predis\Command\Redis\LINSERT',
-            'BRPOPLPUSH' => 'Predis\Command\Redis\BRPOPLPUSH',
+        $commandID = strtoupper($commandID);
 
-            /* commands operating on sorted sets */
-            'ZREVRANGEBYSCORE' => 'Predis\Command\Redis\ZREVRANGEBYSCORE',
+        if (isset($this->commands[$commandID]) || array_key_exists($commandID, $this->commands)) {
+            $commandClass = $this->commands[$commandID];
+        } elseif (class_exists($commandClass = "Predis\Command\Redis\\$commandID")) {
+            $this->commands[$commandID] = $commandClass;
+        } else {
+            return;
+        }
 
-            /* transactions */
-            'WATCH' => 'Predis\Command\Redis\WATCH',
-            'UNWATCH' => 'Predis\Command\Redis\UNWATCH',
-
-            /* remote server control commands */
-            'OBJECT' => 'Predis\Command\Redis\OBJECT',
-            'SLOWLOG' => 'Predis\Command\Redis\SLOWLOG',
-
-            /* ---------------- Redis 2.4 ---------------- */
-
-            /* remote server control commands */
-            'CLIENT' => 'Predis\Command\Redis\CLIENT',
-
-            /* ---------------- Redis 2.6 ---------------- */
-
-            /* commands operating on the key space */
-            'PTTL' => 'Predis\Command\Redis\PTTL',
-            'PEXPIRE' => 'Predis\Command\Redis\PEXPIRE',
-            'PEXPIREAT' => 'Predis\Command\Redis\PEXPIREAT',
-            'MIGRATE' => 'Predis\Command\Redis\MIGRATE',
-
-            /* commands operating on string values */
-            'PSETEX' => 'Predis\Command\Redis\PSETEX',
-            'INCRBYFLOAT' => 'Predis\Command\Redis\INCRBYFLOAT',
-            'BITOP' => 'Predis\Command\Redis\BITOP',
-            'BITCOUNT' => 'Predis\Command\Redis\BITCOUNT',
-
-            /* commands operating on hashes */
-            'HINCRBYFLOAT' => 'Predis\Command\Redis\HINCRBYFLOAT',
-
-            /* scripting */
-            'EVAL' => 'Predis\Command\Redis\EVAL_',
-            'EVALSHA' => 'Predis\Command\Redis\EVALSHA',
-            'SCRIPT' => 'Predis\Command\Redis\SCRIPT',
-
-            /* remote server control commands */
-            'TIME' => 'Predis\Command\Redis\TIME',
-            'SENTINEL' => 'Predis\Command\Redis\SENTINEL',
-
-            /* ---------------- Redis 2.8 ---------------- */
-
-            /* commands operating on the key space */
-            'SCAN' => 'Predis\Command\Redis\SCAN',
-
-            /* commands operating on string values */
-            'BITPOS' => 'Predis\Command\Redis\BITPOS',
-
-            /* commands operating on sets */
-            'SSCAN' => 'Predis\Command\Redis\SSCAN',
-
-            /* commands operating on sorted sets */
-            'ZSCAN' => 'Predis\Command\Redis\ZSCAN',
-            'ZLEXCOUNT' => 'Predis\Command\Redis\ZLEXCOUNT',
-            'ZRANGEBYLEX' => 'Predis\Command\Redis\ZRANGEBYLEX',
-            'ZREMRANGEBYLEX' => 'Predis\Command\Redis\ZREMRANGEBYLEX',
-            'ZREVRANGEBYLEX' => 'Predis\Command\Redis\ZREVRANGEBYLEX',
-
-            /* commands operating on hashes */
-            'HSCAN' => 'Predis\Command\Redis\HSCAN',
-
-            /* publish - subscribe */
-            'PUBSUB' => 'Predis\Command\Redis\PUBSUB',
-
-            /* commands operating on HyperLogLog */
-            'PFADD' => 'Predis\Command\Redis\PFADD',
-            'PFCOUNT' => 'Predis\Command\Redis\PFCOUNT',
-            'PFMERGE' => 'Predis\Command\Redis\PFMERGE',
-
-            /* remote server control commands */
-            'COMMAND' => 'Predis\Command\Redis\COMMAND',
-
-            /* ---------------- Redis 3.2 ---------------- */
-
-            /* commands operating on hashes */
-            'HSTRLEN' => 'Predis\Command\Redis\HSTRLEN',
-            'BITFIELD' => 'Predis\Command\Redis\BITFIELD',
-
-            /* commands performing geospatial operations */
-            'GEOADD' => 'Predis\Command\Redis\GEOADD',
-            'GEOHASH' => 'Predis\Command\Redis\GEOHASH',
-            'GEOPOS' => 'Predis\Command\Redis\GEOPOS',
-            'GEODIST' => 'Predis\Command\Redis\GEODIST',
-            'GEORADIUS' => 'Predis\Command\Redis\GEORADIUS',
-            'GEORADIUSBYMEMBER' => 'Predis\Command\Redis\GEORADIUSBYMEMBER',
-        );
+        return $commandClass;
     }
 }

+ 39 - 30
tests/Predis/Command/RedisFactoryTest.php

@@ -11,7 +11,6 @@
 
 namespace Predis\Command;
 
-use Predis\Command\CommandInterface;
 use Predis\Command\Processor\ProcessorChain;
 use PredisTestCase;
 
@@ -20,16 +19,6 @@ use PredisTestCase;
  */
 class RedisFactoryTest extends PredisTestCase
 {
-    /**
-     * @group disconnected
-     */
-    public function testGetVersion()
-    {
-        $factory = new RedisFactory();
-
-        $this->assertSame('3.2', $factory->getVersion());
-    }
-
     /**
      * @group disconnected
      */
@@ -37,10 +26,9 @@ class RedisFactoryTest extends PredisTestCase
     {
         $factory = new RedisFactory();
 
-        $expected = $this->getExpectedCommands();
-        $commands = $this->getCommands($factory);
-
-        $this->assertSame($expected, $commands);
+        foreach ($this->getExpectedCommands() as $commandID) {
+            $this->assertTrue($factory->supportsCommand($commandID));
+        }
     }
 
     /**
@@ -92,8 +80,8 @@ class RedisFactoryTest extends PredisTestCase
     public function testDefineCommand()
     {
         $factory = new RedisFactory();
-        $command = $this->getMock('Predis\Command\CommandInterface');
 
+        $command = $this->getMock('Predis\Command\CommandInterface');
         $factory->defineCommand('mock', get_class($command));
 
         $this->assertTrue($factory->supportsCommand('mock'));
@@ -102,6 +90,41 @@ class RedisFactoryTest extends PredisTestCase
         $this->assertSame(get_class($command), $factory->getCommandClass('mock'));
     }
 
+    /**
+     * @group disconnected
+     */
+    public function testUndefineCommandInClassAutoload()
+    {
+        $factory = new RedisFactory();
+
+        $this->assertTrue($factory->supportsCommand('PING'));
+        $this->assertSame('Predis\Command\Redis\PING', $factory->getCommandClass('PING'));
+
+        $factory->defineCommand('PING', null);
+
+        $this->assertFalse($factory->supportsCommand('PING'));
+        $this->assertNull($factory->getCommandClass('PING'));
+    }
+
+    /**
+     * @group disconnected
+     */
+    public function testUndefineCommandInClassMap()
+    {
+        $factory = new RedisFactory();
+
+        $commandClass = get_class($this->getMock('Predis\Command\CommandInterface'));
+        $factory->defineCommand('MOCK', $commandClass);
+
+        $this->assertTrue($factory->supportsCommand('MOCK'));
+        $this->assertSame($commandClass, $factory->getCommandClass('MOCK'));
+
+        $factory->defineCommand('MOCK', null);
+
+        $this->assertFalse($factory->supportsCommand('MOCK'));
+        $this->assertNull($factory->getCommandClass('MOCK'));
+    }
+
     /**
      * @group disconnected
      * @expectedException \InvalidArgumentException
@@ -240,20 +263,6 @@ class RedisFactoryTest extends PredisTestCase
     // ---- HELPER METHODS ------------------------------------------------ //
     // ******************************************************************** //
 
-    /**
-     * Returns the list of commands supported by the specified command factory.
-     *
-     * @param FactoryInterface $factory Command factory instance.
-     *
-     * @return array
-     */
-    protected function getCommands(FactoryInterface $factory)
-    {
-        $commands = $factory->getSupportedCommands();
-
-        return array_keys($commands);
-    }
-
     /**
      * Returns the expected list of commands supported by the tested factory.
      *