TYPO3 CMS  TYPO3_7-6
MemcachedBackend.php
Go to the documentation of this file.
1 <?php
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
20 
48 {
54  const MAX_BUCKET_SIZE = 1048534;
55 
61  protected $memcache;
62 
68  protected $usedPeclModule = '';
69 
75  protected $servers = [];
76 
83  protected $flags;
84 
90  protected $identifierPrefix;
91 
99  public function __construct($context, array $options = [])
100  {
101  if (!extension_loaded('memcache') && !extension_loaded('memcached')) {
102  throw new Exception('The PHP extension "memcache" or "memcached" must be installed and loaded in ' . 'order to use the Memcached backend.', 1213987706);
103  }
104 
105  parent::__construct($context, $options);
106 
107  if ($this->usedPeclModule === '') {
108  if (extension_loaded('memcache')) {
109  $this->usedPeclModule = 'memcache';
110  } elseif (extension_loaded('memcached')) {
111  $this->usedPeclModule = 'memcached';
112  }
113  }
114  }
115 
124  protected function setServers(array $servers)
125  {
126  $this->servers = $servers;
127  }
128 
136  protected function setCompression($useCompression)
137  {
138  $compressionFlag = $this->usedPeclModule === 'memcache' ? MEMCACHE_COMPRESSED : \Memcached::OPT_COMPRESSION;
139  if ($useCompression === true) {
140  $this->flags ^= $compressionFlag;
141  } else {
142  $this->flags &= ~$compressionFlag;
143  }
144  }
145 
152  protected function getCompression()
153  {
154  return $this->flags !== 0;
155  }
156 
163  public function initializeObject()
164  {
165  if (empty($this->servers)) {
166  throw new Exception('No servers were given to Memcache', 1213115903);
167  }
168  $memcachedPlugin = '\\' . ucfirst($this->usedPeclModule);
169  $this->memcache = new $memcachedPlugin;
170  $defaultPort = $this->usedPeclModule === 'memcache' ? ini_get('memcache.default_port') : 11211;
171  foreach ($this->servers as $server) {
172  if (substr($server, 0, 7) === 'unix://') {
173  $host = $server;
174  $port = 0;
175  } else {
176  if (substr($server, 0, 6) === 'tcp://') {
177  $server = substr($server, 6);
178  }
179  if (strpos($server, ':') !== false) {
180  list($host, $port) = explode(':', $server, 2);
181  } else {
182  $host = $server;
183  $port = $defaultPort;
184  }
185  }
186  $this->memcache->addserver($host, $port);
187  }
188  if ($this->usedPeclModule === 'memcached') {
189  $this->memcache->setOption(\Memcached::OPT_COMPRESSION, $this->getCompression());
190  }
191  }
192 
199  public function setPeclModule($peclModule)
200  {
201  if ($peclModule !== 'memcache' && $peclModule !== 'memcached') {
202  throw new Exception('PECL module must be either "memcache" or "memcached".', 1442239768);
203  }
204 
205  $this->usedPeclModule = $peclModule;
206  }
207 
215  {
216  parent::setCache($cache);
217  $identifierHash = substr(md5(PATH_site . $this->context . $this->cacheIdentifier), 0, 12);
218  $this->identifierPrefix = 'TYPO3_' . $identifierHash . '_';
219  }
220 
234  public function set($entryIdentifier, $data, array $tags = [], $lifetime = null)
235  {
236  if (strlen($this->identifierPrefix . $entryIdentifier) > 250) {
237  throw new \InvalidArgumentException('Could not set value. Key more than 250 characters (' . $this->identifierPrefix . $entryIdentifier . ').', 1232969508);
238  }
239  if (!$this->cache instanceof FrontendInterface) {
240  throw new Exception('No cache frontend has been set yet via setCache().', 1207149215);
241  }
242  if (!is_string($data)) {
243  throw new Exception\InvalidDataException('The specified data is of type "' . gettype($data) . '" but a string is expected.', 1207149231);
244  }
245  $tags[] = '%MEMCACHEBE%' . $this->cacheIdentifier;
246  $expiration = $lifetime !== null ? $lifetime : $this->defaultLifetime;
247  $memcacheIsUsed = $this->usedPeclModule === 'memcache';
248  // Memcached consideres values over 2592000 sec (30 days) as UNIX timestamp
249  // thus $expiration should be converted from lifetime to UNIX timestamp
250  if ($expiration > 2592000) {
251  $expiration += $GLOBALS['EXEC_TIME'];
252  }
253  try {
254  if (strlen($data) > self::MAX_BUCKET_SIZE) {
255  $data = str_split($data, 1024 * 1000);
256  $success = true;
257  $chunkNumber = 1;
258  foreach ($data as $chunk) {
259  if ($memcacheIsUsed) {
260  $success = $success && $this->memcache->set($this->identifierPrefix . $entryIdentifier . '_chunk_' . $chunkNumber, $chunk, $this->flags, $expiration);
261  } else {
262  $success = $success && $this->memcache->set($this->identifierPrefix . $entryIdentifier . '_chunk_' . $chunkNumber, $chunk, $expiration);
263  }
264 
265  $chunkNumber++;
266  }
267  if ($memcacheIsUsed) {
268  $success = $success && $this->memcache->set($this->identifierPrefix . $entryIdentifier, 'TYPO3*chunked:' . $chunkNumber, $this->flags, $expiration);
269  } else {
270  $success = $success && $this->memcache->set($this->identifierPrefix . $entryIdentifier, 'TYPO3*chunked:' . $chunkNumber, $expiration);
271  }
272  } else {
273  if ($memcacheIsUsed) {
274  $success = $this->memcache->set($this->identifierPrefix . $entryIdentifier, $data, $this->flags, $expiration);
275  } else {
276  $success = $this->memcache->set($this->identifierPrefix . $entryIdentifier, $data, $expiration);
277  }
278  }
279  if ($success === true) {
280  $this->removeIdentifierFromAllTags($entryIdentifier);
281  $this->addIdentifierToTags($entryIdentifier, $tags);
282  } else {
283  throw new Exception('Could not set data to memcache server.', 1275830266);
284  }
285  } catch (\Exception $exception) {
286  GeneralUtility::sysLog('Memcache: could not set value. Reason: ' . $exception->getMessage(), 'core', GeneralUtility::SYSLOG_SEVERITY_WARNING);
287  }
288  }
289 
297  public function get($entryIdentifier)
298  {
299  $value = $this->memcache->get($this->identifierPrefix . $entryIdentifier);
300  if (substr($value, 0, 14) === 'TYPO3*chunked:') {
301  list(, $chunkCount) = explode(':', $value);
302  $value = '';
303  for ($chunkNumber = 1; $chunkNumber < $chunkCount; $chunkNumber++) {
304  $value .= $this->memcache->get($this->identifierPrefix . $entryIdentifier . '_chunk_' . $chunkNumber);
305  }
306  }
307  return $value;
308  }
309 
317  public function has($entryIdentifier)
318  {
319  if ($this->usedPeclModule === 'memcache') {
320  return $this->memcache->get($this->identifierPrefix . $entryIdentifier) !== false;
321  }
322 
323  // pecl-memcached supports storing literal FALSE
324  $this->memcache->get($this->identifierPrefix . $entryIdentifier);
325  return $this->memcache->getResultCode() !== \Memcached::RES_NOTFOUND;
326  }
327 
337  public function remove($entryIdentifier)
338  {
339  $this->removeIdentifierFromAllTags($entryIdentifier);
340  return $this->memcache->delete($this->identifierPrefix . $entryIdentifier, 0);
341  }
342 
351  public function findIdentifiersByTag($tag)
352  {
353  $identifiers = $this->memcache->get($this->identifierPrefix . 'tag_' . $tag);
354  if ($identifiers !== false) {
355  return (array)$identifiers;
356  } else {
357  return [];
358  }
359  }
360 
368  public function flush()
369  {
370  if (!$this->cache instanceof FrontendInterface) {
371  throw new Exception('No cache frontend has been set via setCache() yet.', 1204111376);
372  }
373  $this->flushByTag('%MEMCACHEBE%' . $this->cacheIdentifier);
374  }
375 
383  public function flushByTag($tag)
384  {
385  $identifiers = $this->findIdentifiersByTag($tag);
386  foreach ($identifiers as $identifier) {
387  $this->remove($identifier);
388  }
389  }
390 
398  protected function addIdentifierToTags($entryIdentifier, array $tags)
399  {
400  // Get identifier-to-tag index to look for updates
401  $existingTags = $this->findTagsByIdentifier($entryIdentifier);
402  $existingTagsUpdated = false;
403 
404  foreach ($tags as $tag) {
405  // Update tag-to-identifier index
406  $identifiers = $this->findIdentifiersByTag($tag);
407  if (!in_array($entryIdentifier, $identifiers, true)) {
408  $identifiers[] = $entryIdentifier;
409  $this->memcache->set($this->identifierPrefix . 'tag_' . $tag, $identifiers);
410  }
411  // Test if identifier-to-tag index needs update
412  if (!in_array($tag, $existingTags, true)) {
413  $existingTags[] = $tag;
414  $existingTagsUpdated = true;
415  }
416  }
417 
418  // Update identifier-to-tag index if needed
419  if ($existingTagsUpdated) {
420  $this->memcache->set($this->identifierPrefix . 'ident_' . $entryIdentifier, $existingTags);
421  }
422  }
423 
430  protected function removeIdentifierFromAllTags($entryIdentifier)
431  {
432  // Get tags for this identifier
433  $tags = $this->findTagsByIdentifier($entryIdentifier);
434  // De-associate tags with this identifier
435  foreach ($tags as $tag) {
436  $identifiers = $this->findIdentifiersByTag($tag);
437  // Formally array_search() below should never return FALSE due to
438  // the behavior of findTagsByIdentifier(). But if reverse index is
439  // corrupted, we still can get 'FALSE' from array_search(). This is
440  // not a problem because we are removing this identifier from
441  // anywhere.
442  if (($key = array_search($entryIdentifier, $identifiers)) !== false) {
443  unset($identifiers[$key]);
444  if (!empty($identifiers)) {
445  $this->memcache->set($this->identifierPrefix . 'tag_' . $tag, $identifiers);
446  } else {
447  $this->memcache->delete($this->identifierPrefix . 'tag_' . $tag, 0);
448  }
449  }
450  }
451  // Clear reverse tag index for this identifier
452  $this->memcache->delete($this->identifierPrefix . 'ident_' . $entryIdentifier, 0);
453  }
454 
463  protected function findTagsByIdentifier($identifier)
464  {
465  $tags = $this->memcache->get($this->identifierPrefix . 'ident_' . $identifier);
466  return $tags === false ? [] : (array)$tags;
467  }
468 
475  public function collectGarbage()
476  {
477  }
478 }
addIdentifierToTags($entryIdentifier, array $tags)
$host
Definition: server.php:37
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']