Commit ec52f164 authored by tufengqi's avatar tufengqi

init yii2

parent a63a23be
{
"directory" : "vendor/bower-asset"
}
# phpstorm project files
.idea
# netbeans project files
nbproject
# zend studio for eclipse project files
.buildpath
.project
.settings
# windows thumbnail cache
Thumbs.db
# composer itself is not needed
composer.phar
# Mac DS_Store Files
/build
.DS_Store
# phpunit itself is not needed
phpunit.phar
# local phpunit config
/phpunit.xml
tests/_output/*
tests/_support/_generated
#vagrant folder
/.vagrant
\ No newline at end of file
.idea
/composer.lock
.vscode
\ No newline at end of file
{
"name": "yii2_33cn",
"name": "yiisoft/yii2-app-basic",
"description": "Yii 2 Basic Project Template",
"keywords": ["yii2", "framework", "basic", "project template"],
"homepage": "http://www.yiiframework.com/",
......
......@@ -14,7 +14,7 @@ $config = [
'components' => [
'request' => [
// !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
'cookieValidationKey' => '5C7G_15_s1b0A1tDEa0Ki8MmEOomIn_R',
'cookieValidationKey' => '14hhexVTxr4OC4BBrLE4rJfIbodU7LVz',
],
'cache' => [
'class' => 'yii\caching\FileCache',
......
......@@ -4,4 +4,4 @@
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit2c27aae683e1f1fb7dc2fdde03e9f332::getLoader();
return ComposerAutoloaderInitb7f6fff58df4cda48239fd6d0378e5f0::getLoader();
......@@ -7,6 +7,7 @@ $baseDir = dirname($vendorDir);
return array(
'yii\\swiftmailer\\' => array($vendorDir . '/yiisoft/yii2-swiftmailer'),
'yii\\redis\\' => array($vendorDir . '/yiisoft/yii2-redis/src'),
'yii\\gii\\' => array($vendorDir . '/yiisoft/yii2-gii/src'),
'yii\\faker\\' => array($vendorDir . '/yiisoft/yii2-faker'),
'yii\\debug\\' => array($vendorDir . '/yiisoft/yii2-debug'),
......
......@@ -2,7 +2,7 @@
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit2c27aae683e1f1fb7dc2fdde03e9f332
class ComposerAutoloaderInitb7f6fff58df4cda48239fd6d0378e5f0
{
private static $loader;
......@@ -19,15 +19,15 @@ class ComposerAutoloaderInit2c27aae683e1f1fb7dc2fdde03e9f332
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInit2c27aae683e1f1fb7dc2fdde03e9f332', 'loadClassLoader'), true, true);
spl_autoload_register(array('ComposerAutoloaderInitb7f6fff58df4cda48239fd6d0378e5f0', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit2c27aae683e1f1fb7dc2fdde03e9f332', 'loadClassLoader'));
spl_autoload_unregister(array('ComposerAutoloaderInitb7f6fff58df4cda48239fd6d0378e5f0', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
if ($useStaticLoader) {
require_once __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit2c27aae683e1f1fb7dc2fdde03e9f332::getInitializer($loader));
call_user_func(\Composer\Autoload\ComposerStaticInitb7f6fff58df4cda48239fd6d0378e5f0::getInitializer($loader));
} else {
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
......@@ -48,19 +48,19 @@ class ComposerAutoloaderInit2c27aae683e1f1fb7dc2fdde03e9f332
$loader->register(true);
if ($useStaticLoader) {
$includeFiles = Composer\Autoload\ComposerStaticInit2c27aae683e1f1fb7dc2fdde03e9f332::$files;
$includeFiles = Composer\Autoload\ComposerStaticInitb7f6fff58df4cda48239fd6d0378e5f0::$files;
} else {
$includeFiles = require __DIR__ . '/autoload_files.php';
}
foreach ($includeFiles as $fileIdentifier => $file) {
composerRequire2c27aae683e1f1fb7dc2fdde03e9f332($fileIdentifier, $file);
composerRequireb7f6fff58df4cda48239fd6d0378e5f0($fileIdentifier, $file);
}
return $loader;
}
}
function composerRequire2c27aae683e1f1fb7dc2fdde03e9f332($fileIdentifier, $file)
function composerRequireb7f6fff58df4cda48239fd6d0378e5f0($fileIdentifier, $file)
{
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
require $file;
......
......@@ -4,7 +4,7 @@
namespace Composer\Autoload;
class ComposerStaticInit2c27aae683e1f1fb7dc2fdde03e9f332
class ComposerStaticInitb7f6fff58df4cda48239fd6d0378e5f0
{
public static $files = array (
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
......@@ -20,6 +20,7 @@ class ComposerStaticInit2c27aae683e1f1fb7dc2fdde03e9f332
'y' =>
array (
'yii\\swiftmailer\\' => 16,
'yii\\redis\\' => 10,
'yii\\gii\\' => 8,
'yii\\faker\\' => 10,
'yii\\debug\\' => 10,
......@@ -81,6 +82,10 @@ class ComposerStaticInit2c27aae683e1f1fb7dc2fdde03e9f332
array (
0 => __DIR__ . '/..' . '/yiisoft/yii2-swiftmailer',
),
'yii\\redis\\' =>
array (
0 => __DIR__ . '/..' . '/yiisoft/yii2-redis/src',
),
'yii\\gii\\' =>
array (
0 => __DIR__ . '/..' . '/yiisoft/yii2-gii/src',
......@@ -767,10 +772,10 @@ class ComposerStaticInit2c27aae683e1f1fb7dc2fdde03e9f332
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit2c27aae683e1f1fb7dc2fdde03e9f332::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit2c27aae683e1f1fb7dc2fdde03e9f332::$prefixDirsPsr4;
$loader->prefixesPsr0 = ComposerStaticInit2c27aae683e1f1fb7dc2fdde03e9f332::$prefixesPsr0;
$loader->classMap = ComposerStaticInit2c27aae683e1f1fb7dc2fdde03e9f332::$classMap;
$loader->prefixLengthsPsr4 = ComposerStaticInitb7f6fff58df4cda48239fd6d0378e5f0::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInitb7f6fff58df4cda48239fd6d0378e5f0::$prefixDirsPsr4;
$loader->prefixesPsr0 = ComposerStaticInitb7f6fff58df4cda48239fd6d0378e5f0::$prefixesPsr0;
$loader->classMap = ComposerStaticInitb7f6fff58df4cda48239fd6d0378e5f0::$classMap;
}, null, ClassLoader::class);
}
......
......@@ -2394,7 +2394,7 @@
"dev-master": "4.1-dev"
}
},
"installation-source": "source",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Component\\CssSelector\\": ""
......@@ -2801,7 +2801,7 @@
},
"time": "2017-04-07T12:08:54+00:00",
"type": "library",
"installation-source": "source",
"installation-source": "dist",
"autoload": {
"classmap": [
"src/"
......@@ -2849,7 +2849,7 @@
"dev-master": "1.3-dev"
}
},
"installation-source": "source",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Webmozart\\Assert\\": "src/"
......@@ -3246,6 +3246,59 @@
]
},
{
"name": "yiisoft/yii2-redis",
"version": "2.0.8",
"version_normalized": "2.0.8.0",
"source": {
"type": "git",
"url": "https://github.com/yiisoft/yii2-redis.git",
"reference": "ffe6bff8dc6be4bb84c9495cd3ef7ef1161c1314"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/yiisoft/yii2-redis/zipball/ffe6bff8dc6be4bb84c9495cd3ef7ef1161c1314",
"reference": "ffe6bff8dc6be4bb84c9495cd3ef7ef1161c1314",
"shasum": ""
},
"require": {
"yiisoft/yii2": "~2.0.14"
},
"require-dev": {
"yiisoft/yii2-dev": "~2.0.14"
},
"time": "2018-03-20T11:01:04+00:00",
"type": "yii2-extension",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"yii\\redis\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Carsten Brandt",
"email": "mail@cebe.cc"
}
],
"description": "Redis Cache, Session and ActiveRecord for the Yii framework",
"keywords": [
"active-record",
"cache",
"redis",
"session",
"yii2"
]
},
{
"name": "yiisoft/yii2-swiftmailer",
"version": "2.0.7",
"version_normalized": "2.0.7.0",
......
/tests export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/.travis.yml export-ignore
/appveyor.yml export-ignore
/phpunit.xml.dist export-ignore
language: php
sudo: false
branches:
only:
- master
cache:
directories:
- $HOME/.composer/cache/files
matrix:
include:
- php: 5.3
dist: precise
- php: 7.1
env: COVERAGE=yes
- php: 5.4
- php: 5.5
- php: 5.6
- php: 7.0
- php: hhvm
- php: nightly
- php: 7.1
env: COMPOSER_FLAGS='--prefer-lowest --prefer-stable'
- php: 7.2
allow_failures:
- php: hhvm
- php: nightly
fast_finish: true
before_install:
- if [[ $TRAVIS_PHP_VERSION != hhvm && $COVERAGE != yes ]]; then phpenv config-rm xdebug.ini; fi;
- if [[ $TRAVIS_REPO_SLUG = webmozart/assert ]]; then cp .composer-auth.json ~/.composer/auth.json; fi;
- composer self-update
install: composer update $COMPOSER_FLAGS --prefer-dist --no-interaction
script: if [[ $COVERAGE = yes ]]; then vendor/bin/phpunit --verbose --coverage-clover=coverage.clover; else vendor/bin/phpunit --verbose; fi
after_script: if [[ $COVERAGE = yes ]]; then wget https://scrutinizer-ci.com/ocular.phar && php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi
build: false
platform: x86
clone_folder: c:\projects\webmozart\assert
branches:
only:
- master
cache:
- c:\php -> appveyor.yml
init:
- SET PATH=c:\php;%PATH%
- SET COMPOSER_NO_INTERACTION=1
- SET PHP=1
install:
- IF EXIST c:\php (SET PHP=0) ELSE (mkdir c:\php)
- cd c:\php
- IF %PHP%==1 appveyor DownloadFile http://windows.php.net/downloads/releases/archives/php-7.0.0-nts-Win32-VC14-x86.zip
- IF %PHP%==1 7z x php-7.0.0-nts-Win32-VC14-x86.zip -y >nul
- IF %PHP%==1 del /Q *.zip
- IF %PHP%==1 echo @php %%~dp0composer.phar %%* > composer.bat
- IF %PHP%==1 copy /Y php.ini-development php.ini
- IF %PHP%==1 echo max_execution_time=1200 >> php.ini
- IF %PHP%==1 echo date.timezone="UTC" >> php.ini
- IF %PHP%==1 echo extension_dir=ext >> php.ini
- IF %PHP%==1 echo extension=php_curl.dll >> php.ini
- IF %PHP%==1 echo extension=php_openssl.dll >> php.ini
- IF %PHP%==1 echo extension=php_mbstring.dll >> php.ini
- IF %PHP%==1 echo extension=php_fileinfo.dll >> php.ini
- appveyor DownloadFile https://getcomposer.org/composer.phar
- cd c:\projects\webmozart\assert
- mkdir %APPDATA%\Composer
- IF %APPVEYOR_REPO_NAME%==webmozart/assert copy /Y .composer-auth.json %APPDATA%\Composer\auth.json
- composer update --prefer-dist --no-progress --ansi
test_script:
- cd c:\projects\webmozart\assert
- vendor\bin\phpunit.bat --verbose
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php" colors="true">
<testsuites>
<testsuite name="Webmozart Assert Test Suite">
<directory suffix="Test.php">./tests/</directory>
</testsuite>
</testsuites>
<!-- Whitelist for code coverage -->
<filter>
<whitelist>
<directory suffix=".php">./src/</directory>
</whitelist>
</filter>
</phpunit>
<?php
/*
* This file is part of the webmozart/assert package.
*
* (c) Bernhard Schussek <bschussek@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Webmozart\Assert\Tests;
use ArrayIterator;
use ArrayObject;
use Exception;
use Error;
use LogicException;
use PHPUnit_Framework_TestCase;
use RuntimeException;
use stdClass;
use Webmozart\Assert\Assert;
/**
* @since 1.0
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class AssertTest extends PHPUnit_Framework_TestCase
{
private static $resource;
public static function getResource()
{
if (!static::$resource) {
static::$resource = fopen(__FILE__, 'r');
}
return static::$resource;
}
public static function tearDownAfterClass()
{
@fclose(self::$resource);
}
public function getTests()
{
$resource = self::getResource();
return array(
array('string', array('value'), true),
array('string', array(''), true),
array('string', array(1234), false),
array('stringNotEmpty', array('value'), true),
array('stringNotEmpty', array('0'), true),
array('stringNotEmpty', array(''), false),
array('stringNotEmpty', array(1234), false),
array('integer', array(123), true),
array('integer', array('123'), false),
array('integer', array(1.0), false),
array('integer', array(1.23), false),
array('integerish', array(1.0), true),
array('integerish', array(1.23), false),
array('integerish', array(123), true),
array('integerish', array('123'), true),
array('float', array(1.0), true),
array('float', array(1.23), true),
array('float', array(123), false),
array('float', array('123'), false),
array('numeric', array(1.0), true),
array('numeric', array(1.23), true),
array('numeric', array(123), true),
array('numeric', array('123'), true),
array('numeric', array('foo'), false),
array('natural', array(0), true),
array('natural', array(1), true),
array('natural', array(-1), false),
array('natural', array('1'), false),
array('natural', array(1.0), false),
array('natural', array(1.23), false),
array('boolean', array(true), true),
array('boolean', array(false), true),
array('boolean', array(1), false),
array('boolean', array('1'), false),
array('scalar', array('1'), true),
array('scalar', array(123), true),
array('scalar', array(true), true),
array('scalar', array(null), false),
array('scalar', array(array()), false),
array('scalar', array(new stdClass()), false),
array('object', array(new stdClass()), true),
array('object', array(new RuntimeException()), true),
array('object', array(null), false),
array('object', array(true), false),
array('object', array(1), false),
array('object', array(array()), false),
array('resource', array($resource), true),
array('resource', array($resource, 'stream'), true),
array('resource', array($resource, 'other'), false),
array('resource', array(1), false),
array('isCallable', array('strlen'), true),
array('isCallable', array(array($this, 'getTests')), true),
array('isCallable', array(function () {}), true),
array('isCallable', array(1234), false),
array('isCallable', array('foobar'), false),
array('isArray', array(array()), true),
array('isArray', array(array(1, 2, 3)), true),
array('isArray', array(new ArrayIterator(array())), false),
array('isArray', array(123), false),
array('isArray', array(new stdClass()), false),
array('isTraversable', array(array()), true),
array('isTraversable', array(array(1, 2, 3)), true),
array('isTraversable', array(new ArrayIterator(array())), true),
array('isTraversable', array(123), false),
array('isTraversable', array(new stdClass()), false),
array('isArrayAccessible', array(array()), true),
array('isArrayAccessible', array(array(1, 2, 3)), true),
array('isArrayAccessible', array(new ArrayObject(array())), true),
array('isArrayAccessible', array(123), false),
array('isArrayAccessible', array(new stdClass()), false),
array('isCountable', array(array()), true),
array('isCountable', array(array(1, 2)), true),
array('isCountable', array(new ArrayIterator(array())), true),
array('isCountable', array(new stdClass()), false),
array('isCountable', array('abcd'), false),
array('isCountable', array(123), false),
array('isIterable', array(array()), true),
array('isIterable', array(array(1, 2, 3)), true),
array('isIterable', array(new ArrayIterator(array())), true),
array('isIterable', array(123), false),
array('isIterable', array(new stdClass()), false),
array('isInstanceOf', array(new stdClass(), 'stdClass'), true),
array('isInstanceOf', array(new Exception(), 'stdClass'), false),
array('isInstanceOf', array(123, 'stdClass'), false),
array('isInstanceOf', array(array(), 'stdClass'), false),
array('notInstanceOf', array(new stdClass(), 'stdClass'), false),
array('notInstanceOf', array(new Exception(), 'stdClass'), true),
array('notInstanceOf', array(123, 'stdClass'), true),
array('notInstanceOf', array(array(), 'stdClass'), true),
array('isInstanceOfAny', array(new ArrayIterator(), array('Iterator', 'ArrayAccess')), true),
array('isInstanceOfAny', array(new Exception(), array('Exception', 'Countable')), true),
array('isInstanceOfAny', array(new Exception(), array('ArrayAccess', 'Countable')), false),
array('isInstanceOfAny', array(123, array('stdClass')), false),
array('isInstanceOfAny', array(array(), array('stdClass')), false),
array('true', array(true), true),
array('true', array(false), false),
array('true', array(1), false),
array('true', array(null), false),
array('false', array(false), true),
array('false', array(true), false),
array('false', array(1), false),
array('false', array(0), false),
array('false', array(null), false),
array('null', array(null), true),
array('null', array(false), false),
array('null', array(0), false),
array('notNull', array(false), true),
array('notNull', array(0), true),
array('notNull', array(null), false),
array('isEmpty', array(null), true),
array('isEmpty', array(false), true),
array('isEmpty', array(0), true),
array('isEmpty', array(''), true),
array('isEmpty', array(1), false),
array('isEmpty', array('a'), false),
array('notEmpty', array(1), true),
array('notEmpty', array('a'), true),
array('notEmpty', array(null), false),
array('notEmpty', array(false), false),
array('notEmpty', array(0), false),
array('notEmpty', array(''), false),
array('eq', array(1, 1), true),
array('eq', array(1, '1'), true),
array('eq', array(1, true), true),
array('eq', array(1, 0), false),
array('notEq', array(1, 0), true),
array('notEq', array(1, 1), false),
array('notEq', array(1, '1'), false),
array('notEq', array(1, true), false),
array('same', array(1, 1), true),
array('same', array(1, '1'), false),
array('same', array(1, true), false),
array('same', array(1, 0), false),
array('notSame', array(1, 0), true),
array('notSame', array(1, 1), false),
array('notSame', array(1, '1'), true),
array('notSame', array(1, true), true),
array('greaterThan', array(1, 0), true),
array('greaterThan', array(0, 0), false),
array('greaterThanEq', array(2, 1), true),
array('greaterThanEq', array(1, 1), true),
array('greaterThanEq', array(0, 1), false),
array('lessThan', array(0, 1), true),
array('lessThan', array(1, 1), false),
array('lessThanEq', array(0, 1), true),
array('lessThanEq', array(1, 1), true),
array('lessThanEq', array(2, 1), false),
array('range', array(1, 1, 2), true),
array('range', array(2, 1, 2), true),
array('range', array(0, 1, 2), false),
array('range', array(3, 1, 2), false),
array('oneOf', array(1, array(1, 2, 3)), true),
array('oneOf', array(1, array('1', '2', '3')), false),
array('contains', array('abcd', 'ab'), true),
array('contains', array('abcd', 'bc'), true),
array('contains', array('abcd', 'cd'), true),
array('contains', array('abcd', 'de'), false),
array('contains', array('', 'de'), false),
array('notContains', array('abcd', 'ab'), false),
array('notContains', array('abcd', 'bc'), false),
array('notContains', array('abcd', 'cd'), false),
array('notContains', array('abcd', 'de'), true),
array('notContains', array('', 'de'), true),
array('notWhitespaceOnly', array('abc'), true),
array('notWhitespaceOnly', array('123'), true),
array('notWhitespaceOnly', array(' abc '), true),
array('notWhitespaceOnly', array('a b c'), true),
array('notWhitespaceOnly', array(''), false),
array('notWhitespaceOnly', array(' '), false),
array('notWhitespaceOnly', array("\t"), false),
array('notWhitespaceOnly', array("\n"), false),
array('notWhitespaceOnly', array("\r"), false),
array('notWhitespaceOnly', array("\r\n\t "), false),
array('startsWith', array('abcd', 'ab'), true),
array('startsWith', array('abcd', 'bc'), false),
array('startsWith', array('', 'bc'), false),
array('startsWithLetter', array('abcd'), true),
array('startsWithLetter', array('1abcd'), false),
array('startsWithLetter', array(''), false),
array('endsWith', array('abcd', 'cd'), true),
array('endsWith', array('abcd', 'bc'), false),
array('endsWith', array('', 'bc'), false),
array('regex', array('abcd', '~^ab~'), true),
array('regex', array('abcd', '~^bc~'), false),
array('regex', array('', '~^bc~'), false),
array('alpha', array('abcd'), true),
array('alpha', array('ab1cd'), false),
array('alpha', array(''), false),
array('digits', array('1234'), true),
array('digits', array('12a34'), false),
array('digits', array(''), false),
array('alnum', array('ab12'), true),
array('alnum', array('ab12$'), false),
array('alnum', array(''), false),
array('lower', array('abcd'), true),
array('lower', array('abCd'), false),
array('lower', array('ab_d'), false),
array('lower', array(''), false),
array('upper', array('ABCD'), true),
array('upper', array('ABcD'), false),
array('upper', array('AB_D'), false),
array('upper', array(''), false),
array('length', array('abcd', 4), true),
array('length', array('abc', 4), false),
array('length', array('abcde', 4), false),
array('length', array('äbcd', 4), true, true),
array('length', array('äbc', 4), false, true),
array('length', array('äbcde', 4), false, true),
array('minLength', array('abcd', 4), true),
array('minLength', array('abcde', 4), true),
array('minLength', array('abc', 4), false),
array('minLength', array('äbcd', 4), true, true),
array('minLength', array('äbcde', 4), true, true),
array('minLength', array('äbc', 4), false, true),
array('maxLength', array('abcd', 4), true),
array('maxLength', array('abc', 4), true),
array('maxLength', array('abcde', 4), false),
array('maxLength', array('äbcd', 4), true, true),
array('maxLength', array('äbc', 4), true, true),
array('maxLength', array('äbcde', 4), false, true),
array('lengthBetween', array('abcd', 3, 5), true),
array('lengthBetween', array('abc', 3, 5), true),
array('lengthBetween', array('abcde', 3, 5), true),
array('lengthBetween', array('ab', 3, 5), false),
array('lengthBetween', array('abcdef', 3, 5), false),
array('lengthBetween', array('äbcd', 3, 5), true, true),
array('lengthBetween', array('äbc', 3, 5), true, true),
array('lengthBetween', array('äbcde', 3, 5), true, true),
array('lengthBetween', array('äb', 3, 5), false, true),
array('lengthBetween', array('äbcdef', 3, 5), false, true),
array('fileExists', array(__FILE__), true),
array('fileExists', array(__DIR__), true),
array('fileExists', array(__DIR__.'/foobar'), false),
array('file', array(__FILE__), true),
array('file', array(__DIR__), false),
array('file', array(__DIR__.'/foobar'), false),
array('directory', array(__DIR__), true),
array('directory', array(__FILE__), false),
array('directory', array(__DIR__.'/foobar'), false),
// no tests for readable()/writable() for now
array('classExists', array(__CLASS__), true),
array('classExists', array(__NAMESPACE__.'\Foobar'), false),
array('subclassOf', array(__CLASS__, 'PHPUnit_Framework_TestCase'), true),
array('subclassOf', array(__CLASS__, 'stdClass'), false),
array('implementsInterface', array('ArrayIterator', 'Traversable'), true),
array('implementsInterface', array(__CLASS__, 'Traversable'), false),
array('propertyExists', array((object) array('property' => 0), 'property'), true),
array('propertyExists', array((object) array('property' => null), 'property'), true),
array('propertyExists', array((object) array('property' => null), 'foo'), false),
array('propertyNotExists', array((object) array('property' => 0), 'property'), false),
array('propertyNotExists', array((object) array('property' => null), 'property'), false),
array('propertyNotExists', array((object) array('property' => null), 'foo'), true),
array('methodExists', array('RuntimeException', 'getMessage'), true),
array('methodExists', array(new RuntimeException(), 'getMessage'), true),
array('methodExists', array('stdClass', 'getMessage'), false),
array('methodExists', array(new stdClass(), 'getMessage'), false),
array('methodExists', array(null, 'getMessage'), false),
array('methodExists', array(true, 'getMessage'), false),
array('methodExists', array(1, 'getMessage'), false),
array('methodNotExists', array('RuntimeException', 'getMessage'), false),
array('methodNotExists', array(new RuntimeException(), 'getMessage'), false),
array('methodNotExists', array('stdClass', 'getMessage'), true),
array('methodNotExists', array(new stdClass(), 'getMessage'), true),
array('methodNotExists', array(null, 'getMessage'), true),
array('methodNotExists', array(true, 'getMessage'), true),
array('methodNotExists', array(1, 'getMessage'), true),
array('keyExists', array(array('key' => 0), 'key'), true),
array('keyExists', array(array('key' => null), 'key'), true),
array('keyExists', array(array('key' => null), 'foo'), false),
array('keyNotExists', array(array('key' => 0), 'key'), false),
array('keyNotExists', array(array('key' => null), 'key'), false),
array('keyNotExists', array(array('key' => null), 'foo'), true),
array('count', array(array(0, 1, 2), 3), true),
array('count', array(array(0, 1, 2), 2), false),
array('minCount', array(array(0), 2), false),
array('minCount', array(array(0, 1), 2), true),
array('minCount', array(array(0, 1, 2), 2), true),
array('maxCount', array(array(0, 1, 2), 2), false),
array('maxCount', array(array(0, 1), 2), true),
array('maxCount', array(array(0), 2), true),
array('countBetween', array(array(0, 1, 2), 4, 5), false),
array('countBetween', array(array(0, 1, 2), 1, 2), false),
array('countBetween', array(array(0, 1, 2), 2, 5), true),
array('uuid', array('00000000-0000-0000-0000-000000000000'), true),
array('uuid', array('ff6f8cb0-c57d-21e1-9b21-0800200c9a66'), true),
array('uuid', array('ff6f8cb0-c57d-11e1-9b21-0800200c9a66'), true),
array('uuid', array('ff6f8cb0-c57d-31e1-9b21-0800200c9a66'), true),
array('uuid', array('ff6f8cb0-c57d-41e1-9b21-0800200c9a66'), true),
array('uuid', array('ff6f8cb0-c57d-51e1-9b21-0800200c9a66'), true),
array('uuid', array('FF6F8CB0-C57D-11E1-9B21-0800200C9A66'), true),
array('uuid', array('zf6f8cb0-c57d-11e1-9b21-0800200c9a66'), false),
array('uuid', array('af6f8cb0c57d11e19b210800200c9a66'), false),
array('uuid', array('ff6f8cb0-c57da-51e1-9b21-0800200c9a66'), false),
array('uuid', array('af6f8cb-c57d-11e1-9b21-0800200c9a66'), false),
array('uuid', array('3f6f8cb0-c57d-11e1-9b21-0800200c9a6'), false),
array('throws', array(function() { throw new LogicException('test'); }, 'LogicException'), true),
array('throws', array(function() { throw new LogicException('test'); }, 'IllogicException'), false),
array('throws', array(function() { throw new Exception('test'); }), true),
array('throws', array(function() { trigger_error('test'); }, 'Throwable'), true, false, 70000),
array('throws', array(function() { trigger_error('test'); }, 'Unthrowable'), false, false, 70000),
array('throws', array(function() { throw new Error(); }, 'Throwable'), true, true, 70000),
);
}
public function getMethods()
{
$methods = array();
foreach ($this->getTests() as $params) {
$methods[$params[0]] = array($params[0]);
}
return array_values($methods);
}
/**
* @dataProvider getTests
*/
public function testAssert($method, $args, $success, $multibyte = false, $minVersion = null)
{
if ($minVersion && PHP_VERSION_ID < $minVersion) {
$this->markTestSkipped(sprintf('This test requires php %s or upper.', $minVersion));
return;
}
if ($multibyte && !function_exists('mb_strlen')) {
$this->markTestSkipped('The function mb_strlen() is not available');
}
if (!$success) {
$this->setExpectedException('\InvalidArgumentException');
}
call_user_func_array(array('Webmozart\Assert\Assert', $method), $args);
}
/**
* @dataProvider getTests
*/
public function testNullOr($method, $args, $success, $multibyte = false, $minVersion = null)
{
if ($minVersion && PHP_VERSION_ID < $minVersion) {
$this->markTestSkipped(sprintf('This test requires php %s or upper.', $minVersion));
return;
}
if ($multibyte && !function_exists('mb_strlen')) {
$this->markTestSkipped('The function mb_strlen() is not available');
}
if (!$success && null !== reset($args)) {
$this->setExpectedException('\InvalidArgumentException');
}
call_user_func_array(array('Webmozart\Assert\Assert', 'nullOr'.ucfirst($method)), $args);
}
/**
* @dataProvider getMethods
*/
public function testNullOrAcceptsNull($method)
{
call_user_func(array('Webmozart\Assert\Assert', 'nullOr'.ucfirst($method)), null);
}
/**
* @dataProvider getTests
*/
public function testAllArray($method, $args, $success, $multibyte = false, $minVersion = null)
{
if ($minVersion && PHP_VERSION_ID < $minVersion) {
$this->markTestSkipped(sprintf('This test requires php %s or upper.', $minVersion));
return;
}
if ($multibyte && !function_exists('mb_strlen')) {
$this->markTestSkipped('The function mb_strlen() is not available');
}
if (!$success) {
$this->setExpectedException('\InvalidArgumentException');
}
$arg = array_shift($args);
array_unshift($args, array($arg));
call_user_func_array(array('Webmozart\Assert\Assert', 'all'.ucfirst($method)), $args);
}
/**
* @dataProvider getTests
*/
public function testAllTraversable($method, $args, $success, $multibyte = false, $minVersion = null)
{
if ($minVersion && PHP_VERSION_ID < $minVersion) {
$this->markTestSkipped(sprintf('This test requires php %s or upper.', $minVersion));
return;
}
if ($multibyte && !function_exists('mb_strlen')) {
$this->markTestSkipped('The function mb_strlen() is not available');
}
if (!$success) {
$this->setExpectedException('\InvalidArgumentException');
}
$arg = array_shift($args);
array_unshift($args, new ArrayIterator(array($arg)));
call_user_func_array(array('Webmozart\Assert\Assert', 'all'.ucfirst($method)), $args);
}
public function getStringConversions()
{
return array(
array('integer', array('foobar'), 'Expected an integer. Got: string'),
array('string', array(1), 'Expected a string. Got: integer'),
array('string', array(true), 'Expected a string. Got: boolean'),
array('string', array(null), 'Expected a string. Got: NULL'),
array('string', array(array()), 'Expected a string. Got: array'),
array('string', array(new stdClass()), 'Expected a string. Got: stdClass'),
array('string', array(self::getResource()), 'Expected a string. Got: resource'),
array('eq', array('1', '2'), 'Expected a value equal to "2". Got: "1"'),
array('eq', array(1, 2), 'Expected a value equal to 2. Got: 1'),
array('eq', array(true, false), 'Expected a value equal to false. Got: true'),
array('eq', array(true, null), 'Expected a value equal to null. Got: true'),
array('eq', array(null, true), 'Expected a value equal to true. Got: null'),
array('eq', array(array(1), array(2)), 'Expected a value equal to array. Got: array'),
array('eq', array(new ArrayIterator(array()), new stdClass()), 'Expected a value equal to stdClass. Got: ArrayIterator'),
array('eq', array(1, self::getResource()), 'Expected a value equal to resource. Got: 1'),
);
}
/**
* @dataProvider getStringConversions
*/
public function testConvertValuesToStrings($method, $args, $exceptionMessage)
{
$this->setExpectedException('\InvalidArgumentException', $exceptionMessage);
call_user_func_array(array('Webmozart\Assert\Assert', $method), $args);
}
}
......@@ -48,4 +48,13 @@ return array (
'@yii/faker' => $vendorDir . '/yiisoft/yii2-faker',
),
),
'yiisoft/yii2-redis' =>
array (
'name' => 'yiisoft/yii2-redis',
'version' => '2.0.8.0',
'alias' =>
array (
'@yii/redis' => $vendorDir . '/yiisoft/yii2-redis/src',
),
),
);
Yii Framework 2 redis extension Change Log
==========================================
2.0.8 March 20, 2018
--------------------
- Bug #141: Calling ActiveQuery::indexBy() had no effect since Yii 2.0.14 (cebe)
- Bug: (CVE-2018-8073): Fix possible remote code execution when improperly filtered user input is passed to `ActiveRecord::findOne()` and `::findAll()` (cebe)
- Enh #66: Cache component can be configured to read / get from replicas (ryusoft)
2.0.7 December 11, 2017
-----------------------
- Bug #114: Fixed ActiveQuery `not between` and `not` conditions which where not working correctly (cebe, ak1987)
- Bug #123: Fixed ActiveQuery to work with negative limit values, which are used in ActiveDataProvider for the count query (cebe)
- Enh #9: Added orderBy support to redis ActiveQuery and LuaScriptBuilder (valinurovam)
- Enh #91: Added option to retry connection after failing to communicate with redis server on stale socket (cebe)
- Enh #106: Improved handling of connection errors and introduced `yii\redis\SocketException` for these (cebe)
- Chg #127: Added PHP 7.2 compatibility (brandonkelly)
2.0.6 April 05, 2017
--------------------
- Bug #44: Remove quotes from numeric parts of composite key to avoid problem with different hashes for the same record (uniserpl)
- Bug #67: Fixed regression from 2.0.5, reconnecting a closed connection fails (cebe)
- Bug #82: Fixed session object destruction failure when key expires (juffin-halli, samdark)
- Bug #93: Fixed `yii\redis\ActiveRecord::deleteAll()` with condition (samdark)
- Bug #104: Fixed execution of two-word commands (cebe,branimir93)
- Enh #53: Added `Mutex` that implements a Redis based mutex (turboezh, sergeymakinen)
- Enh #81: Allow setting `Connection::$database` to `null` to avoid sending a `SELECT` command after connection (cebe)
- Enh #89: Added support for `\yii\db\QueryInterface::emulateExecution()` (samdark)
- Enh #103: Added missing commands and `@method` documentation for redis commands (cebe)
- Enh: Optimized find by PK for relational queries and IN condition (cebe, andruha)
2.0.5 March 17, 2016
--------------------
- Bug #22: Fixed string escaping issue in LuaScriptBuilder (vistart)
- Bug #37: Fixed detection of open socket (mirocow)
- Bug #46: Fixed bug to execute session_regenerate_id in PHP 7.0 (githubjeka)
- Enh #31: Added `Connection::$socketClientFlags` property for connection flags to be passed to `stream_socket_client()` (hugh-lee)
- Chg #14: Added missing `BLPOP` command to `$redisCommands` (samdark)
- Chg #61: Added missing `GEO*` commands to `$redisCommands` (leadermt)
2.0.4 May 10, 2015
------------------
- Enh #8: Auto increment value was not updated when a primary key was explicitly set (cebe, andruha)
2.0.3 March 01, 2015
--------------------
- no changes in this release.
2.0.2 January 11, 2015
----------------------
- Bug #6547: Fixed redis connection to deal with large data in combination with `mget()` (pyurin)
2.0.1 December 07, 2014
-----------------------
- Bug #4745: value of simple string returns was ignored by redis client and `true` is returned instead, now only `OK` will result in a `true` while all other values are returned as is (cebe)
- Enh #3714: Added support for connecting to redis server using a unix socket (savvot, robregonm)
2.0.0 October 12, 2014
----------------------
- no changes in this release.
2.0.0-rc September 27, 2014
---------------------------
- Bug #1311: Fixed storage and finding of `null` and boolean values (samdark, cebe)
- Enh #3520: Added `unlinkAll()`-method to active record to remove all records of a model relation (NmDimas, samdark, cebe)
- Enh #4048: Added `init` event to `ActiveQuery` classes (qiangxue)
- Enh #4086: changedAttributes of afterSave Event now contain old values (dizews)
2.0.0-beta April 13, 2014
-------------------------
- Bug #1993: afterFind event in AR is now called after relations have been populated (cebe, creocoder)
- Enh #1773: keyPrefix property of Session and Cache is not restricted to alnum characters anymore (cebe)
- Enh #2002: Added filterWhere() method to yii\redis\ActiveQuery to allow easy addition of search filter conditions by ignoring empty search fields (samdark, cebe)
- Enh #2892: ActiveRecord dirty attributes are now reset after call to `afterSave()` so information about changed attributes is available in `afterSave`-event (cebe)
- Chg #2281: Renamed `ActiveRecord::create()` to `populateRecord()` and changed signature. This method will not call instantiate() anymore (cebe)
- Chg #2146: Removed `ActiveRelation` class and moved the functionality to `ActiveQuery`.
All relational queries are now directly served by `ActiveQuery` allowing to use
custom scopes in relations (cebe)
2.0.0-alpha, December 1, 2013
-----------------------------
- Initial release.
The Yii framework is free software. It is released under the terms of
the following BSD License.
Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com)
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name of Yii Software LLC nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
# default versions to test against
# these can be overridden by setting the environment variables in the shell
REDIS_VERSION=3.0.0
PHP_VERSION=php-5.6.8
YII_VERSION=dev-master
# ensure all the configuration variables above are in environment of the shell commands below
export
help:
@echo "make test - run phpunit tests using a docker environment"
@echo "make clean - stop docker and remove container"
test: docker adjust-config
#composer require "yiisoft/yii2:${YII_VERSION}" --prefer-dist
#composer install --prefer-dist
docker run --rm=true -v $(shell pwd):/opt/test --link $(shell cat tests/dockerids/redis):redis yiitest/php:${PHP_VERSION} phpunit --verbose --color
adjust-config:
echo "<?php \$$config['databases']['redis']['port'] = 6379; \$$config['databases']['redis']['hostname'] = 'redis';" > tests/data/config.local.php
docker: build-docker
docker run -d -P yiitest/redis:${REDIS_VERSION} > tests/dockerids/redis
build-docker:
test -d tests/docker || git clone https://github.com/cebe/jenkins-test-docker tests/docker
cd tests/docker && git checkout -- . && git pull
cd tests/docker/php && sh build.sh
cd tests/docker/redis && sh build.sh
mkdir -p tests/dockerids
clean:
docker stop $(shell cat tests/dockerids/redis)
docker rm $(shell cat tests/dockerids/redis)
rm tests/dockerids/redis
<p align="center">
<a href="http://redis.io/" target="_blank" rel="external">
<img src="http://download.redis.io/logocontest/82.png" height="100px">
</a>
<h1 align="center">Redis Cache, Session and ActiveRecord for Yii 2</h1>
<br>
</p>
This extension provides the [redis](http://redis.io/) key-value store support for the [Yii framework 2.0](http://www.yiiframework.com).
It includes a `Cache` and `Session` storage handler and implements the `ActiveRecord` pattern that allows
you to store active records in redis.
For license information check the [LICENSE](LICENSE.md)-file.
Documentation is at [docs/guide/README.md](docs/guide/README.md).
[![Latest Stable Version](https://poser.pugx.org/yiisoft/yii2-redis/v/stable.png)](https://packagist.org/packages/yiisoft/yii2-redis)
[![Total Downloads](https://poser.pugx.org/yiisoft/yii2-redis/downloads.png)](https://packagist.org/packages/yiisoft/yii2-redis)
[![Build Status](https://travis-ci.org/yiisoft/yii2-redis.svg?branch=master)](https://travis-ci.org/yiisoft/yii2-redis)
Requirements
------------
At least redis version 2.6.12 is required for all components to work properly.
Installation
------------
The preferred way to install this extension is through [composer](http://getcomposer.org/download/).
Either run
```
php composer.phar require --prefer-dist yiisoft/yii2-redis
```
or add
```json
"yiisoft/yii2-redis": "~2.0.0"
```
to the require section of your composer.json.
Configuration
-------------
To use this extension, you have to configure the Connection class in your application configuration:
```php
return [
//....
'components' => [
'redis' => [
'class' => 'yii\redis\Connection',
'hostname' => 'localhost',
'port' => 6379,
'database' => 0,
],
]
];
```
{
"name": "yiisoft/yii2-redis",
"description": "Redis Cache, Session and ActiveRecord for the Yii framework",
"keywords": ["yii2", "redis", "active-record", "cache", "session"],
"type": "yii2-extension",
"license": "BSD-3-Clause",
"support": {
"issues": "https://github.com/yiisoft/yii2-redis/issues",
"forum": "http://www.yiiframework.com/forum/",
"wiki": "http://www.yiiframework.com/wiki/",
"irc": "irc://irc.freenode.net/yii",
"source": "https://github.com/yiisoft/yii2-redis"
},
"authors": [
{
"name": "Carsten Brandt",
"email": "mail@cebe.cc"
}
],
"require": {
"yiisoft/yii2": "~2.0.14"
},
"autoload": {
"psr-4": { "yii\\redis\\": "src" }
},
"autoload-dev": {
"psr-4": { "yiiunit\\extensions\\redis\\": "tests/"}
},
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"repositories": [
{
"type": "composer",
"url": "https://asset-packagist.org"
}
],
"require-dev": {
"yiisoft/yii2-dev": "~2.0.14"
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\redis;
use yii\base\Component;
use yii\base\InvalidParamException;
use yii\base\NotSupportedException;
use yii\db\ActiveQueryInterface;
use yii\db\ActiveQueryTrait;
use yii\db\ActiveRelationTrait;
use yii\db\QueryTrait;
/**
* ActiveQuery represents a query associated with an Active Record class.
*
* An ActiveQuery can be a normal query or be used in a relational context.
*
* ActiveQuery instances are usually created by [[ActiveRecord::find()]].
* Relational queries are created by [[ActiveRecord::hasOne()]] and [[ActiveRecord::hasMany()]].
*
* Normal Query
* ------------
*
* ActiveQuery mainly provides the following methods to retrieve the query results:
*
* - [[one()]]: returns a single record populated with the first row of data.
* - [[all()]]: returns all records based on the query results.
* - [[count()]]: returns the number of records.
* - [[sum()]]: returns the sum over the specified column.
* - [[average()]]: returns the average over the specified column.
* - [[min()]]: returns the min over the specified column.
* - [[max()]]: returns the max over the specified column.
* - [[scalar()]]: returns the value of the first column in the first row of the query result.
* - [[exists()]]: returns a value indicating whether the query result has data or not.
*
* You can use query methods, such as [[where()]], [[limit()]] and [[orderBy()]] to customize the query options.
*
* ActiveQuery also provides the following additional query options:
*
* - [[with()]]: list of relations that this query should be performed with.
* - [[indexBy()]]: the name of the column by which the query result should be indexed.
* - [[asArray()]]: whether to return each record as an array.
*
* These options can be configured using methods of the same name. For example:
*
* ```php
* $customers = Customer::find()->with('orders')->asArray()->all();
* ```
*
* Relational query
* ----------------
*
* In relational context ActiveQuery represents a relation between two Active Record classes.
*
* Relational ActiveQuery instances are usually created by calling [[ActiveRecord::hasOne()]] and
* [[ActiveRecord::hasMany()]]. An Active Record class declares a relation by defining
* a getter method which calls one of the above methods and returns the created ActiveQuery object.
*
* A relation is specified by [[link]] which represents the association between columns
* of different tables; and the multiplicity of the relation is indicated by [[multiple]].
*
* If a relation involves a junction table, it may be specified by [[via()]].
* This methods may only be called in a relational context. Same is true for [[inverseOf()]], which
* marks a relation as inverse of another relation.
*
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class ActiveQuery extends Component implements ActiveQueryInterface
{
use QueryTrait;
use ActiveQueryTrait;
use ActiveRelationTrait;
/**
* @event Event an event that is triggered when the query is initialized via [[init()]].
*/
const EVENT_INIT = 'init';
/**
* Constructor.
* @param string $modelClass the model class associated with this query
* @param array $config configurations to be applied to the newly created query object
*/
public function __construct($modelClass, $config = [])
{
$this->modelClass = $modelClass;
parent::__construct($config);
}
/**
* Initializes the object.
* This method is called at the end of the constructor. The default implementation will trigger
* an [[EVENT_INIT]] event. If you override this method, make sure you call the parent implementation at the end
* to ensure triggering of the event.
*/
public function init()
{
parent::init();
$this->trigger(self::EVENT_INIT);
}
/**
* Executes the query and returns all results as an array.
* @param Connection $db the database connection used to execute the query.
* If this parameter is not given, the `db` application component will be used.
* @return array|ActiveRecord[] the query results. If the query results in nothing, an empty array will be returned.
*/
public function all($db = null)
{
if ($this->emulateExecution) {
return [];
}
// TODO add support for orderBy
$data = $this->executeScript($db, 'All');
if (empty($data)) {
return [];
}
$rows = [];
foreach ($data as $dataRow) {
$row = [];
$c = count($dataRow);
for ($i = 0; $i < $c;) {
$row[$dataRow[$i++]] = $dataRow[$i++];
}
$rows[] = $row;
}
if (empty($rows)) {
return [];
}
$models = $this->createModels($rows);
if (!empty($this->with)) {
$this->findWith($this->with, $models);
}
if ($this->indexBy !== null) {
$indexedModels = [];
if (is_string($this->indexBy)) {
foreach ($models as $model) {
$key = $model[$this->indexBy];
$indexedModels[$key] = $model;
}
} else {
foreach ($models as $model) {
$key = call_user_func($this->indexBy, $model);
$indexedModels[$key] = $model;
}
}
$models = $indexedModels;
}
if (!$this->asArray) {
foreach ($models as $model) {
$model->afterFind();
}
}
return $models;
}
/**
* Executes the query and returns a single row of result.
* @param Connection $db the database connection used to execute the query.
* If this parameter is not given, the `db` application component will be used.
* @return ActiveRecord|array|null a single row of query result. Depending on the setting of [[asArray]],
* the query result may be either an array or an ActiveRecord object. Null will be returned
* if the query results in nothing.
*/
public function one($db = null)
{
if ($this->emulateExecution) {
return null;
}
// TODO add support for orderBy
$data = $this->executeScript($db, 'One');
if (empty($data)) {
return null;
}
$row = [];
$c = count($data);
for ($i = 0; $i < $c;) {
$row[$data[$i++]] = $data[$i++];
}
if ($this->asArray) {
$model = $row;
} else {
/* @var $class ActiveRecord */
$class = $this->modelClass;
$model = $class::instantiate($row);
$class = get_class($model);
$class::populateRecord($model, $row);
}
if (!empty($this->with)) {
$models = [$model];
$this->findWith($this->with, $models);
$model = $models[0];
}
if (!$this->asArray) {
$model->afterFind();
}
return $model;
}
/**
* Returns the number of records.
* @param string $q the COUNT expression. This parameter is ignored by this implementation.
* @param Connection $db the database connection used to execute the query.
* If this parameter is not given, the `db` application component will be used.
* @return int number of records
*/
public function count($q = '*', $db = null)
{
if ($this->emulateExecution) {
return 0;
}
if ($this->where === null) {
/* @var $modelClass ActiveRecord */
$modelClass = $this->modelClass;
if ($db === null) {
$db = $modelClass::getDb();
}
return $db->executeCommand('LLEN', [$modelClass::keyPrefix()]);
} else {
return $this->executeScript($db, 'Count');
}
}
/**
* Returns a value indicating whether the query result contains any row of data.
* @param Connection $db the database connection used to execute the query.
* If this parameter is not given, the `db` application component will be used.
* @return bool whether the query result contains any row of data.
*/
public function exists($db = null)
{
if ($this->emulateExecution) {
return false;
}
return $this->one($db) !== null;
}
/**
* Executes the query and returns the first column of the result.
* @param string $column name of the column to select
* @param Connection $db the database connection used to execute the query.
* If this parameter is not given, the `db` application component will be used.
* @return array the first column of the query result. An empty array is returned if the query results in nothing.
*/
public function column($column, $db = null)
{
if ($this->emulateExecution) {
return [];
}
// TODO add support for orderBy
return $this->executeScript($db, 'Column', $column);
}
/**
* Returns the number of records.
* @param string $column the column to sum up
* @param Connection $db the database connection used to execute the query.
* If this parameter is not given, the `db` application component will be used.
* @return int number of records
*/
public function sum($column, $db = null)
{
if ($this->emulateExecution) {
return 0;
}
return $this->executeScript($db, 'Sum', $column);
}
/**
* Returns the average of the specified column values.
* @param string $column the column name or expression.
* Make sure you properly quote column names in the expression.
* @param Connection $db the database connection used to execute the query.
* If this parameter is not given, the `db` application component will be used.
* @return int the average of the specified column values.
*/
public function average($column, $db = null)
{
if ($this->emulateExecution) {
return 0;
}
return $this->executeScript($db, 'Average', $column);
}
/**
* Returns the minimum of the specified column values.
* @param string $column the column name or expression.
* Make sure you properly quote column names in the expression.
* @param Connection $db the database connection used to execute the query.
* If this parameter is not given, the `db` application component will be used.
* @return int the minimum of the specified column values.
*/
public function min($column, $db = null)
{
if ($this->emulateExecution) {
return null;
}
return $this->executeScript($db, 'Min', $column);
}
/**
* Returns the maximum of the specified column values.
* @param string $column the column name or expression.
* Make sure you properly quote column names in the expression.
* @param Connection $db the database connection used to execute the query.
* If this parameter is not given, the `db` application component will be used.
* @return int the maximum of the specified column values.
*/
public function max($column, $db = null)
{
if ($this->emulateExecution) {
return null;
}
return $this->executeScript($db, 'Max', $column);
}
/**
* Returns the query result as a scalar value.
* The value returned will be the specified attribute in the first record of the query results.
* @param string $attribute name of the attribute to select
* @param Connection $db the database connection used to execute the query.
* If this parameter is not given, the `db` application component will be used.
* @return string the value of the specified attribute in the first record of the query result.
* Null is returned if the query result is empty.
*/
public function scalar($attribute, $db = null)
{
if ($this->emulateExecution) {
return null;
}
$record = $this->one($db);
if ($record !== null) {
return $record->hasAttribute($attribute) ? $record->$attribute : null;
} else {
return null;
}
}
/**
* Executes a script created by [[LuaScriptBuilder]]
* @param Connection|null $db the database connection used to execute the query.
* If this parameter is not given, the `db` application component will be used.
* @param string $type the type of the script to generate
* @param string $columnName
* @throws NotSupportedException
* @return array|bool|null|string
*/
protected function executeScript($db, $type, $columnName = null)
{
if ($this->primaryModel !== null) {
// lazy loading
if ($this->via instanceof self) {
// via junction table
$viaModels = $this->via->findJunctionRows([$this->primaryModel]);
$this->filterByModels($viaModels);
} elseif (is_array($this->via)) {
// via relation
/* @var $viaQuery ActiveQuery */
list($viaName, $viaQuery) = $this->via;
if ($viaQuery->multiple) {
$viaModels = $viaQuery->all();
$this->primaryModel->populateRelation($viaName, $viaModels);
} else {
$model = $viaQuery->one();
$this->primaryModel->populateRelation($viaName, $model);
$viaModels = $model === null ? [] : [$model];
}
$this->filterByModels($viaModels);
} else {
$this->filterByModels([$this->primaryModel]);
}
}
/* @var $modelClass ActiveRecord */
$modelClass = $this->modelClass;
if ($db === null) {
$db = $modelClass::getDb();
}
// find by primary key if possible. This is much faster than scanning all records
if (is_array($this->where) && (
!isset($this->where[0]) && $modelClass::isPrimaryKey(array_keys($this->where)) ||
isset($this->where[0]) && $this->where[0] === 'in' && $modelClass::isPrimaryKey((array) $this->where[1])
)) {
return $this->findByPk($db, $type, $columnName);
}
$method = 'build' . $type;
$script = $db->getLuaScriptBuilder()->$method($this, $columnName);
return $db->executeCommand('EVAL', [$script, 0]);
}
/**
* Fetch by pk if possible as this is much faster
* @param Connection $db the database connection used to execute the query.
* If this parameter is not given, the `db` application component will be used.
* @param string $type the type of the script to generate
* @param string $columnName
* @return array|bool|null|string
* @throws \yii\base\InvalidParamException
* @throws \yii\base\NotSupportedException
*/
private function findByPk($db, $type, $columnName = null)
{
$needSort = !empty($this->orderBy) && in_array($type, ['All', 'One', 'Column']);
if ($needSort) {
if (!is_array($this->orderBy) || count($this->orderBy) > 1) {
throw new NotSupportedException(
'orderBy by multiple columns is not currently supported by redis ActiveRecord.'
);
}
$k = key($this->orderBy);
$v = $this->orderBy[$k];
if (is_numeric($k)) {
$orderColumn = $v;
$orderType = SORT_ASC;
} else {
$orderColumn = $k;
$orderType = $v;
}
}
if (isset($this->where[0]) && $this->where[0] === 'in') {
$pks = (array) $this->where[2];
} elseif (count($this->where) == 1) {
$pks = (array) reset($this->where);
} else {
foreach ($this->where as $values) {
if (is_array($values)) {
// TODO support composite IN for composite PK
throw new NotSupportedException('Find by composite PK is not supported by redis ActiveRecord.');
}
}
$pks = [$this->where];
}
/* @var $modelClass ActiveRecord */
$modelClass = $this->modelClass;
if ($type === 'Count') {
$start = 0;
$limit = null;
} else {
$start = ($this->offset === null || $this->offset < 0) ? 0 : $this->offset;
$limit = ($this->limit < 0) ? null : $this->limit;
}
$i = 0;
$data = [];
$orderArray = [];
foreach ($pks as $pk) {
if (++$i > $start && ($limit === null || $i <= $start + $limit)) {
$key = $modelClass::keyPrefix() . ':a:' . $modelClass::buildKey($pk);
$result = $db->executeCommand('HGETALL', [$key]);
if (!empty($result)) {
$data[] = $result;
if ($needSort) {
$orderArray[] = $db->executeCommand('HGET', [$key, $orderColumn]);
}
if ($type === 'One' && $this->orderBy === null) {
break;
}
}
}
}
if ($needSort) {
$resultData = [];
if ($orderType === SORT_ASC) {
asort($orderArray, SORT_NATURAL);
} else {
arsort($orderArray, SORT_NATURAL);
}
foreach ($orderArray as $orderKey => $orderItem) {
$resultData[] = $data[$orderKey];
}
$data = $resultData;
}
switch ($type) {
case 'All':
return $data;
case 'One':
return reset($data);
case 'Count':
return count($data);
case 'Column':
$column = [];
foreach ($data as $dataRow) {
$row = [];
$c = count($dataRow);
for ($i = 0; $i < $c;) {
$row[$dataRow[$i++]] = $dataRow[$i++];
}
$column[] = $row[$columnName];
}
return $column;
case 'Sum':
$sum = 0;
foreach ($data as $dataRow) {
$c = count($dataRow);
for ($i = 0; $i < $c;) {
if ($dataRow[$i++] == $columnName) {
$sum += $dataRow[$i];
break;
}
}
}
return $sum;
case 'Average':
$sum = 0;
$count = 0;
foreach ($data as $dataRow) {
$count++;
$c = count($dataRow);
for ($i = 0; $i < $c;) {
if ($dataRow[$i++] == $columnName) {
$sum += $dataRow[$i];
break;
}
}
}
return $sum / $count;
case 'Min':
$min = null;
foreach ($data as $dataRow) {
$c = count($dataRow);
for ($i = 0; $i < $c;) {
if ($dataRow[$i++] == $columnName && ($min == null || $dataRow[$i] < $min)) {
$min = $dataRow[$i];
break;
}
}
}
return $min;
case 'Max':
$max = null;
foreach ($data as $dataRow) {
$c = count($dataRow);
for ($i = 0; $i < $c;) {
if ($dataRow[$i++] == $columnName && ($max == null || $dataRow[$i] > $max)) {
$max = $dataRow[$i];
break;
}
}
}
return $max;
}
throw new InvalidParamException('Unknown fetch type: ' . $type);
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\redis;
use Yii;
use yii\base\InvalidConfigException;
use yii\db\BaseActiveRecord;
use yii\helpers\Inflector;
use yii\helpers\StringHelper;
/**
* ActiveRecord is the base class for classes representing relational data in terms of objects.
*
* This class implements the ActiveRecord pattern for the [redis](http://redis.io/) key-value store.
*
* For defining a record a subclass should at least implement the [[attributes()]] method to define
* attributes. A primary key can be defined via [[primaryKey()]] which defaults to `id` if not specified.
*
* The following is an example model called `Customer`:
*
* ```php
* class Customer extends \yii\redis\ActiveRecord
* {
* public function attributes()
* {
* return ['id', 'name', 'address', 'registration_date'];
* }
* }
* ```
*
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class ActiveRecord extends BaseActiveRecord
{
/**
* Returns the database connection used by this AR class.
* By default, the "redis" application component is used as the database connection.
* You may override this method if you want to use a different database connection.
* @return Connection the database connection used by this AR class.
*/
public static function getDb()
{
return Yii::$app->get('redis');
}
/**
* @inheritdoc
* @return ActiveQuery the newly created [[ActiveQuery]] instance.
*/
public static function find()
{
return Yii::createObject(ActiveQuery::className(), [get_called_class()]);
}
/**
* Returns the primary key name(s) for this AR class.
* This method should be overridden by child classes to define the primary key.
*
* Note that an array should be returned even when it is a single primary key.
*
* @return string[] the primary keys of this record.
*/
public static function primaryKey()
{
return ['id'];
}
/**
* Returns the list of all attribute names of the model.
* This method must be overridden by child classes to define available attributes.
* @return array list of attribute names.
*/
public function attributes()
{
throw new InvalidConfigException('The attributes() method of redis ActiveRecord has to be implemented by child classes.');
}
/**
* Declares prefix of the key that represents the keys that store this records in redis.
* By default this method returns the class name as the table name by calling [[Inflector::camel2id()]].
* For example, 'Customer' becomes 'customer', and 'OrderItem' becomes
* 'order_item'. You may override this method if you want different key naming.
* @return string the prefix to apply to all AR keys
*/
public static function keyPrefix()
{
return Inflector::camel2id(StringHelper::basename(get_called_class()), '_');
}
/**
* @inheritdoc
*/
public function insert($runValidation = true, $attributes = null)
{
if ($runValidation && !$this->validate($attributes)) {
return false;
}
if (!$this->beforeSave(true)) {
return false;
}
$db = static::getDb();
$values = $this->getDirtyAttributes($attributes);
$pk = [];
foreach ($this->primaryKey() as $key) {
$pk[$key] = $values[$key] = $this->getAttribute($key);
if ($pk[$key] === null) {
// use auto increment if pk is null
$pk[$key] = $values[$key] = $db->executeCommand('INCR', [static::keyPrefix() . ':s:' . $key]);
$this->setAttribute($key, $values[$key]);
} elseif (is_numeric($pk[$key])) {
// if pk is numeric update auto increment value
$currentPk = $db->executeCommand('GET', [static::keyPrefix() . ':s:' . $key]);
if ($pk[$key] > $currentPk) {
$db->executeCommand('SET', [static::keyPrefix() . ':s:' . $key, $pk[$key]]);
}
}
}
// save pk in a findall pool
$pk = static::buildKey($pk);
$db->executeCommand('RPUSH', [static::keyPrefix(), $pk]);
$key = static::keyPrefix() . ':a:' . $pk;
// save attributes
$setArgs = [$key];
foreach ($values as $attribute => $value) {
// only insert attributes that are not null
if ($value !== null) {
if (is_bool($value)) {
$value = (int) $value;
}
$setArgs[] = $attribute;
$setArgs[] = $value;
}
}
if (count($setArgs) > 1) {
$db->executeCommand('HMSET', $setArgs);
}
$changedAttributes = array_fill_keys(array_keys($values), null);
$this->setOldAttributes($values);
$this->afterSave(true, $changedAttributes);
return true;
}
/**
* Updates the whole table using the provided attribute values and conditions.
* For example, to change the status to be 1 for all customers whose status is 2:
*
* ~~~
* Customer::updateAll(['status' => 1], ['id' => 2]);
* ~~~
*
* @param array $attributes attribute values (name-value pairs) to be saved into the table
* @param array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
* Please refer to [[ActiveQuery::where()]] on how to specify this parameter.
* @return int the number of rows updated
*/
public static function updateAll($attributes, $condition = null)
{
if (empty($attributes)) {
return 0;
}
$db = static::getDb();
$n = 0;
foreach (self::fetchPks($condition) as $pk) {
$newPk = $pk;
$pk = static::buildKey($pk);
$key = static::keyPrefix() . ':a:' . $pk;
// save attributes
$delArgs = [$key];
$setArgs = [$key];
foreach ($attributes as $attribute => $value) {
if (isset($newPk[$attribute])) {
$newPk[$attribute] = $value;
}
if ($value !== null) {
if (is_bool($value)) {
$value = (int) $value;
}
$setArgs[] = $attribute;
$setArgs[] = $value;
} else {
$delArgs[] = $attribute;
}
}
$newPk = static::buildKey($newPk);
$newKey = static::keyPrefix() . ':a:' . $newPk;
// rename index if pk changed
if ($newPk != $pk) {
$db->executeCommand('MULTI');
if (count($setArgs) > 1) {
$db->executeCommand('HMSET', $setArgs);
}
if (count($delArgs) > 1) {
$db->executeCommand('HDEL', $delArgs);
}
$db->executeCommand('LINSERT', [static::keyPrefix(), 'AFTER', $pk, $newPk]);
$db->executeCommand('LREM', [static::keyPrefix(), 0, $pk]);
$db->executeCommand('RENAME', [$key, $newKey]);
$db->executeCommand('EXEC');
} else {
if (count($setArgs) > 1) {
$db->executeCommand('HMSET', $setArgs);
}
if (count($delArgs) > 1) {
$db->executeCommand('HDEL', $delArgs);
}
}
$n++;
}
return $n;
}
/**
* Updates the whole table using the provided counter changes and conditions.
* For example, to increment all customers' age by 1,
*
* ~~~
* Customer::updateAllCounters(['age' => 1]);
* ~~~
*
* @param array $counters the counters to be updated (attribute name => increment value).
* Use negative values if you want to decrement the counters.
* @param array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
* Please refer to [[ActiveQuery::where()]] on how to specify this parameter.
* @return int the number of rows updated
*/
public static function updateAllCounters($counters, $condition = null)
{
if (empty($counters)) {
return 0;
}
$db = static::getDb();
$n = 0;
foreach (self::fetchPks($condition) as $pk) {
$key = static::keyPrefix() . ':a:' . static::buildKey($pk);
foreach ($counters as $attribute => $value) {
$db->executeCommand('HINCRBY', [$key, $attribute, $value]);
}
$n++;
}
return $n;
}
/**
* Deletes rows in the table using the provided conditions.
* WARNING: If you do not specify any condition, this method will delete ALL rows in the table.
*
* For example, to delete all customers whose status is 3:
*
* ~~~
* Customer::deleteAll(['status' => 3]);
* ~~~
*
* @param array $condition the conditions that will be put in the WHERE part of the DELETE SQL.
* Please refer to [[ActiveQuery::where()]] on how to specify this parameter.
* @return int the number of rows deleted
*/
public static function deleteAll($condition = null)
{
$pks = self::fetchPks($condition);
if (empty($pks)) {
return 0;
}
$db = static::getDb();
$attributeKeys = [];
$db->executeCommand('MULTI');
foreach ($pks as $pk) {
$pk = static::buildKey($pk);
$db->executeCommand('LREM', [static::keyPrefix(), 0, $pk]);
$attributeKeys[] = static::keyPrefix() . ':a:' . $pk;
}
$db->executeCommand('DEL', $attributeKeys);
$result = $db->executeCommand('EXEC');
return end($result);
}
private static function fetchPks($condition)
{
$query = static::find();
$query->where($condition);
$records = $query->asArray()->all(); // TODO limit fetched columns to pk
$primaryKey = static::primaryKey();
$pks = [];
foreach ($records as $record) {
$pk = [];
foreach ($primaryKey as $key) {
$pk[$key] = $record[$key];
}
$pks[] = $pk;
}
return $pks;
}
/**
* Builds a normalized key from a given primary key value.
*
* @param mixed $key the key to be normalized
* @return string the generated key
*/
public static function buildKey($key)
{
if (is_numeric($key)) {
return $key;
} elseif (is_string($key)) {
return ctype_alnum($key) && StringHelper::byteLength($key) <= 32 ? $key : md5($key);
} elseif (is_array($key)) {
if (count($key) == 1) {
return self::buildKey(reset($key));
}
ksort($key); // ensure order is always the same
$isNumeric = true;
foreach ($key as $value) {
if (!is_numeric($value)) {
$isNumeric = false;
}
}
if ($isNumeric) {
return implode('-', $key);
}
}
return md5(json_encode($key, JSON_NUMERIC_CHECK));
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\redis;
use Yii;
use yii\di\Instance;
/**
* Redis Cache implements a cache application component based on [redis](http://redis.io/) key-value store.
*
* Redis Cache requires redis version 2.6.12 or higher to work properly.
*
* It needs to be configured with a redis [[Connection]] that is also configured as an application component.
* By default it will use the `redis` application component.
*
* See [[Cache]] manual for common cache operations that redis Cache supports.
*
* Unlike the [[Cache]], redis Cache allows the expire parameter of [[set]], [[add]], [[mset]] and [[madd]] to
* be a floating point number, so you may specify the time in milliseconds (e.g. 0.1 will be 100 milliseconds).
*
* To use redis Cache as the cache application component, configure the application as follows,
*
* ~~~
* [
* 'components' => [
* 'cache' => [
* 'class' => 'yii\redis\Cache',
* 'redis' => [
* 'hostname' => 'localhost',
* 'port' => 6379,
* 'database' => 0,
* ]
* ],
* ],
* ]
* ~~~
*
* Or if you have configured the redis [[Connection]] as an application component, the following is sufficient:
*
* ~~~
* [
* 'components' => [
* 'cache' => [
* 'class' => 'yii\redis\Cache',
* // 'redis' => 'redis' // id of the connection application component
* ],
* ],
* ]
* ~~~
*
* If you have multiple redis replicas (e.g. AWS ElasticCache Redis) you can configure the cache to
* send read operations to the replicas. If no replicas are configured, all operations will be performed on the
* master connection configured via the [[redis]] property.
*
* ~~~
* [
* 'components' => [
* 'cache' => [
* 'class' => 'yii\redis\Cache',
* 'enableReplicas' => true,
* 'replicas' => [
* // config for replica redis connections, (default class will be yii\redis\Connection if not provided)
* // you can optionally put in master as hostname as well, as all GET operation will use replicas
* 'redis',//id of Redis [[Connection]] Component
* ['hostname' => 'redis-slave-002.xyz.0001.apse1.cache.amazonaws.com'],
* ['hostname' => 'redis-slave-003.xyz.0001.apse1.cache.amazonaws.com'],
* ],
* ],
* ],
* ]
* ~~~
*
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class Cache extends \yii\caching\Cache
{
/**
* @var Connection|string|array the Redis [[Connection]] object or the application component ID of the Redis [[Connection]].
* This can also be an array that is used to create a redis [[Connection]] instance in case you do not want do configure
* redis connection as an application component.
* After the Cache object is created, if you want to change this property, you should only assign it
* with a Redis [[Connection]] object.
*/
public $redis = 'redis';
/**
* @var bool whether to enable read / get from redis replicas.
* @since 2.0.8
* @see $replicas
*/
public $enableReplicas = false;
/**
* @var array the Redis [[Connection]] configurations for redis replicas.
* Each entry is a class configuration, which will be used to instantiate a replica connection.
* The default class is [[Connection|yii\redis\Connection]]. You should at least provide a hostname.
*
* Configuration example:
*
* ```php
* 'replicas' => [
* 'redis',
* ['hostname' => 'redis-slave-002.xyz.0001.apse1.cache.amazonaws.com'],
* ['hostname' => 'redis-slave-003.xyz.0001.apse1.cache.amazonaws.com'],
* ],
* ```
*
* @since 2.0.8
* @see $enableReplicas
*/
public $replicas = [];
/**
* @var Connection currently active connection.
*/
private $_replica;
/**
* Initializes the redis Cache component.
* This method will initialize the [[redis]] property to make sure it refers to a valid redis connection.
* @throws \yii\base\InvalidConfigException if [[redis]] is invalid.
*/
public function init()
{
parent::init();
$this->redis = Instance::ensure($this->redis, Connection::className());
}
/**
* Checks whether a specified key exists in the cache.
* This can be faster than getting the value from the cache if the data is big.
* Note that this method does not check whether the dependency associated
* with the cached data, if there is any, has changed. So a call to [[get]]
* may return false while exists returns true.
* @param mixed $key a key identifying the cached value. This can be a simple string or
* a complex data structure consisting of factors representing the key.
* @return bool true if a value exists in cache, false if the value is not in the cache or expired.
*/
public function exists($key)
{
return (bool) $this->redis->executeCommand('EXISTS', [$this->buildKey($key)]);
}
/**
* @inheritdoc
*/
protected function getValue($key)
{
return $this->getReplica()->executeCommand('GET', [$key]);
}
/**
* @inheritdoc
*/
protected function getValues($keys)
{
$response = $this->getReplica()->executeCommand('MGET', $keys);
$result = [];
$i = 0;
foreach ($keys as $key) {
$result[$key] = $response[$i++];
}
return $result;
}
/**
* @inheritdoc
*/
protected function setValue($key, $value, $expire)
{
if ($expire == 0) {
return (bool) $this->redis->executeCommand('SET', [$key, $value]);
} else {
$expire = (int) ($expire * 1000);
return (bool) $this->redis->executeCommand('SET', [$key, $value, 'PX', $expire]);
}
}
/**
* @inheritdoc
*/
protected function setValues($data, $expire)
{
$args = [];
foreach ($data as $key => $value) {
$args[] = $key;
$args[] = $value;
}
$failedKeys = [];
if ($expire == 0) {
$this->redis->executeCommand('MSET', $args);
} else {
$expire = (int) ($expire * 1000);
$this->redis->executeCommand('MULTI');
$this->redis->executeCommand('MSET', $args);
$index = [];
foreach ($data as $key => $value) {
$this->redis->executeCommand('PEXPIRE', [$key, $expire]);
$index[] = $key;
}
$result = $this->redis->executeCommand('EXEC');
array_shift($result);
foreach ($result as $i => $r) {
if ($r != 1) {
$failedKeys[] = $index[$i];
}
}
}
return $failedKeys;
}
/**
* @inheritdoc
*/
protected function addValue($key, $value, $expire)
{
if ($expire == 0) {
return (bool) $this->redis->executeCommand('SET', [$key, $value, 'NX']);
} else {
$expire = (int) ($expire * 1000);
return (bool) $this->redis->executeCommand('SET', [$key, $value, 'PX', $expire, 'NX']);
}
}
/**
* @inheritdoc
*/
protected function deleteValue($key)
{
return (bool) $this->redis->executeCommand('DEL', [$key]);
}
/**
* @inheritdoc
*/
protected function flushValues()
{
return $this->redis->executeCommand('FLUSHDB');
}
/**
* It will return the current Replica Redis [[Connection]], and fall back to default [[redis]] [[Connection]]
* defined in this instance. Only used in getValue() and getValues().
* @since 2.0.8
* @return array|string|Connection
* @throws \yii\base\InvalidConfigException
*/
protected function getReplica()
{
if ($this->enableReplicas === false) {
return $this->redis;
}
if ($this->_replica !== null) {
return $this->_replica;
}
if (empty($this->replicas)) {
return $this->_replica = $this->redis;
}
$replicas = $this->replicas;
shuffle($replicas);
$config = array_shift($replicas);
$this->_replica = Instance::ensure($config, Connection::className());
return $this->_replica;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\redis;
use yii\base\Component;
use yii\db\Exception;
use yii\helpers\Inflector;
/**
* The redis connection class is used to establish a connection to a [redis](http://redis.io/) server.
*
* By default it assumes there is a redis server running on localhost at port 6379 and uses the database number 0.
*
* It is possible to connect to a redis server using [[hostname]] and [[port]] or using a [[unixSocket]].
*
* It also supports [the AUTH command](http://redis.io/commands/auth) of redis.
* When the server needs authentication, you can set the [[password]] property to
* authenticate with the server after connect.
*
* The execution of [redis commands](http://redis.io/commands) is possible with via [[executeCommand()]].
*
* @method mixed append($key, $value) Append a value to a key. <https://redis.io/commands/append>
* @method mixed auth($password) Authenticate to the server. <https://redis.io/commands/auth>
* @method mixed bgrewriteaof() Asynchronously rewrite the append-only file. <https://redis.io/commands/bgrewriteaof>
* @method mixed bgsave() Asynchronously save the dataset to disk. <https://redis.io/commands/bgsave>
* @method mixed bitcount($key, $start = null, $end = null) Count set bits in a string. <https://redis.io/commands/bitcount>
* @method mixed bitfield($key, ...$operations) Perform arbitrary bitfield integer operations on strings. <https://redis.io/commands/bitfield>
* @method mixed bitop($operation, $destkey, ...$keys) Perform bitwise operations between strings. <https://redis.io/commands/bitop>
* @method mixed bitpos($key, $bit, $start = null, $end = null) Find first bit set or clear in a string. <https://redis.io/commands/bitpos>
* @method mixed blpop(...$keys, $timeout) Remove and get the first element in a list, or block until one is available. <https://redis.io/commands/blpop>
* @method mixed brpop(...$keys, $timeout) Remove and get the last element in a list, or block until one is available. <https://redis.io/commands/brpop>
* @method mixed brpoplpush($source, $destination, $timeout) Pop a value from a list, push it to another list and return it; or block until one is available. <https://redis.io/commands/brpoplpush>
* @method mixed clientKill(...$filters) Kill the connection of a client. <https://redis.io/commands/client-kill>
* @method mixed clientList() Get the list of client connections. <https://redis.io/commands/client-list>
* @method mixed clientGetname() Get the current connection name. <https://redis.io/commands/client-getname>
* @method mixed clientPause($timeout) Stop processing commands from clients for some time. <https://redis.io/commands/client-pause>
* @method mixed clientReply($option) Instruct the server whether to reply to commands. <https://redis.io/commands/client-reply>
* @method mixed clientSetname($connectionName) Set the current connection name. <https://redis.io/commands/client-setname>
* @method mixed clusterAddslots(...$slots) Assign new hash slots to receiving node. <https://redis.io/commands/cluster-addslots>
* @method mixed clusterCountkeysinslot($slot) Return the number of local keys in the specified hash slot. <https://redis.io/commands/cluster-countkeysinslot>
* @method mixed clusterDelslots(...$slots) Set hash slots as unbound in receiving node. <https://redis.io/commands/cluster-delslots>
* @method mixed clusterFailover($option = null) Forces a slave to perform a manual failover of its master.. <https://redis.io/commands/cluster-failover>
* @method mixed clusterForget($nodeId) Remove a node from the nodes table. <https://redis.io/commands/cluster-forget>
* @method mixed clusterGetkeysinslot($slot, $count) Return local key names in the specified hash slot. <https://redis.io/commands/cluster-getkeysinslot>
* @method mixed clusterInfo() Provides info about Redis Cluster node state. <https://redis.io/commands/cluster-info>
* @method mixed clusterKeyslot($key) Returns the hash slot of the specified key. <https://redis.io/commands/cluster-keyslot>
* @method mixed clusterMeet($ip, $port) Force a node cluster to handshake with another node. <https://redis.io/commands/cluster-meet>
* @method mixed clusterNodes() Get Cluster config for the node. <https://redis.io/commands/cluster-nodes>
* @method mixed clusterReplicate($nodeId) Reconfigure a node as a slave of the specified master node. <https://redis.io/commands/cluster-replicate>
* @method mixed clusterReset($resetType = "SOFT") Reset a Redis Cluster node. <https://redis.io/commands/cluster-reset>
* @method mixed clusterSaveconfig() Forces the node to save cluster state on disk. <https://redis.io/commands/cluster-saveconfig>
* @method mixed clusterSetslot($slot, $type, $nodeid = null) Bind a hash slot to a specific node. <https://redis.io/commands/cluster-setslot>
* @method mixed clusterSlaves($nodeId) List slave nodes of the specified master node. <https://redis.io/commands/cluster-slaves>
* @method mixed clusterSlots() Get array of Cluster slot to node mappings. <https://redis.io/commands/cluster-slots>
* @method mixed command() Get array of Redis command details. <https://redis.io/commands/command>
* @method mixed commandCount() Get total number of Redis commands. <https://redis.io/commands/command-count>
* @method mixed commandGetkeys() Extract keys given a full Redis command. <https://redis.io/commands/command-getkeys>
* @method mixed commandInfo(...$commandNames) Get array of specific Redis command details. <https://redis.io/commands/command-info>
* @method mixed configGet($parameter) Get the value of a configuration parameter. <https://redis.io/commands/config-get>
* @method mixed configRewrite() Rewrite the configuration file with the in memory configuration. <https://redis.io/commands/config-rewrite>
* @method mixed configSet($parameter, $value) Set a configuration parameter to the given value. <https://redis.io/commands/config-set>
* @method mixed configResetstat() Reset the stats returned by INFO. <https://redis.io/commands/config-resetstat>
* @method mixed dbsize() Return the number of keys in the selected database. <https://redis.io/commands/dbsize>
* @method mixed debugObject($key) Get debugging information about a key. <https://redis.io/commands/debug-object>
* @method mixed debugSegfault() Make the server crash. <https://redis.io/commands/debug-segfault>
* @method mixed decr($key) Decrement the integer value of a key by one. <https://redis.io/commands/decr>
* @method mixed decrby($key, $decrement) Decrement the integer value of a key by the given number. <https://redis.io/commands/decrby>
* @method mixed del(...$keys) Delete a key. <https://redis.io/commands/del>
* @method mixed discard() Discard all commands issued after MULTI. <https://redis.io/commands/discard>
* @method mixed dump($key) Return a serialized version of the value stored at the specified key.. <https://redis.io/commands/dump>
* @method mixed echo($message) Echo the given string. <https://redis.io/commands/echo>
* @method mixed eval($script, $numkeys, ...$keys, ...$args) Execute a Lua script server side. <https://redis.io/commands/eval>
* @method mixed evalsha($sha1, $numkeys, ...$keys, ...$args) Execute a Lua script server side. <https://redis.io/commands/evalsha>
* @method mixed exec() Execute all commands issued after MULTI. <https://redis.io/commands/exec>
* @method mixed exists(...$keys) Determine if a key exists. <https://redis.io/commands/exists>
* @method mixed expire($key, $seconds) Set a key's time to live in seconds. <https://redis.io/commands/expire>
* @method mixed expireat($key, $timestamp) Set the expiration for a key as a UNIX timestamp. <https://redis.io/commands/expireat>
* @method mixed flushall($ASYNC = null) Remove all keys from all databases. <https://redis.io/commands/flushall>
* @method mixed flushdb($ASYNC = null) Remove all keys from the current database. <https://redis.io/commands/flushdb>
* @method mixed geoadd($key, $longitude, $latitude, $member, ...$more) Add one or more geospatial items in the geospatial index represented using a sorted set. <https://redis.io/commands/geoadd>
* @method mixed geohash($key, ...$members) Returns members of a geospatial index as standard geohash strings. <https://redis.io/commands/geohash>
* @method mixed geopos($key, ...$members) Returns longitude and latitude of members of a geospatial index. <https://redis.io/commands/geopos>
* @method mixed geodist($key, $member1, $member2, $unit = null) Returns the distance between two members of a geospatial index. <https://redis.io/commands/geodist>
* @method mixed georadius($key, $longitude, $latitude, $radius, $metric, ...$options) Query a sorted set representing a geospatial index to fetch members matching a given maximum distance from a point. <https://redis.io/commands/georadius>
* @method mixed georadiusbymember($key, $member, $radius, $metric, ...$options) Query a sorted set representing a geospatial index to fetch members matching a given maximum distance from a member. <https://redis.io/commands/georadiusbymember>
* @method mixed get($key) Get the value of a key. <https://redis.io/commands/get>
* @method mixed getbit($key, $offset) Returns the bit value at offset in the string value stored at key. <https://redis.io/commands/getbit>
* @method mixed getrange($key, $start, $end) Get a substring of the string stored at a key. <https://redis.io/commands/getrange>
* @method mixed getset($key, $value) Set the string value of a key and return its old value. <https://redis.io/commands/getset>
* @method mixed hdel($key, ...$fields) Delete one or more hash fields. <https://redis.io/commands/hdel>
* @method mixed hexists($key, $field) Determine if a hash field exists. <https://redis.io/commands/hexists>
* @method mixed hget($key, $field) Get the value of a hash field. <https://redis.io/commands/hget>
* @method mixed hgetall($key) Get all the fields and values in a hash. <https://redis.io/commands/hgetall>
* @method mixed hincrby($key, $field, $increment) Increment the integer value of a hash field by the given number. <https://redis.io/commands/hincrby>
* @method mixed hincrbyfloat($key, $field, $increment) Increment the float value of a hash field by the given amount. <https://redis.io/commands/hincrbyfloat>
* @method mixed hkeys($key) Get all the fields in a hash. <https://redis.io/commands/hkeys>
* @method mixed hlen($key) Get the number of fields in a hash. <https://redis.io/commands/hlen>
* @method mixed hmget($key, ...$fields) Get the values of all the given hash fields. <https://redis.io/commands/hmget>
* @method mixed hmset($key, $field, $value, ...$more) Set multiple hash fields to multiple values. <https://redis.io/commands/hmset>
* @method mixed hset($key, $field, $value) Set the string value of a hash field. <https://redis.io/commands/hset>
* @method mixed hsetnx($key, $field, $value) Set the value of a hash field, only if the field does not exist. <https://redis.io/commands/hsetnx>
* @method mixed hstrlen($key, $field) Get the length of the value of a hash field. <https://redis.io/commands/hstrlen>
* @method mixed hvals($key) Get all the values in a hash. <https://redis.io/commands/hvals>
* @method mixed incr($key) Increment the integer value of a key by one. <https://redis.io/commands/incr>
* @method mixed incrby($key, $increment) Increment the integer value of a key by the given amount. <https://redis.io/commands/incrby>
* @method mixed incrbyfloat($key, $increment) Increment the float value of a key by the given amount. <https://redis.io/commands/incrbyfloat>
* @method mixed info($section = null) Get information and statistics about the server. <https://redis.io/commands/info>
* @method mixed keys($pattern) Find all keys matching the given pattern. <https://redis.io/commands/keys>
* @method mixed lastsave() Get the UNIX time stamp of the last successful save to disk. <https://redis.io/commands/lastsave>
* @method mixed lindex($key, $index) Get an element from a list by its index. <https://redis.io/commands/lindex>
* @method mixed linsert($key, $where, $pivot, $value) Insert an element before or after another element in a list. <https://redis.io/commands/linsert>
* @method mixed llen($key) Get the length of a list. <https://redis.io/commands/llen>
* @method mixed lpop($key) Remove and get the first element in a list. <https://redis.io/commands/lpop>
* @method mixed lpush($key, ...$values) Prepend one or multiple values to a list. <https://redis.io/commands/lpush>
* @method mixed lpushx($key, $value) Prepend a value to a list, only if the list exists. <https://redis.io/commands/lpushx>
* @method mixed lrange($key, $start, $stop) Get a range of elements from a list. <https://redis.io/commands/lrange>
* @method mixed lrem($key, $count, $value) Remove elements from a list. <https://redis.io/commands/lrem>
* @method mixed lset($key, $index, $value) Set the value of an element in a list by its index. <https://redis.io/commands/lset>
* @method mixed ltrim($key, $start, $stop) Trim a list to the specified range. <https://redis.io/commands/ltrim>
* @method mixed mget(...$keys) Get the values of all the given keys. <https://redis.io/commands/mget>
* @method mixed migrate($host, $port, $key, $destinationDb, $timeout, ...$options) Atomically transfer a key from a Redis instance to another one.. <https://redis.io/commands/migrate>
* @method mixed monitor() Listen for all requests received by the server in real time. <https://redis.io/commands/monitor>
* @method mixed move($key, $db) Move a key to another database. <https://redis.io/commands/move>
* @method mixed mset(...$keyValuePairs) Set multiple keys to multiple values. <https://redis.io/commands/mset>
* @method mixed msetnx(...$keyValuePairs) Set multiple keys to multiple values, only if none of the keys exist. <https://redis.io/commands/msetnx>
* @method mixed multi() Mark the start of a transaction block. <https://redis.io/commands/multi>
* @method mixed object($subcommand, ...$argumentss) Inspect the internals of Redis objects. <https://redis.io/commands/object>
* @method mixed persist($key) Remove the expiration from a key. <https://redis.io/commands/persist>
* @method mixed pexpire($key, $milliseconds) Set a key's time to live in milliseconds. <https://redis.io/commands/pexpire>
* @method mixed pexpireat($key, $millisecondsTimestamp) Set the expiration for a key as a UNIX timestamp specified in milliseconds. <https://redis.io/commands/pexpireat>
* @method mixed pfadd($key, ...$elements) Adds the specified elements to the specified HyperLogLog.. <https://redis.io/commands/pfadd>
* @method mixed pfcount(...$keys) Return the approximated cardinality of the set(s) observed by the HyperLogLog at key(s).. <https://redis.io/commands/pfcount>
* @method mixed pfmerge($destkey, ...$sourcekeys) Merge N different HyperLogLogs into a single one.. <https://redis.io/commands/pfmerge>
* @method mixed ping($message = null) Ping the server. <https://redis.io/commands/ping>
* @method mixed psetex($key, $milliseconds, $value) Set the value and expiration in milliseconds of a key. <https://redis.io/commands/psetex>
* @method mixed psubscribe(...$patterns) Listen for messages published to channels matching the given patterns. <https://redis.io/commands/psubscribe>
* @method mixed pubsub($subcommand, ...$arguments) Inspect the state of the Pub/Sub subsystem. <https://redis.io/commands/pubsub>
* @method mixed pttl($key) Get the time to live for a key in milliseconds. <https://redis.io/commands/pttl>
* @method mixed publish($channel, $message) Post a message to a channel. <https://redis.io/commands/publish>
* @method mixed punsubscribe(...$patterns) Stop listening for messages posted to channels matching the given patterns. <https://redis.io/commands/punsubscribe>
* @method mixed quit() Close the connection. <https://redis.io/commands/quit>
* @method mixed randomkey() Return a random key from the keyspace. <https://redis.io/commands/randomkey>
* @method mixed readonly() Enables read queries for a connection to a cluster slave node. <https://redis.io/commands/readonly>
* @method mixed readwrite() Disables read queries for a connection to a cluster slave node. <https://redis.io/commands/readwrite>
* @method mixed rename($key, $newkey) Rename a key. <https://redis.io/commands/rename>
* @method mixed renamenx($key, $newkey) Rename a key, only if the new key does not exist. <https://redis.io/commands/renamenx>
* @method mixed restore($key, $ttl, $serializedValue, $REPLACE = null) Create a key using the provided serialized value, previously obtained using DUMP.. <https://redis.io/commands/restore>
* @method mixed role() Return the role of the instance in the context of replication. <https://redis.io/commands/role>
* @method mixed rpop($key) Remove and get the last element in a list. <https://redis.io/commands/rpop>
* @method mixed rpoplpush($source, $destination) Remove the last element in a list, prepend it to another list and return it. <https://redis.io/commands/rpoplpush>
* @method mixed rpush($key, ...$values) Append one or multiple values to a list. <https://redis.io/commands/rpush>
* @method mixed rpushx($key, $value) Append a value to a list, only if the list exists. <https://redis.io/commands/rpushx>
* @method mixed sadd($key, ...$members) Add one or more members to a set. <https://redis.io/commands/sadd>
* @method mixed save() Synchronously save the dataset to disk. <https://redis.io/commands/save>
* @method mixed scard($key) Get the number of members in a set. <https://redis.io/commands/scard>
* @method mixed scriptDebug($option) Set the debug mode for executed scripts.. <https://redis.io/commands/script-debug>
* @method mixed scriptExists(...$sha1s) Check existence of scripts in the script cache.. <https://redis.io/commands/script-exists>
* @method mixed scriptFlush() Remove all the scripts from the script cache.. <https://redis.io/commands/script-flush>
* @method mixed scriptKill() Kill the script currently in execution.. <https://redis.io/commands/script-kill>
* @method mixed scriptLoad($script) Load the specified Lua script into the script cache.. <https://redis.io/commands/script-load>
* @method mixed sdiff(...$keys) Subtract multiple sets. <https://redis.io/commands/sdiff>
* @method mixed sdiffstore($destination, ...$keys) Subtract multiple sets and store the resulting set in a key. <https://redis.io/commands/sdiffstore>
* @method mixed select($index) Change the selected database for the current connection. <https://redis.io/commands/select>
* @method mixed set($key, $value, ...$options) Set the string value of a key. <https://redis.io/commands/set>
* @method mixed setbit($key, $offset, $value) Sets or clears the bit at offset in the string value stored at key. <https://redis.io/commands/setbit>
* @method mixed setex($key, $seconds, $value) Set the value and expiration of a key. <https://redis.io/commands/setex>
* @method mixed setnx($key, $value) Set the value of a key, only if the key does not exist. <https://redis.io/commands/setnx>
* @method mixed setrange($key, $offset, $value) Overwrite part of a string at key starting at the specified offset. <https://redis.io/commands/setrange>
* @method mixed shutdown($saveOption = null) Synchronously save the dataset to disk and then shut down the server. <https://redis.io/commands/shutdown>
* @method mixed sinter(...$keys) Intersect multiple sets. <https://redis.io/commands/sinter>
* @method mixed sinterstore($destination, ...$keys) Intersect multiple sets and store the resulting set in a key. <https://redis.io/commands/sinterstore>
* @method mixed sismember($key, $member) Determine if a given value is a member of a set. <https://redis.io/commands/sismember>
* @method mixed slaveof($host, $port) Make the server a slave of another instance, or promote it as master. <https://redis.io/commands/slaveof>
* @method mixed slowlog($subcommand, $argument = null) Manages the Redis slow queries log. <https://redis.io/commands/slowlog>
* @method mixed smembers($key) Get all the members in a set. <https://redis.io/commands/smembers>
* @method mixed smove($source, $destination, $member) Move a member from one set to another. <https://redis.io/commands/smove>
* @method mixed sort($key, ...$options) Sort the elements in a list, set or sorted set. <https://redis.io/commands/sort>
* @method mixed spop($key, $count = null) Remove and return one or multiple random members from a set. <https://redis.io/commands/spop>
* @method mixed srandmember($key, $count = null) Get one or multiple random members from a set. <https://redis.io/commands/srandmember>
* @method mixed srem($key, ...$members) Remove one or more members from a set. <https://redis.io/commands/srem>
* @method mixed strlen($key) Get the length of the value stored in a key. <https://redis.io/commands/strlen>
* @method mixed subscribe(...$channels) Listen for messages published to the given channels. <https://redis.io/commands/subscribe>
* @method mixed sunion(...$keys) Add multiple sets. <https://redis.io/commands/sunion>
* @method mixed sunionstore($destination, ...$keys) Add multiple sets and store the resulting set in a key. <https://redis.io/commands/sunionstore>
* @method mixed swapdb($index, $index) Swaps two Redis databases. <https://redis.io/commands/swapdb>
* @method mixed sync() Internal command used for replication. <https://redis.io/commands/sync>
* @method mixed time() Return the current server time. <https://redis.io/commands/time>
* @method mixed touch(...$keys) Alters the last access time of a key(s). Returns the number of existing keys specified.. <https://redis.io/commands/touch>
* @method mixed ttl($key) Get the time to live for a key. <https://redis.io/commands/ttl>
* @method mixed type($key) Determine the type stored at key. <https://redis.io/commands/type>
* @method mixed unsubscribe(...$channels) Stop listening for messages posted to the given channels. <https://redis.io/commands/unsubscribe>
* @method mixed unlink(...$keys) Delete a key asynchronously in another thread. Otherwise it is just as DEL, but non blocking.. <https://redis.io/commands/unlink>
* @method mixed unwatch() Forget about all watched keys. <https://redis.io/commands/unwatch>
* @method mixed wait($numslaves, $timeout) Wait for the synchronous replication of all the write commands sent in the context of the current connection. <https://redis.io/commands/wait>
* @method mixed watch(...$keys) Watch the given keys to determine execution of the MULTI/EXEC block. <https://redis.io/commands/watch>
* @method mixed zadd($key, ...$options) Add one or more members to a sorted set, or update its score if it already exists. <https://redis.io/commands/zadd>
* @method mixed zcard($key) Get the number of members in a sorted set. <https://redis.io/commands/zcard>
* @method mixed zcount($key, $min, $max) Count the members in a sorted set with scores within the given values. <https://redis.io/commands/zcount>
* @method mixed zincrby($key, $increment, $member) Increment the score of a member in a sorted set. <https://redis.io/commands/zincrby>
* @method mixed zinterstore($destination, $numkeys, $key, ...$options) Intersect multiple sorted sets and store the resulting sorted set in a new key. <https://redis.io/commands/zinterstore>
* @method mixed zlexcount($key, $min, $max) Count the number of members in a sorted set between a given lexicographical range. <https://redis.io/commands/zlexcount>
* @method mixed zrange($key, $start, $stop, $WITHSCORES = null) Return a range of members in a sorted set, by index. <https://redis.io/commands/zrange>
* @method mixed zrangebylex($key, $min, $max, $LIMIT = null, $offset = null, $count = null) Return a range of members in a sorted set, by lexicographical range. <https://redis.io/commands/zrangebylex>
* @method mixed zrevrangebylex($key, $max, $min, $LIMIT = null, $offset = null, $count = null) Return a range of members in a sorted set, by lexicographical range, ordered from higher to lower strings.. <https://redis.io/commands/zrevrangebylex>
* @method mixed zrangebyscore($key, $min, $max, $WITHSCORES = null, $LIMIT = null, $offset = null, $count = null) Return a range of members in a sorted set, by score. <https://redis.io/commands/zrangebyscore>
* @method mixed zrank($key, $member) Determine the index of a member in a sorted set. <https://redis.io/commands/zrank>
* @method mixed zrem($key, ...$members) Remove one or more members from a sorted set. <https://redis.io/commands/zrem>
* @method mixed zremrangebylex($key, $min, $max) Remove all members in a sorted set between the given lexicographical range. <https://redis.io/commands/zremrangebylex>
* @method mixed zremrangebyrank($key, $start, $stop) Remove all members in a sorted set within the given indexes. <https://redis.io/commands/zremrangebyrank>
* @method mixed zremrangebyscore($key, $min, $max) Remove all members in a sorted set within the given scores. <https://redis.io/commands/zremrangebyscore>
* @method mixed zrevrange($key, $start, $stop, $WITHSCORES = null) Return a range of members in a sorted set, by index, with scores ordered from high to low. <https://redis.io/commands/zrevrange>
* @method mixed zrevrangebyscore($key, $max, $min, $WITHSCORES = null, $LIMIT = null, $offset = null, $count = null) Return a range of members in a sorted set, by score, with scores ordered from high to low. <https://redis.io/commands/zrevrangebyscore>
* @method mixed zrevrank($key, $member) Determine the index of a member in a sorted set, with scores ordered from high to low. <https://redis.io/commands/zrevrank>
* @method mixed zscore($key, $member) Get the score associated with the given member in a sorted set. <https://redis.io/commands/zscore>
* @method mixed zunionstore($destination, $numkeys, $key, ...$options) Add multiple sorted sets and store the resulting sorted set in a new key. <https://redis.io/commands/zunionstore>
* @method mixed scan($cursor, $MATCH = null, $pattern = null, $COUNT = null, $count = null) Incrementally iterate the keys space. <https://redis.io/commands/scan>
* @method mixed sscan($key, $cursor, $MATCH = null, $pattern = null, $COUNT = null, $count = null) Incrementally iterate Set elements. <https://redis.io/commands/sscan>
* @method mixed hscan($key, $cursor, $MATCH = null, $pattern = null, $COUNT = null, $count = null) Incrementally iterate hash fields and associated values. <https://redis.io/commands/hscan>
* @method mixed zscan($key, $cursor, $MATCH = null, $pattern = null, $COUNT = null, $count = null) Incrementally iterate sorted sets elements and associated scores. <https://redis.io/commands/zscan>
*
* @property string $driverName Name of the DB driver. This property is read-only.
* @property bool $isActive Whether the DB connection is established. This property is read-only.
* @property LuaScriptBuilder $luaScriptBuilder This property is read-only.
*
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class Connection extends Component
{
/**
* @event Event an event that is triggered after a DB connection is established
*/
const EVENT_AFTER_OPEN = 'afterOpen';
/**
* @var string the hostname or ip address to use for connecting to the redis server. Defaults to 'localhost'.
* If [[unixSocket]] is specified, hostname and [[port]] will be ignored.
*/
public $hostname = 'localhost';
/**
* @var integer the port to use for connecting to the redis server. Default port is 6379.
* If [[unixSocket]] is specified, [[hostname]] and port will be ignored.
*/
public $port = 6379;
/**
* @var string the unix socket path (e.g. `/var/run/redis/redis.sock`) to use for connecting to the redis server.
* This can be used instead of [[hostname]] and [[port]] to connect to the server using a unix socket.
* If a unix socket path is specified, [[hostname]] and [[port]] will be ignored.
* @since 2.0.1
*/
public $unixSocket;
/**
* @var string the password for establishing DB connection. Defaults to null meaning no AUTH command is sent.
* See http://redis.io/commands/auth
*/
public $password;
/**
* @var integer the redis database to use. This is an integer value starting from 0. Defaults to 0.
* Since version 2.0.6 you can disable the SELECT command sent after connection by setting this property to `null`.
*/
public $database = 0;
/**
* @var float timeout to use for connection to redis. If not set the timeout set in php.ini will be used: `ini_get("default_socket_timeout")`.
*/
public $connectionTimeout = null;
/**
* @var float timeout to use for redis socket when reading and writing data. If not set the php default value will be used.
*/
public $dataTimeout = null;
/**
* @var integer Bitmask field which may be set to any combination of connection flags passed to [stream_socket_client()](http://php.net/manual/en/function.stream-socket-client.php).
* Currently the select of connection flags is limited to `STREAM_CLIENT_CONNECT` (default), `STREAM_CLIENT_ASYNC_CONNECT` and `STREAM_CLIENT_PERSISTENT`.
*
* > Warning: `STREAM_CLIENT_PERSISTENT` will make PHP reuse connections to the same server. If you are using multiple
* > connection objects to refer to different redis [[$database|databases]] on the same [[port]], redis commands may
* > get executed on the wrong database. `STREAM_CLIENT_PERSISTENT` is only safe to use if you use only one database.
* >
* > You may still use persistent connections in this case when disambiguating ports as described
* > in [a comment on the PHP manual](http://php.net/manual/en/function.stream-socket-client.php#105393)
* > e.g. on the connection used for session storage, specify the port as:
* >
* > ```php
* > 'port' => '6379/session'
* > ```
*
* @see http://php.net/manual/en/function.stream-socket-client.php
* @since 2.0.5
*/
public $socketClientFlags = STREAM_CLIENT_CONNECT;
/**
* @var integer The number of times a command execution should be retried when a connection failure occurs.
* This is used in [[executeCommand()]] when a [[SocketException]] is thrown.
* Defaults to 0 meaning no retries on failure.
* @since 2.0.7
*/
public $retries = 0;
/**
* @var array List of available redis commands.
* @see http://redis.io/commands
*/
public $redisCommands = [
'APPEND', // Append a value to a key
'AUTH', // Authenticate to the server
'BGREWRITEAOF', // Asynchronously rewrite the append-only file
'BGSAVE', // Asynchronously save the dataset to disk
'BITCOUNT', // Count set bits in a string
'BITFIELD', // Perform arbitrary bitfield integer operations on strings
'BITOP', // Perform bitwise operations between strings
'BITPOS', // Find first bit set or clear in a string
'BLPOP', // Remove and get the first element in a list, or block until one is available
'BRPOP', // Remove and get the last element in a list, or block until one is available
'BRPOPLPUSH', // Pop a value from a list, push it to another list and return it; or block until one is available
'CLIENT KILL', // Kill the connection of a client
'CLIENT LIST', // Get the list of client connections
'CLIENT GETNAME', // Get the current connection name
'CLIENT PAUSE', // Stop processing commands from clients for some time
'CLIENT REPLY', // Instruct the server whether to reply to commands
'CLIENT SETNAME', // Set the current connection name
'CLUSTER ADDSLOTS', // Assign new hash slots to receiving node
'CLUSTER COUNTKEYSINSLOT', // Return the number of local keys in the specified hash slot
'CLUSTER DELSLOTS', // Set hash slots as unbound in receiving node
'CLUSTER FAILOVER', // Forces a slave to perform a manual failover of its master.
'CLUSTER FORGET', // Remove a node from the nodes table
'CLUSTER GETKEYSINSLOT', // Return local key names in the specified hash slot
'CLUSTER INFO', // Provides info about Redis Cluster node state
'CLUSTER KEYSLOT', // Returns the hash slot of the specified key
'CLUSTER MEET', // Force a node cluster to handshake with another node
'CLUSTER NODES', // Get Cluster config for the node
'CLUSTER REPLICATE', // Reconfigure a node as a slave of the specified master node
'CLUSTER RESET', // Reset a Redis Cluster node
'CLUSTER SAVECONFIG', // Forces the node to save cluster state on disk
'CLUSTER SETSLOT', // Bind a hash slot to a specific node
'CLUSTER SLAVES', // List slave nodes of the specified master node
'CLUSTER SLOTS', // Get array of Cluster slot to node mappings
'COMMAND', // Get array of Redis command details
'COMMAND COUNT', // Get total number of Redis commands
'COMMAND GETKEYS', // Extract keys given a full Redis command
'COMMAND INFO', // Get array of specific Redis command details
'CONFIG GET', // Get the value of a configuration parameter
'CONFIG REWRITE', // Rewrite the configuration file with the in memory configuration
'CONFIG SET', // Set a configuration parameter to the given value
'CONFIG RESETSTAT', // Reset the stats returned by INFO
'DBSIZE', // Return the number of keys in the selected database
'DEBUG OBJECT', // Get debugging information about a key
'DEBUG SEGFAULT', // Make the server crash
'DECR', // Decrement the integer value of a key by one
'DECRBY', // Decrement the integer value of a key by the given number
'DEL', // Delete a key
'DISCARD', // Discard all commands issued after MULTI
'DUMP', // Return a serialized version of the value stored at the specified key.
'ECHO', // Echo the given string
'EVAL', // Execute a Lua script server side
'EVALSHA', // Execute a Lua script server side
'EXEC', // Execute all commands issued after MULTI
'EXISTS', // Determine if a key exists
'EXPIRE', // Set a key's time to live in seconds
'EXPIREAT', // Set the expiration for a key as a UNIX timestamp
'FLUSHALL', // Remove all keys from all databases
'FLUSHDB', // Remove all keys from the current database
'GEOADD', // Add one or more geospatial items in the geospatial index represented using a sorted set
'GEOHASH', // Returns members of a geospatial index as standard geohash strings
'GEOPOS', // Returns longitude and latitude of members of a geospatial index
'GEODIST', // Returns the distance between two members of a geospatial index
'GEORADIUS', // Query a sorted set representing a geospatial index to fetch members matching a given maximum distance from a point
'GEORADIUSBYMEMBER', // Query a sorted set representing a geospatial index to fetch members matching a given maximum distance from a member
'GET', // Get the value of a key
'GETBIT', // Returns the bit value at offset in the string value stored at key
'GETRANGE', // Get a substring of the string stored at a key
'GETSET', // Set the string value of a key and return its old value
'HDEL', // Delete one or more hash fields
'HEXISTS', // Determine if a hash field exists
'HGET', // Get the value of a hash field
'HGETALL', // Get all the fields and values in a hash
'HINCRBY', // Increment the integer value of a hash field by the given number
'HINCRBYFLOAT', // Increment the float value of a hash field by the given amount
'HKEYS', // Get all the fields in a hash
'HLEN', // Get the number of fields in a hash
'HMGET', // Get the values of all the given hash fields
'HMSET', // Set multiple hash fields to multiple values
'HSET', // Set the string value of a hash field
'HSETNX', // Set the value of a hash field, only if the field does not exist
'HSTRLEN', // Get the length of the value of a hash field
'HVALS', // Get all the values in a hash
'INCR', // Increment the integer value of a key by one
'INCRBY', // Increment the integer value of a key by the given amount
'INCRBYFLOAT', // Increment the float value of a key by the given amount
'INFO', // Get information and statistics about the server
'KEYS', // Find all keys matching the given pattern
'LASTSAVE', // Get the UNIX time stamp of the last successful save to disk
'LINDEX', // Get an element from a list by its index
'LINSERT', // Insert an element before or after another element in a list
'LLEN', // Get the length of a list
'LPOP', // Remove and get the first element in a list
'LPUSH', // Prepend one or multiple values to a list
'LPUSHX', // Prepend a value to a list, only if the list exists
'LRANGE', // Get a range of elements from a list
'LREM', // Remove elements from a list
'LSET', // Set the value of an element in a list by its index
'LTRIM', // Trim a list to the specified range
'MGET', // Get the values of all the given keys
'MIGRATE', // Atomically transfer a key from a Redis instance to another one.
'MONITOR', // Listen for all requests received by the server in real time
'MOVE', // Move a key to another database
'MSET', // Set multiple keys to multiple values
'MSETNX', // Set multiple keys to multiple values, only if none of the keys exist
'MULTI', // Mark the start of a transaction block
'OBJECT', // Inspect the internals of Redis objects
'PERSIST', // Remove the expiration from a key
'PEXPIRE', // Set a key's time to live in milliseconds
'PEXPIREAT', // Set the expiration for a key as a UNIX timestamp specified in milliseconds
'PFADD', // Adds the specified elements to the specified HyperLogLog.
'PFCOUNT', // Return the approximated cardinality of the set(s) observed by the HyperLogLog at key(s).
'PFMERGE', // Merge N different HyperLogLogs into a single one.
'PING', // Ping the server
'PSETEX', // Set the value and expiration in milliseconds of a key
'PSUBSCRIBE', // Listen for messages published to channels matching the given patterns
'PUBSUB', // Inspect the state of the Pub/Sub subsystem
'PTTL', // Get the time to live for a key in milliseconds
'PUBLISH', // Post a message to a channel
'PUNSUBSCRIBE', // Stop listening for messages posted to channels matching the given patterns
'QUIT', // Close the connection
'RANDOMKEY', // Return a random key from the keyspace
'READONLY', // Enables read queries for a connection to a cluster slave node
'READWRITE', // Disables read queries for a connection to a cluster slave node
'RENAME', // Rename a key
'RENAMENX', // Rename a key, only if the new key does not exist
'RESTORE', // Create a key using the provided serialized value, previously obtained using DUMP.
'ROLE', // Return the role of the instance in the context of replication
'RPOP', // Remove and get the last element in a list
'RPOPLPUSH', // Remove the last element in a list, prepend it to another list and return it
'RPUSH', // Append one or multiple values to a list
'RPUSHX', // Append a value to a list, only if the list exists
'SADD', // Add one or more members to a set
'SAVE', // Synchronously save the dataset to disk
'SCARD', // Get the number of members in a set
'SCRIPT DEBUG', // Set the debug mode for executed scripts.
'SCRIPT EXISTS', // Check existence of scripts in the script cache.
'SCRIPT FLUSH', // Remove all the scripts from the script cache.
'SCRIPT KILL', // Kill the script currently in execution.
'SCRIPT LOAD', // Load the specified Lua script into the script cache.
'SDIFF', // Subtract multiple sets
'SDIFFSTORE', // Subtract multiple sets and store the resulting set in a key
'SELECT', // Change the selected database for the current connection
'SET', // Set the string value of a key
'SETBIT', // Sets or clears the bit at offset in the string value stored at key
'SETEX', // Set the value and expiration of a key
'SETNX', // Set the value of a key, only if the key does not exist
'SETRANGE', // Overwrite part of a string at key starting at the specified offset
'SHUTDOWN', // Synchronously save the dataset to disk and then shut down the server
'SINTER', // Intersect multiple sets
'SINTERSTORE', // Intersect multiple sets and store the resulting set in a key
'SISMEMBER', // Determine if a given value is a member of a set
'SLAVEOF', // Make the server a slave of another instance, or promote it as master
'SLOWLOG', // Manages the Redis slow queries log
'SMEMBERS', // Get all the members in a set
'SMOVE', // Move a member from one set to another
'SORT', // Sort the elements in a list, set or sorted set
'SPOP', // Remove and return one or multiple random members from a set
'SRANDMEMBER', // Get one or multiple random members from a set
'SREM', // Remove one or more members from a set
'STRLEN', // Get the length of the value stored in a key
'SUBSCRIBE', // Listen for messages published to the given channels
'SUNION', // Add multiple sets
'SUNIONSTORE', // Add multiple sets and store the resulting set in a key
'SWAPDB', // Swaps two Redis databases
'SYNC', // Internal command used for replication
'TIME', // Return the current server time
'TOUCH', // Alters the last access time of a key(s). Returns the number of existing keys specified.
'TTL', // Get the time to live for a key
'TYPE', // Determine the type stored at key
'UNSUBSCRIBE', // Stop listening for messages posted to the given channels
'UNLINK', // Delete a key asynchronously in another thread. Otherwise it is just as DEL, but non blocking.
'UNWATCH', // Forget about all watched keys
'WAIT', // Wait for the synchronous replication of all the write commands sent in the context of the current connection
'WATCH', // Watch the given keys to determine execution of the MULTI/EXEC block
'ZADD', // Add one or more members to a sorted set, or update its score if it already exists
'ZCARD', // Get the number of members in a sorted set
'ZCOUNT', // Count the members in a sorted set with scores within the given values
'ZINCRBY', // Increment the score of a member in a sorted set
'ZINTERSTORE', // Intersect multiple sorted sets and store the resulting sorted set in a new key
'ZLEXCOUNT', // Count the number of members in a sorted set between a given lexicographical range
'ZRANGE', // Return a range of members in a sorted set, by index
'ZRANGEBYLEX', // Return a range of members in a sorted set, by lexicographical range
'ZREVRANGEBYLEX', // Return a range of members in a sorted set, by lexicographical range, ordered from higher to lower strings.
'ZRANGEBYSCORE', // Return a range of members in a sorted set, by score
'ZRANK', // Determine the index of a member in a sorted set
'ZREM', // Remove one or more members from a sorted set
'ZREMRANGEBYLEX', // Remove all members in a sorted set between the given lexicographical range
'ZREMRANGEBYRANK', // Remove all members in a sorted set within the given indexes
'ZREMRANGEBYSCORE', // Remove all members in a sorted set within the given scores
'ZREVRANGE', // Return a range of members in a sorted set, by index, with scores ordered from high to low
'ZREVRANGEBYSCORE', // Return a range of members in a sorted set, by score, with scores ordered from high to low
'ZREVRANK', // Determine the index of a member in a sorted set, with scores ordered from high to low
'ZSCORE', // Get the score associated with the given member in a sorted set
'ZUNIONSTORE', // Add multiple sorted sets and store the resulting sorted set in a new key
'SCAN', // Incrementally iterate the keys space
'SSCAN', // Incrementally iterate Set elements
'HSCAN', // Incrementally iterate hash fields and associated values
'ZSCAN', // Incrementally iterate sorted sets elements and associated scores
];
/**
* @var resource redis socket connection
*/
private $_socket = false;
/**
* Closes the connection when this component is being serialized.
* @return array
*/
public function __sleep()
{
$this->close();
return array_keys(get_object_vars($this));
}
/**
* Returns a value indicating whether the DB connection is established.
* @return bool whether the DB connection is established
*/
public function getIsActive()
{
return $this->_socket !== false;
}
/**
* Establishes a DB connection.
* It does nothing if a DB connection has already been established.
* @throws Exception if connection fails
*/
public function open()
{
if ($this->_socket !== false) {
return;
}
$connection = ($this->unixSocket ?: $this->hostname . ':' . $this->port) . ', database=' . $this->database;
\Yii::trace('Opening redis DB connection: ' . $connection, __METHOD__);
$this->_socket = @stream_socket_client(
$this->unixSocket ? 'unix://' . $this->unixSocket : 'tcp://' . $this->hostname . ':' . $this->port,
$errorNumber,
$errorDescription,
$this->connectionTimeout ? $this->connectionTimeout : ini_get('default_socket_timeout'),
$this->socketClientFlags
);
if ($this->_socket) {
if ($this->dataTimeout !== null) {
stream_set_timeout($this->_socket, $timeout = (int) $this->dataTimeout, (int) (($this->dataTimeout - $timeout) * 1000000));
}
if ($this->password !== null) {
$this->executeCommand('AUTH', [$this->password]);
}
if ($this->database !== null) {
$this->executeCommand('SELECT', [$this->database]);
}
$this->initConnection();
} else {
\Yii::error("Failed to open redis DB connection ($connection): $errorNumber - $errorDescription", __CLASS__);
$message = YII_DEBUG ? "Failed to open redis DB connection ($connection): $errorNumber - $errorDescription" : 'Failed to open DB connection.';
throw new Exception($message, $errorDescription, $errorNumber);
}
}
/**
* Closes the currently active DB connection.
* It does nothing if the connection is already closed.
*/
public function close()
{
if ($this->_socket !== false) {
$connection = ($this->unixSocket ?: $this->hostname . ':' . $this->port) . ', database=' . $this->database;
\Yii::trace('Closing DB connection: ' . $connection, __METHOD__);
try {
$this->executeCommand('QUIT');
} catch (SocketException $e) {
// ignore errors when quitting a closed connection
}
fclose($this->_socket);
$this->_socket = false;
}
}
/**
* Initializes the DB connection.
* This method is invoked right after the DB connection is established.
* The default implementation triggers an [[EVENT_AFTER_OPEN]] event.
*/
protected function initConnection()
{
$this->trigger(self::EVENT_AFTER_OPEN);
}
/**
* Returns the name of the DB driver for the current [[dsn]].
* @return string name of the DB driver
*/
public function getDriverName()
{
return 'redis';
}
/**
* @return LuaScriptBuilder
*/
public function getLuaScriptBuilder()
{
return new LuaScriptBuilder();
}
/**
* Allows issuing all supported commands via magic methods.
*
* ```php
* $redis->hmset('test_collection', 'key1', 'val1', 'key2', 'val2')
* ```
*
* @param string $name name of the missing method to execute
* @param array $params method call arguments
* @return mixed
*/
public function __call($name, $params)
{
$redisCommand = strtoupper(Inflector::camel2words($name, false));
if (in_array($redisCommand, $this->redisCommands)) {
return $this->executeCommand($redisCommand, $params);
} else {
return parent::__call($name, $params);
}
}
/**
* Executes a redis command.
* For a list of available commands and their parameters see http://redis.io/commands.
*
* The params array should contain the params separated by white space, e.g. to execute
* `SET mykey somevalue NX` call the following:
*
* ```php
* $redis->executeCommand('SET', ['mykey', 'somevalue', 'NX']);
* ```
*
* @param string $name the name of the command
* @param array $params list of parameters for the command
* @return array|bool|null|string Dependent on the executed command this method
* will return different data types:
*
* - `true` for commands that return "status reply" with the message `'OK'` or `'PONG'`.
* - `string` for commands that return "status reply" that does not have the message `OK` (since version 2.0.1).
* - `string` for commands that return "integer reply"
* as the value is in the range of a signed 64 bit integer.
* - `string` or `null` for commands that return "bulk reply".
* - `array` for commands that return "Multi-bulk replies".
*
* See [redis protocol description](http://redis.io/topics/protocol)
* for details on the mentioned reply types.
* @throws Exception for commands that return [error reply](http://redis.io/topics/protocol#error-reply).
*/
public function executeCommand($name, $params = [])
{
$this->open();
$params = array_merge(explode(' ', $name), $params);
$command = '*' . count($params) . "\r\n";
foreach ($params as $arg) {
$command .= '$' . mb_strlen($arg, '8bit') . "\r\n" . $arg . "\r\n";
}
\Yii::trace("Executing Redis Command: {$name}", __METHOD__);
if ($this->retries > 0) {
$tries = $this->retries;
while ($tries-- > 0) {
try {
return $this->sendCommandInternal($command, $params);
} catch (SocketException $e) {
\Yii::error($e, __METHOD__);
// backup retries, fail on commands that fail inside here
$retries = $this->retries;
$this->retries = 0;
$this->close();
$this->open();
$this->retries = $retries;
}
}
}
return $this->sendCommandInternal($command, $params);
}
/**
* Sends RAW command string to the server.
* @throws SocketException on connection error.
*/
private function sendCommandInternal($command, $params)
{
$written = @fwrite($this->_socket, $command);
if ($written === false) {
throw new SocketException("Failed to write to socket.\nRedis command was: " . $command);
}
if ($written !== ($len = mb_strlen($command, '8bit'))) {
throw new SocketException("Failed to write to socket. $written of $len bytes written.\nRedis command was: " . $command);
}
return $this->parseResponse(implode(' ', $params));
}
/**
* @param string $command
* @return mixed
* @throws Exception on error
*/
private function parseResponse($command)
{
if (($line = fgets($this->_socket)) === false) {
throw new SocketException("Failed to read from socket.\nRedis command was: " . $command);
}
$type = $line[0];
$line = mb_substr($line, 1, -2, '8bit');
switch ($type) {
case '+': // Status reply
if ($line === 'OK' || $line === 'PONG') {
return true;
} else {
return $line;
}
case '-': // Error reply
throw new Exception("Redis error: " . $line . "\nRedis command was: " . $command);
case ':': // Integer reply
// no cast to int as it is in the range of a signed 64 bit integer
return $line;
case '$': // Bulk replies
if ($line == '-1') {
return null;
}
$length = (int)$line + 2;
$data = '';
while ($length > 0) {
if (($block = fread($this->_socket, $length)) === false) {
throw new SocketException("Failed to read from socket.\nRedis command was: " . $command);
}
$data .= $block;
$length -= mb_strlen($block, '8bit');
}
return mb_substr($data, 0, -2, '8bit');
case '*': // Multi-bulk replies
$count = (int) $line;
$data = [];
for ($i = 0; $i < $count; $i++) {
$data[] = $this->parseResponse($command);
}
return $data;
default:
throw new Exception('Received illegal data from redis: ' . $line . "\nRedis command was: " . $command);
}
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\redis;
use yii\base\InvalidParamException;
use yii\base\NotSupportedException;
use yii\db\Exception;
use yii\db\Expression;
/**
* LuaScriptBuilder builds lua scripts used for retrieving data from redis.
*
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class LuaScriptBuilder extends \yii\base\BaseObject
{
/**
* Builds a Lua script for finding a list of records
* @param ActiveQuery $query the query used to build the script
* @return string
*/
public function buildAll($query)
{
/* @var $modelClass ActiveRecord */
$modelClass = $query->modelClass;
$key = $this->quoteValue($modelClass::keyPrefix() . ':a:');
return $this->build($query, "n=n+1 pks[n]=redis.call('HGETALL',$key .. pk)", 'pks');
}
/**
* Builds a Lua script for finding one record
* @param ActiveQuery $query the query used to build the script
* @return string
*/
public function buildOne($query)
{
/* @var $modelClass ActiveRecord */
$modelClass = $query->modelClass;
$key = $this->quoteValue($modelClass::keyPrefix() . ':a:');
return $this->build($query, "do return redis.call('HGETALL',$key .. pk) end", 'pks');
}
/**
* Builds a Lua script for finding a column
* @param ActiveQuery $query the query used to build the script
* @param string $column name of the column
* @return string
*/
public function buildColumn($query, $column)
{
// TODO add support for indexBy
/* @var $modelClass ActiveRecord */
$modelClass = $query->modelClass;
$key = $this->quoteValue($modelClass::keyPrefix() . ':a:');
return $this->build($query, "n=n+1 pks[n]=redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ")", 'pks');
}
/**
* Builds a Lua script for getting count of records
* @param ActiveQuery $query the query used to build the script
* @return string
*/
public function buildCount($query)
{
return $this->build($query, 'n=n+1', 'n');
}
/**
* Builds a Lua script for finding the sum of a column
* @param ActiveQuery $query the query used to build the script
* @param string $column name of the column
* @return string
*/
public function buildSum($query, $column)
{
/* @var $modelClass ActiveRecord */
$modelClass = $query->modelClass;
$key = $this->quoteValue($modelClass::keyPrefix() . ':a:');
return $this->build($query, "n=n+redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ")", 'n');
}
/**
* Builds a Lua script for finding the average of a column
* @param ActiveQuery $query the query used to build the script
* @param string $column name of the column
* @return string
*/
public function buildAverage($query, $column)
{
/* @var $modelClass ActiveRecord */
$modelClass = $query->modelClass;
$key = $this->quoteValue($modelClass::keyPrefix() . ':a:');
return $this->build($query, "n=n+1 if v==nil then v=0 end v=v+redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ")", 'v/n');
}
/**
* Builds a Lua script for finding the min value of a column
* @param ActiveQuery $query the query used to build the script
* @param string $column name of the column
* @return string
*/
public function buildMin($query, $column)
{
/* @var $modelClass ActiveRecord */
$modelClass = $query->modelClass;
$key = $this->quoteValue($modelClass::keyPrefix() . ':a:');
return $this->build($query, "n=redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ") if v==nil or n<v then v=n end", 'v');
}
/**
* Builds a Lua script for finding the max value of a column
* @param ActiveQuery $query the query used to build the script
* @param string $column name of the column
* @return string
*/
public function buildMax($query, $column)
{
/* @var $modelClass ActiveRecord */
$modelClass = $query->modelClass;
$key = $this->quoteValue($modelClass::keyPrefix() . ':a:');
return $this->build($query, "n=redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ") if v==nil or n>v then v=n end", 'v');
}
/**
* @param ActiveQuery $query the query used to build the script
* @param string $buildResult the lua script for building the result
* @param string $return the lua variable that should be returned
* @throws NotSupportedException when query contains unsupported order by condition
* @return string
*/
private function build($query, $buildResult, $return)
{
$columns = [];
if ($query->where !== null) {
$condition = $this->buildCondition($query->where, $columns);
} else {
$condition = 'true';
}
$start = ($query->offset === null || $query->offset < 0) ? 0 : $query->offset;
$limitCondition = 'i>' . $start . (($query->limit === null || $query->limit < 0) ? '' : ' and i<=' . ($start + $query->limit));
/* @var $modelClass ActiveRecord */
$modelClass = $query->modelClass;
$key = $this->quoteValue($modelClass::keyPrefix());
$loadColumnValues = '';
foreach ($columns as $column => $alias) {
$loadColumnValues .= "local $alias=redis.call('HGET',$key .. ':a:' .. pk, " . $this->quoteValue($column) . ")\n";
}
$getAllPks = <<<EOF
local allpks=redis.call('LRANGE',$key,0,-1)
EOF;
if (!empty($query->orderBy)) {
if (!is_array($query->orderBy) || count($query->orderBy) > 1) {
throw new NotSupportedException(
'orderBy by multiple columns is not currently supported by redis ActiveRecord.'
);
}
$k = key($query->orderBy);
$v = $query->orderBy[$k];
if (is_numeric($k)) {
$orderColumn = $v;
$orderType = 'ASC';
} else {
$orderColumn = $k;
$orderType = $v === SORT_DESC ? 'DESC' : 'ASC';
}
$getAllPks = <<<EOF
local allpks=redis.pcall('SORT', $key, 'BY', $key .. ':a:*->' .. '$orderColumn', '$orderType')
if allpks['err'] then
allpks=redis.pcall('SORT', $key, 'BY', $key .. ':a:*->' .. '$orderColumn', '$orderType', 'ALPHA')
end
EOF;
}
return <<<EOF
$getAllPks
local pks={}
local n=0
local v=nil
local i=0
local key=$key
for k,pk in ipairs(allpks) do
$loadColumnValues
if $condition then
i=i+1
if $limitCondition then
$buildResult
end
end
end
return $return
EOF;
}
/**
* Adds a column to the list of columns to retrieve and creates an alias
* @param string $column the column name to add
* @param array $columns list of columns given by reference
* @return string the alias generated for the column name
*/
private function addColumn($column, &$columns)
{
if (isset($columns[$column])) {
return $columns[$column];
}
$name = 'c' . preg_replace("/[^a-z]+/i", "", $column) . count($columns);
return $columns[$column] = $name;
}
/**
* Quotes a string value for use in a query.
* Note that if the parameter is not a string or int, it will be returned without change.
* @param string $str string to be quoted
* @return string the properly quoted string
*/
private function quoteValue($str)
{
if (!is_string($str) && !is_int($str)) {
return $str;
}
return "'" . addcslashes($str, "\000\n\r\\\032\047") . "'";
}
/**
* Parses the condition specification and generates the corresponding Lua expression.
* @param string|array $condition the condition specification. Please refer to [[ActiveQuery::where()]]
* on how to specify a condition.
* @param array $columns the list of columns and aliases to be used
* @return string the generated SQL expression
* @throws \yii\db\Exception if the condition is in bad format
* @throws \yii\base\NotSupportedException if the condition is not an array
*/
public function buildCondition($condition, &$columns)
{
static $builders = [
'not' => 'buildNotCondition',
'and' => 'buildAndCondition',
'or' => 'buildAndCondition',
'between' => 'buildBetweenCondition',
'not between' => 'buildBetweenCondition',
'in' => 'buildInCondition',
'not in' => 'buildInCondition',
'like' => 'buildLikeCondition',
'not like' => 'buildLikeCondition',
'or like' => 'buildLikeCondition',
'or not like' => 'buildLikeCondition',
];
if (!is_array($condition)) {
throw new NotSupportedException('Where condition must be an array in redis ActiveRecord.');
}
if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ...
$operator = strtolower($condition[0]);
if (isset($builders[$operator])) {
$method = $builders[$operator];
array_shift($condition);
return $this->$method($operator, $condition, $columns);
} else {
throw new Exception('Found unknown operator in query: ' . $operator);
}
} else { // hash format: 'column1' => 'value1', 'column2' => 'value2', ...
return $this->buildHashCondition($condition, $columns);
}
}
private function buildHashCondition($condition, &$columns)
{
$parts = [];
foreach ($condition as $column => $value) {
if (is_array($value)) { // IN condition
$parts[] = $this->buildInCondition('in', [$column, $value], $columns);
} else {
if (is_bool($value)) {
$value = (int) $value;
}
if ($value === null) {
$parts[] = "redis.call('HEXISTS',key .. ':a:' .. pk, ".$this->quoteValue($column).")==0";
} elseif ($value instanceof Expression) {
$column = $this->addColumn($column, $columns);
$parts[] = "$column==" . $value->expression;
} else {
$column = $this->addColumn($column, $columns);
$value = $this->quoteValue($value);
$parts[] = "$column==$value";
}
}
}
return count($parts) === 1 ? $parts[0] : '(' . implode(') and (', $parts) . ')';
}
private function buildNotCondition($operator, $operands, &$params)
{
if (count($operands) != 1) {
throw new InvalidParamException("Operator '$operator' requires exactly one operand.");
}
$operand = reset($operands);
if (is_array($operand)) {
$operand = $this->buildCondition($operand, $params);
}
return "$operator ($operand)";
}
private function buildAndCondition($operator, $operands, &$columns)
{
$parts = [];
foreach ($operands as $operand) {
if (is_array($operand)) {
$operand = $this->buildCondition($operand, $columns);
}
if ($operand !== '') {
$parts[] = $operand;
}
}
if (!empty($parts)) {
return '(' . implode(") $operator (", $parts) . ')';
} else {
return '';
}
}
private function buildBetweenCondition($operator, $operands, &$columns)
{
if (!isset($operands[0], $operands[1], $operands[2])) {
throw new Exception("Operator '$operator' requires three operands.");
}
list($column, $value1, $value2) = $operands;
$value1 = $this->quoteValue($value1);
$value2 = $this->quoteValue($value2);
$column = $this->addColumn($column, $columns);
$condition = "$column >= $value1 and $column <= $value2";
return $operator === 'not between' ? "not ($condition)" : $condition;
}
private function buildInCondition($operator, $operands, &$columns)
{
if (!isset($operands[0], $operands[1])) {
throw new Exception("Operator '$operator' requires two operands.");
}
list($column, $values) = $operands;
$values = (array) $values;
if (empty($values) || $column === []) {
return $operator === 'in' ? 'false' : 'true';
}
if (is_array($column) && count($column) > 1) {
return $this->buildCompositeInCondition($operator, $column, $values, $columns);
} elseif (is_array($column)) {
$column = reset($column);
}
$columnAlias = $this->addColumn($column, $columns);
$parts = [];
foreach ($values as $value) {
if (is_array($value)) {
$value = isset($value[$column]) ? $value[$column] : null;
}
if ($value === null) {
$parts[] = "redis.call('HEXISTS',key .. ':a:' .. pk, ".$this->quoteValue($column).")==0";
} elseif ($value instanceof Expression) {
$parts[] = "$columnAlias==" . $value->expression;
} else {
$value = $this->quoteValue($value);
$parts[] = "$columnAlias==$value";
}
}
$operator = $operator === 'in' ? '' : 'not ';
return "$operator(" . implode(' or ', $parts) . ')';
}
protected function buildCompositeInCondition($operator, $inColumns, $values, &$columns)
{
$vss = [];
foreach ($values as $value) {
$vs = [];
foreach ($inColumns as $column) {
if (isset($value[$column])) {
$columnAlias = $this->addColumn($column, $columns);
$vs[] = "$columnAlias==" . $this->quoteValue($value[$column]);
} else {
$vs[] = "redis.call('HEXISTS',key .. ':a:' .. pk, ".$this->quoteValue($column).")==0";
}
}
$vss[] = '(' . implode(' and ', $vs) . ')';
}
$operator = $operator === 'in' ? '' : 'not ';
return "$operator(" . implode(' or ', $vss) . ')';
}
private function buildLikeCondition($operator, $operands, &$columns)
{
throw new NotSupportedException('LIKE conditions are not suppoerted by redis ActiveRecord.');
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\redis;
use Yii;
use yii\base\InvalidConfigException;
use yii\di\Instance;
/**
* Redis Mutex implements a mutex component using [redis](http://redis.io/) as the storage medium.
*
* Redis Mutex requires redis version 2.6.12 or higher to work properly.
*
* It needs to be configured with a redis [[Connection]] that is also configured as an application component.
* By default it will use the `redis` application component.
*
* To use redis Mutex as the application component, configure the application as follows:
*
* ```php
* [
* 'components' => [
* 'mutex' => [
* 'class' => 'yii\redis\Mutex',
* 'redis' => [
* 'hostname' => 'localhost',
* 'port' => 6379,
* 'database' => 0,
* ]
* ],
* ],
* ]
* ```
*
* Or if you have configured the redis [[Connection]] as an application component, the following is sufficient:
*
* ```php
* [
* 'components' => [
* 'mutex' => [
* 'class' => 'yii\redis\Mutex',
* // 'redis' => 'redis' // id of the connection application component
* ],
* ],
* ]
* ```
*
* @see \yii\mutex\Mutex
* @see http://redis.io/topics/distlock
*
* @author Sergey Makinen <sergey@makinen.ru>
* @author Alexander Zhuravlev <axelhex@gmail.com>
* @since 2.0.6
*/
class Mutex extends \yii\mutex\Mutex
{
/**
* @var int the number of seconds in which the lock will be auto released.
*/
public $expire = 30;
/**
* @var string a string prefixed to every cache key so that it is unique. If not set,
* it will use a prefix generated from [[Application::id]]. You may set this property to be an empty string
* if you don't want to use key prefix. It is recommended that you explicitly set this property to some
* static value if the cached data needs to be shared among multiple applications.
*/
public $keyPrefix;
/**
* @var Connection|string|array the Redis [[Connection]] object or the application component ID of the Redis [[Connection]].
* This can also be an array that is used to create a redis [[Connection]] instance in case you do not want do configure
* redis connection as an application component.
* After the Mutex object is created, if you want to change this property, you should only assign it
* with a Redis [[Connection]] object.
*/
public $redis = 'redis';
/**
* @var array Redis lock values. Used to be safe that only a lock owner can release it.
*/
private $_lockValues = [];
/**
* Initializes the redis Mutex component.
* This method will initialize the [[redis]] property to make sure it refers to a valid redis connection.
* @throws InvalidConfigException if [[redis]] is invalid.
*/
public function init()
{
parent::init();
$this->redis = Instance::ensure($this->redis, Connection::className());
if ($this->keyPrefix === null) {
$this->keyPrefix = substr(md5(Yii::$app->id), 0, 5);
}
}
/**
* Acquires a lock by name.
* @param string $name of the lock to be acquired. Must be unique.
* @param int $timeout time (in seconds) to wait for lock to be released. Defaults to `0` meaning that method will return
* false immediately in case lock was already acquired.
* @return bool lock acquiring result.
*/
protected function acquireLock($name, $timeout = 0)
{
$key = $this->calculateKey($name);
$value = Yii::$app->security->generateRandomString(20);
$waitTime = 0;
while (!$this->redis->executeCommand('SET', [$key, $value, 'NX', 'PX', (int) ($this->expire * 1000)])) {
$waitTime++;
if ($waitTime > $timeout) {
return false;
}
sleep(1);
}
$this->_lockValues[$name] = $value;
return true;
}
/**
* Releases acquired lock. This method will return `false` in case the lock was not found or Redis command failed.
* @param string $name of the lock to be released. This lock must already exist.
* @return bool lock release result: `false` in case named lock was not found or Redis command failed.
*/
protected function releaseLock($name)
{
static $releaseLuaScript = <<<LUA
if redis.call("GET",KEYS[1])==ARGV[1] then
return redis.call("DEL",KEYS[1])
else
return 0
end
LUA;
if (!isset($this->_lockValues[$name]) || !$this->redis->executeCommand('EVAL', [
$releaseLuaScript,
1,
$this->calculateKey($name),
$this->_lockValues[$name]
])) {
return false;
} else {
unset($this->_lockValues[$name]);
return true;
}
}
/**
* Generates a unique key used for storing the mutex in Redis.
* @param string $name mutex name.
* @return string a safe cache key associated with the mutex name.
*/
protected function calculateKey($name)
{
return $this->keyPrefix . md5(json_encode([__CLASS__, $name]));
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\redis;
use Yii;
use yii\base\InvalidConfigException;
/**
* Redis Session implements a session component using [redis](http://redis.io/) as the storage medium.
*
* Redis Session requires redis version 2.6.12 or higher to work properly.
*
* It needs to be configured with a redis [[Connection]] that is also configured as an application component.
* By default it will use the `redis` application component.
*
* To use redis Session as the session application component, configure the application as follows,
*
* ~~~
* [
* 'components' => [
* 'session' => [
* 'class' => 'yii\redis\Session',
* 'redis' => [
* 'hostname' => 'localhost',
* 'port' => 6379,
* 'database' => 0,
* ]
* ],
* ],
* ]
* ~~~
*
* Or if you have configured the redis [[Connection]] as an application component, the following is sufficient:
*
* ~~~
* [
* 'components' => [
* 'session' => [
* 'class' => 'yii\redis\Session',
* // 'redis' => 'redis' // id of the connection application component
* ],
* ],
* ]
* ~~~
*
* @property bool $useCustomStorage Whether to use custom storage. This property is read-only.
*
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class Session extends \yii\web\Session
{
/**
* @var Connection|string|array the Redis [[Connection]] object or the application component ID of the Redis [[Connection]].
* This can also be an array that is used to create a redis [[Connection]] instance in case you do not want do configure
* redis connection as an application component.
* After the Session object is created, if you want to change this property, you should only assign it
* with a Redis [[Connection]] object.
*/
public $redis = 'redis';
/**
* @var string a string prefixed to every cache key so that it is unique. If not set,
* it will use a prefix generated from [[Application::id]]. You may set this property to be an empty string
* if you don't want to use key prefix. It is recommended that you explicitly set this property to some
* static value if the cached data needs to be shared among multiple applications.
*/
public $keyPrefix;
/**
* Initializes the redis Session component.
* This method will initialize the [[redis]] property to make sure it refers to a valid redis connection.
* @throws InvalidConfigException if [[redis]] is invalid.
*/
public function init()
{
if (is_string($this->redis)) {
$this->redis = Yii::$app->get($this->redis);
} elseif (is_array($this->redis)) {
if (!isset($this->redis['class'])) {
$this->redis['class'] = Connection::className();
}
$this->redis = Yii::createObject($this->redis);
}
if (!$this->redis instanceof Connection) {
throw new InvalidConfigException("Session::redis must be either a Redis connection instance or the application component ID of a Redis connection.");
}
if ($this->keyPrefix === null) {
$this->keyPrefix = substr(md5(Yii::$app->id), 0, 5);
}
parent::init();
}
/**
* Returns a value indicating whether to use custom session storage.
* This method overrides the parent implementation and always returns true.
* @return bool whether to use custom storage.
*/
public function getUseCustomStorage()
{
return true;
}
/**
* Session read handler.
* Do not call this method directly.
* @param string $id session ID
* @return string the session data
*/
public function readSession($id)
{
$data = $this->redis->executeCommand('GET', [$this->calculateKey($id)]);
return $data === false || $data === null ? '' : $data;
}
/**
* Session write handler.
* Do not call this method directly.
* @param string $id session ID
* @param string $data session data
* @return bool whether session write is successful
*/
public function writeSession($id, $data)
{
return (bool) $this->redis->executeCommand('SET', [$this->calculateKey($id), $data, 'EX', $this->getTimeout()]);
}
/**
* Session destroy handler.
* Do not call this method directly.
* @param string $id session ID
* @return bool whether session is destroyed successfully
*/
public function destroySession($id)
{
$this->redis->executeCommand('DEL', [$this->calculateKey($id)]);
// @see https://github.com/yiisoft/yii2-redis/issues/82
return true;
}
/**
* Generates a unique key used for storing session data in cache.
* @param string $id session variable name
* @return string a safe cache key associated with the session variable name
*/
protected function calculateKey($id)
{
return $this->keyPrefix . md5(json_encode([__CLASS__, $id]));
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\redis;
use yii\db\Exception;
/**
* SocketException indicates a socket connection failure in [[Connection]].
* @since 2.0.7
*/
class SocketException extends Exception
{
/**
* @return string the user-friendly name of this exception
*/
public function getName()
{
return 'Redis Socket Exception';
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment