3

MySQLi result set iteration - recursive

 2 years ago
source link: http://schlueters.de/blog/archives/114-MySQLi-result-set-iteration-recursive.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

MySQLi result set iteration - recursive

2009-07-01 11:56:59

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!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK