diff --git a/lib/accesslib.php b/lib/accesslib.php index 8c4dd64c1a3..6c7f6bf04f8 100644 --- a/lib/accesslib.php +++ b/lib/accesslib.php @@ -5334,6 +5334,9 @@ abstract class context extends stdClass implements IteratorAggregate { return array(); } + // Preload the contexts to reduce DB calls. + context_helper::preload_contexts_by_id($contextids); + $result = array(); foreach ($contextids as $contextid) { $parent = context::instance_by_id($contextid, MUST_EXIST); @@ -5710,6 +5713,35 @@ class context_helper extends context { context::preload_from_record($rec); } + /** + * Preload a set of contexts using their contextid. + * + * @param array $contextids + */ + public static function preload_contexts_by_id(array $contextids) { + global $DB; + + // Determine which contexts are not already cached. + $tofetch = []; + foreach ($contextids as $contextid) { + if (!self::cache_get_by_id($contextid)) { + $tofetch[] = $contextid; + } + } + + if (count($tofetch) > 1) { + // There are at least two to fetch. + // There is no point only fetching a single context as this would be no more efficient than calling the existing code. + list($insql, $inparams) = $DB->get_in_or_equal($tofetch, SQL_PARAMS_NAMED); + $ctxs = $DB->get_recordset_select('context', "id {$insql}", $inparams, '', + \context_helper::get_preload_record_columns_sql('{context}')); + foreach ($ctxs as $ctx) { + self::preload_from_record($ctx); + } + $ctxs->close(); + } + } + /** * Preload all contexts instances from course. * diff --git a/lib/tests/accesslib_test.php b/lib/tests/accesslib_test.php index 2f9e8738368..257dfa52017 100644 --- a/lib/tests/accesslib_test.php +++ b/lib/tests/accesslib_test.php @@ -3782,6 +3782,47 @@ class core_accesslib_testcase extends advanced_testcase { $this->setUser($user2); $this->assertEquals($expectedteacher, get_profile_roles($coursecontext)); } + + /** + * Ensure that the get_parent_contexts() function limits the number of queries it performs. + */ + public function test_get_parent_contexts_preload() { + global $DB; + + $this->resetAfterTest(); + + /* + * Given the following data structure: + * System + * - Category + * --- Category + * ----- Category + * ------- Category + * --------- Course + * ----------- Activity (Forum) + */ + + $contexts = []; + + $cat1 = $this->getDataGenerator()->create_category(); + $cat2 = $this->getDataGenerator()->create_category(['parent' => $cat1->id]); + $cat3 = $this->getDataGenerator()->create_category(['parent' => $cat2->id]); + $cat4 = $this->getDataGenerator()->create_category(['parent' => $cat3->id]); + $course = $this->getDataGenerator()->create_course(['category' => $cat4->id]); + $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]); + + $modcontext = context_module::instance($forum->cmid); + + context_helper::reset_caches(); + + // There should only be a single DB query. + $predbqueries = $DB->perf_get_reads(); + + $parents = $modcontext->get_parent_contexts(); + // Note: For some databases There is one read, plus one FETCH, plus one CLOSE. + // These all show as reads, when there has actually only been a single query. + $this->assertLessThanOrEqual(3, $DB->perf_get_reads() - $predbqueries); + } } /**