I've made a SPL-based class named Recordset that wraps both MySQLi_STMT and MySQLi_Result objects and allows treating either as a 3-dimensional array. It requires PHP5.3+. Here's excerpts of the important parts... class Recordset implements Iterator, ArrayAccess, Countable { private $MySQLi_set; private $MySQLi_set_type; private $field_metadata; private $num_rows; private $bind_row; private $pointer; function __construct($sql) { $bind_params = func_get_args(); array_shift($bind_params); if (count($bind_params) === 0) // add code here to build MYSQLI_Result obj and store it in $this->MySQLi_set else // add code here to build MYSQLI_STMT obj and store it in $this->MySQLi_set if (!($this->MySQLi_set instanceof MySQLi_STMT OR $this->MySQLi_set instanceof MySQLi_Result)) throw new RuntimeException('No MySQLi_STMT or MySQLi_Result object to work with.'); // cache name of object to avoid repeat instanceof calls $this->MySQLi_set_type = get_class($this->MySQLi_set); if ($this->MySQLi_set_type === 'MySQLi_Result') // Set up special handling for MySQLi_Result objects // set $this->field_metadata to array of field info objects $this->field_metadata = $this->MySQLi_set->fetch_fields(); else { // else this is a MYSQLI_STMT object // add code here to set $this->field_metadata to array of field info objects from MYSQLI_STMT // store results to allow object to act as a countable, seekable array if (!$this->MySQLi_set->store_result()) throw new RuntimeException('MySQLi_STMT Result failed to store. Error: ' . $this->MySQLi_set->error); // because bind_result() cannot bind to an object or array, use the 'call_user_func_array()' technique instead $this->bind_row = array(); $bind_row2 = array(); foreach($this->field_metadata as $field) $bind_row2[$field->name] =& $this->bind_row[$field->name]; if (!call_user_func_array(array($this->MySQLi_set, 'bind_result'), $this->bind_row)) throw new RuntimeException($this->MySQLi_set->error); if ($this->MySQLi_set->error) throw new RuntimeException($this->MySQLi_set->error); } // cache row count to avoid constant calls to MySQLi_x->num_rows $this->num_rows = $this->MySQLi_set->num_rows; // if rows were returned, set internal pointer to 0 if ($this->num_rows > 0) $this->pointer = 0; } function fetchRow($offset = null) { // sets $this->bind_row to an associative array of the next record for MySQLi_STMT wrapped objects $this->MySQLi_set->data_seek((($offset !== null) ? (int) $offset : $this->pointer)); if ($this->MySQLi_set_type === 'MySQLi_Result') return $this->MySQLi_set->fetch_assoc(); // else this is a MySQLi_STMT object $fetch_status = $this->MySQLi_set->fetch(); if ($fetch_status === true) return $this->bind_row; else if ($fetch_status === null) return null; // no more rows // MySQLi_STMT row fetch failed throw new RuntimeException('Error fetching row: . ' . $this->MySQLi_set->error); } function count() {return $this->num_rows;} function current() {return $this->fetchRow();} function key() {return $this->pointer;} function next() {$this->pointer++;} function rewind() {$this->pointer = 0;} function valid() {return $this->offsetExists($this->pointer);} function offsetExists($offset) {return ((int) $offset < $this->num_rows AND (int) $offset >= 0);} function offsetGet($offset) { if (!$this->offsetExists($offset)) throw new OutOfBoundsException('Out of range of result set.'); return $this->fetchRow($offset); } The code is roughly 200 lines long, so instead of making a giant post, here's a pastebin link... http://pastebin.com/LUe4j9DM Objects are created by passing in an sql query string. For handling prepared statements, simply pass in additional arguments as the prepared binding parameters. In those cases, there is a behind-the-scenes `bind_param` going on generating the MySQLi_STMT object to be wrapped. That part isn't included here, so for the purposes of this post, pretend it's magic. For example to automatically handle the query as a prepared statement: $recordset = new Recordset("SELECT * FROM tblComments WHERE(parentBlogID = ?)", 2); if (count($recordset) > 0) { foreach ($recordset as $record) echo $record['commentText'] . '<br>'; } else echo "No records returned."; <b>Advantages...</b> <ul> <li>Allows accessing result rows by array key (<code>echo $Recordset[295]['title'];</code>) or by foreach loops (<code>foreach ($Recordsetas $rec) {echo $rec['title'];}</code>).</li> <li>Allows you to get a row by index number without interfering with the internal result pointer. In other words, right in the middle of a foreach loop, you can do <code>$x = $Recordset[259]['ID'];</code> without causing the internal result row pointer to jump out of order.</li> <li>Allows exchanging this class out for regular 3-dimensional arrays of result dumps.</li> <li>Avoids duplication of result binding code for every statement throughout an application.</li> <li>Lets you still access functions and properties of the wrapped MySQLi object directly (<code>$Recordset->attr_get(1)</code>).</li> </ul> <b>Tests...</b> Here's some speed tests iterating over 10000 records from a localhost table with 6 columns. This test was done with prepared statement results either wrapped in Recordset or not. Timing is measured starting before and ending after each entire loop call.. <b>With Recordset...</b> <ul> <li><code>foreach($Recordset as $rec) {$x = $rec;}</code> ... <br> 0.105 s</li> <li><code>for ($i = 0, $count = count($Recordset); $i < $count; $i++) {$x = $Recordset[$i];}</code> ... <br> 0.056 s</li> <li><code>for ($i = 0, $count = count($Recordset); $i < $count; $i++) {$x = $Recordset->fetchRow($i);}</code> ... <br> 0.0351 s</li> <li><code>while ($x = $Recordset[$i]) {$i++; if (!isset($Recordset[$i])) break;}</code> ... <br> 0.0647 s</li> </ul> <b>Without Recordset, but still using the "bind to array" technique and pre-storing results...</b> <ul> <li><code>for ($i=0, $count=$MySQLI_STMT->num_rows; $i < $count; $i++)) {$MySQLI_STMT->fetch(); $x = $bind_row;}</code> ...<br> 0.0409 s</li> <li><code>while ($MySQLI_STMT->fetch()) {$x = $bind_row;}</code> ... <br> 0.009 s</li> </ul> I'm pretty bummed about the slow foreach loop over my Recordset object, by the slower seek and fetch speeds within Recordset, and by the fact that nothing can even compare to `while ($MySQLI_STMT->fetch()) {}`. I'd like for this to work better. Any suggestions or insight how to speed things up?