118
118
* An array containing just the keys being used in the persist cache.
119
119
* This seems redundant perhaps but is used when managing the size of the persist cache.
120
* Items are added to the end of the array and the when we need to reduce the size of the cache we use the
121
* key that is first on this array.
122
124
private $persistkeys = array();
269
271
* In advanced cases an array may be useful such as in situations requiring the multi-key functionality.
270
272
* @param int $strictness One of IGNORE_MISSING | MUST_EXIST
271
273
* @return mixed|false The data from the cache or false if the key did not exist within the cache.
272
* @throws moodle_exception
274
* @throws coding_exception
274
276
public function get($key, $strictness = IGNORE_MISSING) {
275
277
// 1. Parse the key.
328
330
// 5. Validate strictness.
329
331
if ($strictness === MUST_EXIST && $result === false) {
330
throw new moodle_exception('Requested key did not exist in any cache stores and could not be loaded.');
332
throw new coding_exception('Requested key did not exist in any cache stores and could not be loaded.');
332
334
// 6. Set it to the store if we got it from the loader/datasource.
333
335
if ($setaftervalidation) {
361
363
* @return array An array of key value pairs for the items that could be retrieved from the cache.
362
364
* If MUST_EXIST was used and not all keys existed within the cache then an exception will be thrown.
363
365
* Otherwise any key that did not exist will have a data value of false within the results.
364
* @throws moodle_exception
366
* @throws coding_exception
366
368
public function get_many(array $keys, $strictness = IGNORE_MISSING) {
428
430
$resultmissing = $this->datasource->load_many_for_cache($missingkeys);
430
432
foreach ($resultmissing as $key => $value) {
431
$pkey = ($usingloader) ? $key : $keysparsed[$key];
432
$realkey = ($usingloader) ? $parsedkeys[$key] : $key;
433
$result[$pkey] = $value;
433
$result[$keysparsed[$key]] = $value;
434
434
if ($value !== false) {
435
$this->set($realkey, $value);
435
$this->set($key, $value);
438
438
unset($resultmissing);
451
451
if ($strictness === MUST_EXIST) {
452
452
foreach ($keys as $key) {
453
453
if (!array_key_exists($key, $fullresult)) {
454
throw new moodle_exception('Not all the requested keys existed within the cache stores.');
454
throw new coding_exception('Not all the requested keys existed within the cache stores.');
481
481
if ($this->perfdebug) {
482
482
cache_helper::record_cache_set($this->storetype, $this->definition->get_id());
484
if ($this->loader !== false) {
485
// We have a loader available set it there as well.
486
// We have to let the loader do its own parsing of data as it may be unique.
487
$this->loader->set($key, $data);
484
489
if (is_object($data) && $data instanceof cacheable_object) {
485
490
$data = new cache_cached_object($data);
486
491
} else if (!is_scalar($data)) {
504
509
* Removes references where required.
506
511
* @param stdClass|array $data
512
* @return mixed What ever was put in but without any references.
508
514
protected function unref($data) {
509
515
if ($this->definition->uses_simple_data()) {
591
597
* ... if they care that is.
593
599
public function set_many(array $keyvaluearray) {
600
if ($this->loader !== false) {
601
// We have a loader available set it there as well.
602
// We have to let the loader do its own parsing of data as it may be unique.
603
$this->loader->set_many($keyvaluearray);
595
606
$simulatettl = $this->has_a_ttl() && !$this->store_supports_native_ttl();
596
607
$usepersistcache = $this->is_using_persist_cache();
965
976
if ($this->perfdebug) {
966
977
cache_helper::record_cache_hit('** static persist **', $this->definition->get_id());
979
if ($this->persistmaxsize > 1 && $this->persistcount > 1) {
980
// Check to see if this is the last item on the persist keys array.
981
if (end($this->persistkeys) !== $key) {
982
// It isn't the last item.
983
// Move the item to the end of the array so that it is last to be removed.
984
unset($this->persistkeys[$key]);
985
$this->persistkeys[$key] = $key;
970
990
if ($this->perfdebug) {
989
1009
$this->persistcache[$key] = $data;
990
1010
if ($this->persistmaxsize !== false) {
991
1011
$this->persistcount++;
1012
$this->persistkeys[$key] = $key;
992
1013
if ($this->persistcount > $this->persistmaxsize) {
993
1014
$dropkey = array_shift($this->persistkeys);
994
1015
unset($this->persistcache[$dropkey]);
1328
1349
* @param string|int $key The key for the data being requested.
1329
1350
* @param int $strictness One of IGNORE_MISSING | MUST_EXIST
1330
1351
* @return mixed|false The data from the cache or false if the key did not exist within the cache.
1331
* @throws moodle_exception
1333
1353
public function get($key, $strictness = IGNORE_MISSING) {
1334
1354
if ($this->requirelockingread && $this->check_lock_state($key) === false) {
1352
1372
* @return array An array of key value pairs for the items that could be retrieved from the cache.
1353
1373
* If MUST_EXIST was used and not all keys existed within the cache then an exception will be thrown.
1354
1374
* Otherwise any key that did not exist will have a data value of false within the results.
1355
* @throws moodle_exception
1375
* @throws coding_exception
1357
1377
public function get_many(array $keys, $strictness = IGNORE_MISSING) {
1358
1378
if ($this->requirelockingread) {
1483
1504
const KEY_PREFIX = 'sess_';
1507
* This is the key used to track last access.
1509
const LASTACCESS = '__lastaccess__';
1486
1512
* Override the cache::construct method.
1488
1514
* This function gets overriden so that we can process any invalidation events if need be.
1495
1521
* @param cache_definition $definition
1496
1522
* @param cache_store $store
1497
1523
* @param cache_loader|cache_data_source $loader
1500
1525
public function __construct(cache_definition $definition, cache_store $store, $loader = null) {
1501
1526
// First up copy the loadeduserid to the current user id.
1502
1527
$this->currentuserid = self::$loadeduserid;
1503
1528
parent::__construct($definition, $store, $loader);
1530
// This will trigger check tracked user. If this gets removed a call to that will need to be added here in its place.
1531
$this->set(self::LASTACCESS, cache::now());
1504
1533
if ($definition->has_invalidation_events()) {
1505
1534
$lastinvalidation = $this->get('lastsessioninvalidation');
1506
1535
if ($lastinvalidation === false) {
1582
* Sets the session id for the loader.
1584
protected function set_session_id() {
1585
$this->sessionid = preg_replace('#[^a-zA-Z0-9_]#', '_', session_id());
1589
* Returns the prefix used for all keys.
1592
protected function get_key_prefix() {
1593
return 'u'.$this->currentuserid.'_'.$this->sessionid;
1553
1597
* Parses the key turning it into a string (or array is required) suitable to be passed to the cache store.
1555
1599
* This function is called for every operation that uses keys. For this reason we use this function to also check
1561
1605
* @return string|array String unless the store supports multi-identifiers in which case an array if returned.
1563
1607
protected function parse_key($key) {
1564
if ($key === 'lastaccess') {
1565
$key = '__lastaccess__';
1608
$prefix = $this->get_key_prefix();
1609
if ($key === self::LASTACCESS) {
1610
return $key.$prefix;
1567
return 'sess_'.parent::parse_key($key);
1612
return $prefix.'_'.parent::parse_key($key);
1571
1616
* Check that this cache instance is tracking the current user.
1573
1618
protected function check_tracked_user() {
1574
if (isset($_SESSION['USER']->id)) {
1619
if (isset($_SESSION['USER']->id) && $_SESSION['USER']->id !== null) {
1575
1620
// Get the id of the current user.
1576
1621
$new = $_SESSION['USER']->id;
1585
1630
// This way we don't bloat the session.
1586
1631
$this->purge();
1587
1632
// Update the session id just in case!
1588
$this->sessionid = session_id();
1633
$this->set_session_id();
1590
1635
self::$loadeduserid = $new;
1591
1636
$this->currentuserid = $new;
1592
1637
} else if ($new !== $this->currentuserid) {
1593
1638
// The current user matches the loaded user but not the user last used by this cache.
1639
$this->purge_current_user();
1595
1640
$this->currentuserid = $new;
1596
1641
// Update the session id just in case!
1597
$this->sessionid = session_id();
1602
* Gets the session data.
1604
* @param bool $force If true the session data will be loaded from the store again.
1605
* @return array An array of session data.
1607
protected function get_session_data($force = false) {
1608
if ($this->sessionid === null) {
1609
$this->sessionid = session_id();
1611
if (is_array($this->session) && !$force) {
1612
return $this->session;
1614
$session = parent::get($this->sessionid);
1615
if ($session === false) {
1618
// We have to write here to ensure that the lastaccess time is recorded.
1619
// And also in order to ensure the session entry exists as when we save it on __destruct
1620
// $CFG is likely to have already been destroyed.
1621
$this->save_session($session);
1622
return $this->session;
1626
* Saves the session data.
1628
* This function also updates the last access time.
1630
* @param array $session
1633
protected function save_session(array $session) {
1634
$session['lastaccess'] = time();
1635
$this->session = $session;
1636
return parent::set($this->sessionid, $this->session);
1642
$this->set_session_id();
1647
* Purges the session cache of all data belonging to the current user.
1649
public function purge_current_user() {
1650
$keys = $this->get_store()->find_all($this->get_key_prefix());
1651
$this->get_store()->delete_many($keys);
1644
1659
* In advanced cases an array may be useful such as in situations requiring the multi-key functionality.
1645
1660
* @param int $strictness One of IGNORE_MISSING | MUST_EXIST
1646
1661
* @return mixed|false The data from the cache or false if the key did not exist within the cache.
1647
* @throws moodle_exception
1662
* @throws coding_exception
1649
1664
public function get($key, $strictness = IGNORE_MISSING) {
1650
1665
// Check the tracked user.
1652
1667
// 2. Parse the key.
1653
1668
$parsedkey = $this->parse_key($key);
1654
1669
// 3. Get it from the store.
1656
$session = $this->get_session_data();
1657
if (array_key_exists($parsedkey, $session)) {
1658
$result = $session[$parsedkey];
1670
$result = $this->get_store()->get($parsedkey);
1671
if ($result !== false) {
1659
1672
if ($result instanceof cache_ttl_wrapper) {
1660
1673
if ($result->has_expired()) {
1661
1674
$this->get_store()->delete($parsedkey);
1671
1684
// 4. Load if from the loader/datasource if we don't already have it.
1672
$setaftervalidation = false;
1673
1685
if ($result === false) {
1674
1686
if ($this->perfdebug) {
1675
cache_helper::record_cache_miss('**static session**', $this->get_definition()->get_id());
1687
cache_helper::record_cache_miss($this->storetype, $this->get_definition()->get_id());
1677
1689
if ($this->get_loader() !== false) {
1678
1690
// We must pass the original (unparsed) key to the next loader in the chain.
1682
1694
} else if ($this->get_datasource() !== false) {
1683
1695
$result = $this->get_datasource()->load_for_cache($key);
1685
$setaftervalidation = ($result !== false);
1697
// 5. Set it to the store if we got it from the loader/datasource.
1698
if ($result !== false) {
1699
$this->set($key, $result);
1686
1701
} else if ($this->perfdebug) {
1687
cache_helper::record_cache_hit('**static session**', $this->get_definition()->get_id());
1702
cache_helper::record_cache_hit($this->storetype, $this->get_definition()->get_id());
1689
1704
// 5. Validate strictness.
1690
1705
if ($strictness === MUST_EXIST && $result === false) {
1691
throw new moodle_exception('Requested key did not exist in any cache stores and could not be loaded.');
1693
// 6. Set it to the store if we got it from the loader/datasource.
1694
if ($setaftervalidation) {
1695
$this->set($key, $result);
1697
// 7. Make sure we don't pass back anything that could be a reference.
1706
throw new coding_exception('Requested key did not exist in any cache stores and could not be loaded.');
1708
// 6. Make sure we don't pass back anything that could be a reference.
1698
1709
// We don't want people modifying the data in the cache.
1699
1710
if (!is_scalar($result)) {
1700
1711
// If data is an object it will be a reference.
1726
1737
public function set($key, $data) {
1727
1738
$this->check_tracked_user();
1739
$loader = $this->get_loader();
1740
if ($loader !== false) {
1741
// We have a loader available set it there as well.
1742
// We have to let the loader do its own parsing of data as it may be unique.
1743
$loader->set($key, $data);
1728
1745
if ($this->perfdebug) {
1729
cache_helper::record_cache_set('**static session**', $this->get_definition()->get_id());
1746
cache_helper::record_cache_set($this->storetype, $this->get_definition()->get_id());
1731
1748
if (is_object($data) && $data instanceof cacheable_object) {
1732
1749
$data = new cache_cached_object($data);
1738
1755
$data = $this->unref($data);
1740
1757
// We dont' support native TTL here as we consolidate data for sessions.
1741
if ($this->has_a_ttl()) {
1758
if ($this->has_a_ttl() && !$this->store_supports_native_ttl()) {
1742
1759
$data = new cache_ttl_wrapper($data, $this->get_definition()->get_ttl());
1744
$session = $this->get_session_data();
1745
$session[$this->parse_key($key)] = $data;
1746
return $this->save_session($session);
1761
return $this->get_store()->set($this->parse_key($key), $data);
1755
1770
* @return bool True of success, false otherwise.
1757
1772
public function delete($key, $recurse = true) {
1758
$this->check_tracked_user();
1759
1773
$parsedkey = $this->parse_key($key);
1760
1774
if ($recurse && $this->get_loader() !== false) {
1761
1775
// Delete from the bottom of the stack first.
1762
1776
$this->get_loader()->delete($key, $recurse);
1764
$session = $this->get_session_data();
1765
unset($session[$parsedkey]);
1766
return $this->save_session($session);
1778
return $this->get_store()->delete($parsedkey);
1782
1794
* @return array An array of key value pairs for the items that could be retrieved from the cache.
1783
1795
* If MUST_EXIST was used and not all keys existed within the cache then an exception will be thrown.
1784
1796
* Otherwise any key that did not exist will have a data value of false within the results.
1785
* @throws moodle_exception
1797
* @throws coding_exception
1787
1799
public function get_many(array $keys, $strictness = IGNORE_MISSING) {
1788
1800
$this->check_tracked_user();
1801
$parsedkeys = array();
1803
foreach ($keys as $key) {
1804
$parsedkey = $this->parse_key($key);
1805
$parsedkeys[$key] = $parsedkey;
1806
$keymap[$parsedkey] = $key;
1808
$result = $this->get_store()->get_many($parsedkeys);
1789
1809
$return = array();
1790
foreach ($keys as $key) {
1791
$return[$key] = $this->get($key, $strictness);
1810
$missingkeys = array();
1811
$hasmissingkeys = false;
1812
foreach ($result as $parsedkey => $value) {
1813
$key = $keymap[$parsedkey];
1814
if ($value instanceof cache_ttl_wrapper) {
1815
/* @var cache_ttl_wrapper $value */
1816
if ($value->has_expired()) {
1817
$this->delete($keymap[$parsedkey]);
1820
$value = $value->data;
1823
if ($value instanceof cache_cached_object) {
1824
/* @var cache_cached_object $value */
1825
$value = $value->restore_object();
1827
$return[$key] = $value;
1828
if ($value === false) {
1829
$hasmissingkeys = true;
1830
$missingkeys[$parsedkey] = $key;
1833
if ($hasmissingkeys) {
1834
// We've got missing keys - we've got to check any loaders or data sources.
1835
$loader = $this->get_loader();
1836
$datasource = $this->get_datasource();
1837
if ($loader !== false) {
1838
foreach ($loader->get_many($missingkeys) as $key => $value) {
1839
if ($value !== false) {
1840
$return[$key] = $value;
1841
unset($missingkeys[$parsedkeys[$key]]);
1845
$hasmissingkeys = count($missingkeys) > 0;
1846
if ($datasource !== false && $hasmissingkeys) {
1847
// We're still missing keys but we've got a datasource.
1848
foreach ($datasource->load_many_for_cache($missingkeys) as $key => $value) {
1849
if ($value !== false) {
1850
$return[$key] = $value;
1851
unset($missingkeys[$parsedkeys[$key]]);
1854
$hasmissingkeys = count($missingkeys) > 0;
1857
if ($hasmissingkeys && $strictness === MUST_EXIST) {
1858
throw new coding_exception('Requested key did not exist in any cache stores and could not be loaded.');
1793
1861
return $return;
1802
1871
* @return int The number of items successfully deleted.
1804
1873
public function delete_many(array $keys, $recurse = true) {
1805
$this->check_tracked_user();
1806
1874
$parsedkeys = array_map(array($this, 'parse_key'), $keys);
1807
1875
if ($recurse && $this->get_loader() !== false) {
1808
1876
// Delete from the bottom of the stack first.
1809
1877
$this->get_loader()->delete_many($keys, $recurse);
1811
$session = $this->get_session_data();
1812
foreach ($parsedkeys as $parsedkey) {
1813
unset($session[$parsedkey]);
1815
$this->save_session($session);
1816
return count($keys);
1879
return $this->get_store()->delete_many($parsedkeys);
1842
1905
public function set_many(array $keyvaluearray) {
1843
1906
$this->check_tracked_user();
1844
$session = $this->get_session_data();
1845
$simulatettl = $this->has_a_ttl();
1907
$loader = $this->get_loader();
1908
if ($loader !== false) {
1909
// We have a loader available set it there as well.
1910
// We have to let the loader do its own parsing of data as it may be unique.
1911
$loader->set_many($keyvaluearray);
1914
$definitionid = $this->get_definition()->get_ttl();
1915
$simulatettl = $this->has_a_ttl() && !$this->store_supports_native_ttl();
1846
1916
foreach ($keyvaluearray as $key => $value) {
1847
1917
if (is_object($value) && $value instanceof cacheable_object) {
1848
1918
$value = new cache_cached_object($value);
1854
1924
$value = $this->unref($value);
1856
1926
if ($simulatettl) {
1857
$value = new cache_ttl_wrapper($value, $this->get_definition()->get_ttl());
1927
$value = new cache_ttl_wrapper($value, $definitionid);
1859
$parsedkey = $this->parse_key($key);
1860
$session[$parsedkey] = $value;
1929
$data[$key] = array(
1930
'key' => $this->parse_key($key),
1862
1934
if ($this->perfdebug) {
1863
cache_helper::record_cache_set($this->storetype, $this->get_definition()->get_id());
1935
cache_helper::record_cache_set($this->storetype, $definitionid);
1865
$this->save_session($session);
1866
return count($keyvaluearray);
1937
return $this->get_store()->set_many($data);
1872
1943
* @return bool True on success, false otherwise
1874
1945
public function purge() {
1875
// 1. Purge the session object.
1876
$this->session = array();
1877
// 2. Delete the record for this users session from the store.
1878
$this->get_store()->delete($this->sessionid);
1879
// 3. Optionally purge any stacked loaders in the same way.
1946
$this->get_store()->purge();
1880
1947
if ($this->get_loader()) {
1881
$this->get_loader()->delete($this->sessionid);
1948
$this->get_loader()->purge();
1907
1974
public function has($key, $tryloadifpossible = false) {
1908
1975
$this->check_tracked_user();
1909
1976
$parsedkey = $this->parse_key($key);
1910
$session = $this->get_session_data();
1912
if ($this->has_a_ttl()) {
1977
$store = $this->get_store();
1978
if ($this->has_a_ttl() && !$this->store_supports_native_ttl()) {
1913
1979
// The data has a TTL and the store doesn't support it natively.
1914
1980
// We must fetch the data and expect a ttl wrapper.
1915
if (array_key_exists($parsedkey, $session)) {
1916
$data = $session[$parsedkey];
1917
$has = ($data instanceof cache_ttl_wrapper && !$data->has_expired());
1981
$data = $store->get($parsedkey);
1982
$has = ($data instanceof cache_ttl_wrapper && !$data->has_expired());
1983
} else if (!$this->store_supports_key_awareness()) {
1984
// The store doesn't support key awareness, get the data and check it manually... puke.
1985
// Either no TTL is set of the store supports its handling natively.
1986
$data = $store->get($parsedkey);
1987
$has = ($data !== false);
1920
$has = array_key_exists($parsedkey, $session);
1989
// The store supports key awareness, this is easy!
1990
// Either no TTL is set of the store supports its handling natively.
1991
/* @var cache_store|cache_is_key_aware $store */
1992
$has = $store->has($parsedkey);
1922
1994
if (!$has && $tryloadifpossible) {
1923
1996
if ($this->get_loader() !== false) {
1924
$result = $this->get_loader()->get($key);
1997
$result = $this->get_loader()->get($parsedkey);
1925
1998
} else if ($this->get_datasource() !== null) {
1926
1999
$result = $this->get_datasource()->load_for_cache($key);
1949
2022
public function has_all(array $keys) {
1950
2023
$this->check_tracked_user();
1951
$session = $this->get_session_data();
1952
foreach ($keys as $key) {
1954
$parsedkey = $this->parse_key($key);
1955
if ($this->has_a_ttl()) {
1956
// The data has a TTL and the store doesn't support it natively.
1957
// We must fetch the data and expect a ttl wrapper.
1958
if (array_key_exists($parsedkey, $session)) {
1959
$data = $session[$parsedkey];
1960
$has = ($data instanceof cache_ttl_wrapper && !$data->has_expired());
2024
if (($this->has_a_ttl() && !$this->store_supports_native_ttl()) || !$this->store_supports_key_awareness()) {
2025
foreach ($keys as $key) {
2026
if (!$this->has($key)) {
1963
$has = array_key_exists($parsedkey, $session);
2032
// The cache must be key aware and if support native ttl if it a ttl is set.
2033
/* @var cache_store|cache_is_key_aware $store */
2034
$store = $this->get_store();
2035
return $store->has_all(array_map(array($this, 'parse_key'), $keys));
1983
2049
* @return bool True if the cache has at least one of the given keys
1985
2051
public function has_any(array $keys) {
1986
$this->check_tracked_user();
1987
$session = $this->get_session_data();
1988
foreach ($keys as $key) {
1990
$parsedkey = $this->parse_key($key);
1991
if ($this->has_a_ttl()) {
1992
// The data has a TTL and the store doesn't support it natively.
1993
// We must fetch the data and expect a ttl wrapper.
1994
if (array_key_exists($parsedkey, $session)) {
1995
$data = $session[$parsedkey];
1996
$has = ($data instanceof cache_ttl_wrapper && !$data->has_expired());
2052
if (($this->has_a_ttl() && !$this->store_supports_native_ttl()) || !$this->store_supports_key_awareness()) {
2053
foreach ($keys as $key) {
2054
if ($this->has($key)) {
1999
$has = array_key_exists($parsedkey, $session);
2060
/* @var cache_store|cache_is_key_aware $store */
2061
$store = $this->get_store();
2062
return $store->has_any(array_map(array($this, 'parse_key'), $keys));