From ad23227bd6a099e11f2e82d1158632edf14409c6 Mon Sep 17 00:00:00 2001 From: David Monllao Date: Mon, 23 Feb 2015 15:09:34 +0800 Subject: [PATCH 1/1] MDL-48595 core_dml: Adding a recordset walker --- lib/classes/dml/recordset_walk.php | 161 ++++++++++++++++++++++++++ lib/dml/tests/recordset_walk_test.php | 124 ++++++++++++++++++++ 2 files changed, 285 insertions(+) create mode 100644 lib/classes/dml/recordset_walk.php create mode 100644 lib/dml/tests/recordset_walk_test.php diff --git a/lib/classes/dml/recordset_walk.php b/lib/classes/dml/recordset_walk.php new file mode 100644 index 00000000000..7e9ea692887 --- /dev/null +++ b/lib/classes/dml/recordset_walk.php @@ -0,0 +1,161 @@ +. + +/** + * 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(); + } +} diff --git a/lib/dml/tests/recordset_walk_test.php b/lib/dml/tests/recordset_walk_test.php new file mode 100644 index 00000000000..bd9c804a449 --- /dev/null +++ b/lib/dml/tests/recordset_walk_test.php @@ -0,0 +1,124 @@ +. + +/** + * 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; + } +} -- 2.43.0