MDL-36768 cache: replaced cache_store interface with abstract class
[moodle.git] / cache / classes / config.php
CommitLineData
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 * Cache configuration reader
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
29defined('MOODLE_INTERNAL') || die();
30
31/**
32 * Cache configuration reader.
33 *
34 * This class is used to interact with the cache's configuration.
35 * The configuration is stored in the Moodle data directory.
36 *
37 * @package core
38 * @category cache
39 * @copyright 2012 Sam Hemelryk
40 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
41 */
42class cache_config {
43
44 /**
45 * The configured stores
46 * @var array
47 */
48 protected $configstores = array();
49
50 /**
51 * The configured mode mappings
52 * @var array
53 */
54 protected $configmodemappings = array();
55
56 /**
57 * The configured definitions as picked up from cache.php files
58 * @var array
59 */
60 protected $configdefinitions = array();
61
62 /**
63 * The definition mappings that have been configured.
64 * @var array
65 */
66 protected $configdefinitionmappings = array();
67
34c84c72
SH
68 /**
69 * An array of configured cache lock instances.
70 * @var array
71 */
72 protected $configlocks = array();
73
8139ad13
SH
74 /**
75 * Please use cache_config::instance to get an instance of the cache config that is ready to be used.
76 */
77 public function __construct() {
170f821b 78 // Nothing to do here but look pretty.
8139ad13
SH
79 }
80
81 /**
82 * Gets an instance of the cache_configuration class.
83 *
84 * @return cache_config
85 */
86 public static function instance() {
87 $factory = cache_factory::instance();
88 return $factory->create_config_instance();
89 }
90
91 /**
92 * Checks if the configuration file exists.
93 *
94 * @return bool True if it exists
95 */
96 public static function config_file_exists() {
170f821b 97 // Allow for late static binding.
8139ad13
SH
98 return file_exists(self::get_config_file_path());
99 }
100
101 /**
102 * Returns the expected path to the configuration file.
103 *
104 * @return string The absolute path
105 */
106 protected static function get_config_file_path() {
107 global $CFG;
bb250f02
SH
108 if (!empty($CFG->altcacheconfigpath)) {
109 $path = $CFG->altcacheconfigpath;
110 if (is_dir($path) && is_writable($path)) {
111 // Its a writable directory, thats fine.
112 return $path.'/cacheconfig.php';
113 } else if (is_writable(dirname($path)) && (!file_exists($path) || is_writable($path))) {
114 // Its a file, either it doesn't exist and the directory is writable or the file exists and is writable.
115 return $path;
116 }
117 }
118 // Return the default location within dataroot.
8139ad13
SH
119 return $CFG->dataroot.'/muc/config.php';
120 }
121
122 /**
123 * Loads the configuration file and parses its contents into the expected structure.
124 *
125 * @return boolean
126 */
127 public function load() {
128 global $CFG;
129
130 $configuration = $this->include_configuration();
131
132 $this->configstores = array();
133 $this->configdefinitions = array();
34c84c72 134 $this->configlocks = array();
8139ad13
SH
135 $this->configmodemappings = array();
136 $this->configdefinitionmappings = array();
34c84c72
SH
137 $this->configlockmappings = array();
138
170f821b 139 // Filter the lock instances.
34c84c72
SH
140 $defaultlock = null;
141 foreach ($configuration['locks'] as $conf) {
142 if (!is_array($conf)) {
143 // Something is very wrong here.
144 continue;
145 }
146 if (!array_key_exists('name', $conf)) {
170f821b 147 // Not a valid definition configuration.
34c84c72
SH
148 continue;
149 }
150 $name = $conf['name'];
151 if (array_key_exists($name, $this->configlocks)) {
152 debugging('Duplicate cache lock detected. This should never happen.', DEBUG_DEVELOPER);
153 continue;
154 }
167ad91e
SH
155 $conf['default'] = (!empty($conf['default']));
156 if ($defaultlock === null || $conf['default']) {
34c84c72
SH
157 $defaultlock = $name;
158 }
159 $this->configlocks[$name] = $conf;
160 }
8139ad13 161
170f821b 162 // Filter the stores.
8139ad13
SH
163 $availableplugins = cache_helper::early_get_cache_plugins();
164 foreach ($configuration['stores'] as $store) {
165 if (!is_array($store) || !array_key_exists('name', $store) || !array_key_exists('plugin', $store)) {
170f821b 166 // Not a valid instance configuration.
8139ad13
SH
167 debugging('Invalid cache store in config. Missing name or plugin.', DEBUG_DEVELOPER);
168 continue;
169 }
170 $plugin = $store['plugin'];
6fec1820 171 $class = 'cachestore_'.$plugin;
170f821b 172 $exists = array_key_exists($plugin, $availableplugins);
d4797177 173 if (!$exists) {
8139ad13
SH
174 // Not a valid plugin, or has been uninstalled, just skip it an carry on.
175 debugging('Invalid cache store in config. Not an available plugin.', DEBUG_DEVELOPER);
176 continue;
177 }
178 $file = $CFG->dirroot.'/cache/stores/'.$plugin.'/lib.php';
179 if (!class_exists($class) && file_exists($file)) {
180 require_once($file);
181 }
182 if (!class_exists($class)) {
183 continue;
184 }
75cde6b9 185 if (!array_key_exists('cache_store', class_parents($class))) {
8139ad13
SH
186 continue;
187 }
188 if (!array_key_exists('configuration', $store) || !is_array($store['configuration'])) {
189 $store['configuration'] = array();
190 }
8139ad13
SH
191 $store['class'] = $class;
192 $store['default'] = !empty($store['default']);
167ad91e 193 if (!array_key_exists('lock', $store) || !array_key_exists($store['lock'], $this->configlocks)) {
34c84c72
SH
194 $store['lock'] = $defaultlock;
195 }
196
8139ad13
SH
197 $this->configstores[$store['name']] = $store;
198 }
199
170f821b 200 // Filter the definitions.
8139ad13
SH
201 foreach ($configuration['definitions'] as $id => $conf) {
202 if (!is_array($conf)) {
203 // Something is very wrong here.
204 continue;
205 }
206 if (!array_key_exists('mode', $conf) || !array_key_exists('component', $conf) || !array_key_exists('area', $conf)) {
170f821b 207 // Not a valid definition configuration.
8139ad13
SH
208 continue;
209 }
210 if (array_key_exists($id, $this->configdefinitions)) {
211 debugging('Duplicate cache definition detected. This should never happen.', DEBUG_DEVELOPER);
212 continue;
213 }
214 $conf['mode'] = (int)$conf['mode'];
215 if ($conf['mode'] < cache_store::MODE_APPLICATION || $conf['mode'] > cache_store::MODE_REQUEST) {
170f821b 216 // Invalid cache mode used for the definition.
8139ad13
SH
217 continue;
218 }
219 $this->configdefinitions[$id] = $conf;
220 }
221
170f821b 222 // Filter the mode mappings.
8139ad13
SH
223 foreach ($configuration['modemappings'] as $mapping) {
224 if (!is_array($mapping) || !array_key_exists('mode', $mapping) || !array_key_exists('store', $mapping)) {
170f821b 225 // Not a valid mapping configuration.
8139ad13
SH
226 debugging('A cache mode mapping entry is invalid.', DEBUG_DEVELOPER);
227 continue;
228 }
229 if (!array_key_exists($mapping['store'], $this->configstores)) {
170f821b 230 // Mapped array instance doesn't exist.
8139ad13
SH
231 debugging('A cache mode mapping exists for a mode or store that does not exist.', DEBUG_DEVELOPER);
232 continue;
233 }
234 $mapping['mode'] = (int)$mapping['mode'];
235 if ($mapping['mode'] < 0 || $mapping['mode'] > 4) {
170f821b 236 // Invalid cache type used for the mapping.
8139ad13
SH
237 continue;
238 }
239 if (!array_key_exists('sort', $mapping)) {
240 $mapping['sort'] = 0;
241 }
242 $this->configmodemappings[] = $mapping;
243 }
244
170f821b 245 // Filter the definition mappings.
8139ad13
SH
246 foreach ($configuration['definitionmappings'] as $mapping) {
247 if (!is_array($mapping) || !array_key_exists('definition', $mapping) || !array_key_exists('store', $mapping)) {
170f821b 248 // Not a valid mapping configuration.
8139ad13
SH
249 continue;
250 }
251 if (!array_key_exists($mapping['store'], $this->configstores)) {
170f821b 252 // Mapped array instance doesn't exist.
8139ad13
SH
253 continue;
254 }
255 if (!array_key_exists($mapping['definition'], $this->configdefinitions)) {
170f821b 256 // Mapped array instance doesn't exist.
8139ad13
SH
257 continue;
258 }
259 if (!array_key_exists('sort', $mapping)) {
260 $mapping['sort'] = 0;
261 }
262 $this->configdefinitionmappings[] = $mapping;
263 }
264
265 usort($this->configmodemappings, array($this, 'sort_mappings'));
266 usort($this->configdefinitionmappings, array($this, 'sort_mappings'));
267
268 return true;
269 }
270
271 /**
272 * Includes the configuration file and makes sure it contains the expected bits.
273 *
274 * You need to ensure that the config file exists before this is called.
275 *
276 * @return array
277 * @throws cache_exception
278 */
279 protected function include_configuration() {
280 $configuration = array();
281 $cachefile = self::get_config_file_path();
282
283 if (!file_exists($cachefile)) {
170f821b 284 throw new cache_exception('Default cache config could not be found. It should have already been created by now.');
8139ad13
SH
285 }
286 include($cachefile);
287 if (!is_array($configuration)) {
288 throw new cache_exception('Invalid cache configuration file');
289 }
290 if (!array_key_exists('stores', $configuration) || !is_array($configuration['stores'])) {
291 $configuration['stores'] = array();
292 }
293 if (!array_key_exists('modemappings', $configuration) || !is_array($configuration['modemappings'])) {
294 $configuration['modemappings'] = array();
295 }
296 if (!array_key_exists('definitions', $configuration) || !is_array($configuration['definitions'])) {
297 $configuration['definitions'] = array();
298 }
299 if (!array_key_exists('definitionmappings', $configuration) || !is_array($configuration['definitionmappings'])) {
300 $configuration['definitionmappings'] = array();
301 }
34c84c72
SH
302 if (!array_key_exists('locks', $configuration) || !is_array($configuration['locks'])) {
303 $configuration['locks'] = array();
304 }
8139ad13
SH
305
306 return $configuration;
307 }
308
309 /**
310 * Used to sort cache config arrays based upon a sort key.
311 *
312 * Highest number at the top.
313 *
314 * @param array $a
315 * @param array $b
316 * @return int
317 */
318 protected function sort_mappings(array $a, array $b) {
319 if ($a['sort'] == $b['sort']) {
320 return 0;
321 }
170f821b 322 return ($a['sort'] < $b['sort']) ? 1 : -1;
8139ad13
SH
323 }
324
325 /**
326 * Gets a definition from the config given its name.
327 *
328 * @param string $id
329 * @return bool
330 */
331 public function get_definition_by_id($id) {
332 if (array_key_exists($id, $this->configdefinitions)) {
333 return $this->configdefinitions[$id];
334 }
335 return false;
336 }
337
338 /**
339 * Returns all the known definitions.
340 *
341 * @return array
342 */
343 public function get_definitions() {
344 return $this->configdefinitions;
345 }
346
fb8305de
MS
347 /**
348 * Returns the definitions mapped into the given store name.
349 *
350 * @param string $storename
351 * @return array Associative array of definitions, id=>definition
352 */
353 public static function get_definitions_by_store($storename) {
354 $definitions = array();
355
356 $config = cache_config::instance();
357 $stores = $config->get_all_stores();
358 if (!array_key_exists($storename, $stores)) {
359 // The store does not exist.
360 return false;
361 }
362
363 $defmappings = $config->get_definition_mappings();
364 // Create an associative array for the definition mappings.
365 $thedefmappings = array();
366 foreach ($defmappings as $defmapping) {
367 $thedefmappings[$defmapping['definition']] = $defmapping;
368 }
369
370 // Search for matches in default mappings.
371 $defs = $config->get_definitions();
372 foreach($config->get_mode_mappings() as $modemapping) {
373 if ($modemapping['store'] !== $storename) {
374 continue;
375 }
376 foreach($defs as $id => $definition) {
377 if ($definition['mode'] !== $modemapping['mode']) {
378 continue;
379 }
380 // Exclude custom definitions mapping: they will be managed few lines below.
381 if (array_key_exists($id, $thedefmappings)) {
382 continue;
383 }
384 $definitions[$id] = $definition;
385 }
386 }
387
388 // Search for matches in the custom definitions mapping
389 foreach ($defmappings as $defmapping) {
390 if ($defmapping['store'] !== $storename) {
391 continue;
392 }
393 $definition = $config->get_definition_by_id($defmapping['definition']);
394 if ($definition) {
395 $definitions[$defmapping['definition']] = $definition;
396 }
397 }
398
399 return $definitions;
400 }
401
8139ad13
SH
402 /**
403 * Returns all of the stores that are suitable for the given mode and requirements.
404 *
405 * @param int $mode One of cache_store::MODE_*
406 * @param int $requirements The requirements of the cache as a binary flag
407 * @return array An array of suitable stores.
408 */
409 public function get_stores($mode, $requirements = 0) {
410 $stores = array();
411 foreach ($this->configstores as $name => $store) {
412 // If the mode is supported and all of the requirements are provided features.
413 if (($store['modes'] & $mode) && ($store['features'] & $requirements) === $requirements) {
414 $stores[$name] = $store;
415 }
416 }
417 return $stores;
418 }
419
420 /**
421 * Gets all of the stores that are to be used for the given definition.
422 *
423 * @param cache_definition $definition
424 * @return array
425 */
426 public function get_stores_for_definition(cache_definition $definition) {
427 // Check if MUC has been disabled.
428 if (defined('NO_CACHE_STORES') && NO_CACHE_STORES !== false) {
429 // Yip its been disabled.
430 // To facilitate this we are going to always return an empty array of stores to use.
6fec1820 431 // This will force all cache instances to use the cachestore_dummy.
8139ad13
SH
432 // MUC will still be used essentially so that code using it will still continue to function but because no cache stores
433 // are being used interaction with MUC will be purely based around a static var.
434 return array();
435 }
436
437 $availablestores = $this->get_stores($definition->get_mode(), $definition->get_requirements_bin());
438 $stores = array();
439 $id = $definition->get_id();
440
441 // Now get any mappings and give them priority.
442 foreach ($this->configdefinitionmappings as $mapping) {
443 if ($mapping['definition'] !== $id) {
444 continue;
445 }
446 $storename = $mapping['store'];
447 if (!array_key_exists($storename, $availablestores)) {
448 continue;
449 }
450 if (array_key_exists($storename, $stores)) {
451 $store = $stores[$storename];
452 unset($stores[$storename]);
453 $stores[$storename] = $store;
454 } else {
455 $stores[$storename] = $availablestores[$storename];
456 }
457 }
458
459 if (empty($stores) && !$definition->is_for_mappings_only()) {
460 $mode = $definition->get_mode();
461 // Load the default stores.
462 foreach ($this->configmodemappings as $mapping) {
463 if ($mapping['mode'] === $mode && array_key_exists($mapping['store'], $availablestores)) {
464 $store = $availablestores[$mapping['store']];
465 if (empty($store['mappingsonly'])) {
466 $stores[$mapping['store']] = $store;
467 }
468 }
469 }
470 }
471
472 return $stores;
473 }
474
475 /**
476 * Returns all of the configured stores
477 * @return array
478 */
479 public function get_all_stores() {
480 return $this->configstores;
481 }
482
483 /**
484 * Returns all of the configured mode mappings
485 * @return array
486 */
487 public function get_mode_mappings() {
488 return $this->configmodemappings;
489 }
490
491 /**
492 * Returns all of the known definition mappings.
493 * @return array
494 */
495 public function get_definition_mappings() {
496 return $this->configdefinitionmappings;
497 }
34c84c72
SH
498
499 /**
500 * Returns an array of the configured locks.
167ad91e 501 * @return array Array of name => config
34c84c72
SH
502 */
503 public function get_locks() {
504 return $this->configlocks;
505 }
506
507 /**
508 * Returns the lock store configuration to use with a given store.
509 * @param string $storename
510 * @return array
511 * @throws cache_exception
512 */
513 public function get_lock_for_store($storename) {
514 if (array_key_exists($storename, $this->configstores)) {
515 if (array_key_exists($this->configstores[$storename]['lock'], $this->configlocks)) {
516 $lock = $this->configstores[$storename]['lock'];
517 return $this->configlocks[$lock];
518 }
519 }
520 foreach ($this->configlocks as $lockconf) {
521 if (!empty($lockconf['default'])) {
522 return $lockconf;
523 }
524 }
525 throw new cache_exception('ex_nodefaultlock');
526 }
8139ad13 527}