MDL-37684 - phpunit add missing definitions to phpunit.xml.dist
[moodle.git] / cache / classes / factory.php
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/>.
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  */
29 defined('MOODLE_INTERNAL') || die();
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 {
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;
55     /** The cache stores have been disabled */
56     const STATE_STORES_DISABLED = 11;
58     /**
59      * An instance of the cache_factory class created upon the first request.
60      * @var cache_factory
61      */
62     protected static $instance;
64     /**
65      * An array containing caches created for definitions
66      * @var array
67      */
68     protected $cachesfromdefinitions = array();
70     /**
71      * Array of caches created by parameters, ad-hoc definitions will have been used.
72      * @var array
73      */
74     protected $cachesfromparams = array();
76     /**
77      * An array of instantiated stores.
78      * @var array
79      */
80     protected $stores = array();
82     /**
83      * An array of configuration instances
84      * @var array
85      */
86     protected $configs = array();
88     /**
89      * An array of initialised definitions
90      * @var array
91      */
92     protected $definitions = array();
94     /**
95      * An array of lock plugins.
96      * @var array
97      */
98     protected $lockplugins = null;
100     /**
101      * The current state of the cache API.
102      * @var int
103      */
104     protected $state = 0;
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         global $CFG;
114         if ($forcereload || self::$instance === null) {
115             // Initialise a new factory to facilitate our needs.
116             if (defined('CACHE_DISABLE_ALL') && CACHE_DISABLE_ALL !== false) {
117                 // The cache has been disabled. Load disabledlib and start using the factory designed to handle this
118                 // situation. It will use disabled alternatives where available.
119                 require_once($CFG->dirroot.'/cache/disabledlib.php');
120                 self::$instance = new cache_factory_disabled();
121             } else {
122                 // We're using the regular factory.
123                 self::$instance = new cache_factory();
124                 if (defined('CACHE_DISABLE_STORES') && CACHE_DISABLE_STORES !== false) {
125                     // The cache stores have been disabled.
126                     self::$instance->set_state(self::STATE_STORES_DISABLED);
127                 }
128             }
129         }
130         return self::$instance;
131     }
133     /**
134      * Protected constructor, please use the static instance method.
135      */
136     protected function __construct() {
137         // Nothing to do here.
138     }
140     /**
141      * Resets the arrays containing instantiated caches, stores, and config instances.
142      */
143     public static function reset() {
144         $factory = self::instance();
145         $factory->cachesfromdefinitions = array();
146         $factory->cachesfromparams = array();
147         $factory->stores = array();
148         $factory->configs = array();
149         $factory->definitions = array();
150         $factory->lockplugins = null; // MUST be null in order to force its regeneration.
151         // Reset the state to uninitialised.
152         $factory->state = self::STATE_UNINITIALISED;
153     }
155     /**
156      * Creates a cache object given the parameters for a definition.
157      *
158      * If a cache has already been created for the given definition then that cache instance will be returned.
159      *
160      * @param string $component
161      * @param string $area
162      * @param array $identifiers
163      * @param string $aggregate
164      * @return cache_application|cache_session|cache_request
165      */
166     public function create_cache_from_definition($component, $area, array $identifiers = array(), $aggregate = null) {
167         $definitionname = $component.'/'.$area;
168         if (array_key_exists($definitionname, $this->cachesfromdefinitions)) {
169             $cache = $this->cachesfromdefinitions[$definitionname];
170             $cache->set_identifiers($identifiers);
171             return $cache;
172         }
173         $definition = $this->create_definition($component, $area, $aggregate);
174         $definition->set_identifiers($identifiers);
175         $cache = $this->create_cache($definition, $identifiers);
176         if ($definition->should_be_persistent()) {
177             $this->cachesfromdefinitions[$definitionname] = $cache;
178         }
179         return $cache;
180     }
182     /**
183      * Creates an ad-hoc cache from the given param.
184      *
185      * If a cache has already been created using the same params then that cache instance will be returned.
186      *
187      * @param int $mode
188      * @param string $component
189      * @param string $area
190      * @param array $identifiers
191      * @param array $options An array of options, available options are:
192      *   - simplekeys : Set to true if the keys you will use are a-zA-Z0-9_
193      *   - simpledata : Set to true if the type of the data you are going to store is scalar, or an array of scalar vars
194      *   - persistent : If set to true the cache will persist construction requests.
195      * @return cache_application|cache_session|cache_request
196      */
197     public function create_cache_from_params($mode, $component, $area, array $identifiers = array(), array $options = array()) {
198         $key = "{$mode}_{$component}_{$area}";
199         if (array_key_exists($key, $this->cachesfromparams)) {
200             return $this->cachesfromparams[$key];
201         }
202         // Get the class. Note this is a late static binding so we need to use get_called_class.
203         $definition = cache_definition::load_adhoc($mode, $component, $area, $options);
204         $definition->set_identifiers($identifiers);
205         $cache = $this->create_cache($definition, $identifiers);
206         if ($definition->should_be_persistent()) {
207             $this->cachesfromparams[$key] = $cache;
208         }
209         return $cache;
210     }
212     /**
213      * Common public method to create a cache instance given a definition.
214      *
215      * This is used by the static make methods.
216      *
217      * @param cache_definition $definition
218      * @return cache_application|cache_session|cache_store
219      * @throws coding_exception
220      */
221     public function create_cache(cache_definition $definition) {
222         $class = $definition->get_cache_class();
223         $stores = cache_helper::get_cache_stores($definition);
224         if (count($stores) === 0) {
225             // Hmm no stores, better provide a dummy store to mimick functionality. The dev will be none the wiser.
226             $stores[] = $this->create_dummy_store($definition);
227         }
228         $loader = null;
229         if ($definition->has_data_source()) {
230             $loader = $definition->get_data_source();
231         }
232         while (($store = array_pop($stores)) !== null) {
233             $loader = new $class($definition, $store, $loader);
234         }
235         return $loader;
236     }
238     /**
239      * Creates a store instance given its name and configuration.
240      *
241      * If the store has already been instantiated then the original objetc will be returned. (reused)
242      *
243      * @param string $name The name of the store (must be unique remember)
244      * @param array $details
245      * @param cache_definition $definition The definition to instantiate it for.
246      * @return boolean|cache_store
247      */
248     public function create_store_from_config($name, array $details, cache_definition $definition) {
249         if (!array_key_exists($name, $this->stores)) {
250             // Properties: name, plugin, configuration, class.
251             $class = $details['class'];
252             $store = new $class($details['name'], $details['configuration']);
253             $this->stores[$name] = $store;
254         }
255         $store = $this->stores[$name];
256         if (!$store->is_ready() || !$store->is_supported_mode($definition->get_mode())) {
257             return false;
258         }
259         $store = clone($this->stores[$name]);
260         $store->initialise($definition);
261         return $store;
262     }
264     /**
265      * Creates a cache config instance with the ability to write if required.
266      *
267      * @param bool $writer If set to true an instance that can update the configuration will be returned.
268      * @return cache_config|cache_config_writer
269      */
270     public function create_config_instance($writer = false) {
271         global $CFG;
273         // Check if we need to create a config file with defaults.
274         $needtocreate = !cache_config::config_file_exists();
276         // The class to use.
277         $class = 'cache_config';
278         if ($writer || $needtocreate) {
279             require_once($CFG->dirroot.'/cache/locallib.php');
280             $class .= '_writer';
281         }
283         // Check if this is a PHPUnit test and redirect to the phpunit config classes if it is.
284         if (defined('PHPUNIT_TEST') && PHPUNIT_TEST) {
285             require_once($CFG->dirroot.'/cache/locallib.php');
286             require_once($CFG->dirroot.'/cache/tests/fixtures/lib.php');
287             // We have just a single class for PHP unit tests. We don't care enough about its
288             // performance to do otherwise and having a single method allows us to inject things into it
289             // while testing.
290             $class = 'cache_config_phpunittest';
291         }
293         $error = false;
294         if ($needtocreate) {
295             // Create the default configuration.
296             // Update the state, we are now initialising the cache.
297             self::set_state(self::STATE_INITIALISING);
298             $configuration = $class::create_default_configuration();
299             if ($configuration !== true) {
300                 // Failed to create the default configuration. Disable the cache stores and update the state.
301                 self::set_state(self::STATE_ERROR_INITIALISING);
302                 $this->configs[$class] = new $class;
303                 $this->configs[$class]->load($configuration);
304                 $error = true;
305             }
306         }
308         if (!array_key_exists($class, $this->configs)) {
309             // Create a new instance and call it to load it.
310             $this->configs[$class] = new $class;
311             $this->configs[$class]->load();
312         }
314         if (!$error) {
315             // The cache is now ready to use. Update the state.
316             self::set_state(self::STATE_READY);
317         }
319         // Return the instance.
320         return $this->configs[$class];
321     }
323     /**
324      * Creates a definition instance or returns the existing one if it has already been created.
325      * @param string $component
326      * @param string $area
327      * @param string $aggregate
328      * @return cache_definition
329      */
330     public function create_definition($component, $area, $aggregate = null) {
331         $id = $component.'/'.$area;
332         if ($aggregate) {
333             $id .= '::'.$aggregate;
334         }
335         if (!array_key_exists($id, $this->definitions)) {
336             $instance = $this->create_config_instance();
337             $definition = $instance->get_definition_by_id($id);
338             if (!$definition) {
339                 $this->reset();
340                 $instance = $this->create_config_instance(true);
341                 $instance->update_definitions();
342                 $definition = $instance->get_definition_by_id($id);
343                 if (!$definition) {
344                     throw new coding_exception('The requested cache definition does not exist.'. $id, $id);
345                 } else {
346                     debugging('Cache definitions reparsed causing cache reset in order to locate definition.
347                         You should bump the version number to ensure definitions are reprocessed.', DEBUG_DEVELOPER);
348                 }
349             }
350             $this->definitions[$id] = cache_definition::load($id, $definition, $aggregate);
351         }
352         return $this->definitions[$id];
353     }
355     /**
356      * Creates a dummy store object for use when a loader has no potential stores to use.
357      *
358      * @param cache_definition $definition
359      * @return cachestore_dummy
360      */
361     protected function create_dummy_store(cache_definition $definition) {
362         global $CFG;
363         require_once($CFG->dirroot.'/cache/classes/dummystore.php');
364         $store = new cachestore_dummy();
365         $store->initialise($definition);
366         return $store;
367     }
369     /**
370      * Returns a lock instance ready for use.
371      *
372      * @param array $config
373      * @return cache_lock_interface
374      */
375     public function create_lock_instance(array $config) {
376         if (!array_key_exists('name', $config) || !array_key_exists('type', $config)) {
377             throw new coding_exception('Invalid cache lock instance provided');
378         }
379         $name = $config['name'];
380         $type = $config['type'];
381         unset($config['name']);
382         unset($config['type']);
384         if ($this->lockplugins === null) {
385             $this->lockplugins = get_plugin_list_with_class('cachelock', '', 'lib.php');
386         }
387         if (!array_key_exists($type, $this->lockplugins)) {
388             throw new coding_exception('Invalid cache lock type.');
389         }
390         $class = $this->lockplugins[$type];
391         return new $class($name, $config);
392     }
394     /**
395      * Returns the current state of the cache API.
396      *
397      * @return int
398      */
399     public function get_state() {
400         return $this->state;
401     }
403     /**
404      * Updates the state fo the cache API.
405      *
406      * @param int $state
407      * @return bool
408      */
409     public function set_state($state) {
410         if ($state <= $this->state) {
411             return false;
412         }
413         $this->state = $state;
414         return true;
415     }
417     /**
418      * Returns true if the cache API has been disabled.
419      *
420      * @return bool
421      */
422     public function is_disabled() {
423         return $this->state === self::STATE_DISABLED;
424     }
426     /**
427      * Disables as much of the cache API as possible.
428      *
429      * All of the magic associated with the disabled cache is wrapped into this function.
430      * In switching out the factory for the disabled factory it gains full control over the initialisation of objects
431      * and can use all of the disabled alternatives.
432      * Simple!
433      *
434      * This function has been marked as protected so that it cannot be abused through the public API presently.
435      * Perhaps in the future we will allow this, however as per the build up to the first release containing
436      * MUC it was decided that this was just to risky and abusable.
437      */
438     protected static function disable() {
439         global $CFG;
440         require_once($CFG->dirroot.'/cache/disabledlib.php');
441         self::$instance = new cache_factory_disabled();
442     }
444     /**
445      * Returns true if the cache stores have been disabled.
446      *
447      * @return bool
448      */
449     public function stores_disabled() {
450         return $this->state === self::STATE_STORES_DISABLED || $this->is_disabled();
451     }
453     /**
454      * Disables cache stores.
455      *
456      * The cache API will continue to function however none of the actual stores will be used.
457      * Instead the dummy store will be provided for all cache requests.
458      * This is useful in situations where you cannot be sure any stores are working.
459      *
460      * In order to re-enable the cache you must call the cache factories static reset method:
461      * <code>
462      * // Disable the cache factory.
463      * cache_factory::disable_stores();
464      * // Re-enable the cache factory by resetting it.
465      * cache_factory::reset();
466      * </code>
467      */
468     public static function disable_stores() {
469         $factory = self::instance();
470         $factory->set_state(self::STATE_STORES_DISABLED);
471     }