Ver código fonte

Implement PHP iterator based on the SCAN command (Redis 2.8).

This iterator allows to perform full iterations over the keyspace of a Redis
instance by wrapping the incremental nature of SCAN using an abstraction that
fits perfectly in userland code:

  $client = new Predis\Client('tcp://127.0.0.1', ['profile' => '2.8']);
  $iterator = new Predis\Iterator\Scan\KeyspaceIterator($client);

  foreach ($iterator as $key) {
      echo $key . PHP_EOL;
  }

Memory consumption during an iteration is limited because elements are fetched
incrementally, on the other hand SCAN gives limited guarantees about returned
elements since the underlying collection (the keyspace in this case) can change
during the whole iteration process. The most immediate drawback is that the same
element may be returned multiple times.

See http://redis.io/commands/scan to fully understand the inner workings,
and particularly the "Scan guarantees" paragraph.
Daniele Alessandri 11 anos atrás
pai
commit
70e42f8fca
1 arquivos alterados com 164 adições e 0 exclusões
  1. 164 0
      lib/Predis/Iterator/Scan/KeyspaceIterator.php

+ 164 - 0
lib/Predis/Iterator/Scan/KeyspaceIterator.php

@@ -0,0 +1,164 @@
+<?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\Iterator\Scan;
+
+use Iterator;
+use Predis\ClientInterface;
+use Predis\NotSupportedException;
+
+/**
+ * Abstracts the iteration of the keyspace on a Redis instance
+ * by leveraging the SCAN command (Redis >= 2.8) wrapped in a
+ * fully-rewindable PHP iterator.
+ *
+ * @author Daniele Alessandri <suppakilla@gmail.com>
+ * @link http://redis.io/commands/scan
+ */
+class KeyspaceIterator implements Iterator
+{
+    protected $client;
+    protected $match;
+    protected $count;
+
+    protected $valid;
+    protected $scanmore;
+    protected $elements;
+    protected $position;
+    protected $cursor;
+
+    /**
+     * @param ClientInterface $client Client connected to Redis.
+     * @param string $match Pattern to match during the server-side iteration.
+     * @param int $count Hints used by Redis to compute the number of results per iteration.
+     */
+    public function __construct(ClientInterface $client, $match = null, $count = null)
+    {
+        if (!$client->getProfile()->supportsCommand('SCAN')) {
+            throw new NotSupportedException('The specified server profile does not support the SCAN command.');
+        }
+
+        $this->client = $client;
+        $this->match = $match;
+        $this->count = $count;
+
+        $this->reset();
+    }
+
+    /**
+     * Resets the inner state of the iterator.
+     */
+    protected function reset()
+    {
+        $this->valid = true;
+        $this->scanmore = true;
+        $this->elements = array();
+        $this->position = -1;
+        $this->cursor = 0;
+    }
+
+    /**
+     * Returns an array of options for the SCAN command.
+     *
+     * @return array
+     */
+    protected function getScanOptions()
+    {
+        $options = array();
+
+        if (strlen($this->match) > 0) {
+            $options['MATCH'] = $this->match;
+        }
+
+        if ($this->count > 0) {
+            $options['COUNT'] = $this->count;
+        }
+
+        return $options;
+    }
+
+    /**
+     * Performs a new SCAN to fetch new elements in the collection from
+     * Redis, effectively advancing the iteration process.
+     *
+     * @return array
+     */
+    protected function executeScanCommand()
+    {
+        return $this->client->scan($this->cursor, $this->getScanOptions());
+    }
+
+    /**
+     * Populates the local buffer of elements fetched from the server
+     * during the iteration.
+     */
+    protected function feed()
+    {
+        list($cursor, $elements) = $this->executeScanCommand();
+
+        if (!$cursor) {
+            $this->scanmore = false;
+        }
+
+        $this->cursor = $cursor;
+        $this->elements = $elements;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function rewind()
+    {
+        $this->reset();
+        $this->next();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function current()
+    {
+        return $this->current;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function key()
+    {
+        return $this->position;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function next()
+    {
+        if (!$this->elements && $this->scanmore) {
+            $this->feed();
+        }
+
+        if ($this->elements) {
+            $this->position++;
+            $this->current = array_shift($this->elements);
+        } else {
+            $this->valid = false;
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function valid()
+    {
+        return $this->valid;
+    }
+}