‪TYPO3CMS  9.5
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 
45 {
51  const ‪MAX_BUCKET_SIZE = 1048534;
52 
58  protected ‪$memcache;
59 
65  protected ‪$usedPeclModule = '';
66 
72  protected ‪$servers = [];
73 
80  protected ‪$flags;
81 
87  protected ‪$identifierPrefix;
88 
96  public function ‪__construct(‪$context, array $options = [])
97  {
98  if (!extension_loaded('memcache') && !extension_loaded('memcached')) {
99  throw new ‪Exception('The PHP extension "memcache" or "memcached" must be installed and loaded in ' . 'order to use the Memcached backend.', 1213987706);
100  }
101 
102  parent::__construct(‪$context, $options);
103 
104  if ($this->usedPeclModule === '') {
105  if (extension_loaded('memcache')) {
106  $this->usedPeclModule = 'memcache';
107  } elseif (extension_loaded('memcached')) {
108  $this->usedPeclModule = 'memcached';
109  }
110  }
111  }
112 
119  protected function ‪setServers(array ‪$servers)
120  {
121  $this->servers = ‪$servers;
122  }
123 
129  protected function ‪setCompression($useCompression)
130  {
131  $compressionFlag = $this->usedPeclModule === 'memcache' ? MEMCACHE_COMPRESSED : \Memcached::OPT_COMPRESSION;
132  if ($useCompression === true) {
133  $this->flags ^= $compressionFlag;
134  } else {
135  $this->flags &= ~$compressionFlag;
136  }
137  }
138 
144  protected function ‪getCompression()
145  {
146  return $this->flags !== 0;
147  }
148 
154  public function ‪initializeObject()
155  {
156  if (empty($this->servers)) {
157  throw new Exception('No servers were given to Memcache', 1213115903);
158  }
159  $memcachedPlugin = '\\' . ucfirst($this->usedPeclModule);
160  $this->memcache = new $memcachedPlugin;
161  $defaultPort = $this->usedPeclModule === 'memcache' ? ini_get('memcache.default_port') : 11211;
162  foreach ($this->servers as $server) {
163  if (strpos($server, 'unix://') === 0) {
164  $host = $server;
165  $port = 0;
166  } else {
167  if (strpos($server, 'tcp://') === 0) {
168  $server = substr($server, 6);
169  }
170  if (strpos($server, ':') !== false) {
171  list($host, $port) = explode(':', $server, 2);
172  } else {
173  $host = $server;
174  $port = $defaultPort;
175  }
176  }
177  $this->memcache->addserver($host, $port);
178  }
179  if ($this->usedPeclModule === 'memcached') {
180  $this->memcache->setOption(\Memcached::OPT_COMPRESSION, $this->‪getCompression());
181  }
182  }
183 
190  public function ‪setPeclModule($peclModule)
191  {
192  if ($peclModule !== 'memcache' && $peclModule !== 'memcached') {
193  throw new Exception('PECL module must be either "memcache" or "memcached".', 1442239768);
194  }
195 
196  $this->usedPeclModule = $peclModule;
197  }
198 
204  public function ‪setCache(FrontendInterface ‪$cache)
205  {
206  parent::setCache(‪$cache);
207  $identifierHash = substr(md5(‪Environment::getProjectPath() . $this->context . $this->cacheIdentifier), 0, 12);
208  $this->identifierPrefix = 'TYPO3_' . $identifierHash . '_';
209  }
210 
221  public function set($entryIdentifier, $data, array $tags = [], $lifetime = null)
222  {
223  if (strlen($this->identifierPrefix . $entryIdentifier) > 250) {
224  throw new \InvalidArgumentException('Could not set value. Key more than 250 characters (' . $this->identifierPrefix . $entryIdentifier . ').', 1232969508);
225  }
226  if (!$this->cache instanceof FrontendInterface) {
227  throw new Exception('No cache frontend has been set yet via setCache().', 1207149215);
228  }
229  $tags[] = '%MEMCACHEBE%' . ‪$this->cacheIdentifier;
230  $expiration = $lifetime ?? ‪$this->defaultLifetime;
231 
232  // Memcached consideres values over 2592000 sec (30 days) as UNIX timestamp
233  // thus $expiration should be converted from lifetime to UNIX timestamp
234  if ($expiration > 2592000) {
235  $expiration += ‪$GLOBALS['EXEC_TIME'];
236  }
237  try {
238  if (is_string($data) && strlen($data) > self::MAX_BUCKET_SIZE) {
239  $data = str_split($data, 1024 * 1000);
240  $success = true;
241  $chunkNumber = 1;
242  foreach ($data as $chunk) {
243  $success = $success && $this->‪setInternal($entryIdentifier . '_chunk_' . $chunkNumber, $chunk, $expiration);
244  $chunkNumber++;
245  }
246  $success = $success && $this->‪setInternal($entryIdentifier, 'TYPO3*chunked:' . $chunkNumber, $expiration);
247  } else {
248  $success = $this->‪setInternal($entryIdentifier, $data, $expiration);
249  }
250  if ($success === true) {
251  $this->‪removeIdentifierFromAllTags($entryIdentifier);
252  $this->‪addIdentifierToTags($entryIdentifier, $tags);
253  } else {
254  throw new Exception('Could not set data to memcache server.', 1275830266);
255  }
256  } catch (\Exception $exception) {
257  $this->logger->alert('Memcache: could not set value.', ['exception' => $exception]);
258  }
259  }
260 
269  protected function ‪setInternal($entryIdentifier, $data, $expiration)
270  {
271  if ($this->usedPeclModule === 'memcache') {
272  return $this->memcache->set($this->identifierPrefix . $entryIdentifier, $data, $this->flags, $expiration);
273  }
274  return $this->memcache->set($this->identifierPrefix . $entryIdentifier, $data, $expiration);
275  }
276 
283  public function get($entryIdentifier)
284  {
285  $value = $this->memcache->get($this->identifierPrefix . $entryIdentifier);
286  if (is_string($value) && strpos($value, 'TYPO3*chunked:') === 0) {
287  list(, $chunkCount) = explode(':', $value);
288  $value = '';
289  for ($chunkNumber = 1; $chunkNumber < $chunkCount; $chunkNumber++) {
290  $value .= $this->memcache->get($this->identifierPrefix . $entryIdentifier . '_chunk_' . $chunkNumber);
291  }
292  }
293  return $value;
294  }
295 
302  public function ‪has($entryIdentifier)
303  {
304  if ($this->usedPeclModule === 'memcache') {
305  return $this->memcache->get($this->identifierPrefix . $entryIdentifier) !== false;
306  }
307 
308  // pecl-memcached supports storing literal FALSE
309  $this->memcache->get($this->identifierPrefix . $entryIdentifier);
310  return $this->memcache->getResultCode() !== \Memcached::RES_NOTFOUND;
311  }
312 
321  public function remove($entryIdentifier)
322  {
323  $this->‪removeIdentifierFromAllTags($entryIdentifier);
324  return $this->memcache->delete($this->identifierPrefix . $entryIdentifier, 0);
325  }
326 
334  public function ‪findIdentifiersByTag($tag)
335  {
336  $identifiers = $this->memcache->get($this->identifierPrefix . 'tag_' . $tag);
337  if ($identifiers !== false) {
338  return (array)$identifiers;
339  }
340  return [];
341  }
342 
348  public function ‪flush()
349  {
350  if (!$this->cache instanceof FrontendInterface) {
351  throw new Exception('No cache frontend has been set via setCache() yet.', 1204111376);
352  }
353  $this->‪flushByTag('%MEMCACHEBE%' . $this->cacheIdentifier);
354  }
355 
361  public function ‪flushByTag($tag)
362  {
363  $identifiers = $this->‪findIdentifiersByTag($tag);
364  foreach ($identifiers as $identifier) {
365  $this->remove($identifier);
366  }
367  }
368 
375  protected function ‪addIdentifierToTags($entryIdentifier, array $tags)
376  {
377  // Get identifier-to-tag index to look for updates
378  $existingTags = $this->‪findTagsByIdentifier($entryIdentifier);
379  $existingTagsUpdated = false;
380 
381  foreach ($tags as $tag) {
382  // Update tag-to-identifier index
383  $identifiers = $this->‪findIdentifiersByTag($tag);
384  if (!in_array($entryIdentifier, $identifiers, true)) {
385  $identifiers[] = $entryIdentifier;
386  $this->memcache->set($this->identifierPrefix . 'tag_' . $tag, $identifiers);
387  }
388  // Test if identifier-to-tag index needs update
389  if (!in_array($tag, $existingTags, true)) {
390  $existingTags[] = $tag;
391  $existingTagsUpdated = true;
392  }
393  }
394 
395  // Update identifier-to-tag index if needed
396  if ($existingTagsUpdated) {
397  $this->memcache->set($this->identifierPrefix . 'ident_' . $entryIdentifier, $existingTags);
398  }
399  }
400 
406  protected function ‪removeIdentifierFromAllTags($entryIdentifier)
407  {
408  // Get tags for this identifier
409  $tags = $this->‪findTagsByIdentifier($entryIdentifier);
410  // De-associate tags with this identifier
411  foreach ($tags as $tag) {
412  $identifiers = $this->‪findIdentifiersByTag($tag);
413  // Formally array_search() below should never return FALSE due to
414  // the behavior of findTagsByIdentifier(). But if reverse index is
415  // corrupted, we still can get 'FALSE' from array_search(). This is
416  // not a problem because we are removing this identifier from
417  // anywhere.
418  if (($key = array_search($entryIdentifier, $identifiers)) !== false) {
419  unset($identifiers[$key]);
420  if (!empty($identifiers)) {
421  $this->memcache->set($this->identifierPrefix . 'tag_' . $tag, $identifiers);
422  } else {
423  $this->memcache->delete($this->identifierPrefix . 'tag_' . $tag, 0);
424  }
425  }
426  }
427  // Clear reverse tag index for this identifier
428  $this->memcache->delete($this->identifierPrefix . 'ident_' . $entryIdentifier, 0);
429  }
430 
438  protected function ‪findTagsByIdentifier($identifier)
439  {
440  $tags = $this->memcache->get($this->identifierPrefix . 'ident_' . $identifier);
441  return $tags === false ? [] : (array)$tags;
442  }
443 
447  public function ‪collectGarbage()
448  {
449  }
450 }
‪TYPO3\CMS\Core\Cache\Backend\MemcachedBackend\flush
‪flush()
Definition: MemcachedBackend.php:343
‪TYPO3\CMS\Core\Cache\Backend\MemcachedBackend\$servers
‪array $servers
Definition: MemcachedBackend.php:69
‪TYPO3\CMS\Core\Cache\Backend\TransientBackendInterface
Definition: TransientBackendInterface.php:32
‪TYPO3\CMS\Core\Cache\Backend\MemcachedBackend\has
‪bool has($entryIdentifier)
Definition: MemcachedBackend.php:297
‪TYPO3\CMS\Core\Cache\Backend\MemcachedBackend\findTagsByIdentifier
‪array findTagsByIdentifier($identifier)
Definition: MemcachedBackend.php:433
‪TYPO3\CMS\Core\Cache\Backend\MemcachedBackend\$memcache
‪Memcache Memcached $memcache
Definition: MemcachedBackend.php:57
‪TYPO3\CMS\Core\Cache\Backend\MemcachedBackend\removeIdentifierFromAllTags
‪removeIdentifierFromAllTags($entryIdentifier)
Definition: MemcachedBackend.php:401
‪TYPO3\CMS\Core\Cache\Backend\MemcachedBackend\findIdentifiersByTag
‪array findIdentifiersByTag($tag)
Definition: MemcachedBackend.php:329
‪TYPO3\CMS\Core\Cache\Backend\TaggableBackendInterface
Definition: TaggableBackendInterface.php:21
‪TYPO3\CMS\Core\Cache\Backend\MemcachedBackend\setCompression
‪setCompression($useCompression)
Definition: MemcachedBackend.php:124
‪TYPO3\CMS\Core\Cache\Backend\AbstractBackend\$defaultLifetime
‪int $defaultLifetime
Definition: AbstractBackend.php:54
‪TYPO3\CMS\Core\Cache\Backend\MemcachedBackend\setPeclModule
‪setPeclModule($peclModule)
Definition: MemcachedBackend.php:185
‪TYPO3\CMS\Core\Cache\Backend\MemcachedBackend\MAX_BUCKET_SIZE
‪const MAX_BUCKET_SIZE
Definition: MemcachedBackend.php:51
‪TYPO3\CMS\Core\Cache\Backend\MemcachedBackend\$identifierPrefix
‪string $identifierPrefix
Definition: MemcachedBackend.php:82
‪TYPO3\CMS\Core\Core\Environment\getProjectPath
‪static string getProjectPath()
Definition: Environment.php:142
‪TYPO3\CMS\Core\Cache\Exception
Definition: DuplicateIdentifierException.php:2
‪TYPO3\CMS\Core\Cache\Backend\MemcachedBackend\getCompression
‪bool getCompression()
Definition: MemcachedBackend.php:139
‪TYPO3\CMS\Core\Cache\Backend\MemcachedBackend\setServers
‪setServers(array $servers)
Definition: MemcachedBackend.php:114
‪TYPO3\CMS\Core\Cache\Backend\MemcachedBackend\setCache
‪setCache(FrontendInterface $cache)
Definition: MemcachedBackend.php:199
‪TYPO3\CMS\Core\Cache\Backend\MemcachedBackend\$flags
‪int $flags
Definition: MemcachedBackend.php:76
‪TYPO3\CMS\Core\Cache\Backend\AbstractBackend\$cache
‪TYPO3 CMS Core Cache Frontend FrontendInterface $cache
Definition: AbstractBackend.php:35
‪TYPO3\CMS\Core\Cache\Backend\MemcachedBackend\collectGarbage
‪collectGarbage()
Definition: MemcachedBackend.php:442
‪TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
Definition: FrontendInterface.php:21
‪TYPO3\CMS\Core\Cache\Backend\MemcachedBackend\initializeObject
‪initializeObject()
Definition: MemcachedBackend.php:149
‪TYPO3\CMS\Core\Cache\Backend\MemcachedBackend\$usedPeclModule
‪string $usedPeclModule
Definition: MemcachedBackend.php:63
‪TYPO3\CMS\Core\Cache\Backend\AbstractBackend\$cacheIdentifier
‪string $cacheIdentifier
Definition: AbstractBackend.php:39
‪TYPO3\CMS\Core\Cache\Backend\AbstractBackend
Definition: AbstractBackend.php:26
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪TYPO3\CMS\Core\Core\Environment
Definition: Environment.php:39
‪TYPO3\CMS\Core\Cache\Backend
Definition: AbstractBackend.php:2
‪TYPO3\CMS\Core\Cache\Backend\AbstractBackend\$context
‪string $context
Definition: AbstractBackend.php:48
‪TYPO3\CMS\Core\Cache\Backend\MemcachedBackend
Definition: MemcachedBackend.php:45
‪TYPO3\CMS\Core\Cache\Backend\MemcachedBackend\setInternal
‪bool setInternal($entryIdentifier, $data, $expiration)
Definition: MemcachedBackend.php:264
‪TYPO3\CMS\Core\Cache\Backend\MemcachedBackend\addIdentifierToTags
‪addIdentifierToTags($entryIdentifier, array $tags)
Definition: MemcachedBackend.php:370
‪TYPO3\CMS\Core\Cache\Backend\MemcachedBackend\__construct
‪__construct($context, array $options=[])
Definition: MemcachedBackend.php:91
‪TYPO3\CMS\Core\Cache\Backend\MemcachedBackend\flushByTag
‪flushByTag($tag)
Definition: MemcachedBackend.php:356
‪TYPO3\CMS\Core\Cache\Exception
Definition: Exception.php:21