Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.
   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   * APCu cache store main library.
  19   *
  20   * @package    cachestore_apcu
  21   * @copyright  2012 Sam Hemelryk
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  defined('MOODLE_INTERNAL') || die();
  26  
  27  /**
  28   * The APCu cache store class.
  29   *
  30   * @copyright  2012 Sam Hemelryk
  31   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  32   */
  33  class cachestore_apcu extends cache_store implements cache_is_key_aware, cache_is_configurable {
  34  
  35      /**
  36       * The required version of APCu for this extension.
  37       */
  38      const REQUIRED_VERSION = '4.0.0';
  39  
  40      /**
  41       * The name of this store instance.
  42       * @var string
  43       */
  44      protected $name;
  45  
  46      /**
  47       * The definition used when this instance was initialised.
  48       * @var cache_definition
  49       */
  50      protected $definition = null;
  51  
  52      /**
  53       * The storeprefix to use on all instances of this store.  Configured as part store setup.
  54       * @var string
  55       */
  56      protected $storeprefix = null;
  57  
  58      /**
  59       * The prefix added specifically for this cache.
  60       * @var string
  61       */
  62      protected $cacheprefix = null;
  63  
  64      /**
  65       * Static method to check that the APCu stores requirements have been met.
  66       *
  67       * It checks that the APCu extension has been loaded and that it has been enabled.
  68       *
  69       * @return bool True if the stores software/hardware requirements have been met and it can be used. False otherwise.
  70       */
  71      public static function are_requirements_met() {
  72          $enabled = ini_get('apc.enabled') && (php_sapi_name() != "cli" || ini_get('apc.enable_cli'));
  73          if (!extension_loaded('apcu') || !$enabled) {
  74              return false;
  75          }
  76  
  77          $version = phpversion('apcu');
  78          return $version && version_compare($version, self::REQUIRED_VERSION, '>=');
  79      }
  80  
  81      /**
  82       * Static method to check if a store is usable with the given mode.
  83       *
  84       * @param int $mode One of cache_store::MODE_*
  85       * @return bool True if the mode is supported.
  86       */
  87      public static function is_supported_mode($mode) {
  88          return ($mode === self::MODE_APPLICATION || $mode === self::MODE_SESSION);
  89      }
  90  
  91      /**
  92       * Returns the supported features as a binary flag.
  93       *
  94       * @param array $configuration The configuration of a store to consider specifically.
  95       * @return int The supported features.
  96       */
  97      public static function get_supported_features(array $configuration = array()) {
  98          return self::SUPPORTS_NATIVE_TTL;
  99      }
 100  
 101      /**
 102       * Returns the supported modes as a binary flag.
 103       *
 104       * @param array $configuration The configuration of a store to consider specifically.
 105       * @return int The supported modes.
 106       */
 107      public static function get_supported_modes(array $configuration = array()) {
 108          return self::MODE_APPLICATION + self::MODE_SESSION;
 109      }
 110  
 111      /**
 112       * Constructs an instance of the cache store.
 113       *
 114       * This method should not create connections or perform and processing, it should be used
 115       *
 116       * @param string $name The name of the cache store
 117       * @param array $configuration The configuration for this store instance.
 118       */
 119      public function __construct($name, array $configuration = array()) {
 120          global $CFG;
 121          $this->name = $name;
 122          $this->storeprefix = $CFG->prefix;
 123          if (isset($configuration['prefix'])) {
 124              $this->storeprefix = $configuration['prefix'];
 125          }
 126      }
 127  
 128      /**
 129       * Returns the name of this store instance.
 130       * @return string
 131       */
 132      public function my_name() {
 133          return $this->name;
 134      }
 135  
 136      /**
 137       * Initialises a new instance of the cache store given the definition the instance is to be used for.
 138       *
 139       * This function should prepare any given connections etc.
 140       *
 141       * @param cache_definition $definition
 142       * @return bool
 143       */
 144      public function initialise(cache_definition $definition) {
 145          $this->definition = $definition;
 146          $this->cacheprefix = $this->storeprefix.$definition->generate_definition_hash().'__';
 147          return true;
 148      }
 149  
 150      /**
 151       * Returns true if this cache store instance has been initialised.
 152       * @return bool
 153       */
 154      public function is_initialised() {
 155          return ($this->definition !== null);
 156      }
 157  
 158      /**
 159       * Prepares the given key for use.
 160       *
 161       * Should be called before all interaction.
 162       *
 163       * @param string $key The key to prepare for storing in APCu.
 164       *
 165       * @return string
 166       */
 167      protected function prepare_key($key) {
 168          return $this->cacheprefix . $key;
 169      }
 170  
 171      /**
 172       * Retrieves an item from the cache store given its key.
 173       *
 174       * @param string $key The key to retrieve
 175       * @return mixed The data that was associated with the key, or false if the key did not exist.
 176       */
 177      public function get($key) {
 178          $key = $this->prepare_key($key);
 179          $success = false;
 180          $outcome = apcu_fetch($key, $success);
 181          if ($success) {
 182              return $outcome;
 183          }
 184          return $success;
 185      }
 186  
 187      /**
 188       * Retrieves several items from the cache store in a single transaction.
 189       *
 190       * If not all of the items are available in the cache then the data value for those that are missing will be set to false.
 191       *
 192       * @param array $keys The array of keys to retrieve
 193       * @return array An array of items from the cache. There will be an item for each key, those that were not in the store will
 194       *      be set to false.
 195       */
 196      public function get_many($keys) {
 197          $map = array();
 198          foreach ($keys as $key) {
 199              $map[$key] = $this->prepare_key($key);
 200          }
 201          $outcomes = array();
 202          $success = false;
 203          $results = apcu_fetch($map, $success);
 204          if ($success) {
 205              foreach ($map as $key => $used) {
 206                  if (array_key_exists($used, $results)) {
 207                      $outcomes[$key] = $results[$used];
 208                  } else {
 209                      $outcomes[$key] = false;
 210                  }
 211              }
 212          } else {
 213              $outcomes = array_fill_keys($keys, false);
 214          }
 215          return $outcomes;
 216      }
 217  
 218      /**
 219       * Sets an item in the cache given its key and data value.
 220       *
 221       * @param string $key The key to use.
 222       * @param mixed $data The data to set.
 223       * @return bool True if the operation was a success false otherwise.
 224       */
 225      public function set($key, $data) {
 226          $key = $this->prepare_key($key);
 227          return apcu_store($key, $data, $this->definition->get_ttl());
 228      }
 229  
 230      /**
 231       * Sets many items in the cache in a single transaction.
 232       *
 233       * @param array $keyvaluearray An array of key value pairs. Each item in the array will be an associative array with two
 234       *      keys, 'key' and 'value'.
 235       * @return int The number of items successfully set. It is up to the developer to check this matches the number of items
 236       *      sent ... if they care that is.
 237       */
 238      public function set_many(array $keyvaluearray) {
 239          $map = array();
 240          foreach ($keyvaluearray as $pair) {
 241              $key = $this->prepare_key($pair['key']);
 242              $map[$key] = $pair['value'];
 243          }
 244          $result = apcu_store($map, null, $this->definition->get_ttl());
 245          return count($map) - count($result);
 246      }
 247  
 248      /**
 249       * Deletes an item from the cache store.
 250       *
 251       * @param string $key The key to delete.
 252       * @return bool Returns true if the operation was a success, false otherwise.
 253       */
 254      public function delete($key) {
 255          $key = $this->prepare_key($key);
 256          return apcu_delete($key);
 257      }
 258  
 259      /**
 260       * Deletes several keys from the cache in a single action.
 261       *
 262       * @param array $keys The keys to delete
 263       * @return int The number of items successfully deleted.
 264       */
 265      public function delete_many(array $keys) {
 266          $count = 0;
 267          foreach ($keys as $key) {
 268              if ($this->delete($key)) {
 269                  $count++;
 270              }
 271          }
 272          return $count;
 273      }
 274  
 275      /**
 276       * Purges the cache deleting all items within it.
 277       *
 278       * @return boolean True on success. False otherwise.
 279       */
 280      public function purge() {
 281          if (class_exists('APCUIterator', false)) {
 282              $iterator = new APCUIterator('#^' . preg_quote($this->cacheprefix, '#') . '#');
 283          } else {
 284              $iterator = new APCIterator('user', '#^' . preg_quote($this->cacheprefix, '#') . '#');
 285          }
 286          return apcu_delete($iterator);
 287      }
 288  
 289      /**
 290       * Performs any necessary clean up when the store instance is being deleted.
 291       */
 292      public function instance_deleted() {
 293          if (class_exists('APCUIterator', false)) {
 294              $iterator = new APCUIterator('#^' . preg_quote($this->storeprefix, '#') . '#');
 295          } else {
 296              $iterator = new APCIterator('user', '#^' . preg_quote($this->storeprefix, '#') . '#');
 297          }
 298          return apcu_delete($iterator);
 299      }
 300  
 301      /**
 302       * Generates an instance of the cache store that can be used for testing.
 303       *
 304       * Returns an instance of the cache store, or false if one cannot be created.
 305       *
 306       * @param cache_definition $definition
 307       * @return cache_store
 308       */
 309      public static function initialise_test_instance(cache_definition $definition) {
 310          $testperformance = get_config('cachestore_apcu', 'testperformance');
 311          if (empty($testperformance)) {
 312              return false;
 313          }
 314          if (!self::are_requirements_met()) {
 315              return false;
 316          }
 317          $name = 'APCu test';
 318          $cache = new cachestore_apcu($name);
 319          // No need to check if is_ready() as this has already being done by requirement check.
 320          $cache->initialise($definition);
 321          return $cache;
 322      }
 323  
 324      /**
 325       * Test is a cache has a key.
 326       *
 327       * @param string|int $key
 328       * @return bool True if the cache has the requested key, false otherwise.
 329       */
 330      public function has($key) {
 331          $key = $this->prepare_key($key);
 332          return apcu_exists($key);
 333      }
 334  
 335      /**
 336       * Test if a cache has at least one of the given keys.
 337       *
 338       * @param array $keys
 339       * @return bool True if the cache has at least one of the given keys
 340       */
 341      public function has_any(array $keys) {
 342          foreach ($keys as $arraykey => $key) {
 343              $keys[$arraykey] = $this->prepare_key($key);
 344          }
 345          $result = apcu_exists($keys);
 346          return count($result) > 0;
 347      }
 348  
 349      /**
 350       * Test is a cache has all of the given keys.
 351       *
 352       * @param array $keys
 353       * @return bool True if the cache has all of the given keys, false otherwise.
 354       */
 355      public function has_all(array $keys) {
 356          foreach ($keys as $arraykey => $key) {
 357              $keys[$arraykey] = $this->prepare_key($key);
 358          }
 359          $result = apcu_exists($keys);
 360          return count($result) === count($keys);
 361      }
 362  
 363      /**
 364       * Generates the appropriate configuration required for unit testing.
 365       *
 366       * @return array Array of unit test configuration data to be used by initialise().
 367       */
 368      public static function unit_test_configuration() {
 369          return array('prefix' => 'phpunit');
 370      }
 371  
 372      /**
 373       * Given the data from the add instance form this function creates a configuration array.
 374       *
 375       * @param stdClass $data
 376       * @return array
 377       */
 378      public static function config_get_configuration_array($data) {
 379          $config = array();
 380  
 381          if (isset($data->prefix)) {
 382              $config['prefix'] = $data->prefix;
 383          }
 384          return $config;
 385      }
 386      /**
 387       * Allows the cache store to set its data against the edit form before it is shown to the user.
 388       *
 389       * @param moodleform $editform
 390       * @param array $config
 391       */
 392      public static function config_set_edit_form_data(moodleform $editform, array $config) {
 393          if (isset($config['prefix'])) {
 394              $data['prefix'] = $config['prefix'];
 395          } else {
 396              $data['prefix'] = '';
 397          }
 398          $editform->set_data($data);
 399      }
 400  
 401      /**
 402       * Returns true if this cache store instance is both suitable for testing, and ready for testing.
 403       *
 404       * Cache stores that support being used as the default store for unit and acceptance testing should
 405       * override this function and return true if there requirements have been met.
 406       *
 407       * @return bool
 408       */
 409      public static function ready_to_be_used_for_testing() {
 410          return true;
 411      }
 412  }