分类 文档 下的文章

Stimulus 1.0: 一个针对你已有的 HTML 的 JavaScript 框架

Stimulus 是一款拥有谦逊理想的 JavaScript 框架。Stimulus 不会把你的应用程序整个搬到前端,它的设计理念是通过自动将元素连接到 JavaScript 来强化你的 HTML。

将 HTML 连接到 JavaScript

Stimulus 的工作方式是不停地监控页面,等待 data-controller 这个魔法属性出现。就像 CSS 的 class属性,你可以在它里面放入多个值。不同于 CSS 类名的使用,data-controller 的值是用于与 Stimulus 控制器连接或断开连接的。

你可以这样认为:class 是连接 HTML 和 CSS 的桥梁,data-controller 是连接 HTLM 和 JavaScript 的桥梁。

在此基础上,Stimulus 增加了魔法属性data-action,它描述了页面上的事件应该怎样触发控制器里的方法;还有魔法属性 data-target,它为你提供了在控制器作用域(controller’s scope)中寻找元素的途径(handle )。

关注点分离

Stimulus 的魔法属性让你清晰地将内容与行为分离,如同你使用 CSS 将内容与形式分离那样。此外,Stimulus 的自然约定也鼓励你按名称对相关联的代码进行分组。

这样的安排能帮助你构建可复用的,类似 trait 的控制器,为你的代码带来足够使用的结构层次,使之远离 “JavaScript 乱炖汤”。

可阅读的文档

当你的 JavaScript 行为是由魔法属性映射的,你可以阅读一段 HTML 便能知道发生了什么。这是一个受欢迎的方案,比如当你六个月后返回一个模板,不记得各种东西是如何组合在一起的时候,就能明白了。

可读的代码,也意味着团队中的其他人也能通过阅读模版或者开发者控制台,来快速地追踪行为或判断问题。

水是温暖的,不坑

现在是时候来探索一下 Stimulus 的工作方式了。继续阅读,学习如何构建你的第一个控制器。

一个用于 JWT 编码和界面的简单 PHP 库,符合 RFC 7519 规范。

功能支持

功能支持情况算法支持情况
Sign(JWT注册)支持HS256支持
Verify(JWT验证)支持HS384支持
iss check(签发者检查)支持HS512支持
sub check(jwt所面向的用户)不支持RS256支持
aud check(接收者检查)不支持RS384支持
exp check(过期时间检查)支持RS512支持
nbf check(在此时间前JWT不可用)支持ES256不支持
iat check(JWT签发时间检查)支持ES384不支持
jti check(jwt的唯一标识,回避重放攻击)不支持ES512不支持

安装

使用 Composer 管理你的依赖,下载 PHP-JWT:

composer require firebase/php-jwt

示例

<?php
use \Firebase\JWT\JWT;

$key = "example_key";
$token = array(
    "iss" => "http://example.org",
    "aud" => "http://example.com",
    "iat" => 1356999524,
    "nbf" => 1357000000
);

/**
 * 重要:
 * 你必须为你的应用程序指定一个支持的算法。查阅
 * https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40
 * 了解一系列遵循规范的算法。
 */
$jwt = JWT::encode($token, $key);
$decoded = JWT::decode($jwt, $key, array('HS256'));

print_r($decoded);

/*
 注意: 目前它会是一个对象,而不是关联数组。如果要获取关联数组,你可以这样操作:
*/

$decoded_array = (array) $decoded;

/**
 * 当注册服务器和验证服务器之前存在一些时钟偏差时,你可以添加一个时间余地(leeway)。
 * 建议 leeway 最好不要超过一分钟。
 *
 * Source: http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#nbfDef
 */
JWT::$leeway = 60; // $leeway 是以秒为单位的
$decoded = JWT::decode($jwt, $key, array('HS256'));

?>

示例,使用了 RS256 (openssl)

<?php
use \Firebase\JWT\JWT;

$privateKey = <<<EOD
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQC8kGa1pSjbSYZVebtTRBLxBz5H4i2p/llLCrEeQhta5kaQu/Rn
vuER4W8oDH3+3iuIYW4VQAzyqFpwuzjkDI+17t5t0tyazyZ8JXw+KgXTxldMPEL9
5+qVhgXvwtihXC1c5oGbRlEDvDF6Sa53rcFVsYJ4ehde/zUxo6UvS7UrBQIDAQAB
AoGAb/MXV46XxCFRxNuB8LyAtmLDgi/xRnTAlMHjSACddwkyKem8//8eZtw9fzxz
bWZ/1/doQOuHBGYZU8aDzzj59FZ78dyzNFoF91hbvZKkg+6wGyd/LrGVEB+Xre0J
Nil0GReM2AHDNZUYRv+HYJPIOrB0CRczLQsgFJ8K6aAD6F0CQQDzbpjYdx10qgK1
cP59UHiHjPZYC0loEsk7s+hUmT3QHerAQJMZWC11Qrn2N+ybwwNblDKv+s5qgMQ5
5tNoQ9IfAkEAxkyffU6ythpg/H0Ixe1I2rd0GbF05biIzO/i77Det3n4YsJVlDck
ZkcvY3SK2iRIL4c9yY6hlIhs+K9wXTtGWwJBAO9Dskl48mO7woPR9uD22jDpNSwe
k90OMepTjzSvlhjbfuPN1IdhqvSJTDychRwn1kIJ7LQZgQ8fVz9OCFZ/6qMCQGOb
qaGwHmUK6xzpUbbacnYrIM6nLSkXgOAwv7XXCojvY614ILTK3iXiLBOxPu5Eu13k
eUz9sHyD6vkgZzjtxXECQAkp4Xerf5TGfQXGXhxIX52yH+N2LtujCdkQZjXAsGdm
B2zNzvrlgRmgBrklMTrMYgm1NPcW+bRLGcwgW2PTvNM=
-----END RSA PRIVATE KEY-----
EOD;

$publicKey = <<<EOD
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8kGa1pSjbSYZVebtTRBLxBz5H
4i2p/llLCrEeQhta5kaQu/RnvuER4W8oDH3+3iuIYW4VQAzyqFpwuzjkDI+17t5t
0tyazyZ8JXw+KgXTxldMPEL95+qVhgXvwtihXC1c5oGbRlEDvDF6Sa53rcFVsYJ4
ehde/zUxo6UvS7UrBQIDAQAB
-----END PUBLIC KEY-----
EOD;

$token = array(
    "iss" => "example.org",
    "aud" => "example.com",
    "iat" => 1356999524,
    "nbf" => 1357000000
);

$jwt = JWT::encode($token, $privateKey, 'RS256');
echo "Encode:\n" . print_r($jwt, true) . "\n";

$decoded = JWT::decode($jwt, $publicKey, array('RS256'));

/*
 注意: 目前它会是一个对象,而不是关联数组。如果要获取关联数组,你可以这样操作:
*/

$decoded_array = (array) $decoded;
echo "Decode:\n" . print_r($decoded_array, true) . "\n";
?>

测试

使用 phpunit 来运行测试:

$ pear install PHPUnit
$ phpunit --configuration phpunit.xml.dist
PHPUnit 3.7.10 by Sebastian Bergmann.
.....
Time: 0 seconds, Memory: 2.50Mb
OK (5 tests, 5 assertions)

关于私钥的补充说明

如果你的私钥包含 \n 字符,为了能正确地转义,请确保使用双引号("")将其包裹,而不是单引号('')。

License

3-Clause BSD.


JWT 验证过程中的各种异常整理

基本用法

<?php
use \Firebase\JWT\JWT; //导入JWT
class MainController extends Controller
{

    public function verification()
    {
        $key = '344'; //key要和签发的时候一样

        $jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC93d3cuaGVsbG93ZWJhLm5ldCIsImF1ZCI6Imh0dHA6XC9cL3d3dy5oZWxsb3dlYmEubmV0IiwiaWF0IjoxNTI1MzQwMzE3LCJuYmYiOjE1MjUzNDAzMTcsImV4cCI6MTUyNTM0NzUxNywiZGF0YSI6eyJ1c2VyaWQiOjEsInVzZXJuYW1lIjoiXHU2NzRlXHU1YzBmXHU5Zjk5In19.Ukd7trwYMoQmahOAtvNynSA511mseA2ihejoZs7dxt0"; //签发的Token
        try {
                   JWT::$leeway = 60;//当前时间减去60,把时间留点余地
                   $decoded = JWT::decode($jwt, $key, ['HS256']); //HS256方式,这里要和签发的时候对应
                   $arr = (array)$decoded;
                   print_r($arr);
            } catch(\Firebase\JWT\SignatureInvalidException $e) {  //签名不正确
                echo $e->getMessage();
            }catch(\Firebase\JWT\BeforeValidException $e) {  // 签名在某个时间点之后才能用
                echo $e->getMessage();
            }catch(\Firebase\JWT\ExpiredException $e) {  // token过期
                echo $e->getMessage();
           }catch(Exception $e) {  //其他错误
                echo $e->getMessage();
            }
        //Firebase定义了多个 throw new,我们可以捕获多个catch来定义问题,catch加入自己的业务,比如token过期可以用当前Token刷新一个新Token
    }
}

在src目录下的JWT.php中的方法 decode中,可以看到多个自定义 throw new 抛出异常:

/**
 * 将 JWT 字符串解码成一个 PHP 对象。
 *
 * @param string        $jwt            The JWT
 * @param string|array  $key            The key, or map of keys.
 *                                      如果使用的是非对称算法,则是公钥
 * @param array         $allowed_algs   一系列被支持的验证算法
 *                                      被支持的算法有 'HS256', 'HS384', 'HS512' and 'RS256'
 *
 * @return object 将 JWT 的载荷以 PHP 对象形式返回
 *
 * @throws UnexpectedValueException     JWT 无效
 * @throws SignatureInvalidException    由于签名验证失败,JWT 无效
 * @throws BeforeValidException         尝试在 nbf 前使用 JWT
 * @throws BeforeValidException         尝试在 iat 之前使用 JWT
 * @throws ExpiredException             JWT 已过期,由 exp 定义的过期时间
 *
 * @uses jsonDecode
 * @uses urlsafeB64Decode
 */
public static function decode($jwt, $key, array $allowed_algs = array())
{
    $timestamp = is_null(static::$timestamp) ? time() : static::$timestamp;
    if (empty($key)) {
        // Key 不能为空
        throw new InvalidArgumentException('Key may not be empty');
    }
    $tks = explode('.', $jwt);
    if (count($tks) != 3) {
        // 错误的分段数
        throw new UnexpectedValueException('Wrong number of segments');
    }
    list($headb64, $bodyb64, $cryptob64) = $tks;
    if (null === ($header = static::jsonDecode(static::urlsafeB64Decode($headb64)))) {
        // 无效的 header 编码 
        throw new UnexpectedValueException('Invalid header encoding');
    }
    if (null === $payload = static::jsonDecode(static::urlsafeB64Decode($bodyb64))) {
        // 无效的 claims 编码
        throw new UnexpectedValueException('Invalid claims encoding');
    }
    if (false === ($sig = static::urlsafeB64Decode($cryptob64))) {
        // 无效的 signature 编码
        throw new UnexpectedValueException('Invalid signature encoding');
    }
    if (empty($header->alg)) {
        // 算法为空
        throw new UnexpectedValueException('Empty algorithm');
    }
    if (empty(static::$supported_algs[$header->alg])) {
        // 未被支持的算法
        throw new UnexpectedValueException('Algorithm not supported');
    }
    if (!in_array($header->alg, $allowed_algs)) {
        // 算法未被允许使用
        throw new UnexpectedValueException('Algorithm not allowed');
    }
    if (is_array($key) || $key instanceof \ArrayAccess) {
        if (isset($header->kid)) {
            if (!isset($key[$header->kid])) {
                // "kid" 无效,不能查找正确的 key
                throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key');
            }
            $key = $key[$header->kid];
        } else {
            // "kid" 为空,不能查找正确的 key
            throw new UnexpectedValueException('"kid" empty, unable to lookup correct key');
        }
    }
    // 检查 signature
    if (!static::verify("$headb64.$bodyb64", $sig, $key, $header->alg)) {
        // 签名验证失败
        throw new SignatureInvalidException('Signature verification failed');
    }
    // 检查 nbf 是否已定义。已定义则可以使用该 token;否则还为到可用时间,终止。
    if (isset($payload->nbf) && $payload->nbf > ($timestamp + static::$leeway)) {
        // 不能在该时间前处理该 token
        throw new BeforeValidException(
            'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->nbf)
        );
    }
    // 检查在“此刻”之前已创建的 token。防止使用那些在稍后才能使用的 token (没有正确使用 nbf)        if (isset($payload->iat) && $payload->iat > ($timestamp + static::$leeway)) {
        // 不能在该时间前处理该 token
        throw new BeforeValidException(
            'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->iat)
        );
    }
    // 检查 token 是否已过期
    if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) {
        // token 已过期
        throw new ExpiredException('Expired token');
    }
    return $payload;
}

由于此库不支持subaudjti检查,在完成上述操作后,需按自己的需要对这三者进行检查操作。

JWT

其他文档参考:http://blog.pkcms.cn/php-jwt-token/

用于处理 JSON Web Token 和 JSON Web Signature(PHP 5.5+) 的库。基于 current draft 实现。

安装

此包可以在 Packagist 找到,你可以使用 Composer 进行安装。

composer require lcobucci/jwt

依赖

  • PHP 5.5+
  • OpenSSL Extension

参考:https://yunye.pw/index.php/archives/6.html

基础用法

创建 JWT

只使用 builder 来新建一个 JWT/JWS tokens:

use Lcobucci\JWT\Builder;

$token = (new Builder())->setIssuer('http://example.com') // 设置发行人 (iss claim)
                        ->setAudience('http://example.org') // 设置接收人 (aud claim)
                        ->setId('4f1g23a12aa', true) // 设置 id (jti claim), 复制用做 header 元素
                        ->setIssuedAt(time()) // 设置 token 生成的时间 (iat claim)
                        ->setNotBefore(time() + 60) // 设置在60秒内该token无法使用 (nbf claim)
                        ->setExpiration(time() + 3600) // 设置过期时间 (exp claim)
                        ->set('uid', 1) // 给token设置一个id Configures a new claim, called "uid"
                        ->getToken(); // 获取生成的 token

$token->getHeaders(); // 获取 token 的 header 信息
$token->getClaims(); // 获取 token 的 claims

echo $token->getHeader('jti'); // will print "4f1g23a12aa"
echo $token->getClaim('iss'); // will print "http://example.com"
echo $token->getClaim('uid'); // will print "1"
echo $token; // The string representation of the object is a JWT string (pretty easy, right?)

分析 token 字符串

使用 parser 根据 JWT 字符串新建 token(继续使用上述 token 作为示例):

use Lcobucci\JWT\Parser;

$token = (new Parser())->parse((string) $token); // 分析字符串 
$token->getHeaders(); // 获取 token header
$token->getClaims(); // 获取 token claims

echo $token->getHeader('jti'); // will print "4f1g23a12aa"
echo $token->getClaim('iss'); // will print "http://example.com"
echo $token->getClaim('uid'); // will print "1"

验证

轻松验证 token 是否有效((继续使用上述 token 作为示例):

use Lcobucci\JWT\ValidationData;

$data = new ValidationData(); // 使用当前时间来验证 (iat, nbf and exp)
$data->setIssuer('http://example.com');
$data->setAudience('http://example.org');
$data->setId('4f1g23a12aa');

var_dump($token->validate($data)); // false, 因为我们创建的 token 在 `time() + 60` 前不能使用

$data->setCurrentTime(time() + 60); // 将验证时候改到 token 可用时间之后

var_dump($token->validate($data)); // true, 因为验证信息与 token 包含的信息一直了

$data->setCurrentTime(time() + 4000); // 将验证时间改到未来

var_dump($token->validate($data)); // false, token 已经过了有效期了

Token 签名

使用签名来验证 token 是否在生成后被修改过。本库实现了 Hmac, RSA 以及 ECDSA signatures (using 256, 384 and 512).

Hmac

Hmac 签名真的用起来很简单:

use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Signer\Hmac\Sha256;

$signer = new Sha256();

$token = (new Builder())->setIssuer('http://example.com') // Configures the issuer (iss claim)
                        ->setAudience('http://example.org') // Configures the audience (aud claim)
                        ->setId('4f1g23a12aa', true) // Configures the id (jti claim), replicating as a header item
                        ->setIssuedAt(time()) // Configures the time that the token was issue (iat claim)
                        ->setNotBefore(time() + 60) // Configures the time that the token can be used (nbf claim)
                        ->setExpiration(time() + 3600) // Configures the expiration time of the token (exp claim)
                        ->set('uid', 1) // Configures a new claim, called "uid"
                        ->sign($signer, 'testing') // 使用“testing”作为 key 创建签名
                        ->getToken(); // Retrieves the generated token


var_dump($token->verify($signer, 'testing 1')); // false, key 不一致
var_dump($token->verify($signer, 'testing')); // true, key 值一致

RSA 和 ECDSA

RSA 和 ECDSA 签名是基于公钥和私钥,所以必须使用私钥生成,使用公钥验证:

use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Signer\Keychain; // just to make our life simpler
use Lcobucci\JWT\Signer\Rsa\Sha256; // 如果在使用 ECDSA 可以使用 Lcobucci\JWT\Signer\Ecdsa\Sha256

$signer = new Sha256();

$keychain = new Keychain();

$token = (new Builder())->setIssuer('http://example.com') // Configures the issuer (iss claim)
                        ->setAudience('http://example.org') // Configures the audience (aud claim)
                        ->setId('4f1g23a12aa', true) // Configures the id (jti claim), replicating as a header item
                        ->setIssuedAt(time()) // Configures the time that the token was issue (iat claim)
                        ->setNotBefore(time() + 60) // Configures the time that the token can be used (nbf claim)
                        ->setExpiration(time() + 3600) // Configures the expiration time of the token (nbf claim)
                        ->set('uid', 1) // Configures a new claim, called "uid"
                        ->sign($signer,  $keychain->getPrivateKey('file://{path to your private key}')) // 使用你的私钥创建签名                        ->getToken(); // Retrieves the generated token


var_dump($token->verify($signer, $keychain->getPublicKey('file://{path to your public key}'))); // 如果公钥是使用对应的私钥生成的,即为 true

重要的一点:如果你使用 RSA keys ,那么就不必调用 ECDSA signers(and vice-versa), otherwise sign()verify() 会报异常!

连接服务器

Predis 提供了各式各样连接服务器或服务器集群的方式。通过指定两个以上服务器,Predis 自动切换集群连接,透明地处理多个连接上的客户端分片。应该注意的是,集群连接相比单一连接(即 Predis 被连接到一个单独的服务器)有较多开销,这是由于支持一致的哈希时需要更复杂的内部结构。

  • 连接默认的服务器(127.0.0.1)和端口(6379)

$redis = new Predis\Client();
  • 使用参数连接服务器

redis = new Predis\Client(array(
    'host' => '10.0.0.1', 
    'port' => 6380, 
));

或者

$redis = new Predis\Client('redis://10.0.0.1:6380/');
  • 连接到多个服务器

$redis = new Predis\Client(array(
    array('host' => '10.0.0.1'), 
    array('host' => '10.0.0.2'), 
));

或者

$redis = new Predis\Client(array(
    'redis://10.0.0.1/', 
    'redis://10.0.0.1/', 
));

或者

$redis = Predis\Client::create(
    array('host' => '10.0.0.1'), 
    array('host' => '10.0.0.2')
);

或者

$redis = Predis\Client::create(
    'redis://10.0.0.1/', 
    'redis://10.0.0.1/'
);
  • 在连接时自动执行身份验证和数据库选择

$redis = new Predis\Client(array(
    'host'     => '10.0.0.1', 
    'password' => 'secret', 
    'database' => 10, 
));

或者

$redis = new Predis\Client('redis://10.0.0.1/?password=secret&database=10');

支持的连接参数

参数 描述 默认值
__host__ 服务器的 IP 地址或主机名称 127.0.0.1
__port__ 服务器监听的 TCP 端口 6379
__password__ 服务器用于身份验证的密码 no不使用密码
__database__ 连接后,选择的数据库索引 为设定索引,使用默认的 Redis 数据库
__timeout__ 连接服务器的超时时间 5 秒
__read_write_timeout__ 在网络 socket 上读写操作的超时时间 系统默认 (NOTE: 使用 -1 禁用读写超时)

发送指令

使用 Predis 向 Redis 发送请求真的相当简单,但是处理指令的方式不止一种:

  • 简单方式(最常用的)
$redis->set('library', 'predis');
$retval = $redis->get('library');
  • 较长的方式(但更程序化)
// Client::createCommand uses the current server profile
// to create and return a new command instance
$cmdSet = $redis->createCommand('set');
$cmdSet->setArgumentsArray(array('library', 'predis'));
$cmdSetReply = $redis->executeCommand($cmdSet);
$cmdGet = $redis->createCommand('get');
$cmdGet->setArgumentsArray(array('library'));
$cmdGetReply = $redis->executeCommand($cmdGet);
  • 最长的方式(and partially discouraged)
$cmdSet = new Predis\Commands\Set();
$cmdSet->setArgumentsArray(array('library', 'predis'));
$cmdSetReply = $redis->executeCommand($cmdSet);
$cmdGet = new Predis\Commands\Get();
$cmdGet->setArgumentsArray(array('library'));
$cmdGetReply = $redis->executeCommand($cmdGet);

另外还有各种各样的方式,在处理指令实例时去设置指令的参数:

  • 创建指令实例时传递动态的参数列表
$cmdSet = $redis->createCommand('set', array('library', 'predis'));
  • 在现有的指令实例上传递动态的参数列表
$cmdSet = $redis->createCommand('set');
$cmdSet->setArgumentsArray(array('library', 'predis'));
  • 在现有的指令实例上传递固定的参数列表
$cmdSet = $redis->createCommand('set');
$cmdSet->setArguments('library', 'predis');

如果你想要发送的指令在 Predis 中没有定义,该怎么办?

  • 发送原生指令(目前暂不支持集群链接)
$reply = $redis->rawCommand("NEWCMD $key $param1 $param2");
  • 通过继承以下基类定义你自定的指令:

PredisCommand, PredisInlineCommand, PredisBulkCommand and PredisMultiBulkCommand.

class BrandNewRedisCommand extends \Predis\InlineCommand {
    public function getCommandId() { return 'NEWCMD'; }
}
$redis->getProfile()->registerCommand('BrandNewRedisCommand', 'newcmd');
$reply = $redis->newcmd($key, $param1, $param2);

Server profiles

Predis introduced the concept of server profiles to allow developers to specify, in a programmatic way, which Redis version they are going to connect to. This means that you can know beforehand which commands or features are supported by the selected Redis version and allows us to handle eventual behavioral differences in certain commands. As of today there are three predefined profile classes: PredisRedisServer_v1_0 (Redis v1.0), PredisRedisServer_v1_2 (Redis v1.2) and PredisRedisServer_vNext (the development version of Redis). When you create an instance of PredisClient, the default server profile being used is the latest stable version of Redis available at release time, but you can always force Predis to use a different profile:

  • Set a different server profile when instanciating a new PredisClient instance
$profile1_0 = new Predis\RedisServer_v1_0();
printf("MSET SUPPORTED: %s\n", $profile1_0->supportsCommand('mset') ? 'YES' : 'NO');
$profile1_2 = new Predis\RedisServer_v1_2();
printf("MSET SUPPORTED: %s\n", $profile1_2->supportsCommand('mset') ? 'YES' : 'NO');
$redis = new Predis\Client('redis://127.0.0.1/', $profile1_2);
  • Get an instance of the default server profile supported by the current version of Predis
$defaultProfile = Predis\RedisServerProfile::getDefault();
echo get_class($defaultProfile);        // OUTPUT: Predis\RedisServer_v1_2
  • Get an instance of a server profile supported by Predis by using a version string
$profile = Predis\RedisServerProfile::get('1.2');
echo get_class($profile);        // OUTPUT: Predis\RedisServer_v1_2
$profile = Predis\RedisServerProfile::get('dev');
echo get_class($profile);        // OUTPUT: Predis\RedisServer_vNext

h2. Pipelining

Pipelining can help with performances when you need to issue a whole set of commands to Redis. This is done by recording the commands issued to the client without actually sending them to the server, playing them all at once over the wire when flushing the pipeline (send the commands) and then listening to what the server has to tell us (read the responses).

  • Initialize a pipeline inside of a block of code using an anonymous function (PHP 5.3 only)
$replies = $redis->pipeline(function($pipe) {
    $pipe->ping();
    $pipe->flushdb();
    $pipe->incrby('counter', 10);
    $pipe->exists('counter');
    $pipe->mget('does_not_exist', 'counter');
});
  • Initialize a pipeline and return an instance to play with (this also works with PHP 5.2)
$pipe = $redis->pipeline();
$pipe->ping();
$pipe->flushdb();
$pipe->incrby('counter', 10);
$pipe->exists('counter');
$pipe->mget('does_not_exist', 'counter');
$replies = $pipe->execute();

h2. Client-side sharding

The key-value model allows you to easily distribute your data across several servers and this is usually achieved by hashing the keys against a list of servers to select which one will be used to store their respective values. The same data-partitioning strategy can be used with Redis using a client library capable of handling connections to more than one server, but this is not enough to guarantee a consistent distribution and accessibility of your data as time passes. Predis supports "consistent hashing":http://en.wikipedia.org/wiki/Consistent_hashing when storing your list of servers, so that adding or removing a server does not affect the pre-existing distribution and availability of data on the others. Key tags are another interesting feature that allows you to decide which portion of a key should be hashed against the server list so you can redirect a subset of keys to the same instance. E.g., instead of using plain keys like "uid:1000:username" and "uid:1000:password" that will almost surely produce a different hash (and thus, sent on different servers) you can go with "{uid:1000}:username" and __"{uid:1000}:password"__, where the curly brackets define which part of the key will be hashed. Everything is handled transparently, even when using pipelines.

  • Initialize a clustered client and set/retrieve key-values using key tags
$redis = Predis\Client::create(
    'redis://10.0.0.1/', 
    'redis://10.0.0.2/'
);
$redis->set('{uid:1000}:username', 'nrk');
$redis->set('{uid:1000}:password', md5('mystrongpassword'));
$username = $redis->get('{uid:1000}:username'); // nrk
$password = $redis->get('{uid:1000}:password'); // c924729b0e04eb0d21908a7454c0218a