Skip to content

Commit e094258

Browse files
committed
MDL-86493 mod_quiz: Optimise mod_quiz_cm_info_dynamic
1 parent 74ee977 commit e094258

File tree

8 files changed

+334
-283
lines changed

8 files changed

+334
-283
lines changed

public/mod/quiz/classes/cache/overrides.php

Lines changed: 0 additions & 115 deletions
This file was deleted.
Lines changed: 111 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?php
2-
// This file is part of Moodle - http://moodle.org/
2+
// This file is part of Moodle - https://moodle.org/
33
//
44
// Moodle is free software: you can redistribute it and/or modify
55
// it under the terms of the GNU General Public License as published by
@@ -12,115 +12,165 @@
1212
// GNU General Public License for more details.
1313
//
1414
// You should have received a copy of the GNU General Public License
15-
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
15+
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
1616

1717
namespace mod_quiz\local;
1818

19+
use cache;
20+
use core_cache\data_source_interface;
21+
use core_cache\definition;
22+
1923
/**
20-
* Cache manager for quiz overrides
24+
* Cache encapsulation for quiz overrides.
2125
*
22-
* Override cache data is set via its data source, {@see \mod_quiz\cache\overrides}
23-
* @package mod_quiz
24-
* @copyright 2024 Matthew Hilton <[email protected]>
25-
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26+
* @package mod_quiz
27+
* @copyright 2025 Catalyst IT Australia Pty Ltd
28+
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2629
*/
27-
class override_cache {
28-
/** @var string invalidation event used to purge data when reset_userdata is called, {@see \cache_helper::purge_by_event()} **/
29-
public const INVALIDATION_USERDATARESET = 'userdatareset';
30+
class override_cache implements data_source_interface {
31+
/**
32+
* @var string Invalidation event used to purge data when reset_userdata is called.
33+
* @see \cache_helper::purge_by_event()
34+
**/
35+
public const INVALIDATION_RESET_USERDATA = 'resetuserdata';
36+
37+
/** @var ?override_cache The singleton instance for this class. */
38+
private static $instance = null;
3039

3140
/**
32-
* Create override_cache object and link to quiz
41+
* Returns the singleton instance of this class.
3342
*
34-
* @param int $quizid The quiz to link this cache to
43+
* @param definition $definition The cache definition.
44+
* @return override_cache The singleton instance.
3545
*/
36-
public function __construct(
37-
/** @var int $quizid ID of quiz cache is being operated on **/
38-
protected readonly int $quizid
39-
) {
46+
public static function get_instance_for_cache(definition $definition): override_cache {
47+
return self::$instance ??= new override_cache();
4048
}
4149

4250
/**
43-
* Returns the override cache
51+
* {@inheritdoc}
52+
* @see \core_cache\data_source_interface::load_for_cache()
4453
*
45-
* @return \cache
54+
* @param string|int $key The key to load.
55+
* @return mixed An array of override records or null if none are found or false for invalidation.
4656
*/
47-
protected function get_cache(): \cache {
48-
return \cache::make('mod_quiz', 'overrides');
57+
public function load_for_cache($key) {
58+
global $DB;
59+
60+
if ($key === 'lastinvalidation') {
61+
return false;
62+
}
63+
64+
[$quizid, $userid] = self::split_cache_key($key);
65+
66+
$subquery = "SELECT g.id
67+
FROM {groups} g
68+
JOIN {groups_members} gm ON gm.groupid = g.id
69+
JOIN {quiz} q ON q.course = g.courseid
70+
WHERE q.id = :subqueryquizid AND gm.userid = :subqueryuserid";
71+
72+
$sql = "SELECT *
73+
FROM {quiz_overrides}
74+
WHERE quiz = :quizid AND (userid = :userid OR groupid IN ($subquery))";
75+
76+
$records = $DB->get_records_sql($sql, [
77+
'quizid' => $quizid,
78+
'userid' => $userid,
79+
'subqueryquizid' => $quizid,
80+
'subqueryuserid' => $userid,
81+
]);
82+
83+
return empty($records) ? null : $records;
4984
}
5085

5186
/**
52-
* Returns group cache key
87+
* {@inheritdoc}
88+
* @see \core_cache\data_source_interface::load_many_for_cache()
5389
*
54-
* @param int $groupid
55-
* @return string the group cache key
90+
* @param array $keys An array of keys each of type string.
91+
* @return array An array of matching overrides.
5692
*/
57-
protected function get_group_cache_key(int $groupid): string {
58-
return "{$this->quizid}_g_{$groupid}";
93+
public function load_many_for_cache(array $keys) {
94+
$results = [];
95+
foreach ($keys as $key) {
96+
$results[$key] = $this->load_for_cache($key);
97+
}
98+
return $results;
5999
}
60100

61101
/**
62-
* Returns user cache key
102+
* Get all overrides for a given quiz and user.
63103
*
64-
* @param int $userid
65-
* @return string the user cache key
104+
* @param int $quizid The quiz id.
105+
* @param int $userid The user id.
106+
* @return ?array Array of overrides or null if none found.
66107
*/
67-
protected function get_user_cache_key(int $userid): string {
68-
return "{$this->quizid}_u_{$userid}";
108+
public static function get_overrides(int $quizid, int $userid): array|null {
109+
$cache = self::get_cache();
110+
$key = self::get_cache_key($quizid, $userid);
111+
return $cache->get($key);
69112
}
70113

71114
/**
72-
* Returns the override value in the cache for the given group
73-
*
74-
* @param int $groupid group to get cached override data for
75-
* @return ?\stdClass override value in the cache for the given group, or null if there is none.
115+
* Purge all overrides from the cache.
76116
*/
77-
public function get_cached_group_override(int $groupid): ?\stdClass {
78-
$raw = $this->get_cache()->get($this->get_group_cache_key($groupid));
79-
return empty($raw) || !is_object($raw) ? null : (object) $raw;
117+
public static function purge_all(): void {
118+
self::get_cache()->purge();
80119
}
81120

82121
/**
83-
* Returns the override value in the cache for the given user
122+
* Purge overrides for a specific user in a specific quiz.
84123
*
85-
* @param int $userid user to get cached override data for
86-
* @return ?\stdClass the override value in the cache for the given user, or null if there is none.
124+
* @param int $quizid The quiz id.
125+
* @param int $userid The user id.
87126
*/
88-
public function get_cached_user_override(int $userid): ?\stdClass {
89-
$raw = $this->get_cache()->get($this->get_user_cache_key($userid));
90-
return empty($raw) || !is_object($raw) ? null : (object) $raw;
127+
public static function purge_for_user(int $quizid, int $userid): void {
128+
self::purge_for_users($quizid, [$userid]);
91129
}
92130

93131
/**
94-
* Deletes the cached override data for a given group
132+
* Purge overrides for specific users in a specific quiz.
95133
*
96-
* @param int $groupid group to delete data for
134+
* @param int $quizid The quiz id.
135+
* @param int[] $userids The user ids.
97136
*/
98-
public function clear_for_group(int $groupid): void {
99-
$this->get_cache()->delete($this->get_group_cache_key($groupid));
137+
public static function purge_for_users(int $quizid, array $userids): void {
138+
if (empty($userids)) {
139+
return;
140+
}
141+
142+
$keys = array_map(fn($userid): string => self::get_cache_key($quizid, $userid), $userids);
143+
$cache = self::get_cache();
144+
$cache->delete_many($keys);
100145
}
101146

102147
/**
103-
* Deletes the cached override data for the given user
148+
* Get the cache instance.
104149
*
105-
* @param int $userid user to delete data for
150+
* @return cache The cache instance.
106151
*/
107-
public function clear_for_user(int $userid): void {
108-
$this->get_cache()->delete($this->get_user_cache_key($userid));
152+
private static function get_cache(): cache {
153+
return cache::make('mod_quiz', 'overrides');
109154
}
110155

111156
/**
112-
* Clears the cache for the given user and/or group.
157+
* Generate a cache key for a given quiz and user.
113158
*
114-
* @param ?int $userid user to delete data for, or null.
115-
* @param ?int $groupid group to delete data for, or null.
159+
* @param int $quizid The quiz id.
160+
* @param int $userid The user id.
161+
* @return string The cache key.
116162
*/
117-
public function clear_for(?int $userid = null, ?int $groupid = null): void {
118-
if (!empty($userid)) {
119-
$this->clear_for_user($userid);
120-
}
163+
private static function get_cache_key(int $quizid, int $userid): string {
164+
return "{$quizid}_{$userid}";
165+
}
121166

122-
if (!empty($groupid)) {
123-
$this->clear_for_group($groupid);
124-
}
167+
/**
168+
* Split a cache key into its quizid and userid components.
169+
*
170+
* @param string $key The cache key.
171+
* @return array An array with quizid and userid.
172+
*/
173+
private static function split_cache_key(string $key): array {
174+
return array_map('intval', explode('_', $key));
125175
}
126176
}

0 commit comments

Comments
 (0)