%PDF- <> %âãÏÓ endobj 2 0 obj <> endobj 3 0 obj <>/ExtGState<>/ProcSet[/PDF/Text/ImageB/ImageC/ImageI] >>/Annots[ 28 0 R 29 0 R] /MediaBox[ 0 0 595.5 842.25] /Contents 4 0 R/Group<>/Tabs/S>> endobj ºaâÚÎΞ-ÌE1ÍØÄ÷{òò2ÿ ÛÖ^ÔÀá TÎ{¦?§®¥kuµùÕ5sLOšuY>endobj 2 0 obj<>endobj 2 0 obj<>endobj 2 0 obj<>endobj 2 0 obj<> endobj 2 0 obj<>endobj 2 0 obj<>es 3 0 R>> endobj 2 0 obj<> ox[ 0.000000 0.000000 609.600000 935.600000]/Fi endobj 3 0 obj<> endobj 7 1 obj<>/ProcSet[/PDF/Text/ImageB/ImageC/ImageI]>>/Subtype/Form>> stream
<?php
namespace Codeception\Module;
use Codeception\Lib\Interfaces\RequiresPackage;
use Codeception\Module as CodeceptionModule;
use Codeception\Exception\ModuleException;
use Codeception\TestInterface;
use Predis\Client as RedisDriver;
/**
* This module uses the [Predis](https://github.com/nrk/predis) library
* to interact with a Redis server.
*
* ## Status
*
* * Stability: **beta**
*
* ## Configuration
*
* * **`host`** (`string`, default `'127.0.0.1'`) - The Redis host
* * **`port`** (`int`, default `6379`) - The Redis port
* * **`database`** (`int`, no default) - The Redis database. Needs to be specified.
* * **`cleanupBefore`**: (`string`, default `'never'`) - Whether/when to flush the database:
* * `suite`: at the beginning of every suite
* * `test`: at the beginning of every test
* * Any other value: never
*
* ### Example (`unit.suite.yml`)
*
* ```yaml
* modules:
* - Redis:
* host: '127.0.0.1'
* port: 6379
* database: 0
* cleanupBefore: 'never'
* ```
*
* ## Public Properties
*
* * **driver** - Contains the Predis client/driver
*
* @author Marc Verney <marc@marcverney.net>
*/
class Redis extends CodeceptionModule implements RequiresPackage
{
/**
* {@inheritdoc}
*
* No default value is set for the database, using this parameter.
*/
protected $config = [
'host' => '127.0.0.1',
'port' => 6379,
'cleanupBefore' => 'never'
];
/**
* {@inheritdoc}
*/
protected $requiredFields = [
'database'
];
/**
* The Redis driver
*
* @var RedisDriver
*/
public $driver;
public function _requires()
{
return ['Predis\Client' => '"predis/predis": "^1.0"'];
}
/**
* Instructions to run after configuration is loaded
*
* @throws ModuleException
*/
public function _initialize()
{
try {
$this->driver = new RedisDriver([
'host' => $this->config['host'],
'port' => $this->config['port'],
'database' => $this->config['database']
]);
} catch (\Exception $e) {
throw new ModuleException(
__CLASS__,
$e->getMessage()
);
}
}
/**
* Code to run before each suite
*
* @param array $settings
*/
public function _beforeSuite($settings = [])
{
if ($this->config['cleanupBefore'] === 'suite') {
$this->cleanup();
}
}
/**
* Code to run before each test
*
* @param TestInterface $test
*/
public function _before(TestInterface $test)
{
if ($this->config['cleanupBefore'] === 'test') {
$this->cleanup();
}
}
/**
* Delete all the keys in the Redis database
*
* @throws ModuleException
*/
public function cleanup()
{
try {
$this->driver->flushdb();
} catch (\Exception $e) {
throw new ModuleException(
__CLASS__,
$e->getMessage()
);
}
}
/**
* Returns the value of a given key
*
* Examples:
*
* ``` php
* <?php
* // Strings
* $I->grabFromRedis('string');
*
* // Lists: get all members
* $I->grabFromRedis('example:list');
*
* // Lists: get a specific member
* $I->grabFromRedis('example:list', 2);
*
* // Lists: get a range of elements
* $I->grabFromRedis('example:list', 2, 4);
*
* // Sets: get all members
* $I->grabFromRedis('example:set');
*
* // ZSets: get all members
* $I->grabFromRedis('example:zset');
*
* // ZSets: get a range of members
* $I->grabFromRedis('example:zset', 3, 12);
*
* // Hashes: get all fields of a key
* $I->grabFromRedis('example:hash');
*
* // Hashes: get a specific field of a key
* $I->grabFromRedis('example:hash', 'foo');
* ```
*
* @param string $key The key name
*
* @return mixed
*
* @throws ModuleException if the key does not exist
*/
public function grabFromRedis($key)
{
$args = func_get_args();
switch ($this->driver->type($key)) {
case 'none':
throw new ModuleException(
$this,
"Cannot grab key \"$key\" as it does not exist"
);
break;
case 'string':
$reply = $this->driver->get($key);
break;
case 'list':
if (count($args) === 2) {
$reply = $this->driver->lindex($key, $args[1]);
} else {
$reply = $this->driver->lrange(
$key,
isset($args[1]) ? $args[1] : 0,
isset($args[2]) ? $args[2] : -1
);
}
break;
case 'set':
$reply = $this->driver->smembers($key);
break;
case 'zset':
if (count($args) === 2) {
throw new ModuleException(
$this,
"The method grabFromRedis(), when used with sorted "
. "sets, expects either one argument or three"
);
}
$reply = $this->driver->zrange(
$key,
isset($args[2]) ? $args[1] : 0,
isset($args[2]) ? $args[2] : -1,
'WITHSCORES'
);
break;
case 'hash':
$reply = isset($args[1])
? $this->driver->hget($key, $args[1])
: $this->driver->hgetall($key);
break;
default:
$reply = null;
}
return $reply;
}
/**
* Creates or modifies keys
*
* If $key already exists:
*
* - Strings: its value will be overwritten with $value
* - Other types: $value items will be appended to its value
*
* Examples:
*
* ``` php
* <?php
* // Strings: $value must be a scalar
* $I->haveInRedis('string', 'Obladi Oblada');
*
* // Lists: $value can be a scalar or an array
* $I->haveInRedis('list', ['riri', 'fifi', 'loulou']);
*
* // Sets: $value can be a scalar or an array
* $I->haveInRedis('set', ['riri', 'fifi', 'loulou']);
*
* // ZSets: $value must be an associative array with scores
* $I->haveInRedis('zset', ['riri' => 1, 'fifi' => 2, 'loulou' => 3]);
*
* // Hashes: $value must be an associative array
* $I->haveInRedis('hash', ['obladi' => 'oblada']);
* ```
*
* @param string $type The type of the key
* @param string $key The key name
* @param mixed $value The value
*
* @throws ModuleException
*/
public function haveInRedis($type, $key, $value)
{
switch (strtolower($type)) {
case 'string':
if (!is_scalar($value)) {
throw new ModuleException(
$this,
'If second argument of haveInRedis() method is "string", '
. 'third argument must be a scalar'
);
}
$this->driver->set($key, $value);
break;
case 'list':
$this->driver->rpush($key, $value);
break;
case 'set':
$this->driver->sadd($key, $value);
break;
case 'zset':
if (!is_array($value)) {
throw new ModuleException(
$this,
'If second argument of haveInRedis() method is "zset", '
. 'third argument must be an (associative) array'
);
}
$this->driver->zadd($key, $value);
break;
case 'hash':
if (!is_array($value)) {
throw new ModuleException(
$this,
'If second argument of haveInRedis() method is "hash", '
. 'third argument must be an array'
);
}
$this->driver->hmset($key, $value);
break;
default:
throw new ModuleException(
$this,
"Unknown type \"$type\" for key \"$key\". Allowed types are "
. '"string", "list", "set", "zset", "hash"'
);
}
}
/**
* Asserts that a key does not exist or, optionally, that it doesn't have the
* provided $value
*
* Examples:
*
* ``` php
* <?php
* // With only one argument, only checks the key does not exist
* $I->dontSeeInRedis('example:string');
*
* // Checks a String does not exist or its value is not the one provided
* $I->dontSeeInRedis('example:string', 'life');
*
* // Checks a List does not exist or its value is not the one provided (order of elements is compared).
* $I->dontSeeInRedis('example:list', ['riri', 'fifi', 'loulou']);
*
* // Checks a Set does not exist or its value is not the one provided (order of members is ignored).
* $I->dontSeeInRedis('example:set', ['riri', 'fifi', 'loulou']);
*
* // Checks a ZSet does not exist or its value is not the one provided (scores are required, order of members is compared)
* $I->dontSeeInRedis('example:zset', ['riri' => 1, 'fifi' => 2, 'loulou' => 3]);
*
* // Checks a Hash does not exist or its value is not the one provided (order of members is ignored).
* $I->dontSeeInRedis('example:hash', ['riri' => true, 'fifi' => 'Dewey', 'loulou' => 2]);
* ```
*
* @param string $key The key name
* @param mixed $value Optional. If specified, also checks the key has this
* value. Booleans will be converted to 1 and 0 (even inside arrays)
*/
public function dontSeeInRedis($key, $value = null)
{
$this->assertFalse(
(bool) $this->checkKeyExists($key, $value),
"The key \"$key\" exists" . ($value ? ' and its value matches the one provided' : '')
);
}
/**
* Asserts that a given key does not contain a given item
*
* Examples:
*
* ``` php
* <?php
* // Strings: performs a substring search
* $I->dontSeeRedisKeyContains('string', 'bar');
*
* // Lists
* $I->dontSeeRedisKeyContains('example:list', 'poney');
*
* // Sets
* $I->dontSeeRedisKeyContains('example:set', 'cat');
*
* // ZSets: check whether the zset has this member
* $I->dontSeeRedisKeyContains('example:zset', 'jordan');
*
* // ZSets: check whether the zset has this member with this score
* $I->dontSeeRedisKeyContains('example:zset', 'jordan', 23);
*
* // Hashes: check whether the hash has this field
* $I->dontSeeRedisKeyContains('example:hash', 'magic');
*
* // Hashes: check whether the hash has this field with this value
* $I->dontSeeRedisKeyContains('example:hash', 'magic', 32);
* ```
*
* @param string $key The key
* @param mixed $item The item
* @param null $itemValue Optional and only used for zsets and hashes. If
* specified, the method will also check that the $item has this value/score
*
* @return bool
*/
public function dontSeeRedisKeyContains($key, $item, $itemValue = null)
{
$this->assertFalse(
(bool) $this->checkKeyContains($key, $item, $itemValue),
"The key \"$key\" contains " . (
is_null($itemValue)
? "\"$item\""
: "[\"$item\" => \"$itemValue\"]"
)
);
}
/**
* Asserts that a key exists, and optionally that it has the provided $value
*
* Examples:
*
* ``` php
* <?php
* // With only one argument, only checks the key exists
* $I->seeInRedis('example:string');
*
* // Checks a String exists and has the value "life"
* $I->seeInRedis('example:string', 'life');
*
* // Checks the value of a List. Order of elements is compared.
* $I->seeInRedis('example:list', ['riri', 'fifi', 'loulou']);
*
* // Checks the value of a Set. Order of members is ignored.
* $I->seeInRedis('example:set', ['riri', 'fifi', 'loulou']);
*
* // Checks the value of a ZSet. Scores are required. Order of members is compared.
* $I->seeInRedis('example:zset', ['riri' => 1, 'fifi' => 2, 'loulou' => 3]);
*
* // Checks the value of a Hash. Order of members is ignored.
* $I->seeInRedis('example:hash', ['riri' => true, 'fifi' => 'Dewey', 'loulou' => 2]);
* ```
*
* @param string $key The key name
* @param mixed $value Optional. If specified, also checks the key has this
* value. Booleans will be converted to 1 and 0 (even inside arrays)
*/
public function seeInRedis($key, $value = null)
{
$this->assertTrue(
(bool) $this->checkKeyExists($key, $value),
"Cannot find key \"$key\"" . ($value ? ' with the provided value' : '')
);
}
/**
* Sends a command directly to the Redis driver. See documentation at
* https://github.com/nrk/predis
* Every argument that follows the $command name will be passed to it.
*
* Examples:
*
* ``` php
* <?php
* $I->sendCommandToRedis('incr', 'example:string');
* $I->sendCommandToRedis('strLen', 'example:string');
* $I->sendCommandToRedis('lPop', 'example:list');
* $I->sendCommandToRedis('zRangeByScore', 'example:set', '-inf', '+inf', ['withscores' => true, 'limit' => [1, 2]]);
* $I->sendCommandToRedis('flushdb');
* ```
*
* @param string $command The command name
*
* @return mixed
*/
public function sendCommandToRedis($command)
{
return call_user_func_array(
[$this->driver, $command],
array_slice(func_get_args(), 1)
);
}
/**
* Asserts that a given key contains a given item
*
* Examples:
*
* ``` php
* <?php
* // Strings: performs a substring search
* $I->seeRedisKeyContains('example:string', 'bar');
*
* // Lists
* $I->seeRedisKeyContains('example:list', 'poney');
*
* // Sets
* $I->seeRedisKeyContains('example:set', 'cat');
*
* // ZSets: check whether the zset has this member
* $I->seeRedisKeyContains('example:zset', 'jordan');
*
* // ZSets: check whether the zset has this member with this score
* $I->seeRedisKeyContains('example:zset', 'jordan', 23);
*
* // Hashes: check whether the hash has this field
* $I->seeRedisKeyContains('example:hash', 'magic');
*
* // Hashes: check whether the hash has this field with this value
* $I->seeRedisKeyContains('example:hash', 'magic', 32);
* ```
*
* @param string $key The key
* @param mixed $item The item
* @param null $itemValue Optional and only used for zsets and hashes. If
* specified, the method will also check that the $item has this value/score
*
* @return bool
*/
public function seeRedisKeyContains($key, $item, $itemValue = null)
{
$this->assertTrue(
(bool) $this->checkKeyContains($key, $item, $itemValue),
"The key \"$key\" does not contain " . (
is_null($itemValue)
? "\"$item\""
: "[\"$item\" => \"$itemValue\"]"
)
);
}
/**
* Converts boolean values to "0" and "1"
*
* @param mixed $var The variable
*
* @return mixed
*/
private function boolToString($var)
{
$copy = is_array($var) ? $var : [$var];
foreach ($copy as $key => $value) {
if (is_bool($value)) {
$copy[$key] = $value ? '1' : '0';
}
}
return is_array($var) ? $copy : $copy[0];
}
/**
* Checks whether a key contains a given item
*
* @param string $key The key
* @param mixed $item The item
* @param null $itemValue Optional and only used for zsets and hashes. If
* specified, the method will also check that the $item has this value/score
*
* @return bool
*
* @throws ModuleException
*/
private function checkKeyContains($key, $item, $itemValue = null)
{
$result = null;
if (!is_scalar($item)) {
throw new ModuleException(
$this,
"All arguments of [dont]seeRedisKeyContains() must be scalars"
);
}
switch ($this->driver->type($key)) {
case 'string':
$reply = $this->driver->get($key);
$result = strpos($reply, $item) !== false;
break;
case 'list':
$reply = $this->driver->lrange($key, 0, -1);
$result = in_array($item, $reply);
break;
case 'set':
$result = $this->driver->sismember($key, $item);
break;
case 'zset':
$reply = $this->driver->zscore($key, $item);
if (is_null($reply)) {
$result = false;
} elseif (!is_null($itemValue)) {
$result = (float) $reply === (float) $itemValue;
} else {
$result = true;
}
break;
case 'hash':
$reply = $this->driver->hget($key, $item);
$result = is_null($itemValue)
? !is_null($reply)
: (string) $reply === (string) $itemValue;
break;
case 'none':
throw new ModuleException(
$this,
"Key \"$key\" does not exist"
);
break;
}
return $result;
}
/**
* Checks whether a key exists and, optionally, whether it has a given $value
*
* @param string $key The key name
* @param mixed $value Optional. If specified, also checks the key has this
* value. Booleans will be converted to 1 and 0 (even inside arrays)
*
* @return bool
*/
private function checkKeyExists($key, $value = null)
{
$type = $this->driver->type($key);
if (is_null($value)) {
return $type != 'none';
}
$value = $this->boolToString($value);
switch ($type) {
case 'string':
$reply = $this->driver->get($key);
// Allow non strict equality (2 equals '2')
$result = $reply == $value;
break;
case 'list':
$reply = $this->driver->lrange($key, 0, -1);
// Check both arrays have the same key/value pairs + same order
$result = $reply === $value;
break;
case 'set':
$reply = $this->driver->smembers($key);
// Only check both arrays have the same values
sort($reply);
sort($value);
$result = $reply === $value;
break;
case 'zset':
$reply = $this->driver->zrange($key, 0, -1, 'WITHSCORES');
// Check both arrays have the same key/value pairs + same order
$reply = $this->scoresToFloat($reply);
$value = $this->scoresToFloat($value);
$result = $reply === $value;
break;
case 'hash':
$reply = $this->driver->hgetall($key);
// Only check both arrays have the same key/value pairs (==)
$result = $reply == $value;
break;
default:
$result = false;
}
return $result;
}
/**
* Explicitly cast the scores of a Zset associative array as float/double
*
* @param array $arr The ZSet associative array
*
* @return array
*/
private function scoresToFloat(array $arr)
{
foreach ($arr as $member => $score) {
$arr[$member] = (float) $score;
}
return $arr;
}
}