From 1186d42f09bf2c721e6cac6132bd8419262e7134 Mon Sep 17 00:00:00 2001 From: Andreas Fernandez <a.fernandez@scripting-base.de> Date: Mon, 14 Sep 2015 16:44:35 +0200 Subject: [PATCH] [FEATURE] Support pecl-memcached in MemcachedBackend The MemcachedBackend now also supports the pecl-memcached module. The caching backend detects which modules are installed and uses "memcache" over "memcached" to avoid being a breaking change. If both modules are installed, an integrator can choose the PECL module by setting the new ``peclModule`` option. This feature was originally introduced with TYPO3 v8.0. Due to compatibility reasons with PHP 7 this feature gets backported as the memcache module is not available anymore. Resolves: #83569 Related: #69794 Releases: 7.6 Change-Id: Idd4e85cf8ec71d47217b63dfe55b1231107c7b82 Reviewed-on: https://review.typo3.org/55384 Reviewed-by: Stefan Neufeind <typo3.neufeind@speedpartner.de> Tested-by: Stefan Neufeind <typo3.neufeind@speedpartner.de> Tested-by: TYPO3com <no-reply@typo3.com> Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch> Tested-by: Christian Kuhn <lolli@schwarzbu.ch> --- .../Cache/Backend/MemcachedBackend.php | 133 ++++++++++++++---- ...upportPecl-memcachedInMemcachedBackend.rst | 39 +++++ .../Cache/Backend/MemcachedBackendTest.php | 4 +- 3 files changed, 143 insertions(+), 33 deletions(-) create mode 100644 typo3/sysext/core/Documentation/Changelog/7.6.x/Feature-69794-SupportPecl-memcachedInMemcachedBackend.rst diff --git a/typo3/sysext/core/Classes/Cache/Backend/MemcachedBackend.php b/typo3/sysext/core/Classes/Cache/Backend/MemcachedBackend.php index 2e1196082701..c35b1d7014f3 100644 --- a/typo3/sysext/core/Classes/Cache/Backend/MemcachedBackend.php +++ b/typo3/sysext/core/Classes/Cache/Backend/MemcachedBackend.php @@ -14,6 +14,10 @@ namespace TYPO3\CMS\Core\Cache\Backend; * The TYPO3 project - inspiring people to share! */ +use TYPO3\CMS\Core\Cache\Exception; +use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; +use TYPO3\CMS\Core\Utility\GeneralUtility; + /** * A caching backend which stores cache entries by using Memcached. * @@ -40,7 +44,7 @@ namespace TYPO3\CMS\Core\Cache\Backend; * This file is a backport from FLOW3 by Ingo Renner. * @api */ -class MemcachedBackend extends \TYPO3\CMS\Core\Cache\Backend\AbstractBackend implements \TYPO3\CMS\Core\Cache\Backend\TaggableBackendInterface +class MemcachedBackend extends AbstractBackend implements TaggableBackendInterface { /** * Max bucket size, (1024*1024)-42 bytes @@ -48,13 +52,21 @@ class MemcachedBackend extends \TYPO3\CMS\Core\Cache\Backend\AbstractBackend imp * @var int */ const MAX_BUCKET_SIZE = 1048534; + /** * Instance of the PHP Memcache class * - * @var \Memcache + * @var \Memcache|\Memcached */ protected $memcache; + /** + * Used PECL module for memcached + * + * @var string + */ + protected $usedPeclModule = ''; + /** * Array of Memcache server configurations * @@ -64,7 +76,7 @@ class MemcachedBackend extends \TYPO3\CMS\Core\Cache\Backend\AbstractBackend imp /** * Indicates whether the memcache uses compression or not (requires zlib), - * either 0 or MEMCACHE_COMPRESSED + * either 0 or \Memcached::OPT_COMPRESSION / MEMCACHE_COMPRESSED * * @var int */ @@ -82,14 +94,23 @@ class MemcachedBackend extends \TYPO3\CMS\Core\Cache\Backend\AbstractBackend imp * * @param string $context FLOW3's application context * @param array $options Configuration options - depends on the actual backend - * @throws \TYPO3\CMS\Core\Cache\Exception if memcache is not installed + * @throws Exception if memcache is not installed */ public function __construct($context, array $options = []) { - if (!extension_loaded('memcache')) { - throw new \TYPO3\CMS\Core\Cache\Exception('The PHP extension "memcache" must be installed and loaded in ' . 'order to use the Memcached backend.', 1213987706); + if (!extension_loaded('memcache') && !extension_loaded('memcached')) { + throw new Exception('The PHP extension "memcache" or "memcached" must be installed and loaded in ' . 'order to use the Memcached backend.', 1213987706); } + parent::__construct($context, $options); + + if ($this->usedPeclModule === '') { + if (extension_loaded('memcache')) { + $this->usedPeclModule = 'memcache'; + } elseif (extension_loaded('memcached')) { + $this->usedPeclModule = 'memcached'; + } + } } /** @@ -114,28 +135,41 @@ class MemcachedBackend extends \TYPO3\CMS\Core\Cache\Backend\AbstractBackend imp */ protected function setCompression($useCompression) { + $compressionFlag = $this->usedPeclModule === 'memcache' ? MEMCACHE_COMPRESSED : \Memcached::OPT_COMPRESSION; if ($useCompression === true) { - $this->flags ^= MEMCACHE_COMPRESSED; + $this->flags ^= $compressionFlag; } else { - $this->flags &= ~MEMCACHE_COMPRESSED; + $this->flags &= ~$compressionFlag; } } + /** + * Getter for compression flag + * + * @return bool + * @api + */ + protected function getCompression() + { + return $this->flags !== 0; + } + /** * Initializes the identifier prefix * * @return void - * @throws \TYPO3\CMS\Core\Cache\Exception + * @throws Exception */ public function initializeObject() { if (empty($this->servers)) { - throw new \TYPO3\CMS\Core\Cache\Exception('No servers were given to Memcache', 1213115903); + throw new Exception('No servers were given to Memcache', 1213115903); } - $this->memcache = new \Memcache(); - $defaultPort = ini_get('memcache.default_port'); + $memcachedPlugin = '\\' . ucfirst($this->usedPeclModule); + $this->memcache = new $memcachedPlugin; + $defaultPort = $this->usedPeclModule === 'memcache' ? ini_get('memcache.default_port') : 11211; foreach ($this->servers as $server) { - if (substr($server, 0, 7) == 'unix://') { + if (substr($server, 0, 7) === 'unix://') { $host = $server; $port = 0; } else { @@ -151,15 +185,33 @@ class MemcachedBackend extends \TYPO3\CMS\Core\Cache\Backend\AbstractBackend imp } $this->memcache->addserver($host, $port); } + if ($this->usedPeclModule === 'memcached') { + $this->memcache->setOption(\Memcached::OPT_COMPRESSION, $this->getCompression()); + } + } + + /** + * Sets the preferred PECL module + * + * @param string $peclModule + * @throws Exception + */ + public function setPeclModule($peclModule) + { + if ($peclModule !== 'memcache' && $peclModule !== 'memcached') { + throw new Exception('PECL module must be either "memcache" or "memcached".', 1442239768); + } + + $this->usedPeclModule = $peclModule; } /** * Initializes the identifier prefix when setting the cache. * - * @param \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cache The frontend for this backend + * @param FrontendInterface $cache The frontend for this backend * @return void */ - public function setCache(\TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cache) + public function setCache(FrontendInterface $cache) { parent::setCache($cache); $identifierHash = substr(md5(PATH_site . $this->context . $this->cacheIdentifier), 0, 12); @@ -174,9 +226,9 @@ class MemcachedBackend extends \TYPO3\CMS\Core\Cache\Backend\AbstractBackend imp * @param array $tags Tags to associate with this cache entry * @param int $lifetime Lifetime of this cache entry in seconds. If NULL is specified, the default lifetime is used. "0" means unlimited lifetime. * @return void - * @throws \TYPO3\CMS\Core\Cache\Exception if no cache frontend has been set. + * @throws Exception if no cache frontend has been set. * @throws \InvalidArgumentException if the identifier is not valid or the final memcached key is longer than 250 characters - * @throws \TYPO3\CMS\Core\Cache\Exception\InvalidDataException if $data is not a string + * @throws Exception\InvalidDataException if $data is not a string * @api */ public function set($entryIdentifier, $data, array $tags = [], $lifetime = null) @@ -184,14 +236,15 @@ class MemcachedBackend extends \TYPO3\CMS\Core\Cache\Backend\AbstractBackend imp if (strlen($this->identifierPrefix . $entryIdentifier) > 250) { throw new \InvalidArgumentException('Could not set value. Key more than 250 characters (' . $this->identifierPrefix . $entryIdentifier . ').', 1232969508); } - if (!$this->cache instanceof \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface) { - throw new \TYPO3\CMS\Core\Cache\Exception('No cache frontend has been set yet via setCache().', 1207149215); + if (!$this->cache instanceof FrontendInterface) { + throw new Exception('No cache frontend has been set yet via setCache().', 1207149215); } if (!is_string($data)) { - throw new \TYPO3\CMS\Core\Cache\Exception\InvalidDataException('The specified data is of type "' . gettype($data) . '" but a string is expected.', 1207149231); + throw new Exception\InvalidDataException('The specified data is of type "' . gettype($data) . '" but a string is expected.', 1207149231); } $tags[] = '%MEMCACHEBE%' . $this->cacheIdentifier; $expiration = $lifetime !== null ? $lifetime : $this->defaultLifetime; + $memcacheIsUsed = $this->usedPeclModule === 'memcache'; // Memcached consideres values over 2592000 sec (30 days) as UNIX timestamp // thus $expiration should be converted from lifetime to UNIX timestamp if ($expiration > 2592000) { @@ -203,21 +256,34 @@ class MemcachedBackend extends \TYPO3\CMS\Core\Cache\Backend\AbstractBackend imp $success = true; $chunkNumber = 1; foreach ($data as $chunk) { - $success = $success && $this->memcache->set($this->identifierPrefix . $entryIdentifier . '_chunk_' . $chunkNumber, $chunk, $this->flags, $expiration); + if ($memcacheIsUsed) { + $success = $success && $this->memcache->set($this->identifierPrefix . $entryIdentifier . '_chunk_' . $chunkNumber, $chunk, $this->flags, $expiration); + } else { + $success = $success && $this->memcache->set($this->identifierPrefix . $entryIdentifier . '_chunk_' . $chunkNumber, $chunk, $expiration); + } + $chunkNumber++; } - $success = $success && $this->memcache->set($this->identifierPrefix . $entryIdentifier, 'TYPO3*chunked:' . $chunkNumber, $this->flags, $expiration); + if ($memcacheIsUsed) { + $success = $success && $this->memcache->set($this->identifierPrefix . $entryIdentifier, 'TYPO3*chunked:' . $chunkNumber, $this->flags, $expiration); + } else { + $success = $success && $this->memcache->set($this->identifierPrefix . $entryIdentifier, 'TYPO3*chunked:' . $chunkNumber, $expiration); + } } else { - $success = $this->memcache->set($this->identifierPrefix . $entryIdentifier, $data, $this->flags, $expiration); + if ($memcacheIsUsed) { + $success = $this->memcache->set($this->identifierPrefix . $entryIdentifier, $data, $this->flags, $expiration); + } else { + $success = $this->memcache->set($this->identifierPrefix . $entryIdentifier, $data, $expiration); + } } if ($success === true) { $this->removeIdentifierFromAllTags($entryIdentifier); $this->addIdentifierToTags($entryIdentifier, $tags); } else { - throw new \TYPO3\CMS\Core\Cache\Exception('Could not set data to memcache server.', 1275830266); + throw new Exception('Could not set data to memcache server.', 1275830266); } } catch (\Exception $exception) { - \TYPO3\CMS\Core\Utility\GeneralUtility::sysLog('Memcache: could not set value. Reason: ' . $exception->getMessage(), 'core', \TYPO3\CMS\Core\Utility\GeneralUtility::SYSLOG_SEVERITY_WARNING); + GeneralUtility::sysLog('Memcache: could not set value. Reason: ' . $exception->getMessage(), 'core', GeneralUtility::SYSLOG_SEVERITY_WARNING); } } @@ -250,7 +316,13 @@ class MemcachedBackend extends \TYPO3\CMS\Core\Cache\Backend\AbstractBackend imp */ public function has($entryIdentifier) { - return $this->memcache->get($this->identifierPrefix . $entryIdentifier) !== false; + if ($this->usedPeclModule === 'memcache') { + return $this->memcache->get($this->identifierPrefix . $entryIdentifier) !== false; + } + + // pecl-memcached supports storing literal FALSE + $this->memcache->get($this->identifierPrefix . $entryIdentifier); + return $this->memcache->getResultCode() !== \Memcached::RES_NOTFOUND; } /** @@ -290,13 +362,13 @@ class MemcachedBackend extends \TYPO3\CMS\Core\Cache\Backend\AbstractBackend imp * Removes all cache entries of this cache. * * @return void - * @throws \TYPO3\CMS\Core\Cache\Exception + * @throws Exception * @api */ public function flush() { - if (!$this->cache instanceof \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface) { - throw new \TYPO3\CMS\Core\Cache\Exception('No cache frontend has been set via setCache() yet.', 1204111376); + if (!$this->cache instanceof FrontendInterface) { + throw new Exception('No cache frontend has been set via setCache() yet.', 1204111376); } $this->flushByTag('%MEMCACHEBE%' . $this->cacheIdentifier); } @@ -353,14 +425,13 @@ class MemcachedBackend extends \TYPO3\CMS\Core\Cache\Backend\AbstractBackend imp * Removes association of the identifier with the given tags * * @param string $entryIdentifier - * @param array Array of tags * @return void */ protected function removeIdentifierFromAllTags($entryIdentifier) { // Get tags for this identifier $tags = $this->findTagsByIdentifier($entryIdentifier); - // Deassociate tags with this identifier + // De-associate tags with this identifier foreach ($tags as $tag) { $identifiers = $this->findIdentifiersByTag($tag); // Formally array_search() below should never return FALSE due to diff --git a/typo3/sysext/core/Documentation/Changelog/7.6.x/Feature-69794-SupportPecl-memcachedInMemcachedBackend.rst b/typo3/sysext/core/Documentation/Changelog/7.6.x/Feature-69794-SupportPecl-memcachedInMemcachedBackend.rst new file mode 100644 index 000000000000..8bfb58fd3bd4 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/7.6.x/Feature-69794-SupportPecl-memcachedInMemcachedBackend.rst @@ -0,0 +1,39 @@ + +.. include:: ../../Includes.txt + +============================================================ +Feature: #69794 - Support pecl-memcached in MemcachedBackend +============================================================ + +See :issue:`69794` + +Description +=========== + +Support for the PECL module "memcached" has been added to the MemcachedBackend of the Caching Framework. + + +Impact +====== + +The MemcachedBackend checks if either "memcache" or "memcached" is installed. If both plugins are installed, the +MemcachedBackend uses "memcache" over "memcached" to avoid being a breaking change. An integrator may set the option +``peclModule` to use the preferred PECL module. + +Example code: + +.. code-block:: php + + $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['my_memcached'] = [ + 'frontend' => \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend::class, + 'backend' => \TYPO3\CMS\Core\Cache\Backend\MemcachedBackend::class, + 'options' => [ + 'peclModule' => 'memcached', + 'servers' => [ + 'localhost', + 'server2:port' + ] + ] + ]; + +.. index:: PHP-API, LocalConfiguration diff --git a/typo3/sysext/core/Tests/Unit/Cache/Backend/MemcachedBackendTest.php b/typo3/sysext/core/Tests/Unit/Cache/Backend/MemcachedBackendTest.php index 0cac73ac4e72..e74d167aca71 100644 --- a/typo3/sysext/core/Tests/Unit/Cache/Backend/MemcachedBackendTest.php +++ b/typo3/sysext/core/Tests/Unit/Cache/Backend/MemcachedBackendTest.php @@ -30,8 +30,8 @@ class MemcachedBackendTest extends \TYPO3\CMS\Core\Tests\UnitTestCase */ protected function setUp() { - if (!extension_loaded('memcache')) { - $this->markTestSkipped('memcache extension was not available'); + if (!extension_loaded('memcache') && !extension_loaded('memcached')) { + $this->markTestSkipped('Neither "memcache" nor "memcached" extension is available'); } try { if (!@fsockopen('localhost', 11211)) { -- GitLab