dmitry
dmitry

Reputation: 5039

Using PHP iterators

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.

Upvotes: 3

Views: 780

Answers (1)

muhqu
muhqu

Reputation: 12799

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"));
    }
}
?>

Upvotes: 4

Related Questions