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 * The library file for the session cache store. 19 * 20 * This file is part of the session cache store, it contains the API for interacting with an instance of the store. 21 * This is used as a default cache store within the Cache API. It should never be deleted. 22 * 23 * @package cachestore_session 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 session data store class. 33 * 34 * @copyright 2012 Sam Hemelryk 35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 36 */ 37 abstract class session_data_store extends cache_store { 38 39 /** 40 * Used for the actual storage. 41 * @var array 42 */ 43 private static $sessionstore = null; 44 45 /** 46 * Returns a static store by reference... REFERENCE SUPER IMPORTANT. 47 * 48 * @param string $id 49 * @return array 50 */ 51 protected static function ®ister_store_id($id) { 52 if (is_null(self::$sessionstore)) { 53 global $SESSION; 54 if (!isset($SESSION->cachestore_session)) { 55 $SESSION->cachestore_session = array(); 56 } 57 self::$sessionstore =& $SESSION->cachestore_session; 58 } 59 if (!array_key_exists($id, self::$sessionstore)) { 60 self::$sessionstore[$id] = array(); 61 } 62 return self::$sessionstore[$id]; 63 } 64 65 /** 66 * Flushes the data belong to the given store id. 67 * @param string $id 68 */ 69 protected static function flush_store_by_id($id) { 70 unset(self::$sessionstore[$id]); 71 self::$sessionstore[$id] = array(); 72 } 73 74 /** 75 * Flushes the store of all data. 76 */ 77 protected static function flush_store() { 78 $ids = array_keys(self::$sessionstore); 79 unset(self::$sessionstore); 80 self::$sessionstore = array(); 81 foreach ($ids as $id) { 82 self::$sessionstore[$id] = array(); 83 } 84 } 85 } 86 87 /** 88 * The Session store class. 89 * 90 * @copyright 2012 Sam Hemelryk 91 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 92 */ 93 class cachestore_session extends session_data_store implements cache_is_key_aware, cache_is_searchable { 94 95 /** 96 * The name of the store 97 * @var store 98 */ 99 protected $name; 100 101 /** 102 * The store id (should be unique) 103 * @var string 104 */ 105 protected $storeid; 106 107 /** 108 * The store we use for data. 109 * @var array 110 */ 111 protected $store; 112 113 /** 114 * The ttl if there is one. Hopefully not. 115 * @var int 116 */ 117 protected $ttl = 0; 118 119 /** 120 * The maximum size for the store, or false if there isn't one. 121 * @var bool|int 122 */ 123 protected $maxsize = false; 124 125 /** 126 * The number of items currently being stored. 127 * @var int 128 */ 129 protected $storecount = 0; 130 131 /** 132 * Constructs the store instance. 133 * 134 * Noting that this function is not an initialisation. It is used to prepare the store for use. 135 * The store will be initialised when required and will be provided with a cache_definition at that time. 136 * 137 * @param string $name 138 * @param array $configuration 139 */ 140 public function __construct($name, array $configuration = array()) { 141 $this->name = $name; 142 } 143 144 /** 145 * Returns the supported features as a combined int. 146 * 147 * @param array $configuration 148 * @return int 149 */ 150 public static function get_supported_features(array $configuration = array()) { 151 return self::SUPPORTS_DATA_GUARANTEE + 152 self::SUPPORTS_NATIVE_TTL + 153 self::IS_SEARCHABLE; 154 } 155 156 /** 157 * Returns false as this store does not support multiple identifiers. 158 * (This optional function is a performance optimisation; it must be 159 * consistent with the value from get_supported_features.) 160 * 161 * @return bool False 162 */ 163 public function supports_multiple_identifiers() { 164 return false; 165 } 166 167 /** 168 * Returns the supported modes as a combined int. 169 * 170 * @param array $configuration 171 * @return int 172 */ 173 public static function get_supported_modes(array $configuration = array()) { 174 return self::MODE_SESSION; 175 } 176 177 /** 178 * Returns true if the store requirements are met. 179 * 180 * @return bool 181 */ 182 public static function are_requirements_met() { 183 return true; 184 } 185 186 /** 187 * Returns true if the given mode is supported by this store. 188 * 189 * @param int $mode One of cache_store::MODE_* 190 * @return bool 191 */ 192 public static function is_supported_mode($mode) { 193 return ($mode === self::MODE_SESSION); 194 } 195 196 /** 197 * Initialises the cache. 198 * 199 * Once this has been done the cache is all set to be used. 200 * 201 * @param cache_definition $definition 202 */ 203 public function initialise(cache_definition $definition) { 204 $this->storeid = $definition->generate_definition_hash(); 205 $this->store = &self::register_store_id($this->name.'-'.$definition->get_id()); 206 $this->ttl = $definition->get_ttl(); 207 $maxsize = $definition->get_maxsize(); 208 if ($maxsize !== null) { 209 // Must be a positive int. 210 $this->maxsize = abs((int)$maxsize); 211 $this->storecount = count($this->store); 212 } 213 $this->check_ttl(); 214 } 215 216 /** 217 * Returns true once this instance has been initialised. 218 * 219 * @return bool 220 */ 221 public function is_initialised() { 222 return (is_array($this->store)); 223 } 224 225 /** 226 * Retrieves an item from the cache store given its key. 227 * 228 * @param string $key The key to retrieve 229 * @return mixed The data that was associated with the key, or false if the key did not exist. 230 */ 231 public function get($key) { 232 if (isset($this->store[$key])) { 233 if ($this->ttl == 0) { 234 $value = $this->store[$key][0]; 235 if ($this->maxsize !== false) { 236 // Make sure the element is now in the end of array. 237 $this->set($key, $value); 238 } 239 return $value; 240 } else if ($this->store[$key][1] >= (cache::now() - $this->ttl)) { 241 return $this->store[$key][0]; 242 } else { 243 // Element is present but has expired. 244 $this->check_ttl(); 245 } 246 } 247 return false; 248 } 249 250 /** 251 * Retrieves several items from the cache store in a single transaction. 252 * 253 * 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. 254 * 255 * @param array $keys The array of keys to retrieve 256 * @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 257 * be set to false. 258 */ 259 public function get_many($keys) { 260 $return = array(); 261 $maxtime = 0; 262 if ($this->ttl != 0) { 263 $maxtime = cache::now() - $this->ttl; 264 } 265 266 $hasexpiredelements = false; 267 foreach ($keys as $key) { 268 $return[$key] = false; 269 if (isset($this->store[$key])) { 270 if ($this->ttl == 0) { 271 $return[$key] = $this->store[$key][0]; 272 if ($this->maxsize !== false) { 273 // Make sure the element is now in the end of array. 274 $this->set($key, $return[$key], false); 275 } 276 } else if ($this->store[$key][1] >= $maxtime) { 277 $return[$key] = $this->store[$key][0]; 278 } else { 279 $hasexpiredelements = true; 280 } 281 } 282 } 283 if ($hasexpiredelements) { 284 // There are some elements that are present but have expired. 285 $this->check_ttl(); 286 } 287 return $return; 288 } 289 290 /** 291 * Sets an item in the cache given its key and data value. 292 * 293 * @param string $key The key to use. 294 * @param mixed $data The data to set. 295 * @param bool $testmaxsize If set to true then we test the maxsize arg and reduce if required. If this is set to false you will 296 * need to perform these checks yourself. This allows for bulk set's to be performed and maxsize tests performed once. 297 * @return bool True if the operation was a success false otherwise. 298 */ 299 public function set($key, $data, $testmaxsize = true) { 300 $testmaxsize = ($testmaxsize && $this->maxsize !== false); 301 $increment = $this->maxsize !== false && !isset($this->store[$key]); 302 if (($this->maxsize !== false && !$increment) || $this->ttl != 0) { 303 // Make sure the element is added to the end of $this->store array. 304 unset($this->store[$key]); 305 } 306 if ($this->ttl === 0) { 307 $this->store[$key] = array($data, 0); 308 } else { 309 $this->store[$key] = array($data, cache::now()); 310 } 311 if ($increment) { 312 $this->storecount++; 313 } 314 if ($testmaxsize && $this->storecount > $this->maxsize) { 315 $this->reduce_for_maxsize(); 316 } 317 return true; 318 } 319 320 /** 321 * Sets many items in the cache in a single transaction. 322 * 323 * @param array $keyvaluearray An array of key value pairs. Each item in the array will be an associative array with two 324 * keys, 'key' and 'value'. 325 * @return int The number of items successfully set. It is up to the developer to check this matches the number of items 326 * sent ... if they care that is. 327 */ 328 public function set_many(array $keyvaluearray) { 329 $count = 0; 330 $increment = 0; 331 foreach ($keyvaluearray as $pair) { 332 $key = $pair['key']; 333 $data = $pair['value']; 334 $count++; 335 if ($this->maxsize !== false || $this->ttl !== 0) { 336 // Make sure the element is added to the end of $this->store array. 337 $this->delete($key); 338 $increment++; 339 } else if (!isset($this->store[$key])) { 340 $increment++; 341 } 342 if ($this->ttl === 0) { 343 $this->store[$key] = array($data, 0); 344 } else { 345 $this->store[$key] = array($data, cache::now()); 346 } 347 } 348 if ($this->maxsize !== false) { 349 $this->storecount += $increment; 350 if ($this->storecount > $this->maxsize) { 351 $this->reduce_for_maxsize(); 352 } 353 } 354 return $count; 355 } 356 357 /** 358 * Checks if the store has a record for the given key and returns true if so. 359 * 360 * @param string $key 361 * @return bool 362 */ 363 public function has($key) { 364 if (isset($this->store[$key])) { 365 if ($this->ttl == 0) { 366 return true; 367 } else if ($this->store[$key][1] >= (cache::now() - $this->ttl)) { 368 return true; 369 } 370 } 371 return false; 372 } 373 374 /** 375 * Returns true if the store contains records for all of the given keys. 376 * 377 * @param array $keys 378 * @return bool 379 */ 380 public function has_all(array $keys) { 381 $maxtime = 0; 382 if ($this->ttl != 0) { 383 $maxtime = cache::now() - $this->ttl; 384 } 385 386 foreach ($keys as $key) { 387 if (!isset($this->store[$key])) { 388 return false; 389 } 390 if ($this->ttl != 0 && $this->store[$key][1] < $maxtime) { 391 return false; 392 } 393 } 394 return true; 395 } 396 397 /** 398 * Returns true if the store contains records for any of the given keys. 399 * 400 * @param array $keys 401 * @return bool 402 */ 403 public function has_any(array $keys) { 404 $maxtime = 0; 405 if ($this->ttl != 0) { 406 $maxtime = cache::now() - $this->ttl; 407 } 408 409 foreach ($keys as $key) { 410 if (isset($this->store[$key]) && ($this->ttl == 0 || $this->store[$key][1] >= $maxtime)) { 411 return true; 412 } 413 } 414 return false; 415 } 416 417 /** 418 * Deletes an item from the cache store. 419 * 420 * @param string $key The key to delete. 421 * @return bool Returns true if the operation was a success, false otherwise. 422 */ 423 public function delete($key) { 424 if (!isset($this->store[$key])) { 425 return false; 426 } 427 unset($this->store[$key]); 428 if ($this->maxsize !== false) { 429 $this->storecount--; 430 } 431 return true; 432 } 433 434 /** 435 * Deletes several keys from the cache in a single action. 436 * 437 * @param array $keys The keys to delete 438 * @return int The number of items successfully deleted. 439 */ 440 public function delete_many(array $keys) { 441 // The number of items that have actually being removed. 442 $reduction = 0; 443 foreach ($keys as $key) { 444 if (isset($this->store[$key])) { 445 $reduction++; 446 } 447 unset($this->store[$key]); 448 } 449 if ($this->maxsize !== false) { 450 $this->storecount -= $reduction; 451 } 452 return $reduction; 453 } 454 455 /** 456 * Purges the cache deleting all items within it. 457 * 458 * @return boolean True on success. False otherwise. 459 */ 460 public function purge() { 461 $this->store = array(); 462 // Don't worry about checking if we're using max size just set it as thats as fast as the check. 463 $this->storecount = 0; 464 return true; 465 } 466 467 /** 468 * Reduces the size of the array if maxsize has been hit. 469 * 470 * This function reduces the size of the store reducing it by 10% of its maxsize. 471 * It removes the oldest items in the store when doing this. 472 * The reason it does this an doesn't use a least recently used system is purely the overhead such a system 473 * requires. The current approach is focused on speed, MUC already adds enough overhead to static/session caches 474 * and avoiding more is of benefit. 475 * 476 * @return int 477 */ 478 protected function reduce_for_maxsize() { 479 $diff = $this->storecount - $this->maxsize; 480 if ($diff < 1) { 481 return 0; 482 } 483 // Reduce it by an extra 10% to avoid calling this repetitively if we are in a loop. 484 $diff += floor($this->maxsize / 10); 485 $this->store = array_slice($this->store, $diff, null, true); 486 $this->storecount -= $diff; 487 return $diff; 488 } 489 490 /** 491 * Returns true if the user can add an instance of the store plugin. 492 * 493 * @return bool 494 */ 495 public static function can_add_instance() { 496 return false; 497 } 498 499 /** 500 * Performs any necessary clean up when the store instance is being deleted. 501 */ 502 public function instance_deleted() { 503 $this->purge(); 504 } 505 506 /** 507 * Generates an instance of the cache store that can be used for testing. 508 * 509 * @param cache_definition $definition 510 * @return cachestore_session 511 */ 512 public static function initialise_test_instance(cache_definition $definition) { 513 // Do something here perhaps. 514 $cache = new cachestore_session('Session test'); 515 $cache->initialise($definition); 516 return $cache; 517 } 518 519 /** 520 * Generates the appropriate configuration required for unit testing. 521 * 522 * @return array Array of unit test configuration data to be used by initialise(). 523 */ 524 public static function unit_test_configuration() { 525 return array(); 526 } 527 /** 528 * Returns the name of this instance. 529 * @return string 530 */ 531 public function my_name() { 532 return $this->name; 533 } 534 535 /** 536 * Removes expired elements. 537 * @return int number of removed elements 538 */ 539 protected function check_ttl() { 540 if ($this->ttl === 0) { 541 return 0; 542 } 543 $maxtime = cache::now() - $this->ttl; 544 $count = 0; 545 for ($value = reset($this->store); $value !== false; $value = next($this->store)) { 546 if ($value[1] >= $maxtime) { 547 // We know that elements are sorted by ttl so no need to continue. 548 break; 549 } 550 $count++; 551 } 552 if ($count) { 553 // Remove first $count elements as they are expired. 554 $this->store = array_slice($this->store, $count, null, true); 555 if ($this->maxsize !== false) { 556 $this->storecount -= $count; 557 } 558 } 559 return $count; 560 } 561 562 /** 563 * Finds all of the keys being stored in the cache store instance. 564 * 565 * @return array 566 */ 567 public function find_all() { 568 $this->check_ttl(); 569 return array_keys($this->store); 570 } 571 572 /** 573 * Finds all of the keys whose keys start with the given prefix. 574 * 575 * @param string $prefix 576 * @return array An array of keys. 577 */ 578 public function find_by_prefix($prefix) { 579 $return = array(); 580 foreach ($this->find_all() as $key) { 581 if (strpos($key, $prefix) === 0) { 582 $return[] = $key; 583 } 584 } 585 return $return; 586 } 587 588 /** 589 * This store supports native TTL handling. 590 * @return bool 591 */ 592 public function store_supports_native_ttl() { 593 return true; 594 } 595 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body