MDL-36466 cache: implemented functionality to disable the bulk of the cache API
[moodle.git] / cache / classes / factory.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 * 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
29defined('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 */
41class 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}