code:
#!/usr/bin/env php
<?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.
 */

// -------------------------------------------------------------------------- //
// This script can be used to automatically generate a file with the scheleton
// of a test case to test a Redis command by specifying the name of the class
// in the Predis\Command namespace (only classes in this namespace are valid).
// For example, to generate a test case for SET (which is represented by the
// Predis\Command\StringSet class):
//
//   $ ./bin/generate-command-test --class=StringSet
//
// Here is a list of optional arguments:
//
// --realm: each command has its own realm (commands that operate on strings,
// lists, sets and such) but while this realm is usually inferred from the name
// of the specified class, sometimes it can be useful to override it with a
// custom one.
//
// --output: write the generated test case to the specified path instead of
// the default one.
//
// --overwrite: pre-existing test files are not overwritten unless this option
// is explicitly specified.
// -------------------------------------------------------------------------- //

use Predis\Command\CommandInterface;
use Predis\Command\PrefixableCommandInterface;

class CommandTestCaseGenerator
{
    private $options;

    public function __construct(array $options)
    {
        if (!isset($options['class'])) {
            throw new RuntimeException("Missing 'class' option.");
        }
        $this->options = $options;
    }

    public static function fromCommandLine()
    {
        $parameters = array(
            'c:'  => 'class:',
            'r::' => 'realm::',
            'o::' => 'output::',
            'x::' => 'overwrite::'
        );

        $getops = getopt(implode(array_keys($parameters)), $parameters);

        $options = array(
            'overwrite' => false,
            'tests' => __DIR__.'/../tests/Predis',
        );

        foreach ($getops as $option => $value) {
            switch ($option) {
                case 'c':
                case 'class':
                    $options['class'] = $value;
                    break;

                case 'r':
                case 'realm':
                    $options['realm'] = $value;
                    break;

                case 'o':
                case 'output':
                    $options['output'] = $value;
                    break;

                case 'x':
                case 'overwrite':
                    $options['overwrite'] = true;
                    break;
            }
        }

        if (!isset($options['class'])) {
            throw new RuntimeException("Missing 'class' option.");
        }

        $options['fqn'] = "Predis\\Command\\{$options['class']}";
        $options['path'] = "Command/{$options['class']}.php";

        $source = __DIR__.'/../src/'.$options['path'];
        if (!file_exists($source)) {
            throw new RuntimeException("Cannot find class file for {$options['fqn']} in $source.");
        }

        if (!isset($options['output'])) {
            $options['output'] = sprintf("%s/%s", $options['tests'], str_replace('.php', 'Test.php', $options['path']));
        }

        return new self($options);
    }

    protected function getTestRealm()
    {
        if (isset($this->options['realm'])) {
            if (!$this->options['realm']) {
                throw new RuntimeException('Invalid value for realm has been sepcified (empty).');
            }
            return $this->options['realm'];
        }

        $fqnParts = explode('\\', $this->options['fqn']);
        $class = array_pop($fqnParts);
        list($realm,) = preg_split('/([[:upper:]][[:lower:]]+)/', $class, 2, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);

        return strtolower($realm);
    }

    public function generate()
    {
        $reflection = new ReflectionClass($class = $this->options['fqn']);

        if (!$reflection->isInstantiable()) {
            throw new RuntimeException("Class $class must be instantiable, abstract classes or interfaces are not allowed.");
        }
        if (!$reflection->implementsInterface('Predis\Command\CommandInterface')) {
            throw new RuntimeException("Class $class must implement Predis\Command\CommandInterface.");
        }

        /*
         * @var CommandInterface
         */
        $instance = $reflection->newInstance();

        $buffer = $this->getTestCaseBuffer($instance);

        return $buffer;
    }

    public function save()
    {
        $options = $this->options;
        if (file_exists($options['output']) && !$options['overwrite']) {
            throw new RuntimeException("File {$options['output']} already exist. Specify the --overwrite option to overwrite the existing file.");
        }
        file_put_contents($options['output'], $this->generate());
    }

    protected function getTestCaseBuffer(CommandInterface $instance)
    {
        $id = $instance->getId();
        $fqn = get_class($instance);
        $fqnParts = explode('\\', $fqn);
        $class = array_pop($fqnParts) . "Test";
        $realm = $this->getTestRealm();

        $buffer =<<<PHP
<?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\Command;

/**
 * @group commands
 * @group realm-$realm
 */
class $class extends PredisCommandTestCase
{
    /**
     * {@inheritdoc}
     */
    protected function getExpectedCommand()
    {
        return '$fqn';
    }

    /**
     * {@inheritdoc}
     */
    protected function getExpectedId()
    {
        return '$id';
    }

    /**
     * @group disconnected
     */
    public function testFilterArguments()
    {
        \$this->markTestIncomplete('This test has not been implemented yet.');

        \$arguments = array(/* add arguments */);
        \$expected = array(/* add arguments */);

        \$command = \$this->getCommand();
        \$command->setArguments(\$arguments);

        \$this->assertSame(\$expected, \$command->getArguments());
    }

    /**
     * @group disconnected
     */
    public function testParseResponse()
    {
        \$this->markTestIncomplete('This test has not been implemented yet.');

        \$raw = null;
        \$expected = null;

        \$command = \$this->getCommand();

        \$this->assertSame(\$expected, \$command->parseResponse(\$raw));
    }

PHP;

        if ($instance instanceof PrefixableCommandInterface) {
            $buffer .=<<<PHP

    /**
     * @group disconnected
     */
    public function testPrefixKeys()
    {
        \$this->markTestIncomplete('This test has not been implemented yet.');

        \$arguments = array(/* add arguments */);
        \$expected = array(/* add arguments */);

        \$command = \$this->getCommandWithArgumentsArray(\$arguments);
        \$command->prefixKeys('prefix:');

        \$this->assertSame(\$expected, \$command->getArguments());
    }

    /**
     * @group disconnected
     */
    public function testPrefixKeysIgnoredOnEmptyArguments()
    {
        \$command = \$this->getCommand();
        \$command->prefixKeys('prefix:');

        \$this->assertSame(array(), \$command->getArguments());
    }

PHP;
        }

        return "$buffer}\n";
    }
}

// ------------------------------------------------------------------------- //

require __DIR__.'/../autoload.php';

$generator = CommandTestCaseGenerator::fromCommandLine();
$generator->save();


code:
#!/usr/bin/env php
<?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.
 */

// -------------------------------------------------------------------------- //
// In order to be able to execute this script to create a PEAR package of Predis
// the `pear` binary must be available and executable in your $PATH.
// The parts used to parse author and version strings are taken from Onion (used
// by this library in the past) just to keep on relying on the package.ini file
// to simplify things. We might consider to switch to using the PEAR classes
// directly in the future.
// -------------------------------------------------------------------------- //

function executeWithBackup($file, $callback)
{
    $exception = null;
    $backup = "$file.backup";

    copy($file, $backup);

    try {
        call_user_func($callback, $file);
    } catch (Exception $exception) {
        // NOOP
    }

    unlink($file);
    rename($backup, $file);

    if ($exception) {
        throw $exception;
    }
}

function parseAuthor($string)
{
    $author = array();

    if (preg_match('/^\s*(.+?)\s*(?:"(\S+)"\s*)?<(\S+)>\s*$/x', $string , $regs)) {
        if (count($regs) == 4) {
            list($_,$name,$user,$email) = $regs;
            $author['name'] = $name;
            $author['user'] = $user;
            $author['email'] = $email;
        } elseif (count($regs) == 3) {
            list($_,$name,$email) = $regs;
            $author['name'] = $name;
            $author['email'] = $email;
        }
    } else {
        $author['name'] = $string;
    }

    return $author;
}

function parseVersion($string)
{
    $version_pattern = '([0-9.]+)';

    if (preg_match("/^\s*$version_pattern\s*\$/x", $string, $regs)) {
        return array('min' => $regs[1] ?: '0.0.0');
    } elseif (preg_match("/^\s*[>=]+\s*$version_pattern\s*\$/x", $string, $regs)) {
        return array('min' => $regs[1] ?: '0.0.0');
    } elseif (preg_match("/^\s*[<=]+\s*$version_pattern\s*\$/x", $string, $regs)) {
        return array('max' => $regs[1]);
    } elseif (preg_match("/^\s*$version_pattern\s*<=>\s*$version_pattern\s*\$/x", $string, $regs)) {
        return array(
            'min' => $regs[1] ?: '0.0.0',
            'max' => $regs[2],
        );
    }

    return null;
}

function addRolePath($pkg, $path, $role)
{
    if (is_dir($path)) {
        $dirRoot = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
        $dirTree = new RecursiveIteratorIterator($dirRoot, RecursiveIteratorIterator::CHILD_FIRST);

        foreach ($dirTree as $fileinfo) {
            if ($fileinfo->isFile()) {
                addPackageFile($pkg, $fileinfo, $role, $path);
            }
        }
    } else {
        foreach (glob($path) as $filename) {
            addPackageFile($pkg, new SplFileInfo($filename), $role);
        }
    }
}

function addPackageFile($pkg, $fileinfo, $role, $baseDir = '')
{
    $fileNode = $pkg->contents->dir->addChild('file');
    $fileNode->addAttribute('name', $filepath = $fileinfo->getPathname());
    $fileNode->addAttribute('role', $role);
    $fileNode->addAttribute('md5sum', md5_file($filepath));

    $installNode = $pkg->phprelease->filelist->addChild('install');
    $installNode->addAttribute('name', $filepath);
    $installNode->addAttribute('as', !$baseDir ? basename($filepath) : substr($filepath, strlen($baseDir) + 1));
}

function generatePackageXml($packageINI)
{
    $XML = <<<XML
<?xml version="1.0"?>
<package packagerversion="1.4.10" version="2.0"
    xmlns="http://pear.php.net/dtd/package-2.0"
    xmlns:tasks="http://pear.php.net/dtd/tasks-1.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
              http://pear.php.net/dtd/tasks-1.0.xsd
              http://pear.php.net/dtd/package-2.0
              http://pear.php.net/dtd/package-2.0.xsd" />
XML;

    $cfg = parse_ini_file($packageINI, true);
    $pkg = new SimpleXMLElement($XML);

    $pkg->name = $cfg['package']['name'];
    $pkg->channel = $cfg['package']['channel'];
    $pkg->summary = $cfg['package']['desc'];
    $pkg->description = $cfg['package']['desc'];

    $author = parseAuthor($cfg['package']['author']);
    $pkg->addChild('lead');
    $pkg->lead->name = $author['name'];
    $pkg->lead->user = $author['user'];
    $pkg->lead->email = $author['email'];
    $pkg->lead->active = 'yes';

    $datetime = new DateTime('now');
    $pkg->date = $datetime->format('Y-m-d');
    $pkg->time = $datetime->format('H:i:s');

    $pkg->addChild('version');
    $pkg->version->release = $cfg['package']['version'];
    $pkg->version->api = $cfg['package']['version'];

    $pkg->addChild('stability');
    $pkg->stability->release = $cfg['package']['stability'];
    $pkg->stability->api = $cfg['package']['stability'];

    $pkg->license = $cfg['package']['license'];
    $pkg->notes = '-';

    $pkg->addChild('contents')->addChild('dir')->addAttribute('name', '/');

    $pkg->addChild('dependencies')->addChild('required');
    foreach ($cfg['require'] as $required => $version) {
        $version = parseVersion($version);
        $pkg->dependencies->required->addChild($required);

        if (isset($version['min'])) {
            $pkg->dependencies->required->$required->min = $version['min'];
        }
        if (isset($version['max'])) {
            $pkg->dependencies->required->$required->min = $version['max'];
        }
    }

    $pkg->addChild('phprelease')->addChild('filelist');

    $pathToRole = array(
        'doc' => 'doc', 'docs' => 'doc', 'examples' => 'doc',
        'lib' => 'php', 'src' => 'php',
        'test' => 'test', 'tests' => 'test',
    );

    foreach (array_merge($pathToRole, $cfg['roles'] ?: array()) as $path => $role) {
        addRolePath($pkg, $path, $role);
    }

    return $pkg;
}

function rewritePackageInstallAs($pkg)
{
    foreach ($pkg->phprelease->filelist->install as $file) {
        if (preg_match('/^src\//', $file['name'])) {
            $file['as'] = "Predis/{$file['as']}";
        }
    }
}

function savePackageXml($xml)
{
    $dom = new DOMDocument("1.0");
    $dom->preserveWhiteSpace = false;
    $dom->formatOutput = true;
    $dom->loadXML($xml->asXML());

    file_put_contents('package.xml', $dom->saveXML());
}

function buildPackage()
{
    passthru('pear -q package && rm package.xml');
}

function modifyPhpunitXml($file)
{
    $cfg = new SimpleXMLElement($file, null, true);

    $cfg[0]['bootstrap'] = str_replace('tests/', '', $cfg[0]['bootstrap']);
    $cfg->testsuites->testsuite->directory = str_replace('tests/', '', $cfg->testsuites->testsuite->directory);

    $cfg->saveXml($file);
}

// -------------------------------------------------------------------------- //

executeWithBackup(__DIR__.'/../phpunit.xml.dist', function ($file) {
    modifyPhpunitXml($file);

    $pkg = generatePackageXml('package.ini');
    rewritePackageInstallAs($pkg);
    savePackageXml($pkg);

    buildPackage();
});

repo_name: spookie/gnu

path: vendor/predis/predis/bin/create-pear

license: mit


code:
#!/usr/bin/env php
<?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.
 */

// -------------------------------------------------------------------------- //
// In order to be able to execute this script to create a Phar archive of Predis,
// the Phar module must be loaded and the "phar.readonly" directive php.ini must
// be set to "off". You can change the values in the $options array to customize
// the creation of the Phar archive to better suit your needs.
// -------------------------------------------------------------------------- //

$options = array(
    'name'           => 'predis',
    'project_path'   => __DIR__ . '/../src',
    'compression'    => Phar::NONE,
    'append_version' => true,
);

function getPharFilename($options)
{
    $filename = $options['name'];

    // NOTE: do not consider "append_version" with Phar compression do to a bug in
    // Phar::compress() when renaming phar archives containing dots in their name.
    if ($options['append_version'] && $options['compression'] === Phar::NONE) {
        $versionFile = @fopen(__DIR__ . '/../VERSION', 'r');

        if ($versionFile === false) {
            throw new Exception("Could not locate the VERSION file.");
        }

        $version = trim(fgets($versionFile));
        fclose($versionFile);
        $filename .= "_$version";
    }

    return "$filename.phar";
}

function getPharStub($options)
{
    return <<<EOSTUB
<?php
Phar::mapPhar('predis.phar');
spl_autoload_register(function (\$class) {
    if (strpos(\$class, 'Predis\\\\') === 0) {
        \$file = 'phar://predis.phar/'.strtr(substr(\$class, 7), '\\\', '/').'.php';
        if (file_exists(\$file)) {
            require \$file;
            return true;
        }
    }
});
__HALT_COMPILER();
EOSTUB;
}

// -------------------------------------------------------------------------- //

$phar = new Phar(getPharFilename($options));
$phar->compress($options['compression']);
$phar->setStub(getPharStub($options));
$phar->buildFromDirectory($options['project_path']);

path: vendor/predis/predis/bin/create-phar


code:
#!/usr/bin/env php
<?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.
 */

// -------------------------------------------------------------------------- //
// This script can be used to automatically glue all the .php files of Predis
// into a single monolithic script file that can be used without an autoloader,
// just like the other previous versions of the library.
//
// Much of its complexity is due to the fact that we cannot simply join PHP
// files, but namespaces and classes definitions must follow a precise order
// when dealing with subclassing and inheritance.
//
// The current implementation is pretty naïve, but it should do for now.
// -------------------------------------------------------------------------- //

class CommandLine
{
    public static function getOptions()
    {
        $parameters = array(
            's:' => 'source:',
            'o:' => 'output:',
            'e:' => 'exclude:',
            'E:' => 'exclude-classes:',
        );

        $getops = getopt(implode(array_keys($parameters)), $parameters);

        $options = array(
            'source'  => __DIR__ . "/../src",
            'output'  => PredisFile::NS_ROOT . '.php',
            'exclude' => array(),
        );

        foreach ($getops as $option => $value) {
            switch ($option) {
                case 's':
                case 'source':
                    $options['source'] = $value;
                    break;

                case 'o':
                case 'output':
                    $options['output'] = $value;
                    break;

                case 'E':
                case 'exclude-classes':
                    $options['exclude'] = @file($value, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: $value;
                    break;

                case 'e':
                case 'exclude':
                    $options['exclude'] = is_array($value) ? $value : array($value);
                    break;
            }
        }

        return $options;
    }
}

class PredisFile
{
    const NS_ROOT = 'Predis';

    private $namespaces;

    public function __construct()
    {
        $this->namespaces = array();
    }

    public static function from($libraryPath, array $exclude = array())
    {
        $predisFile = new PredisFile();
        $libIterator = new RecursiveDirectoryIterator($libraryPath);

        foreach (new RecursiveIteratorIterator($libIterator) as $classFile)
        {
            if (!$classFile->isFile()) {
                continue;
            }

            $namespace = self::NS_ROOT.strtr(str_replace($libraryPath, '', $classFile->getPath()), '/', '\\');

            if (in_array(sprintf('%s\\%s', $namespace, $classFile->getBasename('.php')), $exclude)) {
                continue;
            }

            $phpNamespace = $predisFile->getNamespace($namespace);

            if ($phpNamespace === false) {
                $phpNamespace = new PhpNamespace($namespace);
                $predisFile->addNamespace($phpNamespace);
            }

            $phpClass = new PhpClass($phpNamespace, $classFile);
        }

        return $predisFile;
    }

    public function addNamespace(PhpNamespace $namespace)
    {
        if (isset($this->namespaces[(string)$namespace])) {
            throw new InvalidArgumentException("Duplicated namespace");
        }
        $this->namespaces[(string)$namespace] = $namespace;
    }

    public function getNamespaces()
    {
        return $this->namespaces;
    }

    public function getNamespace($namespace)
    {
        if (!isset($this->namespaces[$namespace])) {
            return false;
        }

        return $this->namespaces[$namespace];
    }

    public function getClassByFQN($classFqn)
    {
        if (($nsLastPos = strrpos($classFqn, '\\')) !== false) {
            $namespace = $this->getNamespace(substr($classFqn, 0, $nsLastPos));
            if ($namespace === false) {
                return null;
            }
            $className = substr($classFqn, $nsLastPos + 1);

            return $namespace->getClass($className);
        }

        return null;
    }

    private function calculateDependencyScores(&$classes, $fqn)
    {
        if (!isset($classes[$fqn])) {
            $classes[$fqn] = 0;
        }

        $classes[$fqn] += 1;

        if (($phpClass = $this->getClassByFQN($fqn)) === null) {
            throw new RuntimeException(
                "Cannot found the class $fqn which is required by other subclasses. Are you missing a file?"
            );
        }

        foreach ($phpClass->getDependencies() as $fqn) {
            $this->calculateDependencyScores($classes, $fqn);
        }
    }

    private function getDependencyScores()
    {
        $classes = array();

        foreach ($this->getNamespaces() as $phpNamespace) {
            foreach ($phpNamespace->getClasses() as $phpClass) {
                $this->calculateDependencyScores($classes, $phpClass->getFQN());
            }
        }

        return $classes;
    }

    private function getOrderedNamespaces($dependencyScores)
    {
        $namespaces = array_fill_keys(array_unique(
            array_map(
                function ($fqn) { return PhpNamespace::extractName($fqn); },
                array_keys($dependencyScores)
            )
        ), 0);

        foreach ($dependencyScores as $classFqn => $score) {
            $namespaces[PhpNamespace::extractName($classFqn)] += $score;
        }

        arsort($namespaces);

        return array_keys($namespaces);
    }

    private function getOrderedClasses(PhpNamespace $phpNamespace, $classes)
    {
        $nsClassesFQNs = array_map(function ($cl) { return $cl->getFQN(); }, $phpNamespace->getClasses());
        $nsOrderedClasses = array();

        foreach ($nsClassesFQNs as $nsClassFQN) {
            $nsOrderedClasses[$nsClassFQN] = $classes[$nsClassFQN];
        }

        arsort($nsOrderedClasses);

        return array_keys($nsOrderedClasses);
    }

    public function getPhpCode()
    {
        $buffer = array("<?php\n\n", PhpClass::LICENSE_HEADER, "\n\n");
        $classes = $this->getDependencyScores();
        $namespaces = $this->getOrderedNamespaces($classes);

        foreach ($namespaces as $namespace) {
            $phpNamespace = $this->getNamespace($namespace);

            // generate namespace directive
            $buffer[] = $phpNamespace->getPhpCode();
            $buffer[] = "\n";

            // generate use directives
            $useDirectives = $phpNamespace->getUseDirectives();
            if (count($useDirectives) > 0) {
                $buffer[] = $useDirectives->getPhpCode();
                $buffer[] = "\n";
            }

            // generate classes bodies
            $nsClasses = $this->getOrderedClasses($phpNamespace, $classes);
            foreach ($nsClasses as $classFQN) {
                $buffer[] = $this->getClassByFQN($classFQN)->getPhpCode();
                $buffer[] = "\n\n";
            }

            $buffer[] = "/* " . str_repeat("-", 75) . " */";
            $buffer[] = "\n\n";
        }

        return implode($buffer);
    }

    public function saveTo($outputFile)
    {
        // TODO: add more sanity checks
        if ($outputFile === null || $outputFile === '') {
            throw new InvalidArgumentException('You must specify a valid output file');
        }
        file_put_contents($outputFile, $this->getPhpCode());
    }
}

class PhpNamespace implements IteratorAggregate
{
    private $namespace;
    private $classes;

    public function __construct($namespace)
    {
        $this->namespace = $namespace;
        $this->classes = array();
        $this->useDirectives = new PhpUseDirectives($this);
    }

    public static function extractName($fqn)
    {
        $nsSepLast = strrpos($fqn, '\\');
        if ($nsSepLast === false) {
            return $fqn;
        }
        $ns = substr($fqn, 0, $nsSepLast);

        return $ns !== '' ? $ns : null;
    }

    public function addClass(PhpClass $class)
    {
        $this->classes[$class->getName()] = $class;
    }

    public function getClass($className)
    {
        if (isset($this->classes[$className])) {
            return $this->classes[$className];
        }
    }

    public function getClasses()
    {
        return array_values($this->classes);
    }

    public function getIterator()
    {
        return new \ArrayIterator($this->getClasses());
    }

    public function getUseDirectives()
    {
        return $this->useDirectives;
    }

    public function getPhpCode()
    {
        return "namespace $this->namespace;\n";
    }

    public function __toString()
    {
        return $this->namespace;
    }
}

class PhpUseDirectives implements Countable, IteratorAggregate
{
    private $use;
    private $aliases;
    private $reverseAliases;
    private $namespace;

    public function __construct(PhpNamespace $namespace)
    {
        $this->namespace = $namespace;
        $this->use = array();
        $this->aliases = array();
        $this->reverseAliases = array();
    }

    public function add($use, $as = null)
    {
        if (in_array($use, $this->use)) {
            return;
        }

        $rename = null;
        $this->use[] = $use;
        $aliasedClassName = $as ?: PhpClass::extractName($use);

        if (isset($this->aliases[$aliasedClassName])) {
            $parentNs = $this->getParentNamespace();

            if ($parentNs && false !== $pos = strrpos($parentNs, '\\')) {
                $parentNs = substr($parentNs, $pos);
            }

            $newAlias = "{$parentNs}_{$aliasedClassName}";
            $rename = (object) array(
                'namespace' => $this->namespace,
                'from' => $aliasedClassName,
                'to' => $newAlias,
            );

            $this->aliases[$newAlias] = $use;
            $as = $newAlias;
        } else {
            $this->aliases[$aliasedClassName] = $use;
        }

        if ($as !== null) {
            $this->reverseAliases[$use] = $as;
        }

        return $rename;
    }

    public function getList()
    {
        return $this->use;
    }

    public function getIterator()
    {
        return new \ArrayIterator($this->getList());
    }

    public function getPhpCode()
    {
        $reverseAliases = $this->reverseAliases;

        $reducer = function ($str, $use) use ($reverseAliases) {
            if (isset($reverseAliases[$use])) {
                return $str .= "use $use as {$reverseAliases[$use]};\n";
            } else {
                return $str .= "use $use;\n";
            }
        };

        return array_reduce($this->getList(), $reducer, '');
    }

    public function getNamespace()
    {
        return $this->namespace;
    }

    public function getParentNamespace()
    {
        if (false !== $pos = strrpos($this->namespace, '\\')) {
            return substr($this->namespace, 0, $pos);
        }

        return '';
    }

    public function getFQN($className)
    {
        if (($nsSepFirst = strpos($className, '\\')) === false) {
            if (isset($this->aliases[$className])) {
                return $this->aliases[$className];
            }

            return (string)$this->getNamespace() . "\\$className";
        }

        if ($nsSepFirst != 0) {
            throw new InvalidArgumentException("Partially qualified names are not supported");
        }

        return $className;
    }

    public function count()
    {
        return count($this->use);
    }
}

class PhpClass
{
    const LICENSE_HEADER = <<<LICENSE
/*
 * 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.
 */
LICENSE;

    private $namespace;
    private $file;
    private $body;
    private $implements;
    private $extends;
    private $name;

    public function __construct(PhpNamespace $namespace, SplFileInfo $classFile)
    {
        $this->namespace = $namespace;
        $this->file = $classFile;
        $this->implements = array();
        $this->extends = array();

        $this->extractData();
        $namespace->addClass($this);
    }

    public static function extractName($fqn)
    {
        $nsSepLast = strrpos($fqn, '\\');
        if ($nsSepLast === false) {
            return $fqn;
        }

        return substr($fqn, $nsSepLast + 1);
    }

    private function extractData()
    {
        $renames = array();
        $useDirectives = $this->getNamespace()->getUseDirectives();

        $useExtractor = function ($m) use ($useDirectives, &$renames) {
            array_shift($m);

            if (isset($m[1])) {
                $m[1] = str_replace(" as ", '', $m[1]);
            }

            if ($rename = call_user_func_array(array($useDirectives, 'add'), $m)) {
                $renames[] = $rename;
            }
        };

        $classBuffer = stream_get_contents(fopen($this->getFile()->getPathname(), 'r'));

        $classBuffer = str_replace(self::LICENSE_HEADER, '', $classBuffer);

        $classBuffer = preg_replace('/<\?php\s?\\n\s?/', '', $classBuffer);
        $classBuffer = preg_replace('/\s?\?>\n?/ms', '', $classBuffer);
        $classBuffer = preg_replace('/namespace\s+[\w\d_\\\\]+;\s?/', '', $classBuffer);
        $classBuffer = preg_replace_callback('/use\s+([\w\d_\\\\]+)(\s+as\s+.*)?;\s?\n?/', $useExtractor, $classBuffer);

        foreach ($renames as $rename) {
            $classBuffer = str_replace($rename->from, $rename->to, $classBuffer);
        }

        $this->body = trim($classBuffer);

        $this->extractHierarchy();
    }

    private function extractHierarchy()
    {
        $implements = array();
        $extends =  array();

        $extractor = function ($iterator, $callback) {
            $className = '';
            $iterator->seek($iterator->key() + 1);

            while ($iterator->valid()) {
                $token = $iterator->current();

                if (is_string($token)) {
                    if (preg_match('/\s?,\s?/', $token)) {
                        $callback(trim($className));
                        $className = '';
                    } else if ($token == '{') {
                        $callback(trim($className));
                        return;
                    }
                }

                switch ($token[0]) {
                    case T_NS_SEPARATOR:
                        $className .= '\\';
                        break;

                    case T_STRING:
                        $className .= $token[1];
                        break;

                    case T_IMPLEMENTS:
                    case T_EXTENDS:
                        $callback(trim($className));
                        $iterator->seek($iterator->key() - 1);
                        return;
                }

                $iterator->next();
            }
        };

        $tokens = token_get_all("<?php\n" . trim($this->getPhpCode()));
        $iterator = new ArrayIterator($tokens);

        while ($iterator->valid()) {
            $token = $iterator->current();
            if (is_string($token)) {
                $iterator->next();
                continue;
            }

            switch ($token[0]) {
                case T_CLASS:
                case T_INTERFACE:
                    $iterator->seek($iterator->key() + 2);
                    $tk = $iterator->current();
                    $this->name = $tk[1];
                    break;

                case T_IMPLEMENTS:
                    $extractor($iterator, function ($fqn) use (&$implements) {
                        $implements[] = $fqn;
                    });
                    break;

                case T_EXTENDS:
                    $extractor($iterator, function ($fqn) use (&$extends) {
                        $extends[] = $fqn;
                    });
                    break;
            }

            $iterator->next();
        }

        $this->implements = $this->guessFQN($implements);
        $this->extends = $this->guessFQN($extends);
    }

    public function guessFQN($classes)
    {
        $useDirectives = $this->getNamespace()->getUseDirectives();
        return array_map(array($useDirectives, 'getFQN'), $classes);
    }

    public function getImplementedInterfaces($all = false)
    {
        if ($all) {
            return $this->implements;
        }

        return array_filter(
            $this->implements,
            function ($cn) { return strpos($cn, 'Predis\\') === 0; }
        );
    }

    public function getExtendedClasses($all = false)
    {
        if ($all) {
            return $this->extemds;
        }

        return array_filter(
            $this->extends,
            function ($cn) { return strpos($cn, 'Predis\\') === 0; }
        );
    }

    public function getDependencies($all = false)
    {
        return array_merge(
            $this->getImplementedInterfaces($all),
            $this->getExtendedClasses($all)
        );
    }

    public function getNamespace()
    {
        return $this->namespace;
    }

    public function getFile()
    {
        return $this->file;
    }

    public function getName()
    {
        return $this->name;
    }

    public function getFQN()
    {
        return (string)$this->getNamespace() . '\\' . $this->name;
    }

    public function getPhpCode()
    {
        return $this->body;
    }

    public function __toString()
    {
        return "class " . $this->getName() . '{ ... }';
    }
}

/* -------------------------------------------------------------------------- */

$options = CommandLine::getOptions();
$predisFile = PredisFile::from($options['source'], $options['exclude']);
$predisFile->saveTo($options['output']);


predis/predis

type: library

description: Flexible and feature-complete Redis client for PHP and HHVM

keywords: nosql redis predis

license: MIT

ext-phpiredis: Allows faster serialization and deserialization of the Redis protocol

ext-curl: Allows access to Webdis when paired with phpiredis

Predis\: src/


code:
<?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.
 */

require __DIR__.'/shared.php';

// Developers can implement Predis\Distribution\DistributorInterface to create
// their own distributors used by the client to distribute keys among a cluster
// of servers.

use Predis\Cluster\Distributor\DistributorInterface;
use Predis\Cluster\Hash\HashGeneratorInterface;
use Predis\Cluster\PredisStrategy;
use Predis\Connection\Aggregate\PredisCluster;

class NaiveDistributor implements DistributorInterface, HashGeneratorInterface
{
    private $nodes;
    private $nodesCount;

    public function __construct()
    {
        $this->nodes = array();
        $this->nodesCount = 0;
    }

    public function add($node, $weight = null)
    {
        $this->nodes[] = $node;
        ++$this->nodesCount;
    }

    public function remove($node)
    {
        $this->nodes = array_filter($this->nodes, function ($n) use ($node) {
            return $n !== $node;
        });

        $this->nodesCount = count($this->nodes);
    }

    public function getSlot($hash)
    {
        return $this->nodesCount > 1 ? abs($hash % $this->nodesCount) : 0;
    }

    public function getBySlot($slot)
    {
        return isset($this->nodes[$slot]) ? $this->nodes[$slot] : null;
    }

    public function getByHash($hash)
    {
        if (!$this->nodesCount) {
            throw new RuntimeException('No connections.');
        }

        $slot = $this->getSlot($hash);
        $node = $this->getBySlot($slot);

        return $node;
    }

    public function get($value)
    {
        $hash = $this->hash($value);
        $node = $this->getByHash($hash);

        return $node;
    }

    public function hash($value)
    {
        return crc32($value);
    }

    public function getHashGenerator()
    {
        return $this;
    }
}

$options = array(
    'cluster' => function () {
        $distributor = new NaiveDistributor();
        $strategy = new PredisStrategy($distributor);
        $cluster = new PredisCluster($strategy);

        return $cluster;
    },
);

$client = new Predis\Client($multiple_servers, $options);

for ($i = 0; $i < 100; ++$i) {
    $client->set("key:$i", str_pad($i, 4, '0', 0));
    $client->get("key:$i");
}

$server1 = $client->getClientFor('first')->info();
$server2 = $client->getClientFor('second')->info();

if (isset($server1['Keyspace'], $server2['Keyspace'])) {
    $server1 = $server1['Keyspace'];
    $server2 = $server2['Keyspace'];
}

printf("Server '%s' has %d keys while server '%s' has %d keys.\n",
    'first', $server1['db15']['keys'], 'second', $server2['db15']['keys']
);


code:
<?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.
 */

require __DIR__.'/shared.php';

// This is an example of how you can easily extend an existing connection class
// and trace the execution of commands for debugging purposes. This can be quite
// useful as a starting poing to understand how your application interacts with
// Redis.

use Predis\Command\CommandInterface;
use Predis\Connection\StreamConnection;

class SimpleDebuggableConnection extends StreamConnection
{
    private $tstart = 0;
    private $debugBuffer = array();

    public function connect()
    {
        $this->tstart = microtime(true);

        parent::connect();
    }

    private function storeDebug(CommandInterface $command, $direction)
    {
        $firtsArg = $command->getArgument(0);
        $timestamp = round(microtime(true) - $this->tstart, 4);

        $debug = $command->getId();
        $debug .= isset($firtsArg) ? " $firtsArg " : ' ';
        $debug .= "$direction $this";
        $debug .= " [{$timestamp}s]";

        $this->debugBuffer[] = $debug;
    }

    public function writeRequest(CommandInterface $command)
    {
        parent::writeRequest($command);

        $this->storeDebug($command, '->');
    }

    public function readResponse(CommandInterface $command)
    {
        $response = parent::readResponse($command);
        $this->storeDebug($command, '<-');

        return $response;
    }

    public function getDebugBuffer()
    {
        return $this->debugBuffer;
    }
}

$options = array(
    'connections' => array(
        'tcp' => 'SimpleDebuggableConnection',
    ),
);

$client = new Predis\Client($single_server, $options);
$client->set('foo', 'bar');
$client->get('foo');
$client->info();

var_export($client->getConnection()->getDebugBuffer());

/* OUTPUT:
array (
  0 => 'SELECT 15 -> 127.0.0.1:6379 [0.0008s]',
  1 => 'SELECT 15 <- 127.0.0.1:6379 [0.001s]',
  2 => 'SET foo -> 127.0.0.1:6379 [0.001s]',
  3 => 'SET foo <- 127.0.0.1:6379 [0.0011s]',
  4 => 'GET foo -> 127.0.0.1:6379 [0.0013s]',
  5 => 'GET foo <- 127.0.0.1:6379 [0.0015s]',
  6 => 'INFO -> 127.0.0.1:6379 [0.0019s]',
  7 => 'INFO <- 127.0.0.1:6379 [0.0022s]',
)
*/


code:
<?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.
 */

require __DIR__.'/shared.php';

// This is a basic example on how to use the Predis\DispatcherLoop class.
//
// To see this example in action you can just use redis-cli and publish some
// messages to the 'events' and 'control' channel, e.g.:

// ./redis-cli
// PUBLISH events first
// PUBLISH events second
// PUBLISH events third
// PUBLISH control terminate_dispatcher

// Create a client and disable r/w timeout on the socket
$client = new Predis\Client($single_server + array('read_write_timeout' => 0));

// Return an initialized PubSub consumer instance from the client.
$pubsub = $client->pubSubLoop();

// Create a dispatcher loop instance and attach a bunch of callbacks.
$dispatcher = new Predis\PubSub\DispatcherLoop($pubsub);

// Demonstrate how to use a callable class as a callback for the dispatcher loop.
class EventsListener implements Countable
{
    private $events;

    public function __construct()
    {
        $this->events = array();
    }

    public function count()
    {
        return count($this->events);
    }

    public function getEvents()
    {
        return $this->events;
    }

    public function __invoke($payload)
    {
        $this->events[] = $payload;
    }
}

// Attach our callable class to the dispatcher.
$dispatcher->attachCallback('events', ($events = new EventsListener()));

// Attach a function to control the dispatcher loop termination with a message.
$dispatcher->attachCallback('control', function ($payload) use ($dispatcher) {
    if ($payload === 'terminate_dispatcher') {
        $dispatcher->stop();
    }
});

// Run the dispatcher loop until the callback attached to the 'control' channel
// receives 'terminate_dispatcher' as a message.
$dispatcher->run();

// Display our achievements!
echo "We received {$events->count()} messages!", PHP_EOL;

// Say goodbye :-)
$version = redis_version($client->info());
echo "Goodbye from Redis $version!", PHP_EOL;


code:
<?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.
 */

require __DIR__.'/shared.php';

$client = new Predis\Client($single_server);

// Plain old SET and GET example...
$client->set('library', 'predis');
$response = $client->get('library');

var_export($response); echo PHP_EOL;
/* OUTPUT: 'predis' */

// Redis has the MSET and MGET commands to set or get multiple keys in one go,
// cases like this Predis accepts arguments for variadic commands both as a list
// of arguments or an array containing all of the keys and/or values.
$mkv = array(
    'uid:0001' => '1st user',
    'uid:0002' => '2nd user',
    'uid:0003' => '3rd user',
);

$client->mset($mkv);
$response = $client->mget(array_keys($mkv));

var_export($response); echo PHP_EOL;
/* OUTPUT:
array (
  0 => '1st user',
  1 => '2nd user',
  2 => '3rd user',
) */

// Predis can also send "raw" commands to Redis. The difference between sending
// commands to Redis the usual way and the "raw" way is that in the latter case
// their arguments are not filtered nor responses coming from Redis are parsed.

$response = $client->executeRaw(array(
    'MGET', 'uid:0001', 'uid:0002', 'uid:0003',
));

var_export($response); echo PHP_EOL;
/* OUTPUT:
array (
  0 => '1st user',
  1 => '2nd user',
  2 => '3rd user',
) */


code:
<?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.
 */

require __DIR__.'/shared.php';

// Predis can prefix keys found in commands arguments before sending commands to
// Redis, even for complex commands such as SORT, ZUNIONSTORE and ZINTERSTORE.
// Prefixing keys can be useful to create user-level namespaces for you keyspace
// thus reducing the need for separate logical databases in certain scenarios.

$client = new Predis\Client($single_server, array('prefix' => 'nrk:'));

$client->mset(array('foo' => 'bar', 'lol' => 'wut'));
var_export($client->mget('foo', 'lol'));
/*
array (
  0 => 'bar',
  1 => 'wut',
)
*/

var_export($client->keys('*'));
/*
array (
  0 => 'nrk:foo',
  1 => 'nrk:lol',
)
*/


code:
<?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.
 */

require __DIR__.'/shared.php';

// This example will not work with versions of Redis < 2.6.
//
// Additionally to the EVAL command defined in the current development profile,
// the Predis\Command\ScriptCommand class can be used to build an higher level
// abstraction for "scriptable" commands so that they will appear just like any
// other command on the client-side. This is a quick example used to implement
// INCREX.

use Predis\Command\ScriptCommand;

class IncrementExistingKeysBy extends ScriptCommand
{
    public function getKeysCount()
    {
        // Tell Predis to use all the arguments but the last one as arguments
        // for KEYS. The last one will be used to populate ARGV.
        return -1;
    }

    public function getScript()
    {
        return <<<LUA
local cmd, insert = redis.call, table.insert
local increment, results = ARGV[1], { }

for idx, key in ipairs(KEYS) do
  if cmd('exists', key) == 1 then
    insert(results, idx, cmd('incrby', key, increment))
  else
    insert(results, idx, false)
  end
end

return results
LUA;
    }
}

$client = new Predis\Client($single_server, array(
    'profile' => function ($options) {
        $profile = $options->getDefault('profile');
        $profile->defineCommand('increxby', 'IncrementExistingKeysBy');

        return $profile;
    },
));

$client->mset('foo', 10, 'foobar', 100);

var_export($client->increxby('foo', 'foofoo', 'foobar', 50));

/*
array (
  0 => 60,
  1 => NULL,
  2 => 150,
)
*/


code:
<?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.
 */

require __DIR__.'/shared.php';

// This is a basic example on how to use the Predis\Monitor\Consumer class. You
// can use redis-cli to send commands to the same Redis instance your client is
// connected to, and then type "ECHO QUIT_MONITOR" in redis-cli when you want to
// exit the monitor loop and terminate this script in a graceful way.

// Create a client and disable r/w timeout on the socket.
$client = new Predis\Client($single_server + array('read_write_timeout' => 0));

// Use only one instance of DateTime, we will update the timestamp later.
$timestamp = new DateTime();

foreach (($monitor = $client->monitor()) as $event) {
    $timestamp->setTimestamp((int) $event->timestamp);

    // If we notice a ECHO command with the message QUIT_MONITOR, we stop the
    // monitor consumer and then break the loop.
    if ($event->command === 'ECHO' && $event->arguments === '"QUIT_MONITOR"') {
        echo 'Exiting the monitor loop...', PHP_EOL;
        $monitor->stop();
        break;
    }

    echo "* Received {$event->command} on DB {$event->database} at {$timestamp->format(DateTime::W3C)}", PHP_EOL;
    if (isset($event->arguments)) {
        echo "    Arguments: {$event->arguments}", PHP_EOL;
    }
}

// Say goodbye :-)
$version = redis_version($client->info());
echo "Goodbye from Redis $version!", PHP_EOL;


code:
<?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.
 */

require __DIR__.'/shared.php';

// When you have a whole set of consecutive commands to send to a redis server,
// you can use a pipeline to dramatically improve performances. Pipelines can
// greatly reduce the effects of network round-trips.

$client = new Predis\Client($single_server);

$responses = $client->pipeline(function ($pipe) {
    $pipe->flushdb();
    $pipe->incrby('counter', 10);
    $pipe->incrby('counter', 30);
    $pipe->exists('counter');
    $pipe->get('counter');
    $pipe->mget('does_not_exist', 'counter');
});

var_export($responses);

/* OUTPUT:
array (
  0 => Predis\Response\Status::__set_state(array(
    'payload' => 'OK',
  )),
  1 => 10,
  2 => 40,
  3 => true,
  4 => '40',
  5 => array (
    0 => NULL,
    1 => '40',
  ),
)
*/


code:
<?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.
 */

require __DIR__.'/shared.php';

// Starting from Redis 2.0 clients can subscribe and listen for events published
// on certain channels using a Publish/Subscribe (PUB/SUB) approach.

// Create a client and disable r/w timeout on the socket
$client = new Predis\Client($single_server + array('read_write_timeout' => 0));

// Initialize a new pubsub consumer.
$pubsub = $client->pubSubLoop();

// Subscribe to your channels
$pubsub->subscribe('control_channel', 'notifications');

// Start processing the pubsup messages. Open a terminal and use redis-cli
// to push messages to the channels. Examples:
//   ./redis-cli PUBLISH notifications "this is a test"
//   ./redis-cli PUBLISH control_channel quit_loop
foreach ($pubsub as $message) {
    switch ($message->kind) {
        case 'subscribe':
            echo "Subscribed to {$message->channel}", PHP_EOL;
            break;

        case 'message':
            if ($message->channel == 'control_channel') {
                if ($message->payload == 'quit_loop') {
                    echo 'Aborting pubsub loop...', PHP_EOL;
                    $pubsub->unsubscribe();
                } else {
                    echo "Received an unrecognized command: {$message->payload}.", PHP_EOL;
                }
            } else {
                echo "Received the following message from {$message->channel}:",
                     PHP_EOL, "  {$message->payload}", PHP_EOL, PHP_EOL;
            }
            break;
    }
}

// Always unset the pubsub consumer instance when you are done! The
// class destructor will take care of cleanups and prevent protocol
// desynchronizations between the client and the server.
unset($pubsub);

// Say goodbye :-)
$version = redis_version($client->info());
echo "Goodbye from Redis $version!", PHP_EOL;


code:
<?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.
 */

require __DIR__.'/shared.php';

use Predis\Collection\Iterator;

// Starting from Redis 2.8, clients can iterate incrementally over collections
// without blocking the server like it happens when a command such as KEYS is
// executed on a Redis instance storing millions of keys. These commands are:
//
//   - SCAN (iterates over the keyspace)
//   - SSCAN (iterates over members of a set)
//   - ZSCAN (iterates over members and ranks of a sorted set)
//   - HSCAN (iterates over fields and values of an hash).

// Predis provides a specialized abstraction for each command based on standard
// SPL iterators making it possible to easily consume SCAN-based iterations in
// your PHP code.
//
// See http://redis.io/commands/scan for more details.
//

// Create a client using `2.8` as a server profile (needs Redis 2.8!)
$client = new Predis\Client($single_server, array('profile' => '2.8'));

// Prepare some keys for our example
$client->del('predis:set', 'predis:zset', 'predis:hash');
for ($i = 0; $i < 5; ++$i) {
    $client->sadd('predis:set', "member:$i");
    $client->zadd('predis:zset', -$i, "member:$i");
    $client->hset('predis:hash', "field:$i", "value:$i");
}

// === Keyspace iterator based on SCAN ===
echo 'Scan the keyspace matching only our prefixed keys:', PHP_EOL;
foreach (new Iterator\Keyspace($client, 'predis:*') as $key) {
    echo " - $key", PHP_EOL;
}

/* OUTPUT
Scan the keyspace matching only our prefixed keys:
 - predis:zset
 - predis:set
 - predis:hash
*/

// === Set iterator based on SSCAN ===
echo 'Scan members of `predis:set`:', PHP_EOL;
foreach (new Iterator\SetKey($client, 'predis:set') as $member) {
    echo " - $member", PHP_EOL;
}

/* OUTPUT
Scan members of `predis:set`:
 - member:1
 - member:4
 - member:0
 - member:3
 - member:2
*/

// === Sorted set iterator based on ZSCAN ===
echo 'Scan members and ranks of `predis:zset`:', PHP_EOL;
foreach (new Iterator\SortedSetKey($client, 'predis:zset') as $member => $rank) {
    echo " - $member [rank: $rank]", PHP_EOL;
}

/* OUTPUT
Scan members and ranks of `predis:zset`:
 - member:4 [rank: -4]
 - member:3 [rank: -3]
 - member:2 [rank: -2]
 - member:1 [rank: -1]
 - member:0 [rank: 0]
*/

// === Hash iterator based on HSCAN ===
echo 'Scan fields and values of `predis:hash`:', PHP_EOL;
foreach (new Iterator\HashKey($client, 'predis:hash') as $field => $value) {
    echo " - $field => $value", PHP_EOL;
}

/* OUTPUT
Scan fields and values of `predis:hash`:
 - field:0 => value:0
 - field:1 => value:1
 - field:2 => value:2
 - field:3 => value:3
 - field:4 => value:4
*/


code:
<?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.
 */

require __DIR__.'/shared.php';

// Predis allows to set Lua scripts as read-only operations for replication.
// This works for both EVAL and EVALSHA and also for the client-side abstraction
// built upon them (Predis\Command\ScriptCommand). This example shows a slightly
// more complex configuration that injects a new script command in the server
// profile used by the new client instance and marks it marks it as a read-only
// operation for replication so that it will be executed on slaves.

use Predis\Command\ScriptCommand;
use Predis\Connection\Aggregate\MasterSlaveReplication;
use Predis\Replication\ReplicationStrategy;

// ------------------------------------------------------------------------- //

// Define a new script command that returns all the fields of a variable number
// of hashes with a single roundtrip.

class HashMultipleGetAll extends ScriptCommand
{
    const BODY = <<<LUA
local hashes = {}
for _, key in pairs(KEYS) do
    table.insert(hashes, key)
    table.insert(hashes, redis.call('hgetall', key))
end
return hashes
LUA;

    public function getScript()
    {
        return self::BODY;
    }
}

// ------------------------------------------------------------------------- //

$parameters = array(
    'tcp://127.0.0.1:6379/?alias=master',
    'tcp://127.0.0.1:6380/?alias=slave',
);

$options = array(
    'profile' => function ($options, $option) {
        $profile = $options->getDefault($option);
        $profile->defineCommand('hmgetall', 'HashMultipleGetAll');

        return $profile;
    },
    'replication' => function () {
        $strategy = new ReplicationStrategy();
        $strategy->setScriptReadOnly(HashMultipleGetAll::BODY);

        $replication = new MasterSlaveReplication($strategy);

        return $replication;
    },
);

// ------------------------------------------------------------------------- //

$client = new Predis\Client($parameters, $options);

// Execute the following commands on the master server using redis-cli:
// $ ./redis-cli HMSET metavars foo bar hoge piyo
// $ ./redis-cli HMSET servers master host1 slave host2

$hashes = $client->hmgetall('metavars', 'servers');

$replication = $client->getConnection();
$stillOnSlave = $replication->getCurrent() === $replication->getConnectionById('slave');

echo 'Is still on slave? ', $stillOnSlave ? 'YES!' : 'NO!', PHP_EOL;
var_export($hashes);


code:
<?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.
 */

require __DIR__.'/shared.php';

// Predis supports redis-sentinel to provide high availability in master / slave
// scenarios. The only but relevant difference with a basic replication scenario
// is that sentinel servers can manage the master server and its slaves based on
// their state, which means that they are able to provide an authoritative and
// updated configuration to clients thus avoiding static configurations for the
// replication servers and their roles.

// Instead of connection parameters pointing to redis nodes, we provide a list
// of instances of redis-sentinel. Users should always provide a timeout value
// low enough to not hinder operations just in case a sentinel is unreachable
// but Predis uses a default value of 100 milliseconds for sentinel parameters
// without an explicit timeout value.
//
// NOTE: in real-world scenarios sentinels should be running on different hosts!
$sentinels = array(
    'tcp://127.0.0.1:5380?timeout=0.100',
    'tcp://127.0.0.1:5381?timeout=0.100',
    'tcp://127.0.0.1:5382?timeout=0.100',
);

$client = new Predis\Client($sentinels, array(
    'replication' => 'sentinel',
    'service' => 'mymaster',
));

// Read operation.
$exists = $client->exists('foo') ? 'yes' : 'no';
$current = $client->getConnection()->getCurrent()->getParameters();
echo "Does 'foo' exist on {$current->alias}? $exists.", PHP_EOL;

// Write operation.
$client->set('foo', 'bar');
$current = $client->getConnection()->getCurrent()->getParameters();
echo "Now 'foo' has been set to 'bar' on {$current->alias}!", PHP_EOL;

// Read operation.
$bar = $client->get('foo');
$current = $client->getConnection()->getCurrent()->getParameters();
echo "We fetched 'foo' from {$current->alias} and its value is '$bar'.", PHP_EOL;

/* OUTPUT:
Does 'foo' exist on slave-127.0.0.1:6381? yes.
Now 'foo' has been set to 'bar' on master!
We fetched 'foo' from master and its value is 'bar'.
*/


code:
<?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.
 */

require __DIR__.'/shared.php';

// Predis supports master / slave replication scenarios where write operations
// are performed on the master server and read operations are executed against
// one of the slaves. The behavior of commands or EVAL scripts can be customized
// at will. As soon as a write operation is performed the client switches to the
// master server for all the subsequent requests (either reads and writes).
//
// This example must be executed using the second Redis server configured as the
// slave of the first one (see the "SLAVEOF" command).
//

$parameters = array(
    'tcp://127.0.0.1:6379?database=15&alias=master',
    'tcp://127.0.0.1:6380?database=15&alias=slave',
);

$options = array('replication' => true);

$client = new Predis\Client($parameters, $options);

// Read operation.
$exists = $client->exists('foo') ? 'yes' : 'no';
$current = $client->getConnection()->getCurrent()->getParameters();
echo "Does 'foo' exist on {$current->alias}? $exists.", PHP_EOL;

// Write operation.
$client->set('foo', 'bar');
$current = $client->getConnection()->getCurrent()->getParameters();
echo "Now 'foo' has been set to 'bar' on {$current->alias}!", PHP_EOL;

// Read operation.
$bar = $client->get('foo');
$current = $client->getConnection()->getCurrent()->getParameters();
echo "We fetched 'foo' from {$current->alias} and its value is '$bar'.", PHP_EOL;

/* OUTPUT:
Does 'foo' exist on slave? yes.
Now 'foo' has been set to 'bar' on master!
We fetched 'foo' from master and its value is 'bar'.
*/


code:
<?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.
 */

require __DIR__.'/shared.php';

// This example demonstrates how to use Predis to save PHP sessions on Redis.
//
// The value of `session.gc_maxlifetime` in `php.ini` will be used by default as
// the TTL for keys holding session data but this value can be overridden when
// creating the session handler instance using the `gc_maxlifetime` option.
//
// NOTE: this class requires PHP >= 5.4 but can be used on PHP 5.3 if a polyfill
// for SessionHandlerInterface is provided either by you or an external package
// like `symfony/http-foundation`.
//
// See http://www.php.net/class.sessionhandlerinterface.php for more details.
//

if (!interface_exists('SessionHandlerInterface')) {
    die('ATTENTION: the session handler implemented by Predis requires PHP >= 5.4.0 '.
        "or a polyfill for SessionHandlerInterface provided by an external package.\n");
}

// Instantiate a new client just like you would normally do. Using a prefix for
// keys will effectively prefix all session keys with the specified string.
$client = new Predis\Client($single_server, array('prefix' => 'sessions:'));

// Set `gc_maxlifetime` to specify a time-to-live of 5 seconds for session keys.
$handler = new Predis\Session\Handler($client, array('gc_maxlifetime' => 5));

// Register the session handler.
$handler->register();

// We just set a fixed session ID only for the sake of our example.
session_id('example_session_id');

session_start();

if (isset($_SESSION['foo'])) {
    echo "Session has `foo` set to {$_SESSION['foo']}", PHP_EOL;
} else {
    $_SESSION['foo'] = $value = mt_rand();
    echo "Empty session, `foo` has been set with $value", PHP_EOL;
}


code:
<?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.
 */

require __DIR__.'/../autoload.php';

function redis_version($info)
{
    if (isset($info['Server']['redis_version'])) {
        return $info['Server']['redis_version'];
    } elseif (isset($info['redis_version'])) {
        return $info['redis_version'];
    } else {
        return 'unknown version';
    }
}

$single_server = array(
    'host' => '127.0.0.1',
    'port' => 6379,
    'database' => 15,
);

$multiple_servers = array(
    array(
       'host' => '127.0.0.1',
       'port' => 6379,
       'database' => 15,
       'alias' => 'first',
    ),
    array(
       'host' => '127.0.0.1',
       'port' => 6380,
       'database' => 15,
       'alias' => 'second',
    ),
);


code:
<?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.
 */

require __DIR__.'/shared.php';

// This is an implementation of an atomic client-side ZPOP using the support for
// check-and-set (CAS) operations with MULTI/EXEC transactions, as described in
// "WATCH explained" from http://redis.io/topics/transactions
//
// First, populate your database with a tiny sample data set:
//
// ./redis-cli
// SELECT 15
// ZADD zset 1 a 2 b 3 c
//
// Then execute this script four times and see its output.
//

function zpop($client, $key)
{
    $element = null;
    $options = array(
        'cas' => true,      // Initialize with support for CAS operations
        'watch' => $key,    // Key that needs to be WATCHed to detect changes
        'retry' => 3,       // Number of retries on aborted transactions, after
                            // which the client bails out with an exception.
    );

    $client->transaction($options, function ($tx) use ($key, &$element) {
        @list($element) = $tx->zrange($key, 0, 0);

        if (isset($element)) {
            $tx->multi();   // With CAS, MULTI *must* be explicitly invoked.
            $tx->zrem($key, $element);
        }
    });

    return $element;
}

$client = new Predis\Client($single_server);
$zpopped = zpop($client, 'zset');

echo isset($zpopped) ? "ZPOPed $zpopped" : 'Nothing to ZPOP!', PHP_EOL;


code:
; This file is meant to be used with Onion http://c9s.github.com/Onion/
; For instructions on how to build a PEAR package of Predis please follow
; the instructions at this URL:
;
; https://github.com/c9s/Onion#a-quick-tutorial-for-building-pear-package
;

[package]
name        = "Predis"
desc        = "Flexible and feature-complete Redis client for PHP and HHVM"
homepage    = "http://github.com/nrk/predis"
license     = "MIT"
version     = "1.1.1"
stability   = "stable"
channel     = "pear.nrk.io"

author      = "Daniele Alessandri \"nrk\" <suppakilla@gmail.com>"

[require]
php = ">= 5.3.9"
pearinstaller = "1.4.1"

[roles]
*.xml.dist = test
*.md = doc
LICENSE = doc

[optional phpiredis]
hint = "Add support for faster protocol handling with phpiredis"
extensions[] = socket
extensions[] = phpiredis

[optional webdis]
hint = "Add support for Webdis"
extensions[] = curl
extensions[] = phpiredis


code:
<?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;

/**
 * Implements a lightweight PSR-0 compliant autoloader for Predis.
 *
 * @author Eric Naeseth <eric@thumbtack.com>
 * @author Daniele Alessandri <suppakilla@gmail.com>
 */
class Autoloader
{
    private $directory;
    private $prefix;
    private $prefixLength;

    /**
     * @param string $baseDirectory Base directory where the source files are located.
     */
    public function __construct($baseDirectory = __DIR__)
    {
        $this->directory = $baseDirectory;
        $this->prefix = __NAMESPACE__.'\\';
        $this->prefixLength = strlen($this->prefix);
    }

    /**
     * Registers the autoloader class with the PHP SPL autoloader.
     *
     * @param bool $prepend Prepend the autoloader on the stack instead of appending it.
     */
    public static function register($prepend = false)
    {
        spl_autoload_register(array(new self(), 'autoload'), true, $prepend);
    }

    /**
     * Loads a class from a file using its fully qualified name.
     *
     * @param string $className Fully qualified name of a class.
     */
    public function autoload($className)
    {
        if (0 === strpos($className, $this->prefix)) {
            $parts = explode('\\', substr($className, $this->prefixLength));
            $filepath = $this->directory.DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $parts).'.php';

            if (is_file($filepath)) {
                require $filepath;
            }
        }
    }
}


code:
<?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;

use Predis\Command\CommandInterface;
use Predis\Command\RawCommand;
use Predis\Command\ScriptCommand;
use Predis\Configuration\Options;
use Predis\Configuration\OptionsInterface;
use Predis\Connection\AggregateConnectionInterface;
use Predis\Connection\ConnectionInterface;
use Predis\Connection\ParametersInterface;
use Predis\Monitor\Consumer as MonitorConsumer;
use Predis\Pipeline\Pipeline;
use Predis\PubSub\Consumer as PubSubConsumer;
use Predis\Response\ErrorInterface as ErrorResponseInterface;
use Predis\Response\ResponseInterface;
use Predis\Response\ServerException;
use Predis\Transaction\MultiExec as MultiExecTransaction;

/**
 * Client class used for connecting and executing commands on Redis.
 *
 * This is the main high-level abstraction of Predis upon which various other
 * abstractions are built. Internally it aggregates various other classes each
 * one with its own responsibility and scope.
 *
 * {@inheritdoc}
 *
 * @author Daniele Alessandri <suppakilla@gmail.com>
 */
class Client implements ClientInterface, \IteratorAggregate
{
    const VERSION = '1.1.1';

    protected $connection;
    protected $options;
    private $profile;

    /**
     * @param mixed $parameters Connection parameters for one or more servers.
     * @param mixed $options    Options to configure some behaviours of the client.
     */
    public function __construct($parameters = null, $options = null)
    {
        $this->options = $this->createOptions($options ?: array());
        $this->connection = $this->createConnection($parameters ?: array());
        $this->profile = $this->options->profile;
    }

    /**
     * Creates a new instance of Predis\Configuration\Options from different
     * types of arguments or simply returns the passed argument if it is an
     * instance of Predis\Configuration\OptionsInterface.
     *
     * @param mixed $options Client options.
     *
     * @throws \InvalidArgumentException
     *
     * @return OptionsInterface
     */
    protected function createOptions($options)
    {
        if (is_array($options)) {
            return new Options($options);
        }

        if ($options instanceof OptionsInterface) {
            return $options;
        }

        throw new \InvalidArgumentException('Invalid type for client options.');
    }

    /**
     * Creates single or aggregate connections from different types of arguments
     * (string, array) or returns the passed argument if it is an instance of a
     * class implementing Predis\Connection\ConnectionInterface.
     *
     * Accepted types for connection parameters are:
     *
     *  - Instance of Predis\Connection\ConnectionInterface.
     *  - Instance of Predis\Connection\ParametersInterface.
     *  - Array
     *  - String
     *  - Callable
     *
     * @param mixed $parameters Connection parameters or connection instance.
     *
     * @throws \InvalidArgumentException
     *
     * @return ConnectionInterface
     */
    protected function createConnection($parameters)
    {
        if ($parameters instanceof ConnectionInterface) {
            return $parameters;
        }

        if ($parameters instanceof ParametersInterface || is_string($parameters)) {
            return $this->options->connections->create($parameters);
        }

        if (is_array($parameters)) {
            if (!isset($parameters[0])) {
                return $this->options->connections->create($parameters);
            }

            $options = $this->options;

            if ($options->defined('aggregate')) {
                $initializer = $this->getConnectionInitializerWrapper($options->aggregate);
                $connection = $initializer($parameters, $options);
            } elseif ($options->defined('replication')) {
                $replication = $options->replication;

                if ($replication instanceof AggregateConnectionInterface) {
                    $connection = $replication;
                    $options->connections->aggregate($connection, $parameters);
                } else {
                    $initializer = $this->getConnectionInitializerWrapper($replication);
                    $connection = $initializer($parameters, $options);
                }
            } else {
                $connection = $options->cluster;
                $options->connections->aggregate($connection, $parameters);
            }

            return $connection;
        }

        if (is_callable($parameters)) {
            $initializer = $this->getConnectionInitializerWrapper($parameters);
            $connection = $initializer($this->options);

            return $connection;
        }

        throw new \InvalidArgumentException('Invalid type for connection parameters.');
    }

    /**
     * Wraps a callable to make sure that its returned value represents a valid
     * connection type.
     *
     * @param mixed $callable
     *
     * @return \Closure
     */
    protected function getConnectionInitializerWrapper($callable)
    {
        return function () use ($callable) {
            $connection = call_user_func_array($callable, func_get_args());

            if (!$connection instanceof ConnectionInterface) {
                throw new \UnexpectedValueException(
                    'The callable connection initializer returned an invalid type.'
                );
            }

            return $connection;
        };
    }

    /**
     * {@inheritdoc}
     */
    public function getProfile()
    {
        return $this->profile;
    }

    /**
     * {@inheritdoc}
     */
    public function getOptions()
    {
        return $this->options;
    }

    /**
     * Creates a new client instance for the specified connection ID or alias,
     * only when working with an aggregate connection (cluster, replication).
     * The new client instances uses the same options of the original one.
     *
     * @param string $connectionID Identifier of a connection.
     *
     * @throws \InvalidArgumentException
     *
     * @return Client
     */
    public function getClientFor($connectionID)
    {
        if (!$connection = $this->getConnectionById($connectionID)) {
            throw new \InvalidArgumentException("Invalid connection ID: $connectionID.");
        }

        return new static($connection, $this->options);
    }

    /**
     * Opens the underlying connection and connects to the server.
     */
    public function connect()
    {
        $this->connection->connect();
    }

    /**
     * Closes the underlying connection and disconnects from the server.
     */
    public function disconnect()
    {
        $this->connection->disconnect();
    }

    /**
     * Closes the underlying connection and disconnects from the server.
     *
     * This is the same as `Client::disconnect()` as it does not actually send
     * the `QUIT` command to Redis, but simply closes the connection.
     */
    public function quit()
    {
        $this->disconnect();
    }

    /**
     * Returns the current state of the underlying connection.
     *
     * @return bool
     */
    public function isConnected()
    {
        return $this->connection->isConnected();
    }

    /**
     * {@inheritdoc}
     */
    public function getConnection()
    {
        return $this->connection;
    }

    /**
     * Retrieves the specified connection from the aggregate connection when the
     * client is in cluster or replication mode.
     *
     * @param string $connectionID Index or alias of the single connection.
     *
     * @throws NotSupportedException
     *
     * @return Connection\NodeConnectionInterface
     */
    public function getConnectionById($connectionID)
    {
        if (!$this->connection instanceof AggregateConnectionInterface) {
            throw new NotSupportedException(
                'Retrieving connections by ID is supported only by aggregate connections.'
            );
        }

        return $this->connection->getConnectionById($connectionID);
    }

    /**
     * Executes a command without filtering its arguments, parsing the response,
     * applying any prefix to keys or throwing exceptions on Redis errors even
     * regardless of client options.
     *
     * It is possible to identify Redis error responses from normal responses
     * using the second optional argument which is populated by reference.
     *
     * @param array $arguments Command arguments as defined by the command signature.
     * @param bool  $error     Set to TRUE when Redis returned an error response.
     *
     * @return mixed
     */
    public function executeRaw(array $arguments, &$error = null)
    {
        $error = false;

        $response = $this->connection->executeCommand(
            new RawCommand($arguments)
        );

        if ($response instanceof ResponseInterface) {
            if ($response instanceof ErrorResponseInterface) {
                $error = true;
            }

            return (string) $response;
        }

        return $response;
    }

    /**
     * {@inheritdoc}
     */
    public function __call($commandID, $arguments)
    {
        return $this->executeCommand(
            $this->createCommand($commandID, $arguments)
        );
    }

    /**
     * {@inheritdoc}
     */
    public function createCommand($commandID, $arguments = array())
    {
        return $this->profile->createCommand($commandID, $arguments);
    }

    /**
     * {@inheritdoc}
     */
    public function executeCommand(CommandInterface $command)
    {
        $response = $this->connection->executeCommand($command);

        if ($response instanceof ResponseInterface) {
            if ($response instanceof ErrorResponseInterface) {
                $response = $this->onErrorResponse($command, $response);
            }

            return $response;
        }

        return $command->parseResponse($response);
    }

    /**
     * Handles -ERR responses returned by Redis.
     *
     * @param CommandInterface       $command  Redis command that generated the error.
     * @param ErrorResponseInterface $response Instance of the error response.
     *
     * @throws ServerException
     *
     * @return mixed
     */
    protected function onErrorResponse(CommandInterface $command, ErrorResponseInterface $response)
    {
        if ($command instanceof ScriptCommand && $response->getErrorType() === 'NOSCRIPT') {
            $eval = $this->createCommand('EVAL');
            $eval->setRawArguments($command->getEvalArguments());

            $response = $this->executeCommand($eval);

            if (!$response instanceof ResponseInterface) {
                $response = $command->parseResponse($response);
            }

            return $response;
        }

        if ($this->options->exceptions) {
            throw new ServerException($response->getMessage());
        }

        return $response;
    }

    /**
     * Executes the specified initializer method on `$this` by adjusting the
     * actual invokation depending on the arity (0, 1 or 2 arguments). This is
     * simply an utility method to create Redis contexts instances since they
     * follow a common initialization path.
     *
     * @param string $initializer Method name.
     * @param array  $argv        Arguments for the method.
     *
     * @return mixed
     */
    private function sharedContextFactory($initializer, $argv = null)
    {
        switch (count($argv)) {
            case 0:
                return $this->$initializer();

            case 1:
                return is_array($argv[0])
                    ? $this->$initializer($argv[0])
                    : $this->$initializer(null, $argv[0]);

            case 2:
                list($arg0, $arg1) = $argv;

                return $this->$initializer($arg0, $arg1);

            default:
                return $this->$initializer($this, $argv);
        }
    }

    /**
     * Creates a new pipeline context and returns it, or returns the results of
     * a pipeline executed inside the optionally provided callable object.
     *
     * @param mixed ... Array of options, a callable for execution, or both.
     *
     * @return Pipeline|array
     */
    public function pipeline(/* arguments */)
    {
        return $this->sharedContextFactory('createPipeline', func_get_args());
    }

    /**
     * Actual pipeline context initializer method.
     *
     * @param array $options  Options for the context.
     * @param mixed $callable Optional callable used to execute the context.
     *
     * @return Pipeline|array
     */
    protected function createPipeline(array $options = null, $callable = null)
    {
        if (isset($options['atomic']) && $options['atomic']) {
            $class = 'Predis\Pipeline\Atomic';
        } elseif (isset($options['fire-and-forget']) && $options['fire-and-forget']) {
            $class = 'Predis\Pipeline\FireAndForget';
        } else {
            $class = 'Predis\Pipeline\Pipeline';
        }

        /*
         * @var ClientContextInterface
         */
        $pipeline = new $class($this);

        if (isset($callable)) {
            return $pipeline->execute($callable);
        }

        return $pipeline;
    }

    /**
     * Creates a new transaction context and returns it, or returns the results
     * of a transaction executed inside the optionally provided callable object.
     *
     * @param mixed ... Array of options, a callable for execution, or both.
     *
     * @return MultiExecTransaction|array
     */
    public function transaction(/* arguments */)
    {
        return $this->sharedContextFactory('createTransaction', func_get_args());
    }

    /**
     * Actual transaction context initializer method.
     *
     * @param array $options  Options for the context.
     * @param mixed $callable Optional callable used to execute the context.
     *
     * @return MultiExecTransaction|array
     */
    protected function createTransaction(array $options = null, $callable = null)
    {
        $transaction = new MultiExecTransaction($this, $options);

        if (isset($callable)) {
            return $transaction->execute($callable);
        }

        return $transaction;
    }

    /**
     * Creates a new publish/subscribe context and returns it, or starts its loop
     * inside the optionally provided callable object.
     *
     * @param mixed ... Array of options, a callable for execution, or both.
     *
     * @return PubSubConsumer|null
     */
    public function pubSubLoop(/* arguments */)
    {
        return $this->sharedContextFactory('createPubSub', func_get_args());
    }

    /**
     * Actual publish/subscribe context initializer method.
     *
     * @param array $options  Options for the context.
     * @param mixed $callable Optional callable used to execute the context.
     *
     * @return PubSubConsumer|null
     */
    protected function createPubSub(array $options = null, $callable = null)
    {
        $pubsub = new PubSubConsumer($this, $options);

        if (!isset($callable)) {
            return $pubsub;
        }

        foreach ($pubsub as $message) {
            if (call_user_func($callable, $pubsub, $message) === false) {
                $pubsub->stop();
            }
        }
    }

    /**
     * Creates a new monitor consumer and returns it.
     *
     * @return MonitorConsumer
     */
    public function monitor()
    {
        return new MonitorConsumer($this);
    }

    /**
     * {@inheritdoc}
     */
    public function getIterator()
    {
        $clients = array();
        $connection = $this->getConnection();

        if (!$connection instanceof \Traversable) {
            throw new ClientException('The underlying connection is not traversable');
        }

        foreach ($connection as $node) {
            $clients[(string) $node] = new static($node, $this->getOptions());
        }

        return new \ArrayIterator($clients);
    }
}


code:
<?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;

use Predis\Command\CommandInterface;

/**
 * Interface defining a client-side context such as a pipeline or transaction.
 *
 * @method $this del(array $keys)
 * @method $this dump($key)
 * @method $this exists($key)
 * @method $this expire($key, $seconds)
 * @method $this expireat($key, $timestamp)
 * @method $this keys($pattern)
 * @method $this move($key, $db)
 * @method $this object($subcommand, $key)
 * @method $this persist($key)
 * @method $this pexpire($key, $milliseconds)
 * @method $this pexpireat($key, $timestamp)
 * @method $this pttl($key)
 * @method $this randomkey()
 * @method $this rename($key, $target)
 * @method $this renamenx($key, $target)
 * @method $this scan($cursor, array $options = null)
 * @method $this sort($key, array $options = null)
 * @method $this ttl($key)
 * @method $this type($key)
 * @method $this append($key, $value)
 * @method $this bitcount($key, $start = null, $end = null)
 * @method $this bitop($operation, $destkey, $key)
 * @method $this bitfield($key, $subcommand, ...$subcommandArg)
 * @method $this decr($key)
 * @method $this decrby($key, $decrement)
 * @method $this get($key)
 * @method $this getbit($key, $offset)
 * @method $this getrange($key, $start, $end)
 * @method $this getset($key, $value)
 * @method $this incr($key)
 * @method $this incrby($key, $increment)
 * @method $this incrbyfloat($key, $increment)
 * @method $this mget(array $keys)
 * @method $this mset(array $dictionary)
 * @method $this msetnx(array $dictionary)
 * @method $this psetex($key, $milliseconds, $value)
 * @method $this set($key, $value, $expireResolution = null, $expireTTL = null, $flag = null)
 * @method $this setbit($key, $offset, $value)
 * @method $this setex($key, $seconds, $value)
 * @method $this setnx($key, $value)
 * @method $this setrange($key, $offset, $value)
 * @method $this strlen($key)
 * @method $this hdel($key, array $fields)
 * @method $this hexists($key, $field)
 * @method $this hget($key, $field)
 * @method $this hgetall($key)
 * @method $this hincrby($key, $field, $increment)
 * @method $this hincrbyfloat($key, $field, $increment)
 * @method $this hkeys($key)
 * @method $this hlen($key)
 * @method $this hmget($key, array $fields)
 * @method $this hmset($key, array $dictionary)
 * @method $this hscan($key, $cursor, array $options = null)
 * @method $this hset($key, $field, $value)
 * @method $this hsetnx($key, $field, $value)
 * @method $this hvals($key)
 * @method $this hstrlen($key, $field)
 * @method $this blpop(array $keys, $timeout)
 * @method $this brpop(array $keys, $timeout)
 * @method $this brpoplpush($source, $destination, $timeout)
 * @method $this lindex($key, $index)
 * @method $this linsert($key, $whence, $pivot, $value)
 * @method $this llen($key)
 * @method $this lpop($key)
 * @method $this lpush($key, array $values)
 * @method $this lpushx($key, $value)
 * @method $this lrange($key, $start, $stop)
 * @method $this lrem($key, $count, $value)
 * @method $this lset($key, $index, $value)
 * @method $this ltrim($key, $start, $stop)
 * @method $this rpop($key)
 * @method $this rpoplpush($source, $destination)
 * @method $this rpush($key, array $values)
 * @method $this rpushx($key, $value)
 * @method $this sadd($key, array $members)
 * @method $this scard($key)
 * @method $this sdiff(array $keys)
 * @method $this sdiffstore($destination, array $keys)
 * @method $this sinter(array $keys)
 * @method $this sinterstore($destination, array $keys)
 * @method $this sismember($key, $member)
 * @method $this smembers($key)
 * @method $this smove($source, $destination, $member)
 * @method $this spop($key, $count = null)
 * @method $this srandmember($key, $count = null)
 * @method $this srem($key, $member)
 * @method $this sscan($key, $cursor, array $options = null)
 * @method $this sunion(array $keys)
 * @method $this sunionstore($destination, array $keys)
 * @method $this zadd($key, array $membersAndScoresDictionary)
 * @method $this zcard($key)
 * @method $this zcount($key, $min, $max)
 * @method $this zincrby($key, $increment, $member)
 * @method $this zinterstore($destination, array $keys, array $options = null)
 * @method $this zrange($key, $start, $stop, array $options = null)
 * @method $this zrangebyscore($key, $min, $max, array $options = null)
 * @method $this zrank($key, $member)
 * @method $this zrem($key, $member)
 * @method $this zremrangebyrank($key, $start, $stop)
 * @method $this zremrangebyscore($key, $min, $max)
 * @method $this zrevrange($key, $start, $stop, array $options = null)
 * @method $this zrevrangebyscore($key, $min, $max, array $options = null)
 * @method $this zrevrank($key, $member)
 * @method $this zunionstore($destination, array $keys, array $options = null)
 * @method $this zscore($key, $member)
 * @method $this zscan($key, $cursor, array $options = null)
 * @method $this zrangebylex($key, $start, $stop, array $options = null)
 * @method $this zrevrangebylex($key, $start, $stop, array $options = null)
 * @method $this zremrangebylex($key, $min, $max)
 * @method $this zlexcount($key, $min, $max)
 * @method $this pfadd($key, array $elements)
 * @method $this pfmerge($destinationKey, array $sourceKeys)
 * @method $this pfcount(array $keys)
 * @method $this pubsub($subcommand, $argument)
 * @method $this publish($channel, $message)
 * @method $this discard()
 * @method $this exec()
 * @method $this multi()
 * @method $this unwatch()
 * @method $this watch($key)
 * @method $this eval($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
 * @method $this evalsha($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
 * @method $this script($subcommand, $argument = null)
 * @method $this auth($password)
 * @method $this echo($message)
 * @method $this ping($message = null)
 * @method $this select($database)
 * @method $this bgrewriteaof()
 * @method $this bgsave()
 * @method $this client($subcommand, $argument = null)
 * @method $this config($subcommand, $argument = null)
 * @method $this dbsize()
 * @method $this flushall()
 * @method $this flushdb()
 * @method $this info($section = null)
 * @method $this lastsave()
 * @method $this save()
 * @method $this slaveof($host, $port)
 * @method $this slowlog($subcommand, $argument = null)
 * @method $this time()
 * @method $this command()
 * @method $this geoadd($key, $longitude, $latitude, $member)
 * @method $this geohash($key, array $members)
 * @method $this geopos($key, array $members)
 * @method $this geodist($key, $member1, $member2, $unit = null)
 * @method $this georadius($key, $longitude, $latitude, $radius, $unit, array $options = null)
 * @method $this georadiusbymember($key, $member, $radius, $unit, array $options = null)
 *
 * @author Daniele Alessandri <suppakilla@gmail.com>
 */
interface ClientContextInterface
{
    /**
     * Sends the specified command instance to Redis.
     *
     * @param CommandInterface $command Command instance.
     *
     * @return mixed
     */
    public function executeCommand(CommandInterface $command);

    /**
     * Sends the specified command with its arguments to Redis.
     *
     * @param string $method    Command ID.
     * @param array  $arguments Arguments for the command.
     *
     * @return mixed
     */
    public function __call($method, $arguments);

    /**
     * Starts the execution of the context.
     *
     * @param mixed $callable Optional callback for execution.
     *
     * @return array
     */
    public function execute($callable = null);
}


code:
<?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;

/**
 * Exception class that identifies client-side errors.
 *
 * @author Daniele Alessandri <suppakilla@gmail.com>
 */
class ClientException extends PredisException
{
}