--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Applies the same callback to all recorset records.
+ *
+ * @since Moodle 2.9
+ * @package core
+ * @category dml
+ * @copyright 2015 David Monllao
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\dml;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Iterator that walks through a moodle_recordset applying the provided function.
+ *
+ * The internal recordset can be closed using the close() function.
+ *
+ * Note that consumers of this class are responsible of closing the recordset,
+ * although there are some implicit closes under some ciscumstances:
+ * - Once all recordset records have been iterated
+ * - The object is destroyed
+ *
+ * @since Moodle 2.9
+ * @package core
+ * @category dml
+ * @copyright 2015 David Monllao
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class recordset_walk implements \Iterator {
+
+ /**
+ * @var \moodle_recordset The recordset.
+ */
+ protected $recordset;
+
+ /**
+ * @var callable The callback.
+ */
+ protected $callback;
+
+ /**
+ * @var array|false Extra params for the callback.
+ */
+ protected $callbackextra;
+
+ /**
+ * Create a new iterator applying the callback to each record.
+ *
+ * @param \moodle_recordset $recordset Recordset to iterate.
+ * @param callable $callback Apply this function to each record. If using a method, it should be public.
+ * @param array $callbackextra Array of arguments to pass to the callback.
+ */
+ public function __construct(\moodle_recordset $recordset, callable $callback, $callbackextra = false) {
+ $this->recordset = $recordset;
+ $this->callback = $callback;
+ $this->callbackextra = $callbackextra;
+ }
+
+ /**
+ * Closes the recordset.
+ *
+ * @return void
+ */
+ public function __destruct() {
+ $this->close();
+ }
+
+ /**
+ * Returns the current element after applying the callback.
+ *
+ * @return mixed|bool The returned value type will depend on the callback.
+ */
+ public function current() {
+
+ if (!$this->recordset->valid()) {
+ return false;
+ }
+
+ if (!$record = $this->recordset->current()) {
+ return false;
+ }
+
+ // Apply callback and return.
+ if ($this->callbackextra) {
+ return call_user_func($this->callback, $record);
+ } else {
+ return call_user_func($this->callback, $record, $this->callbackextra);
+ }
+ }
+
+ /**
+ * Moves the internal pointer to the next record.
+ *
+ * @return void
+ */
+ public function next() {
+ return $this->recordset->next();
+ }
+
+ /**
+ * Returns current record key.
+ *
+ * @return int
+ */
+ public function key() {
+ return $this->recordset->key();
+ }
+
+ /**
+ * Returns whether the current position is valid or not.
+ *
+ * If we reached the end of the recordset we close as we
+ * don't allow rewinds. Doing do so we reduce the chance
+ * of unclosed recordsets.
+ *
+ * @return bool
+ */
+ public function valid() {
+ if (!$valid = $this->recordset->valid()) {
+ $this->close();
+ }
+ return $valid;
+ }
+
+ /**
+ * Rewind is not supported.
+ *
+ * @return void
+ */
+ public function rewind() {
+ // No rewind as it is not implemented in moodle_recordset.
+ return;
+ }
+
+ /**
+ * Closes the recordset.
+ *
+ * @return void
+ */
+ public function close() {
+ $this->recordset->close();
+ }
+}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Test \core\dml\recordset_walk.
+ *
+ * @package core
+ * @category dml
+ * @copyright 2015 David Monllao
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Test case for recordset_walk.
+ *
+ * @package core
+ * @category dml
+ * @copyright 2015 David Monllao
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_recordset_walk_testcase extends advanced_testcase {
+
+ public function setUp() {
+ parent::setUp();
+ $this->resetAfterTest();
+ }
+
+ public function test_no_data() {
+ global $DB;
+
+ $recordset = $DB->get_recordset('assign');
+ $walker = new \core\dml\recordset_walk($recordset, array($this, 'simple_callback'));
+ $this->assertEquals(0, iterator_count($walker));
+ $this->assertFalse($walker->valid());
+ foreach ($walker as $data) {
+ // No error here.
+ }
+ $walker->close();
+ }
+
+ public function test_simple_callback() {
+ global $DB;
+
+ $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+ $courses = array();
+ for ($i = 0; $i < 10; $i++) {
+ $courses[$i] = $generator->create_instance(array('course' => SITEID));
+ }
+
+ // Simple iteration.
+ $recordset = $DB->get_recordset('assign');
+ $walker = new \core\dml\recordset_walk($recordset, array($this, 'simple_callback'));
+ $this->assertEquals(10, iterator_count($walker));
+ foreach ($walker as $data) {
+ // Checking that the callback is being executed on each iteration.
+ $this->assertEquals($data->id . ' potatoes', $data->newfield);
+ }
+ // No exception if we double-close.
+ $walker->close();
+ }
+
+ public function test_extra_params_callback() {
+ global $DB;
+
+ $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+ $courses = array();
+ for ($i = 0; $i < 10; $i++) {
+ $courses[$i] = $generator->create_instance(array('course' => SITEID));
+ }
+
+ // Iteration with extra callback arguments.
+ $recordset = $DB->get_recordset('assign');
+ $walker = new \core\dml\recordset_walk(
+ $recordset,
+ array($this, 'extra_callback'),
+ array('brown' => 'onions')
+ );
+ $this->assertEquals(10, iterator_count($walker));
+ foreach ($walker as $data) {
+ // Checking that the callback is being executed on each
+ // iteration and the param is being passed.
+ $this->assertEquals('onions', $data->brown);
+ }
+ $walker->close();
+ }
+
+ /**
+ * Simple callback requiring 1 row fields.
+ *
+ * @param stdClass $data
+ * @return \Traversable
+ */
+ public function simple_callback($data) {
+ $data->newfield = $data->id . ' potatoes';
+ return $data;
+ }
+
+ /**
+ * Callback requiring 1 row fields + other params.
+ *
+ * @param stdClass $data
+ * @param mixed $extra
+ * @return \Traversable
+ */
+ public function extra_callback($data, $extra) {
+ $data->brown = $extra['brown'];
+ return $data;
+ }
+}