3

friends. I know, there are many questions here already on these iterators. I've read something, and I'm not a beginner... but my mind is somewhat stuck on this. Please, help me to comprehend how I use iterators in practice.

Suppose, I have an ORM object that can select instances from database. And one instance contains fields and can insert, uodate etc. As usual. I want to iterate through all objects of a type, but as there can be plenty of them, I prefer to select them by "pages". My code:

$limit = 100;
$offset = 0;
do
{
  $recs = $orm->select($filter, $sorting, $limit , $offset);
  $offset += $limit;
  foreach ($recs as $rec)
  {
    // doing something with record
  }
}
while (count($recs) == $limit);

I feel that iterator paradigm is what suits here, but what interface is better to implement in this case or maybe some base SPL class?

UPDATE Ideally code above with iterator may look like:

$iterator = new ORMPagedIterator($ormobject, $filter, $sorting);
foreach ($iterator as $rec)
{
  // do something with record
}

E.g. all that page by page behavior is inside the iterator.

7
  • you might want to elaborate on your scenario. I dont get what you mean by "I prefer to select them by pages". Also, check out stackoverflow.com/questions/1957133/… for various SPL related resources. Commented Nov 8, 2011 at 9:45
  • "By pages" means "by some portions" in contrast to "all in one request". Actually, I don't have much time now to go through extensive documentation, so, I hoped for a good advice. Commented Nov 8, 2011 at 9:48
  • AFAIK usually it is easier on the server to extract more data, than run same query more times Commented Nov 8, 2011 at 10:06
  • @max4ever, yes, usually it is. But in case you fetch 100000 records and wrap them into objects you may get "memory limit exceeded". That is why there are "pages". Commented Nov 8, 2011 at 10:13
  • then your code seems fine, i don't understand what interface are you trying to force and where? Commented Nov 8, 2011 at 10:15

1 Answer 1

4

I would use an Iterator that iterates over another Iterator and asks for a next Iterator once it reaches the end of the previous Iterator... ok, sounds a mo complicated than it actually is:

<?php
$limit = 100;
$offset = 0;

$iter = new NextIteratorCallbackIterator(function($i) use ($orm, $limit, &$offset) {
    printf("selecting next bunch at offset %d\n", $offset);
    $recs = $orm->select($filter, $sorting, $limit , $offset);
    $offset += $limit;
    if ($recs) {
        return new ArrayIterator($recs);
    }
    return null; // end reached
});

foreach ($iter as $rec) {
    // do something with record
}
?>

And here is a sample Implementation of that NextIteratorCallbackIterator:

<?php
class NextIteratorCallbackIterator implements Iterator {
    private $_iterator = null;
    private $_count = 0;
    private $_callback;

    public function __construct($callback) {
        if (!is_callable($callback)) {
            throw new Exception(__CLASS__.": callback must be callable");
        }
        $this->_callback = $callback;
    }

    public function current() {
        return $this->_iterator !== null ? $this->_iterator->current() : null;
    }

    public function key() {
        return $this->_iterator !== null ? $this->_iterator->key() : null;
    }

    public function next() {
        $tryNext = ($this->_iterator === null);
        do {
            if ($tryNext) {
                $tryNext = false;
                $this->_iterator = call_user_func($this->_callback, ++$this->_count);
            }
            elseif ($this->_iterator !== null) {
                $this->_iterator->next();
                if ($this->_iterator->valid() == false) {
                    $tryNext = true;
                }
            }
        } while ($tryNext);
    }

    public function rewind() {
        $this->_iterator = call_user_func($this->_callback, $this->_count = 0);
    }

    public function valid () {
        return $this->_iterator !== null;
    }
}
?>

UPDATE: Your ORMPagedIterator can be implemented using NextIteratorCallbackIterator as easy as:

<?php
class ORMPagedIterator implements IteratorAggregate {
    function __construct($orm, $filter, $sorting, $chunksize = 100) {
        $this->orm = $orm;
        $this->filter = $filter;
        $this->sorting = $sorting;
        $this->chunksize = $chunksize;
    }

    function iteratorNext($i) {
        $offset = $this->chunksize * $i;
        $recs = $this->orm->select($this->filter, $this->sorting, $this->chunksize, $offset);
        if ($recs) {
            return new ArrayIterator($recs);
        }
        return null; // end reached
    }

    function getIterator() {
        return new NextIteratorCallbackIterator(array($this,"iteratorNext"));
    }
}
?>
Sign up to request clarification or add additional context in comments.

1 Comment

I like to add that you should avoid selecting with large offsets, because with increasing offsets your DB will have to do more and more work to get you the results. If your result set is sorted in by its primary key, you should use the last key of the previous result set as the start key for the next... that way your querys will not slow down...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.