Browse Source

Merge branch '4.next' into 5.next

Mark Story 2 years ago
parent
commit
162e296fb1
3 changed files with 360 additions and 13 deletions
  1. 10 0
      phpstan-baseline.neon
  2. 91 13
      src/Cache/Engine/RedisEngine.php
  3. 259 0
      tests/TestCase/Cache/Engine/RedisEngineTest.php

+ 10 - 0
phpstan-baseline.neon

@@ -1,6 +1,16 @@
 parameters:
 	ignoreErrors:
 		-
+			message: "#^Method Redis\\:\\:connect\\(\\) invoked with 7 parameters, 1\\-6 required\\.$#"
+			count: 1
+			path: src/Cache/Engine/RedisEngine.php
+
+		-
+			message: "#^Method Redis\\:\\:pconnect\\(\\) invoked with 7 parameters, 1\\-5 required\\.$#"
+			count: 1
+			path: src/Cache/Engine/RedisEngine.php
+
+		-
 			message: "#^Unsafe usage of new static\\(\\)\\.$#"
 			count: 1
 			path: src/Collection/Iterator/NestIterator.php

+ 91 - 13
src/Cache/Engine/RedisEngine.php

@@ -46,6 +46,7 @@ class RedisEngine extends CacheEngine
      * - `password` Redis server password.
      * - `persistent` Connect to the Redis server with a persistent connection
      * - `port` port number to the Redis server.
+     * - `tls` connect to the Redis server using TLS.
      * - `prefix` Prefix appended to all entries. Good for when you need to share a keyspace
      *    with either another cache config or another application.
      * - `scanCount` Number of keys to ask for each scan (default: 10)
@@ -62,6 +63,7 @@ class RedisEngine extends CacheEngine
         'password' => false,
         'persistent' => true,
         'port' => 6379,
+        'tls' => false,
         'prefix' => 'cake_',
         'host' => null,
         'server' => '127.0.0.1',
@@ -100,24 +102,29 @@ class RedisEngine extends CacheEngine
      */
     protected function _connect(): bool
     {
+        $tls = $this->_config['tls'] === true ? 'tls://' : '';
+
+        $map = [
+            'ssl_ca' => 'cafile',
+            'ssl_key' => 'local_pk',
+            'ssl_cert' => 'local_cert',
+        ];
+
+        $ssl = [];
+        foreach ($map as $key => $context) {
+            if (!empty($this->_config[$key])) {
+                $ssl[$context] = $this->_config[$key];
+            }
+        }
+
         try {
-            $this->_Redis = new Redis();
+            $this->_Redis = $this->_createRedisInstance();
             if (!empty($this->_config['unix_socket'])) {
                 $return = $this->_Redis->connect($this->_config['unix_socket']);
             } elseif (empty($this->_config['persistent'])) {
-                $return = $this->_Redis->connect(
-                    $this->_config['server'],
-                    (int)$this->_config['port'],
-                    (int)$this->_config['timeout']
-                );
+                $return = $this->_connectTransient($tls . $this->_config['server'], $ssl);
             } else {
-                $persistentId = $this->_config['port'] . $this->_config['timeout'] . $this->_config['database'];
-                $return = $this->_Redis->pconnect(
-                    $this->_config['server'],
-                    (int)$this->_config['port'],
-                    (int)$this->_config['timeout'],
-                    $persistentId
-                );
+                $return = $this->_connectPersistent($tls . $this->_config['server'], $ssl);
             }
         } catch (RedisException $e) {
             if (class_exists(Log::class)) {
@@ -137,6 +144,67 @@ class RedisEngine extends CacheEngine
     }
 
     /**
+     * Connects to a Redis server using a new connection.
+     *
+     * @param string $server Server to connect to.
+     * @param array $ssl SSL context options.
+     * @throws \RedisException
+     * @return bool True if Redis server was connected
+     */
+    protected function _connectTransient($server, array $ssl): bool
+    {
+        if (empty($ssl)) {
+            return $this->_Redis->connect(
+                $server,
+                (int)$this->_config['port'],
+                (int)$this->_config['timeout']
+            );
+        }
+
+        return $this->_Redis->connect(
+            $server,
+            (int)$this->_config['port'],
+            (int)$this->_config['timeout'],
+            null,
+            0,
+            0.0,
+            ['ssl' => $ssl]
+        );
+    }
+
+    /**
+     * Connects to a Redis server using a persistent connection.
+     *
+     * @param string $server Server to connect to.
+     * @param array $ssl SSL context options.
+     * @throws \RedisException
+     * @return bool True if Redis server was connected
+     */
+    protected function _connectPersistent($server, array $ssl): bool
+    {
+        $persistentId = $this->_config['port'] . $this->_config['timeout'] . $this->_config['database'];
+
+        if (empty($ssl)) {
+            return $this->_Redis->pconnect(
+                $server,
+                (int)$this->_config['port'],
+                (int)$this->_config['timeout'],
+                $persistentId
+            );
+        }
+
+        return $this->_Redis->pconnect(
+            $server,
+            (int)$this->_config['port'],
+            (int)$this->_config['timeout'],
+            $persistentId,
+            0,
+            0.0,
+            ['ssl' => $ssl]
+        );
+    }
+
+    /**
      * Write data for key into cache.
      *
      * @param string $key Identifier for the data
@@ -396,6 +464,16 @@ class RedisEngine extends CacheEngine
     }
 
     /**
+     * Create new Redis instance.
+     *
+     * @return \Redis
+     */
+    protected function _createRedisInstance(): Redis
+    {
+        return new Redis();
+    }
+
+    /**
      * Disconnects from the redis server
      */
     public function __destruct()

+ 259 - 0
tests/TestCase/Cache/Engine/RedisEngineTest.php

@@ -92,6 +92,7 @@ class RedisEngineTest extends TestCase
             'groups' => [],
             'server' => '127.0.0.1',
             'port' => $this->port,
+            'tls' => false,
             'timeout' => 0,
             'persistent' => true,
             'password' => false,
@@ -119,6 +120,7 @@ class RedisEngineTest extends TestCase
             'groups' => [],
             'server' => 'localhost',
             'port' => $this->port,
+            'tls' => false,
             'timeout' => 0,
             'persistent' => true,
             'password' => false,
@@ -134,6 +136,44 @@ class RedisEngineTest extends TestCase
     }
 
     /**
+     * testConfigDsnSSLContext method
+     */
+    public function testConfigDsnSSLContext(): void
+    {
+        $url = 'redis://localhost:' . $this->port;
+
+        $url .= '?ssl_ca=/tmp/cert.crt';
+        $url .= '&ssl_key=/tmp/local.key';
+        $url .= '&ssl_cert=/tmp/local.crt';
+
+        Cache::setConfig('redis_dsn', compact('url'));
+
+        $config = Cache::pool('redis_dsn')->getConfig();
+        $expecting = [
+            'prefix' => 'cake_',
+            'duration' => 3600,
+            'groups' => [],
+            'server' => 'localhost',
+            'port' => $this->port,
+            'tls' => false,
+            'timeout' => 0,
+            'persistent' => true,
+            'password' => false,
+            'database' => 0,
+            'unix_socket' => false,
+            'host' => 'localhost',
+            'scheme' => 'redis',
+            'scanCount' => 10,
+            'ssl_ca' => '/tmp/cert.crt',
+            'ssl_key' => '/tmp/local.key',
+            'ssl_cert' => '/tmp/local.crt',
+        ];
+        $this->assertEquals($expecting, $config);
+
+        Cache::drop('redis_dsn');
+    }
+
+    /**
      * testConnect method
      */
     public function testConnect(): void
@@ -143,6 +183,225 @@ class RedisEngineTest extends TestCase
     }
 
     /**
+     * testConnectTransient method
+     */
+    public function testConnectTransient(): void
+    {
+        $Redis = $this->createPartialMock(RedisEngine::class, ['_createRedisInstance']);
+        $phpredis = $this->createMock(\Redis::class);
+
+        $phpredis->expects($this->once())
+            ->method('select')
+            ->with((int)$Redis->getConfig('database'))
+            ->willReturn(true);
+
+        $phpredis->expects($this->once())
+            ->method('connect')
+            ->with(
+                $Redis->getConfig('server'),
+                (int)$this->port,
+                (int)$Redis->getConfig('timeout'),
+            )
+            ->willReturn(true);
+
+        $Redis->expects($this->once())
+            ->method('_createRedisInstance')
+            ->willReturn($phpredis);
+
+        $config = [
+            'port' => $this->port,
+            'persistent' => false,
+        ];
+        $this->assertTrue($Redis->init($config + Cache::pool('redis')->getConfig()));
+
+        $Redis = $this->createPartialMock(RedisEngine::class, ['_createRedisInstance']);
+        $phpredis = $this->createMock(\Redis::class);
+
+        $phpredis->expects($this->once())
+            ->method('select')
+            ->with((int)$Redis->getConfig('database'))
+            ->willReturn(true);
+
+        $phpredis->expects($this->once())
+            ->method('connect')
+            ->with(
+                'tls://' . $Redis->getConfig('server'),
+                (int)$this->port,
+                (int)$Redis->getConfig('timeout'),
+            )
+            ->willReturn(true);
+
+        $Redis->expects($this->once())
+            ->method('_createRedisInstance')
+            ->willReturn($phpredis);
+
+        $config = [
+            'port' => $this->port,
+            'persistent' => false,
+            'tls' => true,
+        ];
+        $this->assertTrue($Redis->init($config + Cache::pool('redis')->getConfig()));
+    }
+
+    /**
+     * testConnectTransientContext method
+     */
+    public function testConnectTransientContext(): void
+    {
+        $Redis = $this->createPartialMock(RedisEngine::class, ['_createRedisInstance']);
+        $phpredis = $this->createMock(\Redis::class);
+
+        $cafile = ROOT . DS . 'vendor' . DS . 'composer' . DS . 'ca-bundle' . DS . 'res' . DS . 'cacert.pem';
+
+        $context = [
+            'ssl' => [
+                'cafile' => $cafile,
+            ],
+        ];
+
+        $phpredis->expects($this->once())
+            ->method('select')
+            ->with((int)$Redis->getConfig('database'))
+            ->willReturn(true);
+
+        $phpredis->expects($this->once())
+            ->method('connect')
+            ->with(
+                $Redis->getConfig('server'),
+                (int)$this->port,
+                (int)$Redis->getConfig('timeout'),
+                null,
+                0,
+                0.0,
+                $context
+            )
+            ->willReturn(true);
+
+        $Redis->expects($this->once())
+            ->method('_createRedisInstance')
+            ->willReturn($phpredis);
+
+        $config = [
+            'port' => $this->port,
+            'persistent' => false,
+            'ssl_ca' => $cafile,
+        ];
+
+        $this->assertTrue($Redis->init($config + Cache::pool('redis')->getConfig()));
+    }
+
+    /**
+     * testConnectPersistent method
+     */
+    public function testConnectPersistent(): void
+    {
+        $Redis = $this->createPartialMock(RedisEngine::class, ['_createRedisInstance']);
+        $phpredis = $this->createMock(\Redis::class);
+
+        $expectedPersistentId = $this->port . $Redis->getConfig('timeout') . $Redis->getConfig('database');
+
+        $phpredis->expects($this->once())
+            ->method('select')
+            ->with((int)$Redis->getConfig('database'))
+            ->willReturn(true);
+
+        $phpredis->expects($this->once())
+            ->method('pconnect')
+            ->with(
+                $Redis->getConfig('server'),
+                (int)$this->port,
+                (int)$Redis->getConfig('timeout'),
+                $expectedPersistentId
+            )
+            ->willReturn(true);
+
+        $Redis->expects($this->once())
+            ->method('_createRedisInstance')
+            ->willReturn($phpredis);
+
+        $config = [
+            'port' => $this->port,
+        ];
+        $this->assertTrue($Redis->init($config + Cache::pool('redis')->getConfig()));
+
+        $Redis = $this->createPartialMock(RedisEngine::class, ['_createRedisInstance']);
+        $phpredis = $this->createMock(\Redis::class);
+
+        $phpredis->expects($this->once())
+            ->method('select')
+            ->with((int)$Redis->getConfig('database'))
+            ->willReturn(true);
+
+        $phpredis->expects($this->once())
+            ->method('pconnect')
+            ->with(
+                'tls://' . $Redis->getConfig('server'),
+                (int)$this->port,
+                (int)$Redis->getConfig('timeout'),
+                $expectedPersistentId
+            )
+            ->willReturn(true);
+
+        $Redis->expects($this->once())
+            ->method('_createRedisInstance')
+            ->willReturn($phpredis);
+
+        $config = [
+            'port' => $this->port,
+            'tls' => true,
+        ];
+        $this->assertTrue($Redis->init($config + Cache::pool('redis')->getConfig()));
+    }
+
+    /**
+     * testConnectPersistentContext method
+     */
+    public function testConnectPersistentContext(): void
+    {
+        $Redis = $this->createPartialMock(RedisEngine::class, ['_createRedisInstance']);
+        $phpredis = $this->createMock(\Redis::class);
+
+        $expectedPersistentId = $this->port . $Redis->getConfig('timeout') . $Redis->getConfig('database');
+
+        $cafile = ROOT . DS . 'vendor' . DS . 'composer' . DS . 'ca-bundle' . DS . 'res' . DS . 'cacert.pem';
+
+        $context = [
+            'ssl' => [
+                'cafile' => $cafile,
+            ],
+        ];
+
+        $phpredis->expects($this->once())
+            ->method('select')
+            ->with((int)$Redis->getConfig('database'))
+            ->willReturn(true);
+
+        $phpredis->expects($this->once())
+            ->method('pconnect')
+            ->with(
+                $Redis->getConfig('server'),
+                (int)$this->port,
+                (int)$Redis->getConfig('timeout'),
+                $expectedPersistentId,
+                0,
+                0.0,
+                $context
+            )
+            ->willReturn(true);
+
+        $Redis->expects($this->once())
+            ->method('_createRedisInstance')
+            ->willReturn($phpredis);
+
+        $config = [
+            'port' => $this->port,
+            'persistent' => true,
+            'ssl_ca' => $cafile,
+        ];
+        $this->assertTrue($Redis->init($config + Cache::pool('redis')->getConfig()));
+    }
+
+    /**
      * testMultiDatabaseOperations method
      */
     public function testMultiDatabaseOperations(): void