Commit | Line | Data |
---|---|---|
8139ad13 SH |
1 | <?php |
2 | // This file is part of Moodle - http://moodle.org/ | |
3 | // | |
4 | // Moodle is free software: you can redistribute it and/or modify | |
5 | // it under the terms of the GNU General Public License as published by | |
6 | // the Free Software Foundation, either version 3 of the License, or | |
7 | // (at your option) any later version. | |
8 | // | |
9 | // Moodle is distributed in the hope that it will be useful, | |
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | // GNU General Public License for more details. | |
13 | // | |
14 | // You should have received a copy of the GNU General Public License | |
15 | // along with Moodle. If not, see <http://www.gnu.org/licenses/>. | |
16 | ||
17 | /** | |
18 | * This file contains the cache factory class. | |
19 | * | |
20 | * This file is part of Moodle's cache API, affectionately called MUC. | |
21 | * It contains the components that are requried in order to use caching. | |
22 | * | |
23 | * @package core | |
24 | * @category cache | |
25 | * @copyright 2012 Sam Hemelryk | |
26 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
27 | */ | |
28 | ||
29 | defined('MOODLE_INTERNAL') || die(); | |
30 | ||
31 | /** | |
32 | * The cache factory class. | |
33 | * | |
34 | * This factory class is important because it stores instances of objects used by the cache API and returns them upon requests. | |
35 | * This allows us to both reuse objects saving on overhead, and gives us an easy place to "reset" the cache API in situations that | |
36 | * we need such as unit testing. | |
37 | * | |
38 | * @copyright 2012 Sam Hemelryk | |
39 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
40 | */ | |
41 | class cache_factory { | |
42 | ||
7383a7e2 SH |
43 | /** The cache has not been initialised yet. */ |
44 | const STATE_UNINITIALISED = 0; | |
45 | /** The cache is in the process of initialising itself. */ | |
46 | const STATE_INITIALISING = 1; | |
47 | /** The cache is in the process of saving its configuration file. */ | |
48 | const STATE_SAVING = 2; | |
49 | /** The cache is ready to use. */ | |
50 | const STATE_READY = 3; | |
51 | /** The cache encountered an error while initialising. */ | |
52 | const STATE_ERROR_INITIALISING = 9; | |
53 | /** The cache has been disabled. */ | |
54 | const STATE_DISABLED = 10; | |
82afd05c SH |
55 | /** The cache stores have been disabled */ |
56 | const STATE_STORES_DISABLED = 11; | |
7383a7e2 | 57 | |
8139ad13 SH |
58 | /** |
59 | * An instance of the cache_factory class created upon the first request. | |
60 | * @var cache_factory | |
61 | */ | |
62 | protected static $instance; | |
63 | ||
64 | /** | |
65 | * An array containing caches created for definitions | |
66 | * @var array | |
67 | */ | |
68 | protected $cachesfromdefinitions = array(); | |
69 | ||
70 | /** | |
71 | * Array of caches created by parameters, ad-hoc definitions will have been used. | |
72 | * @var array | |
73 | */ | |
74 | protected $cachesfromparams = array(); | |
75 | ||
76 | /** | |
77 | * An array of instantiated stores. | |
78 | * @var array | |
79 | */ | |
80 | protected $stores = array(); | |
81 | ||
82 | /** | |
83 | * An array of configuration instances | |
84 | * @var array | |
85 | */ | |
86 | protected $configs = array(); | |
87 | ||
88 | /** | |
89 | * An array of initialised definitions | |
90 | * @var array | |
91 | */ | |
92 | protected $definitions = array(); | |
93 | ||
34c84c72 SH |
94 | /** |
95 | * An array of lock plugins. | |
96 | * @var array | |
97 | */ | |
98 | protected $lockplugins = null; | |
99 | ||
7383a7e2 SH |
100 | /** |
101 | * The current state of the cache API. | |
102 | * @var int | |
103 | */ | |
104 | protected $state = 0; | |
105 | ||
8139ad13 SH |
106 | /** |
107 | * Returns an instance of the cache_factor method. | |
108 | * | |
109 | * @param bool $forcereload If set to true a new cache_factory instance will be created and used. | |
110 | * @return cache_factory | |
111 | */ | |
112 | public static function instance($forcereload = false) { | |
113 | if ($forcereload || self::$instance === null) { | |
114 | self::$instance = new cache_factory(); | |
c9f40e77 | 115 | if (defined('CACHE_DISABLE_STORES') && CACHE_DISABLE_STORES !== false) { |
7383a7e2 | 116 | // The cache stores have been disabled. |
82afd05c | 117 | self::$instance->set_state(self::STATE_STORES_DISABLED);; |
7383a7e2 | 118 | } |
8139ad13 SH |
119 | } |
120 | return self::$instance; | |
121 | } | |
122 | ||
123 | /** | |
124 | * Protected constructor, please use the static instance method. | |
125 | */ | |
126 | protected function __construct() { | |
127 | // Nothing to do here. | |
128 | } | |
129 | ||
130 | /** | |
131 | * Resets the arrays containing instantiated caches, stores, and config instances. | |
132 | */ | |
133 | public static function reset() { | |
134 | $factory = self::instance(); | |
135 | $factory->cachesfromdefinitions = array(); | |
136 | $factory->cachesfromparams = array(); | |
137 | $factory->stores = array(); | |
138 | $factory->configs = array(); | |
139 | $factory->definitions = array(); | |
34c84c72 | 140 | $factory->lockplugins = null; // MUST be null in order to force its regeneration. |
7383a7e2 SH |
141 | // Reset the state to uninitialised. |
142 | $factory->state = self::STATE_UNINITIALISED; | |
8139ad13 SH |
143 | } |
144 | ||
145 | /** | |
146 | * Creates a cache object given the parameters for a definition. | |
147 | * | |
148 | * If a cache has already been created for the given definition then that cache instance will be returned. | |
149 | * | |
150 | * @param string $component | |
151 | * @param string $area | |
152 | * @param array $identifiers | |
153 | * @param string $aggregate | |
154 | * @return cache_application|cache_session|cache_request | |
155 | */ | |
156 | public function create_cache_from_definition($component, $area, array $identifiers = array(), $aggregate = null) { | |
157 | $definitionname = $component.'/'.$area; | |
158 | if (array_key_exists($definitionname, $this->cachesfromdefinitions)) { | |
159 | $cache = $this->cachesfromdefinitions[$definitionname]; | |
160 | $cache->set_identifiers($identifiers); | |
161 | return $cache; | |
162 | } | |
163 | $definition = $this->create_definition($component, $area, $aggregate); | |
164 | $definition->set_identifiers($identifiers); | |
165 | $cache = $this->create_cache($definition, $identifiers); | |
166 | if ($definition->should_be_persistent()) { | |
167 | $this->cachesfromdefinitions[$definitionname] = $cache; | |
168 | } | |
169 | return $cache; | |
170 | } | |
171 | ||
172 | /** | |
173 | * Creates an ad-hoc cache from the given param. | |
174 | * | |
175 | * If a cache has already been created using the same params then that cache instance will be returned. | |
176 | * | |
177 | * @param int $mode | |
178 | * @param string $component | |
179 | * @param string $area | |
180 | * @param array $identifiers | |
2566210c SH |
181 | * @param array $options An array of options, available options are: |
182 | * - simplekeys : Set to true if the keys you will use are a-zA-Z0-9_ | |
183 | * - simpledata : Set to true if the type of the data you are going to store is scalar, or an array of scalar vars | |
184 | * - persistent : If set to true the cache will persist construction requests. | |
8139ad13 SH |
185 | * @return cache_application|cache_session|cache_request |
186 | */ | |
2566210c | 187 | public function create_cache_from_params($mode, $component, $area, array $identifiers = array(), array $options = array()) { |
8139ad13 SH |
188 | $key = "{$mode}_{$component}_{$area}"; |
189 | if (array_key_exists($key, $this->cachesfromparams)) { | |
190 | return $this->cachesfromparams[$key]; | |
191 | } | |
192 | // Get the class. Note this is a late static binding so we need to use get_called_class. | |
2566210c | 193 | $definition = cache_definition::load_adhoc($mode, $component, $area, $options); |
8139ad13 SH |
194 | $definition->set_identifiers($identifiers); |
195 | $cache = $this->create_cache($definition, $identifiers); | |
196 | if ($definition->should_be_persistent()) { | |
8139ad13 SH |
197 | $this->cachesfromparams[$key] = $cache; |
198 | } | |
199 | return $cache; | |
200 | } | |
201 | ||
202 | /** | |
34c84c72 | 203 | * Common public method to create a cache instance given a definition. |
8139ad13 SH |
204 | * |
205 | * This is used by the static make methods. | |
206 | * | |
207 | * @param cache_definition $definition | |
208 | * @return cache_application|cache_session|cache_store | |
209 | * @throws coding_exception | |
210 | */ | |
211 | public function create_cache(cache_definition $definition) { | |
212 | $class = $definition->get_cache_class(); | |
213 | $stores = cache_helper::get_cache_stores($definition); | |
214 | if (count($stores) === 0) { | |
215 | // Hmm no stores, better provide a dummy store to mimick functionality. The dev will be none the wiser. | |
216 | $stores[] = $this->create_dummy_store($definition); | |
217 | } | |
218 | $loader = null; | |
219 | if ($definition->has_data_source()) { | |
220 | $loader = $definition->get_data_source(); | |
221 | } | |
222 | while (($store = array_pop($stores)) !== null) { | |
223 | $loader = new $class($definition, $store, $loader); | |
224 | } | |
225 | return $loader; | |
226 | } | |
227 | ||
228 | /** | |
229 | * Creates a store instance given its name and configuration. | |
230 | * | |
231 | * If the store has already been instantiated then the original objetc will be returned. (reused) | |
232 | * | |
233 | * @param string $name The name of the store (must be unique remember) | |
234 | * @param array $details | |
235 | * @param cache_definition $definition The definition to instantiate it for. | |
3680c61a | 236 | * @return boolean|cache_store |
8139ad13 SH |
237 | */ |
238 | public function create_store_from_config($name, array $details, cache_definition $definition) { | |
239 | if (!array_key_exists($name, $this->stores)) { | |
170f821b | 240 | // Properties: name, plugin, configuration, class. |
8139ad13 SH |
241 | $class = $details['class']; |
242 | $store = new $class($details['name'], $details['configuration']); | |
243 | $this->stores[$name] = $store; | |
244 | } | |
245 | $store = $this->stores[$name]; | |
246 | if (!$store->is_ready() || !$store->is_supported_mode($definition->get_mode())) { | |
247 | return false; | |
248 | } | |
249 | $store = clone($this->stores[$name]); | |
250 | $store->initialise($definition); | |
251 | return $store; | |
252 | } | |
253 | ||
254 | /** | |
255 | * Creates a cache config instance with the ability to write if required. | |
256 | * | |
257 | * @param bool $writer If set to true an instance that can update the configuration will be returned. | |
258 | * @return cache_config|cache_config_writer | |
259 | */ | |
260 | public function create_config_instance($writer = false) { | |
261 | global $CFG; | |
262 | ||
263 | // Check if we need to create a config file with defaults. | |
264 | $needtocreate = !cache_config::config_file_exists(); | |
265 | ||
266 | // The class to use. | |
267 | $class = 'cache_config'; | |
268 | if ($writer || $needtocreate) { | |
269 | require_once($CFG->dirroot.'/cache/locallib.php'); | |
270 | $class .= '_writer'; | |
271 | } | |
170f821b | 272 | |
8139ad13 SH |
273 | // Check if this is a PHPUnit test and redirect to the phpunit config classes if it is. |
274 | if (defined('PHPUNIT_TEST') && PHPUNIT_TEST) { | |
275 | require_once($CFG->dirroot.'/cache/locallib.php'); | |
276 | require_once($CFG->dirroot.'/cache/tests/fixtures/lib.php'); | |
277 | // We have just a single class for PHP unit tests. We don't care enough about its | |
278 | // performance to do otherwise and having a single method allows us to inject things into it | |
279 | // while testing. | |
280 | $class = 'cache_config_phpunittest'; | |
281 | } | |
282 | ||
7383a7e2 | 283 | $error = false; |
8139ad13 SH |
284 | if ($needtocreate) { |
285 | // Create the default configuration. | |
7383a7e2 SH |
286 | // Update the state, we are now initialising the cache. |
287 | self::set_state(self::STATE_INITIALISING); | |
288 | $configuration = $class::create_default_configuration(); | |
289 | if ($configuration !== true) { | |
290 | // Failed to create the default configuration. Disable the cache stores and update the state. | |
291 | self::set_state(self::STATE_ERROR_INITIALISING); | |
292 | $this->configs[$class] = new $class; | |
293 | $this->configs[$class]->load($configuration); | |
294 | $error = true; | |
295 | } | |
8139ad13 SH |
296 | } |
297 | ||
298 | if (!array_key_exists($class, $this->configs)) { | |
299 | // Create a new instance and call it to load it. | |
300 | $this->configs[$class] = new $class; | |
301 | $this->configs[$class]->load(); | |
302 | } | |
303 | ||
7383a7e2 SH |
304 | if (!$error) { |
305 | // The cache is now ready to use. Update the state. | |
306 | self::set_state(self::STATE_READY); | |
307 | } | |
308 | ||
8139ad13 SH |
309 | // Return the instance. |
310 | return $this->configs[$class]; | |
311 | } | |
312 | ||
313 | /** | |
314 | * Creates a definition instance or returns the existing one if it has already been created. | |
315 | * @param string $component | |
316 | * @param string $area | |
317 | * @param string $aggregate | |
318 | * @return cache_definition | |
319 | */ | |
320 | public function create_definition($component, $area, $aggregate = null) { | |
321 | $id = $component.'/'.$area; | |
322 | if ($aggregate) { | |
323 | $id .= '::'.$aggregate; | |
324 | } | |
325 | if (!array_key_exists($id, $this->definitions)) { | |
326 | $instance = $this->create_config_instance(); | |
327 | $definition = $instance->get_definition_by_id($id); | |
328 | if (!$definition) { | |
329 | $this->reset(); | |
330 | $instance = $this->create_config_instance(true); | |
331 | $instance->update_definitions(); | |
332 | $definition = $instance->get_definition_by_id($id); | |
333 | if (!$definition) { | |
334 | throw new coding_exception('The requested cache definition does not exist.'. $id, $id); | |
335 | } else { | |
170f821b SH |
336 | debugging('Cache definitions reparsed causing cache reset in order to locate definition. |
337 | You should bump the version number to ensure definitions are reprocessed.', DEBUG_DEVELOPER); | |
8139ad13 SH |
338 | } |
339 | } | |
340 | $this->definitions[$id] = cache_definition::load($id, $definition, $aggregate); | |
341 | } | |
342 | return $this->definitions[$id]; | |
343 | } | |
344 | ||
345 | /** | |
346 | * Creates a dummy store object for use when a loader has no potential stores to use. | |
347 | * | |
348 | * @param cache_definition $definition | |
6fec1820 | 349 | * @return cachestore_dummy |
8139ad13 SH |
350 | */ |
351 | protected function create_dummy_store(cache_definition $definition) { | |
352 | global $CFG; | |
353 | require_once($CFG->dirroot.'/cache/classes/dummystore.php'); | |
6fec1820 | 354 | $store = new cachestore_dummy(); |
8139ad13 SH |
355 | $store->initialise($definition); |
356 | return $store; | |
357 | } | |
34c84c72 SH |
358 | |
359 | /** | |
360 | * Returns a lock instance ready for use. | |
361 | * | |
362 | * @param array $config | |
363 | * @return cache_lock_interface | |
364 | */ | |
365 | public function create_lock_instance(array $config) { | |
366 | if (!array_key_exists('name', $config) || !array_key_exists('type', $config)) { | |
367 | throw new coding_exception('Invalid cache lock instance provided'); | |
368 | } | |
369 | $name = $config['name']; | |
370 | $type = $config['type']; | |
371 | unset($config['name']); | |
372 | unset($config['type']); | |
373 | ||
374 | if ($this->lockplugins === null) { | |
375 | $this->lockplugins = get_plugin_list_with_class('cachelock', '', 'lib.php'); | |
376 | } | |
377 | if (!array_key_exists($type, $this->lockplugins)) { | |
378 | throw new coding_exception('Invalid cache lock type.'); | |
379 | } | |
380 | $class = $this->lockplugins[$type]; | |
381 | return new $class($name, $config); | |
382 | } | |
7383a7e2 SH |
383 | |
384 | /** | |
385 | * Returns the current state of the cache API. | |
386 | * | |
387 | * @return int | |
388 | */ | |
389 | public function get_state() { | |
390 | return $this->state; | |
391 | } | |
392 | ||
393 | /** | |
394 | * Updates the state fo the cache API. | |
395 | * | |
396 | * @param int $state | |
397 | * @return bool | |
398 | */ | |
399 | public function set_state($state) { | |
400 | if ($state <= $this->state) { | |
401 | return false; | |
402 | } | |
403 | $this->state = $state; | |
404 | return true; | |
405 | } | |
406 | ||
407 | /** | |
408 | * Returns true if the cache API has been disabled. | |
409 | * | |
410 | * @return bool | |
411 | */ | |
412 | public function is_disabled() { | |
413 | return $this->state === self::STATE_DISABLED; | |
414 | } | |
82afd05c | 415 | |
3680c61a SH |
416 | /** |
417 | * Disables as much of the cache API as possible. | |
418 | * | |
419 | * All of the magic associated with the disabled cache is wrapped into this function. | |
420 | * In switching out the factory for the disabled factory it gains full control over the initialisation of objects | |
421 | * and can use all of the disabled alternatives. | |
422 | * Simple! | |
423 | */ | |
424 | public static function disable() { | |
425 | global $CFG; | |
426 | require_once($CFG->dirroot.'/cache/disabledlib.php'); | |
427 | self::$instance = null; | |
428 | self::$instance = new cache_factory_disabled(); | |
429 | } | |
430 | ||
82afd05c SH |
431 | /** |
432 | * Returns true if the cache stores have been disabled. | |
433 | * | |
434 | * @return bool | |
435 | */ | |
436 | public function stores_disabled() { | |
3680c61a | 437 | return $this->state === self::STATE_STORES_DISABLED || $this->is_disabled(); |
82afd05c SH |
438 | } |
439 | ||
440 | /** | |
441 | * Disables cache stores. | |
442 | * | |
443 | * The cache API will continue to function however none of the actual stores will be used. | |
444 | * Instead the dummy store will be provided for all cache requests. | |
445 | * This is useful in situations where you cannot be sure any stores are working. | |
446 | * | |
447 | * In order to re-enable the cache you must call the cache factories static reset method: | |
448 | * <code> | |
449 | * // Disable the cache factory. | |
450 | * cache_factory::disable_stores(); | |
451 | * // Re-enable the cache factory by resetting it. | |
452 | * cache_factory::reset(); | |
453 | * </code> | |
454 | */ | |
455 | public static function disable_stores() { | |
456 | $factory = self::instance(); | |
457 | $factory->set_state(self::STATE_STORES_DISABLED); | |
458 | } | |
8139ad13 | 459 | } |