Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402]

   1  <?php
   2  /**
   3   * Memory caching.
   4   *
   5   * This file is part of ADOdb, a Database Abstraction Layer library for PHP.
   6   *
   7   * @package ADOdb
   8   * @link https://adodb.org Project's web site and documentation
   9   * @link https://github.com/ADOdb/ADOdb Source code and issue tracker
  10   *
  11   * The ADOdb Library is dual-licensed, released under both the BSD 3-Clause
  12   * and the GNU Lesser General Public Licence (LGPL) v2.1 or, at your option,
  13   * any later version. This means you can use it in proprietary products.
  14   * See the LICENSE.md file distributed with this source code for details.
  15   * @license BSD-3-Clause
  16   * @license LGPL-2.1-or-later
  17   *
  18   * @copyright 2000-2013 John Lim
  19   * @copyright 2014 Damien Regad, Mark Newnham and the ADOdb community
  20   *
  21   * @noinspection PhpUnused
  22   */
  23  
  24  // security - hide paths
  25  if (!defined('ADODB_DIR')) die();
  26  
  27  global $ADODB_INCLUDED_MEMCACHE;
  28  $ADODB_INCLUDED_MEMCACHE = 1;
  29  
  30  global $ADODB_INCLUDED_CSV;
  31  if (empty($ADODB_INCLUDED_CSV)) {
  32  	 include_once (ADODB_DIR . '/adodb-csvlib.inc.php');
  33  }
  34  
  35  class ADODB_Cache_MemCache
  36  {
  37  	 /**
  38  	  * @var bool Prevents parent class calling non-existant function
  39  	  */
  40  	 public $createdir = false;
  41  
  42  	 /**
  43  	  * @var array of hosts
  44  	  */
  45  	 private $hosts;
  46  
  47  	 /**
  48  	  * @var int Connection Port, uses default
  49  	  */
  50  	 private $port;
  51  
  52  	 /**
  53  	  * @var bool memcache compression with zlib
  54  	  */
  55  	 private $compress;
  56  
  57  	 /**
  58  	  * @var array of options for memcached only
  59  	  */
  60  	 private $options;
  61  
  62  	 /**
  63  	  * @var bool Internal flag indicating successful connection
  64  	  */
  65  	 private $isConnected = false;
  66  
  67  	 /**
  68  	  * @var Memcache|Memcached Handle for the Memcache library
  69  	  *
  70  	  * Populated with the proper library on connect, used later when
  71  	  * there are differences in specific calls between memcache and memcached
  72  	  */
  73  	 private $memcacheLibrary = false;
  74  
  75  	 /**
  76  	  * @var array New server feature controller lists available servers
  77  	  */
  78  	 private $serverControllers = array();
  79  
  80  	 /**
  81  	  * @var array New server feature template uses granular server controller
  82  	  */
  83  	 private $serverControllerTemplate = array(
  84  	 	 'host' => '',
  85  	 	 'port' => 11211,
  86  	 	 'weight' => 0,
  87  	 );
  88  
  89  	 /**
  90  	  * An integer index into the libraries
  91  	  * @see $libraries
  92  	  */
  93  	 const MCLIB = 1;
  94  	 const MCLIBD = 2;
  95  
  96  	 /**
  97  	  * @var array Xrefs the library flag to the actual class name
  98  	  */
  99  	 private $libraries = array(
 100  	 	 self::MCLIB => 'Memcache',
 101  	 	 self::MCLIBD => 'Memcached'
 102  	 );
 103  
 104  	 /**
 105  	  * @var int An indicator of which library we are using
 106  	  */
 107  	 private $libraryFlag;
 108  
 109  	 /**
 110  	  * Class Constructor.
 111  	  *
 112  	  * @param ADOConnection $db
 113  	  */
 114  	public function __construct($db)
 115  	 {
 116  	 	 $this->hosts = $db->memCacheHost;
 117  	 	 $this->port = $this->serverControllerTemplate['port'] = $db->memCachePort;
 118  	 	 $this->compress = $db->memCacheCompress;
 119  	 	 $this->options = $db->memCacheOptions;
 120  	 }
 121  
 122  	 /**
 123  	  * Return true if the current library is Memcached.
 124  	  * @return bool
 125  	  */
 126  	public function isLibMemcached(): bool
 127  	 {
 128  	 	 return $this->libraryFlag == self::MCLIBD;
 129  	 }
 130  
 131  	 /**
 132  	  * Lazy connection.
 133  	  *
 134  	  * The connection only occurs on CacheExecute call.
 135  	  *
 136  	  * @param string $err
 137  	  *
 138  	  * @return bool success of connecting to a server
 139  	  */
 140  	public function connect(&$err)
 141  	 {
 142  	 	 // do we have memcache or memcached? see the note at adodb.org on memcache
 143  	 	 if (class_exists('Memcache')) {
 144  	 	 	 $this->libraryFlag = self::MCLIB;
 145  	 	 } elseif (class_exists('Memcached')) {
 146  	 	 	 $this->libraryFlag = self::MCLIBD;
 147  	 	 } else {
 148  	 	 	 $err = 'Neither the Memcache nor Memcached PECL extensions were found!';
 149  	 	 	 return false;
 150  	 	 }
 151  
 152  	 	 $usedLibrary = $this->libraries[$this->libraryFlag];
 153  
 154  	 	 /** @var Memcache|Memcached $memCache */
 155  	 	 $memCache = new $usedLibrary;
 156  	 	 if (!$memCache) {
 157  	 	 	 $err = 'Memcache library failed to initialize';
 158  	 	 	 return false;
 159  	 	 }
 160  
 161  	 	 // Convert simple compression flag for memcached
 162  	 	 if ($this->isLibMemcached()) {
 163  	 	 	 $this->options[Memcached::OPT_COMPRESSION] = $this->compress;
 164  	 	 }
 165  
 166  	 	 // Are there any options available for memcached
 167  	 	 if ($this->isLibMemcached() && count($this->options) > 0) {
 168  	 	 	 $optionSuccess = $memCache->setOptions($this->options);
 169  	 	 	 if (!$optionSuccess) {
 170  	 	 	 	 $err = 'Invalid option parameters passed to Memcached';
 171  	 	 	 	 return false;
 172  	 	 	 }
 173  	 	 }
 174  
 175  	 	 // Have we passed a controller array
 176  	 	 if (!is_array($this->hosts)) {
 177  	 	 	 $this->hosts = array($this->hosts);
 178  	 	 }
 179  
 180  	 	 if (!is_array($this->hosts[0])) {
 181  	 	 	 // Old way, convert to controller
 182  	 	 	 foreach ($this->hosts as $ipAddress) {
 183  	 	 	 	 $connector = $this->serverControllerTemplate;
 184  	 	 	 	 $connector['host'] = $ipAddress;
 185  	 	 	 	 $connector['port'] = $this->port;
 186  
 187  	 	 	 	 $this->serverControllers[] = $connector;
 188  	 	 	 }
 189  	 	 } else {
 190  	 	 	 // New way, must validate port, etc
 191  	 	 	 foreach ($this->hosts as $controller) {
 192  	 	 	 	 $connector = array_merge($this->serverControllerTemplate, $controller);
 193  	 	 	 	 if ($this->isLibMemcached()) {
 194  	 	 	 	 	 $connector['weight'] = (int)$connector['weight'];
 195  	 	 	 	 } else {
 196  	 	 	 	 	 // Cannot use weight in memcache, simply discard
 197  	 	 	 	 	 $connector['weight'] = 0;
 198  	 	 	 	 }
 199  
 200  	 	 	 	 $this->serverControllers[] = $connector;
 201  	 	 	 }
 202  	 	 }
 203  
 204  	 	 // Checks for existing connections ( but only for memcached )
 205  	 	 if ($this->isLibMemcached() && !empty($memCache->getServerList())) {
 206  	 	 	 // Use the existing configuration
 207  	 	 	 $this->isConnected = true;
 208  	 	 	 $this->memcacheLibrary = $memCache;
 209  	 	 	 return true;
 210  	 	 }
 211  
 212  	 	 $failcnt = 0;
 213  	 	 foreach ($this->serverControllers as $controller) {
 214  	 	 	 if ($this->isLibMemcached()) {
 215  	 	 	 	 if (!@$memCache->addServer($controller['host'], $controller['port'], $controller['weight'])) {
 216  	 	 	 	 	 $failcnt++;
 217  	 	 	 	 }
 218  	 	 	 } else {
 219  	 	 	 	 if (!@$memCache->addServer($controller['host'], $controller['port'])) {
 220  	 	 	 	 	 $failcnt++;
 221  	 	 	 	 }
 222  	 	 	 }
 223  	 	 }
 224  	 	 if ($failcnt == sizeof($this->serverControllers)) {
 225  	 	 	 $err = 'Can\'t connect to any memcache server';
 226  	 	 	 return false;
 227  	 	 }
 228  
 229  	 	 $this->memcacheLibrary = $memCache;
 230  
 231  	 	 // A valid memcache connection is available
 232  	 	 $this->isConnected = true;
 233  	 	 return true;
 234  	 }
 235  
 236  	 /**
 237  	  * Writes a cached query to the server
 238  	  *
 239  	  * @param string $filename The MD5 of the query to cache
 240  	  * @param string $contents The query results
 241  	  * @param bool $debug
 242  	  * @param int $secs2cache
 243  	  *
 244  	  * @return bool true or false. true if successful save
 245  	  */
 246  	public function writeCache($filename, $contents, $debug, $secs2cache)
 247  	 {
 248  	 	 $err = '';
 249  	 	 if (!$this->isConnected && $debug) {
 250  	 	 	 // Call to writeCache() before connect(), try to connect
 251  	 	 	 if (!$this->connect($err)) {
 252  	 	 	 	 ADOConnection::outp($err);
 253  	 	 	 }
 254  	 	 } else {
 255  	 	 	 if (!$this->isConnected) {
 256  	 	 	 	 $this->connect($err);
 257  	 	 	 }
 258  	 	 }
 259  
 260  	 	 if (!$this->memcacheLibrary) {
 261  	 	 	 return false;
 262  	 	 }
 263  
 264  	 	 $failed = false;
 265  	 	 switch ($this->libraryFlag) {
 266  	 	 	 case self::MCLIB:
 267  	 	 	 	 if (!$this->memcacheLibrary->set($filename, $contents, $this->compress ? MEMCACHE_COMPRESSED : 0,
 268  	 	 	 	 	 $secs2cache)) {
 269  	 	 	 	 	 $failed = true;
 270  	 	 	 	 }
 271  	 	 	 	 break;
 272  	 	 	 case self::MCLIBD:
 273  	 	 	 	 if (!$this->memcacheLibrary->set($filename, $contents, $secs2cache)) {
 274  	 	 	 	 	 $failed = true;
 275  	 	 	 	 }
 276  	 	 	 	 break;
 277  	 	 	 default:
 278  	 	 	 	 $failed = true;
 279  	 	 	 	 break;
 280  	 	 }
 281  
 282  	 	 if ($failed) {
 283  	 	 	 if ($debug) {
 284  	 	 	 	 ADOConnection::outp(" Failed to save data at the memcache server!<br>\n");
 285  	 	 	 }
 286  	 	 	 return false;
 287  	 	 }
 288  
 289  	 	 return true;
 290  	 }
 291  
 292  	 /**
 293  	  * Reads a cached query from the server.
 294  	  *
 295  	  * @param string $filename The MD5 of the query to read
 296  	  * @param string $err The query results
 297  	  * @param int $secs2cache
 298  	  * @param object $rsClass **UNUSED**
 299  	  *
 300  	  * @return object|bool record or false.
 301  	  *
 302  	  * @noinspection PhpUnusedParameterInspection
 303  	  */
 304  	public function readCache($filename, &$err, $secs2cache, $rsClass)
 305  	 {
 306  	 	 if (!$this->isConnected) {
 307  	 	 	 $this->connect($err);
 308  	 	 }
 309  	 	 if (!$this->memcacheLibrary) {
 310  	 	 	 return false;
 311  	 	 }
 312  
 313  	 	 $rs = $this->memcacheLibrary->get($filename);
 314  	 	 if (!$rs) {
 315  	 	 	 $err = 'Item with such key doesn\'t exist on the memcache server.';
 316  	 	 	 return false;
 317  	 	 }
 318  
 319  	 	 // hack, should actually use _csv2rs
 320  	 	 $rs = explode("\n", $rs);
 321  	 	 unset($rs[0]);
 322  	 	 $rs = join("\n", $rs);
 323  	 	 $rs = unserialize($rs);
 324  	 	 if (!is_object($rs)) {
 325  	 	 	 $err = 'Unable to unserialize $rs';
 326  	 	 	 return false;
 327  	 	 }
 328  	 	 if ($rs->timeCreated == 0) {
 329  	 	 	 return $rs;
 330  	 	 } // apparently have been reports that timeCreated was set to 0 somewhere
 331  
 332  	 	 $tdiff = intval($rs->timeCreated + $secs2cache - time());
 333  	 	 if ($tdiff <= 2) {
 334  	 	 	 switch ($tdiff) {
 335  	 	 	 	 case 2:
 336  	 	 	 	 	 if ((rand() & 15) == 0) {
 337  	 	 	 	 	 	 $err = "Timeout 2";
 338  	 	 	 	 	 	 return false;
 339  	 	 	 	 	 }
 340  	 	 	 	 	 break;
 341  	 	 	 	 case 1:
 342  	 	 	 	 	 if ((rand() & 3) == 0) {
 343  	 	 	 	 	 	 $err = "Timeout 1";
 344  	 	 	 	 	 	 return false;
 345  	 	 	 	 	 }
 346  	 	 	 	 	 break;
 347  	 	 	 	 default:
 348  	 	 	 	 	 $err = "Timeout 0";
 349  	 	 	 	 	 return false;
 350  	 	 	 }
 351  	 	 }
 352  	 	 return $rs;
 353  	 }
 354  
 355  	 /**
 356  	  * Flushes all of the stored memcache data
 357  	  *
 358  	  * @param bool $debug
 359  	  *
 360  	  * @return bool The response from the memcache server
 361  	  */
 362  	public function flushAll($debug = false)
 363  	 {
 364  	 	 if (!$this->isConnected) {
 365  	 	 	 $err = '';
 366  	 	 	 if (!$this->connect($err) && $debug) {
 367  	 	 	 	 ADOConnection::outp($err);
 368  	 	 	 }
 369  	 	 }
 370  	 	 if (!$this->memcacheLibrary) {
 371  	 	 	 return false;
 372  	 	 }
 373  
 374  	 	 $del = $this->memcacheLibrary->flush();
 375  
 376  	 	 if ($debug) {
 377  	 	 	 if (!$del) {
 378  	 	 	 	 ADOConnection::outp("flushall: failed!<br>\n");
 379  	 	 	 } else {
 380  	 	 	 	 ADOConnection::outp("flushall: succeeded!<br>\n");
 381  	 	 	 }
 382  	 	 }
 383  
 384  	 	 return $del;
 385  	 }
 386  
 387  	 /**
 388  	  * Flushes the contents of a specified query
 389  	  *
 390  	  * @param string $filename The MD5 of the query to flush
 391  	  * @param bool $debug
 392  	  *
 393  	  * @return bool The response from the memcache server
 394  	  */
 395  	public function flushCache($filename, $debug = false)
 396  	 {
 397  	 	 if (!$this->isConnected) {
 398  	 	 	 $err = '';
 399  	 	 	 if (!$this->connect($err) && $debug) {
 400  	 	 	 	 ADOConnection::outp($err);
 401  	 	 	 }
 402  	 	 }
 403  	 	 if (!$this->memcacheLibrary) {
 404  	 	 	 return false;
 405  	 	 }
 406  
 407  	 	 $del = $this->memcacheLibrary->delete($filename);
 408  
 409  	 	 if ($debug) {
 410  	 	 	 if (!$del) {
 411  	 	 	 	 ADOConnection::outp("flushcache: $filename entry doesn't exist on memcache server!<br>\n");
 412  	 	 	 } else {
 413  	 	 	 	 ADOConnection::outp("flushcache: $filename entry flushed from memcache server!<br>\n");
 414  	 	 	 }
 415  	 	 }
 416  
 417  	 	 return $del;
 418  	 }
 419  
 420  }