Retwis ported to modern Redis data types.

The example (and related documentation at redis.io) was not updated for
5 years. Redis had no sorted sets and hashes when the original code was
written (!). An update was really needed.

We also use a modern PHP client now: Predis. A copy is shipped within
this repository to make life easier to newcomers trying Redis for the
first time via this example.
This commit is contained in:
antirez 2014-05-28 11:06:06 +02:00
commit c58f935fdc
277 changed files with 18503 additions and 0 deletions

View file

@ -0,0 +1,55 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Pipeline;
use SplQueue;
use Predis\Connection\ConnectionInterface;
use Predis\Connection\ReplicationConnectionInterface;
/**
* Implements a pipeline executor strategy that writes a list of commands to
* the connection object but does not read back their replies.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class FireAndForgetExecutor implements PipelineExecutorInterface
{
/**
* Allows the pipeline executor to perform operations on the
* connection before starting to execute the commands stored
* in the pipeline.
*
* @param ConnectionInterface $connection Connection instance.
*/
protected function checkConnection(ConnectionInterface $connection)
{
if ($connection instanceof ReplicationConnectionInterface) {
$connection->switchTo('master');
}
}
/**
* {@inheritdoc}
*/
public function execute(ConnectionInterface $connection, SplQueue $commands)
{
$this->checkConnection($connection);
while (!$commands->isEmpty()) {
$connection->writeCommand($commands->dequeue());
}
$connection->disconnect();
return array();
}
}

View file

@ -0,0 +1,168 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Pipeline;
use Iterator;
use SplQueue;
use Predis\ClientException;
use Predis\ResponseErrorInterface;
use Predis\ResponseObjectInterface;
use Predis\ServerException;
use Predis\Connection\ConnectionInterface;
use Predis\Connection\SingleConnectionInterface;
use Predis\Profile\ServerProfile;
use Predis\Profile\ServerProfileInterface;
/**
* Implements a pipeline executor that wraps the whole pipeline
* in a MULTI / EXEC context to make sure that it is executed
* correctly.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class MultiExecExecutor implements PipelineExecutorInterface
{
protected $profile;
/**
*
*/
public function __construct(ServerProfileInterface $profile = null)
{
$this->setProfile($profile ?: ServerProfile::getDefault());
}
/**
* Allows the pipeline executor to perform operations on the
* connection before starting to execute the commands stored
* in the pipeline.
*
* @param ConnectionInterface $connection Connection instance.
*/
protected function checkConnection(ConnectionInterface $connection)
{
if (!$connection instanceof SingleConnectionInterface) {
$class = __CLASS__;
throw new ClientException("$class can be used only with single connections");
}
}
/**
* {@inheritdoc}
*/
public function execute(ConnectionInterface $connection, SplQueue $commands)
{
$this->checkConnection($connection);
$cmd = $this->profile->createCommand('multi');
$connection->executeCommand($cmd);
foreach ($commands as $command) {
$connection->writeCommand($command);
}
foreach ($commands as $command) {
$response = $connection->readResponse($command);
if ($response instanceof ResponseErrorInterface) {
$cmd = $this->profile->createCommand('discard');
$connection->executeCommand($cmd);
throw new ServerException($response->getMessage());
}
}
$cmd = $this->profile->createCommand('exec');
$responses = $connection->executeCommand($cmd);
if (!isset($responses)) {
throw new ClientException('The underlying transaction has been aborted by the server');
}
if (count($responses) !== count($commands)) {
throw new ClientException("Invalid number of replies [expected: ".count($commands)." - actual: ".count($responses)."]");
}
$consumer = $responses instanceof Iterator ? 'consumeIteratorResponse' : 'consumeArrayResponse';
return $this->$consumer($commands, $responses);
}
/**
* Consumes an iterator response returned by EXEC.
*
* @param SplQueue $commands Pipelined commands
* @param Iterator $responses Responses returned by EXEC.
* @return array
*/
protected function consumeIteratorResponse(SplQueue $commands, Iterator $responses)
{
$values = array();
foreach ($responses as $response) {
$command = $commands->dequeue();
if ($response instanceof ResponseObjectInterface) {
if ($response instanceof Iterator) {
$response = iterator_to_array($response);
$values[] = $command->parseResponse($response);
} else {
$values[] = $response;
}
} else {
$values[] = $command->parseResponse($response);
}
}
return $values;
}
/**
* Consumes an array response returned by EXEC.
*
* @param SplQueue $commands Pipelined commands
* @param Array $responses Responses returned by EXEC.
* @return array
*/
protected function consumeArrayResponse(SplQueue $commands, Array &$responses)
{
$size = count($commands);
$values = array();
for ($i = 0; $i < $size; $i++) {
$command = $commands->dequeue();
$response = $responses[$i];
if ($response instanceof ResponseObjectInterface) {
$values[$i] = $response;
} else {
$values[$i] = $command->parseResponse($response);
}
unset($responses[$i]);
}
return $values;
}
/**
* @param ServerProfileInterface $profile Server profile.
*/
public function setProfile(ServerProfileInterface $profile)
{
if (!$profile->supportsCommands(array('multi', 'exec', 'discard'))) {
throw new ClientException('The specified server profile must support MULTI, EXEC and DISCARD.');
}
$this->profile = $profile;
}
}

View file

@ -0,0 +1,189 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Pipeline;
use SplQueue;
use Predis\BasicClientInterface;
use Predis\ClientException;
use Predis\ClientInterface;
use Predis\ExecutableContextInterface;
use Predis\Command\CommandInterface;
/**
* Abstraction of a pipeline context where write and read operations
* of commands and their replies over the network are pipelined.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class PipelineContext implements BasicClientInterface, ExecutableContextInterface
{
private $client;
private $executor;
private $pipeline;
private $replies = array();
private $running = false;
/**
* @param ClientInterface $client Client instance used by the context.
* @param PipelineExecutorInterface $executor Pipeline executor instace.
*/
public function __construct(ClientInterface $client, PipelineExecutorInterface $executor = null)
{
$this->client = $client;
$this->executor = $executor ?: $this->createExecutor($client);
$this->pipeline = new SplQueue();
}
/**
* Returns a pipeline executor depending on the kind of the underlying
* connection and the passed options.
*
* @param ClientInterface $client Client instance used by the context.
* @return PipelineExecutorInterface
*/
protected function createExecutor(ClientInterface $client)
{
$options = $client->getOptions();
if (isset($options->exceptions)) {
return new StandardExecutor($options->exceptions);
}
return new StandardExecutor();
}
/**
* Queues a command into the pipeline buffer.
*
* @param string $method Command ID.
* @param array $arguments Arguments for the command.
* @return $this
*/
public function __call($method, $arguments)
{
$command = $this->client->createCommand($method, $arguments);
$this->recordCommand($command);
return $this;
}
/**
* Queues a command instance into the pipeline buffer.
*
* @param CommandInterface $command Command to queue in the buffer.
*/
protected function recordCommand(CommandInterface $command)
{
$this->pipeline->enqueue($command);
}
/**
* Queues a command instance into the pipeline buffer.
*
* @param CommandInterface $command Command to queue in the buffer.
* @return $this
*/
public function executeCommand(CommandInterface $command)
{
$this->recordCommand($command);
return $this;
}
/**
* Flushes the buffer that holds the queued commands.
*
* @param bool $send Specifies if the commands in the buffer should be sent to Redis.
* @return PipelineContext
*/
public function flushPipeline($send = true)
{
if ($send && !$this->pipeline->isEmpty()) {
$connection = $this->client->getConnection();
$replies = $this->executor->execute($connection, $this->pipeline);
$this->replies = array_merge($this->replies, $replies);
} else {
$this->pipeline = new SplQueue();
}
return $this;
}
/**
* Marks the running status of the pipeline.
*
* @param bool $bool True if the pipeline is running.
* False if the pipeline is not running.
*/
private function setRunning($bool)
{
if ($bool === true && $this->running === true) {
throw new ClientException("This pipeline is already opened");
}
$this->running = $bool;
}
/**
* Handles the actual execution of the whole pipeline.
*
* @param mixed $callable Optional callback for execution.
* @return array
*/
public function execute($callable = null)
{
if ($callable && !is_callable($callable)) {
throw new \InvalidArgumentException('Argument passed must be a callable object');
}
$this->setRunning(true);
$pipelineBlockException = null;
try {
if ($callable !== null) {
call_user_func($callable, $this);
}
$this->flushPipeline();
} catch (\Exception $exception) {
$pipelineBlockException = $exception;
}
$this->setRunning(false);
if ($pipelineBlockException !== null) {
throw $pipelineBlockException;
}
return $this->replies;
}
/**
* Returns the underlying client instance used by the pipeline object.
*
* @return ClientInterface
*/
public function getClient()
{
return $this->client;
}
/**
* Returns the underlying pipeline executor used by the pipeline object.
*
* @return PipelineExecutorInterface
*/
public function getExecutor()
{
return $this->executor;
}
}

View file

@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Pipeline;
use SplQueue;
use Predis\Connection\ConnectionInterface;
/**
* Defines a strategy to write a list of commands to the network
* and read back their replies.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface PipelineExecutorInterface
{
/**
* Writes a list of commands to the network and reads back their replies.
*
* @param ConnectionInterface $connection Connection to Redis.
* @param SplQueue $commands Commands queued for execution.
* @return array
*/
public function execute(ConnectionInterface $connection, SplQueue $commands);
}

View file

@ -0,0 +1,72 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Pipeline;
use SplQueue;
use Predis\CommunicationException;
use Predis\Connection\ConnectionInterface;
/**
* Implements a pipeline executor strategy for connection clusters that does
* not fail when an error is encountered, but adds the returned error in the
* replies array.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class SafeClusterExecutor implements PipelineExecutorInterface
{
/**
* {@inheritdoc}
*/
public function execute(ConnectionInterface $connection, SplQueue $commands)
{
$size = count($commands);
$values = array();
$connectionExceptions = array();
foreach ($commands as $command) {
$cmdConnection = $connection->getConnection($command);
if (isset($connectionExceptions[spl_object_hash($cmdConnection)])) {
continue;
}
try {
$cmdConnection->writeCommand($command);
} catch (CommunicationException $exception) {
$connectionExceptions[spl_object_hash($cmdConnection)] = $exception;
}
}
for ($i = 0; $i < $size; $i++) {
$command = $commands->dequeue();
$cmdConnection = $connection->getConnection($command);
$connectionObjectHash = spl_object_hash($cmdConnection);
if (isset($connectionExceptions[$connectionObjectHash])) {
$values[$i] = $connectionExceptions[$connectionObjectHash];
continue;
}
try {
$response = $cmdConnection->readResponse($command);
$values[$i] = $response instanceof \Iterator ? iterator_to_array($response) : $response;
} catch (CommunicationException $exception) {
$values[$i] = $exception;
$connectionExceptions[$connectionObjectHash] = $exception;
}
}
return $values;
}
}

View file

@ -0,0 +1,57 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Pipeline;
use SplQueue;
use Predis\CommunicationException;
use Predis\Connection\ConnectionInterface;
/**
* Implements a pipeline executor strategy that does not fail when an error is
* encountered, but adds the returned error in the replies array.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class SafeExecutor implements PipelineExecutorInterface
{
/**
* {@inheritdoc}
*/
public function execute(ConnectionInterface $connection, SplQueue $commands)
{
$size = count($commands);
$values = array();
foreach ($commands as $command) {
try {
$connection->writeCommand($command);
} catch (CommunicationException $exception) {
return array_fill(0, $size, $exception);
}
}
for ($i = 0; $i < $size; $i++) {
$command = $commands->dequeue();
try {
$response = $connection->readResponse($command);
$values[$i] = $response instanceof \Iterator ? iterator_to_array($response) : $response;
} catch (CommunicationException $exception) {
$toAdd = count($commands) - count($values);
$values = array_merge($values, array_fill(0, $toAdd, $exception));
break;
}
}
return $values;
}
}

View file

@ -0,0 +1,123 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Pipeline;
use Iterator;
use SplQueue;
use Predis\ResponseErrorInterface;
use Predis\ResponseObjectInterface;
use Predis\ServerException;
use Predis\Command\CommandInterface;
use Predis\Connection\ConnectionInterface;
use Predis\Connection\ReplicationConnectionInterface;
/**
* Implements the standard pipeline executor strategy used
* to write a list of commands and read their replies over
* a connection to Redis.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class StandardExecutor implements PipelineExecutorInterface
{
protected $exceptions;
/**
* @param bool $exceptions Specifies if the executor should throw exceptions on server errors.
*/
public function __construct($exceptions = true)
{
$this->exceptions = (bool) $exceptions;
}
/**
* Allows the pipeline executor to perform operations on the
* connection before starting to execute the commands stored
* in the pipeline.
*
* @param ConnectionInterface $connection Connection instance.
*/
protected function checkConnection(ConnectionInterface $connection)
{
if ($connection instanceof ReplicationConnectionInterface) {
$connection->switchTo('master');
}
}
/**
* Handles a response object.
*
* @param ConnectionInterface $connection
* @param CommandInterface $command
* @param ResponseObjectInterface $response
* @return mixed
*/
protected function onResponseObject(ConnectionInterface $connection, CommandInterface $command, ResponseObjectInterface $response)
{
if ($response instanceof ResponseErrorInterface) {
return $this->onResponseError($connection, $response);
}
if ($response instanceof Iterator) {
return $command->parseResponse(iterator_to_array($response));
}
return $response;
}
/**
* Handles -ERR responses returned by Redis.
*
* @param ConnectionInterface $connection The connection that returned the error.
* @param ResponseErrorInterface $response The error response instance.
* @return ResponseErrorInterface
*/
protected function onResponseError(ConnectionInterface $connection, ResponseErrorInterface $response)
{
if (!$this->exceptions) {
return $response;
}
// Force disconnection to prevent protocol desynchronization.
$connection->disconnect();
$message = $response->getMessage();
throw new ServerException($message);
}
/**
* {@inheritdoc}
*/
public function execute(ConnectionInterface $connection, SplQueue $commands)
{
$this->checkConnection($connection);
foreach ($commands as $command) {
$connection->writeCommand($command);
}
$values = array();
while (!$commands->isEmpty()) {
$command = $commands->dequeue();
$response = $connection->readResponse($command);
if ($response instanceof ResponseObjectInterface) {
$values[] = $this->onResponseObject($connection, $command, $response);
} else {
$values[] = $command->parseResponse($response);
}
}
return $values;
}
}