|
TYPO3 API
SVNRelease
|
00001 <?php 00002 /*************************************************************** 00003 * Copyright notice 00004 * 00005 * (c) 2009-2011 Ingo Renner <ingo@typo3.org> 00006 * All rights reserved 00007 * 00008 * This script is part of the TYPO3 project. The TYPO3 project is 00009 * free software; you can redistribute it and/or modify 00010 * it under the terms of the GNU General Public License as published by 00011 * the Free Software Foundation; either version 2 of the License, or 00012 * (at your option) any later version. 00013 * 00014 * The GNU General Public License can be found at 00015 * http://www.gnu.org/copyleft/gpl.html. 00016 * 00017 * This script is distributed in the hope that it will be useful, 00018 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00019 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00020 * GNU General Public License for more details. 00021 * 00022 * This copyright notice MUST APPEAR in all copies of the script! 00023 ***************************************************************/ 00024 00025 00026 /** 00027 * A caching backend which stores cache entries by using Memcached. 00028 * 00029 * This backend uses the following types of Memcache keys: 00030 * - tag_xxx 00031 * xxx is tag name, value is array of associated identifiers identifier. This 00032 * is "forward" tag index. It is mainly used for obtaining content by tag 00033 * (get identifier by tag -> get content by identifier) 00034 * - ident_xxx 00035 * xxx is identifier, value is array of associated tags. This is "reverse" tag 00036 * index. It provides quick access for all tags associated with this identifier 00037 * and used when removing the identifier 00038 * 00039 * Each key is prepended with a prefix. By default prefix consists from two parts 00040 * separated by underscore character and ends in yet another underscore character: 00041 * - "TYPO3" 00042 * - Current site path obtained from the PATH_site constant 00043 * This prefix makes sure that keys from the different installations do not 00044 * conflict. 00045 * 00046 * Note: When using the Memcached backend to store values of more than ~1 MB, 00047 * the data will be split into chunks to make them fit into the memcached limits. 00048 * 00049 * This file is a backport from FLOW3 by Ingo Renner. 00050 * 00051 * @package TYPO3 00052 * @subpackage t3lib_cache 00053 * @api 00054 * @version $Id: class.t3lib_cache_backend_memcachedbackend.php 10443 2011-02-11 22:46:06Z lolli $ 00055 */ 00056 class t3lib_cache_backend_MemcachedBackend extends t3lib_cache_backend_AbstractBackend { 00057 00058 /** 00059 * Max bucket size, (1024*1024)-42 bytes 00060 * @var int 00061 */ 00062 const MAX_BUCKET_SIZE = 1048534; 00063 00064 /** 00065 * Instance of the PHP Memcache class 00066 * 00067 * @var Memcache 00068 */ 00069 protected $memcache; 00070 00071 /** 00072 * Array of Memcache server configurations 00073 * 00074 * @var array 00075 */ 00076 protected $servers = array(); 00077 00078 /** 00079 * Indicates whether the memcache uses compression or not (requires zlib), 00080 * either 0 or MEMCACHE_COMPRESSED 00081 * 00082 * @var int 00083 */ 00084 protected $flags; 00085 00086 /** 00087 * A prefix to seperate stored data from other data possibly stored in the 00088 * memcache. This prefix must be unique for each site in the tree. Default 00089 * implementation uses MD5 of the current site path to make identifier prefix 00090 * unique. 00091 * 00092 * @var string 00093 */ 00094 protected $identifierPrefix; 00095 00096 /** 00097 * Indicates whther the server is connected 00098 * 00099 * @var boolean 00100 */ 00101 protected $serverConnected = false; 00102 00103 /** 00104 * Constructs this backend 00105 * 00106 * @param array $options Configuration options - depends on the actual backend 00107 * @author Robert Lemke <robert@typo3.org> 00108 */ 00109 public function __construct(array $options = array()) { 00110 if (!extension_loaded('memcache')) { 00111 throw new t3lib_cache_Exception( 00112 'The PHP extension "memcache" must be installed and loaded in ' . 00113 'order to use the Memcached backend.', 00114 1213987706 00115 ); 00116 } 00117 00118 parent::__construct($options); 00119 00120 $this->memcache = new Memcache(); 00121 $defaultPort = ini_get('memcache.default_port'); 00122 00123 if (!count($this->servers)) { 00124 throw new t3lib_cache_Exception( 00125 'No servers were given to Memcache', 00126 1213115903 00127 ); 00128 } 00129 00130 foreach ($this->servers as $serverConfiguration) { 00131 if (substr($serverConfiguration, 0, 7) == 'unix://') { 00132 $host = $serverConfiguration; 00133 $port = 0; 00134 } else { 00135 if (substr($serverConfiguration, 0, 6) === 'tcp://') { 00136 $serverConfiguration = substr($serverConfiguration, 6); 00137 } 00138 if (strstr($serverConfiguration, ':') !== FALSE) { 00139 list($host, $port) = explode(':', $serverConfiguration, 2); 00140 } else { 00141 $host = $serverConfiguration; 00142 $port = $defaultPort; 00143 } 00144 } 00145 00146 if ($this->serverConnected) { 00147 $this->memcache->addserver($host, $port); 00148 } else { 00149 // pconnect throws PHP warnings when it cannot connect! 00150 $this->serverConnected = @$this->memcache->pconnect($host, $port); 00151 } 00152 } 00153 00154 if (!$this->serverConnected) { 00155 t3lib_div::sysLog('Unable to connect to any Memcached server', 'core', 3); 00156 } 00157 } 00158 00159 /** 00160 * Setter for servers to be used. Expects an array, the values are expected 00161 * to be formatted like "<host>[:<port>]" or "unix://<path>" 00162 * 00163 * @param array An array of servers to add. 00164 * @return void 00165 * @author Christian Jul Jensen <julle@typo3.org> 00166 */ 00167 protected function setServers(array $servers) { 00168 $this->servers = $servers; 00169 } 00170 00171 /** 00172 * Setter for compression flags bit 00173 * 00174 * @param boolean $useCompression 00175 * @return void 00176 * @author Christian Jul Jensen <julle@typo3.org> 00177 */ 00178 protected function setCompression($useCompression) { 00179 if ($useCompression === TRUE) { 00180 $this->flags ^= MEMCACHE_COMPRESSED; 00181 } else { 00182 $this->flags &= ~MEMCACHE_COMPRESSED; 00183 } 00184 } 00185 00186 /** 00187 * Initializes the identifier prefix when setting the cache. 00188 * 00189 * @param t3lib_cache_frontend_Frontend $cache The frontend for this backend 00190 * @return void 00191 * @author Robert Lemke <robert@typo3.org> 00192 * @author Dmitry Dulepov 00193 */ 00194 public function setCache(t3lib_cache_frontend_Frontend $cache) { 00195 parent::setCache($cache); 00196 $this->identifierPrefix = 'TYPO3_' . md5(PATH_site) . '_'; 00197 } 00198 00199 /** 00200 * Saves data in the cache. 00201 * 00202 * @param string An identifier for this specific cache entry 00203 * @param string The data to be stored 00204 * @param array Tags to associate with this cache entry 00205 * @param integer Lifetime of this cache entry in seconds. If NULL is specified, the default lifetime is used. "0" means unlimited liftime. 00206 * @return void 00207 * @throws t3lib_cache_Exception if no cache frontend has been set. 00208 * @throws InvalidArgumentException if the identifier is not valid or the final memcached key is longer than 250 characters 00209 * @throws t3lib_cache_exception_InvalidData if $data is not a string 00210 * @author Christian Jul Jensen <julle@typo3.org> 00211 * @author Karsten Dambekalns <karsten@typo3.org> 00212 */ 00213 public function set($entryIdentifier, $data, array $tags = array(), $lifetime = NULL) { 00214 if (strlen($this->identifierPrefix . $entryIdentifier) > 250) { 00215 throw new InvalidArgumentException( 00216 'Could not set value. Key more than 250 characters (' . $this->identifierPrefix . $entryIdentifier . ').', 00217 1235839340 00218 ); 00219 } 00220 00221 if (!$this->cache instanceof t3lib_cache_frontend_Frontend) { 00222 throw new t3lib_cache_Exception( 00223 'No cache frontend has been set yet via setCache().', 00224 1207149215 00225 ); 00226 } 00227 00228 if (!is_string($data)) { 00229 throw new t3lib_cache_Exception_InvalidData( 00230 'The specified data is of type "' . gettype($data) . 00231 '" but a string is expected.', 00232 1207149231 00233 ); 00234 } 00235 00236 $tags[] = '%MEMCACHEBE%' . $this->cacheIdentifier; 00237 $expiration = $lifetime !== NULL ? $lifetime : $this->defaultLifetime; 00238 00239 // Memcached consideres values over 2592000 sec (30 days) as UNIX timestamp 00240 // thus $expiration should be converted from lifetime to UNIX timestamp 00241 if ($expiration > 2592000) { 00242 $expiration += $GLOBALS['EXEC_TIME']; 00243 } 00244 00245 try { 00246 if (strlen($data) > self::MAX_BUCKET_SIZE) { 00247 $data = str_split($data, 1024 * 1000); 00248 $success = TRUE; 00249 $chunkNumber = 1; 00250 00251 foreach ($data as $chunk) { 00252 $success = $success && $this->memcache->set( 00253 $this->identifierPrefix . $entryIdentifier . '_chunk_' . $chunkNumber, 00254 $chunk, 00255 $this->flags, 00256 $expiration 00257 ); 00258 $chunkNumber++; 00259 } 00260 $success = $success && $this->memcache->set( 00261 $this->identifierPrefix . $entryIdentifier, 00262 'TYPO3*chunked:' . $chunkNumber, 00263 $this->flags, 00264 $expiration 00265 ); 00266 } else { 00267 $success = $this->memcache->set( 00268 $this->identifierPrefix . $entryIdentifier, 00269 $data, 00270 $this->flags, 00271 $expiration 00272 ); 00273 } 00274 00275 if ($success === TRUE) { 00276 $this->removeIdentifierFromAllTags($entryIdentifier); 00277 $this->addIdentifierToTags($entryIdentifier, $tags); 00278 } else { 00279 throw new t3lib_cache_Exception( 00280 'Could not set data to memcache server.', 00281 1275830266 00282 ); 00283 } 00284 } catch (Exception $exception) { 00285 throw new t3lib_cache_Exception( 00286 'Could not set value. ' . 00287 $exception->getMessage(), 00288 1207208100 00289 ); 00290 } 00291 } 00292 00293 /** 00294 * Loads data from the cache. 00295 * 00296 * @param string An identifier which describes the cache entry to load 00297 * @return mixed The cache entry's content as a string or FALSE if the cache entry could not be loaded 00298 * @author Christian Jul Jensen <julle@typo3.org> 00299 * @author Karsten Dambekalns <karsten@typo3.org> 00300 */ 00301 public function get($entryIdentifier) { 00302 $value = $this->memcache->get($this->identifierPrefix . $entryIdentifier); 00303 00304 if (substr($value, 0, 14) === 'TYPO3*chunked:') { 00305 list(, $chunkCount) = explode(':', $value); 00306 $value = ''; 00307 00308 for ($chunkNumber = 1; $chunkNumber < $chunkCount; $chunkNumber++) { 00309 $value .= $this->memcache->get($this->identifierPrefix . $entryIdentifier . '_chunk_' . $chunkNumber); 00310 } 00311 } 00312 00313 return $value; 00314 } 00315 00316 /** 00317 * Checks if a cache entry with the specified identifier exists. 00318 * 00319 * @param string An identifier specifying the cache entry 00320 * @return boolean TRUE if such an entry exists, FALSE if not 00321 * @author Christian Jul Jensen <julle@typo3.org> 00322 * @author Karsten Dambekalns <karsten@typo3.org> 00323 */ 00324 public function has($entryIdentifier) { 00325 return $this->serverConnected && $this->memcache->get($this->identifierPrefix . $entryIdentifier) !== false; 00326 } 00327 00328 /** 00329 * Removes all cache entries matching the specified identifier. 00330 * Usually this only affects one entry but if - for what reason ever - 00331 * old entries for the identifier still exist, they are removed as well. 00332 * 00333 * @param string Specifies the cache entry to remove 00334 * @return boolean TRUE if (at least) an entry could be removed or FALSE if no entry was found 00335 * @author Christian Jul Jensen <julle@typo3.org> 00336 * @author Karsten Dambekalns <karsten@typo3.org> 00337 */ 00338 public function remove($entryIdentifier) { 00339 $this->removeIdentifierFromAllTags($entryIdentifier); 00340 return $this->memcache->delete($this->identifierPrefix . $entryIdentifier, 0); 00341 } 00342 00343 /** 00344 * Finds and returns all cache entry identifiers which are tagged by the 00345 * specified tag. 00346 * 00347 * @param string The tag to search for 00348 * @return array An array of entries with all matching entries. An empty array if no entries matched 00349 * @author Karsten Dambekalns <karsten@typo3.org> 00350 */ 00351 public function findIdentifiersByTag($tag) { 00352 $identifiers = $this->memcache->get($this->identifierPrefix . 'tag_' . $tag); 00353 00354 if ($identifiers !== FALSE) { 00355 return (array) $identifiers; 00356 } else { 00357 return array(); 00358 } 00359 } 00360 00361 00362 /** 00363 * Finds and returns all cache entry identifiers which are tagged by the 00364 * specified tags. 00365 * 00366 * @param array Array of tags to search for 00367 * @return array An array with identifiers of all matching entries. An empty array if no entries matched 00368 * @author Ingo Renner <ingo@typo3.org> 00369 */ 00370 public function findIdentifiersByTags(array $tags) { 00371 $taggedEntries = array(); 00372 $foundEntries = array(); 00373 00374 foreach ($tags as $tag) { 00375 $taggedEntries[$tag] = $this->findIdentifiersByTag($tag); 00376 } 00377 00378 $intersectedTaggedEntries = call_user_func_array('array_intersect', $taggedEntries); 00379 00380 foreach ($intersectedTaggedEntries as $entryIdentifier) { 00381 $foundEntries[$entryIdentifier] = $entryIdentifier; 00382 } 00383 00384 return $foundEntries; 00385 } 00386 00387 /** 00388 * Removes all cache entries of this cache. 00389 * 00390 * @return void 00391 * @author Karsten Dambekalns <karsten@typo3.org> 00392 */ 00393 public function flush() { 00394 if (!$this->cache instanceof t3lib_cache_frontend_Frontend) { 00395 throw new t3lib_cache_Exception('No cache frontend has been set via setCache() yet.', 1204111376); 00396 } 00397 00398 $this->flushByTag('%MEMCACHEBE%' . $this->cacheIdentifier); 00399 } 00400 00401 /** 00402 * Removes all cache entries of this cache which are tagged by the specified tag. 00403 * 00404 * @param string $tag The tag the entries must have 00405 * @return void 00406 * @author Karsten Dambekalns <karsten@typo3.org> 00407 */ 00408 public function flushByTag($tag) { 00409 $identifiers = $this->findIdentifiersByTag($tag); 00410 00411 foreach ($identifiers as $identifier) { 00412 $this->remove($identifier); 00413 } 00414 } 00415 00416 00417 /** 00418 * Removes all cache entries of this cache which are tagged by the specified tag. 00419 * 00420 * @param array The tags the entries must have 00421 * @return void 00422 * @author Ingo Renner <ingo@typo3.org> 00423 */ 00424 public function flushByTags(array $tags) { 00425 foreach ($tags as $tag) { 00426 $this->flushByTag($tag); 00427 } 00428 } 00429 00430 /** 00431 * Associates the identifier with the given tags 00432 * 00433 * @param string $entryIdentifier 00434 * @param array Array of tags 00435 * @author Karsten Dambekalns <karsten@typo3.org> 00436 * @author Dmitry Dulepov <dmitry@typo3.org> 00437 * @internal 00438 */ 00439 protected function addIdentifierToTags($entryIdentifier, array $tags) { 00440 if ($this->serverConnected) { 00441 foreach ($tags as $tag) { 00442 // Update tag-to-identifier index 00443 $identifiers = $this->findIdentifiersByTag($tag); 00444 if (array_search($entryIdentifier, $identifiers) === false) { 00445 $identifiers[] = $entryIdentifier; 00446 $this->memcache->set($this->identifierPrefix . 'tag_' . $tag, 00447 $identifiers); 00448 } 00449 00450 // Update identifier-to-tag index 00451 $existingTags = $this->findTagsByIdentifier($entryIdentifier); 00452 if (array_search($tag, $existingTags) === FALSE) { 00453 $this->memcache->set($this->identifierPrefix . 'ident_' . $entryIdentifier, 00454 array_merge($existingTags, $tags)); 00455 } 00456 } 00457 } 00458 } 00459 00460 /** 00461 * Removes association of the identifier with the given tags 00462 * 00463 * @param string $entryIdentifier 00464 * @param array Array of tags 00465 * @author Karsten Dambekalns <karsten@typo3.org> 00466 * @author Dmitry Dulepov <dmitry@typo3.org> 00467 * @internal 00468 */ 00469 protected function removeIdentifierFromAllTags($entryIdentifier) { 00470 if ($this->serverConnected) { 00471 // Get tags for this identifier 00472 $tags = $this->findTagsByIdentifier($entryIdentifier); 00473 // Deassociate tags with this identifier 00474 foreach ($tags as $tag) { 00475 $identifiers = $this->findIdentifiersByTag($tag); 00476 // Formally array_search() below should never return false 00477 // due to the behavior of findTagsForIdentifier(). But if 00478 // reverse index is corrupted, we still can get 'false' from 00479 // array_search(). This is not a problem because we are 00480 // removing this identifier from anywhere. 00481 if (($key = array_search($entryIdentifier, $identifiers)) !== false) { 00482 unset($identifiers[$key]); 00483 00484 if (count($identifiers)) { 00485 $this->memcache->set( 00486 $this->identifierPrefix . 'tag_' . $tag, 00487 $identifiers 00488 ); 00489 } else { 00490 $this->memcache->delete($this->identifierPrefix . 'tag_' . $tag, 0); 00491 } 00492 } 00493 } 00494 00495 // Clear reverse tag index for this identifier 00496 $this->memcache->delete($this->identifierPrefix . 'ident_' . $entryIdentifier, 0); 00497 } 00498 } 00499 00500 /** 00501 * Finds all tags for the given identifier. This function uses reverse tag 00502 * index to search for tags. 00503 * 00504 * @param string Identifier to find tags by 00505 * @return array Array with tags 00506 * @author Dmitry Dulepov <dmitry@typo3.org> 00507 * @internal 00508 */ 00509 protected function findTagsByIdentifier($identifier) { 00510 $tags = $this->memcache->get($this->identifierPrefix . 'ident_' . $identifier); 00511 return ($tags === FALSE ? array() : (array) $tags); 00512 } 00513 00514 /** 00515 * Does nothing, as memcached does GC itself 00516 * 00517 * @return void 00518 */ 00519 public function collectGarbage() { 00520 } 00521 } 00522 00523 00524 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/cache/backend/class.t3lib_cache_backend_memcachedbackend.php'])) { 00525 include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/cache/backend/class.t3lib_cache_backend_memcachedbackend.php']); 00526 } 00527 00528 ?>
1.8.0