PHP 5.3 is released and after the release stress is over my mind is open for new ideas. While relaxing yesterday I thought about many things, among them was the Resultset iterator I recently discussed.
Now I wondered where to go next with this and had the idea that an individual Resultset is a child of the whole result and this might be wrapped in an Recursive Iterator. For doing so we don't implement the Iterator interface but RecursiveIterator. RecursiveIterator extends a typical Iterator with two methods: hasChildren() and getChildren(). But now we have a problem: The Iterator returned by getChildren() has to be a RecursiveIterator, too, which makes sense, in general. But I want to return a MySQLi Resultset which isn't recursive - so making this a RecursiveIterator is wrong. My solution now is to introduce yet another Iterator which goes by the name of MySQLi_PseudoRecursiveResultIterator and is implemented by extending IteratorIterator which will wrap the MySQLi_Result and implements RecursiveIterator telling the caller that there are no children.
As a sidenote: In our experimental tree Andrey made MySQLi_Result an iterator but that's not yet in php.net's CVS (might need some more testing, and probably we might change the design there...) so I'm emulating this with MySQLi_Result::fetch_all() combined with an ArrayIterator, using the experimental code the constructor can be dropped.
So let's finally look at the code of these two classes:
<?php class MySQLi_ResultsetIterator implements RecursiveIterator { private $mysqli; private $counter = 0; private $current = null; private $rewinded = false; public function __construct(mysqli $mysqli) { $this->mysqli = $mysqli; } private function freeCurrent() { if ($this->current) { $this->current->free(); $this->current = null; } } public function rewind() { if ($this->rewinded) { throw new Exception("Already rewinded"); } $this->freeCurrent(); $this->counter = 0; $this->rewinded = true; } public function valid() { $this->current = $this->mysqli->store_result(); return (bool)$this->current; } public function next() { $this->freeCurrent(); $this->counter++; $this->mysqli->next_result(); } public function key() { return $this->counter; } public function current() { if (!$this->current) { throw new Exception("valid() not called"); } return $this->current; } public function hasChildren() { return true; } public function getChildren() { return new MySQLi_PseudoRecursiveResultIterator($this->current); } } class MySQLi_PseudoRecursiveResultIterator extends IteratorIterator implements RecursiveIterator { public function __construct(MySQLi_Result $result) { // This ctor can be dropped with the experimental bzr sources // as IteratorIterator::__construct() directly works with // MySQLi_Result parent::__construct(new ArrayIterator($result->fetch_all())); } public function hasChildren() { return false; } public function getChildren() { throw new Exception("This should never be called"); } } ?>
Now we can use this code. For properly using a RecursiveIterator one should use a RecursiveIteratorIterator (RII). To get some nice labels I'm extending the RII and then have a single foreach:
<?php class MyRecursive_IteratorIterator extends RecursiveIteratorIterator { public function __construct(MySQLi $mysqli, $flags = 0) { parent::__construct( new Mysqli_ResultSetIterator($mysqli), $flags | RecursiveIteratorIterator::LEAVES_ONLY); } public function beginChildren() { echo "Next ResultSet:\n"; } } $mysqli = new MySQLi("localhost", "root", "", "test"); $query = "SELECT 1,2 UNION SELECT 3, 4;". "SELECT 'hi world' UNION SELECT 'foobar'"; if ($mysqli->multi_query($query)) { foreach (new MyRecursive_IteratorIterator($mysqli) as $key => $row) { printf(" %s\n", $row[0]); } } ?>
Now calling this code gives us a result similar to the following:
Next ResultSet: 1 3 Next ResultSet: hi world foobar
Isn't that nice? - I think that's a cool API! What do you think? Do you have use cases for such an API? Should we implement this in C and bundle it with PHP? Any feedback welcome!