[TASK] Make MemcachedBackend a transient backend 15/52015/12
authorClaus Due <claus@namelesscoder.net>
Sun, 12 Mar 2017 18:01:29 +0000 (19:01 +0100)
committerChristian Kuhn <lolli@schwarzbu.ch>
Thu, 11 May 2017 19:43:56 +0000 (21:43 +0200)
Allows passing non-string values to the backend, which
is perfectly allowed for this type of backend. The change
means that VariableFrontends used with this backend will
store non-string values without serializing, thus optimising
performance and transparency.

Key changes:

* Exceptions are no longer thrown when a non-string is passed
* Chunk-splitting does not happen on non-strings; entries
   exceeding the maximum bucket size get logged and ignored.
* Serializer decision is delegated to memcached configuration.

Change-Id: Ie11736be621a2dd27bfde60b82cd5f6b3a04d981
Resolves: #80246
Releases: master
Reviewed-on: https://review.typo3.org/52015
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Claus Due <claus@phpmind.net>
Tested-by: Claus Due <claus@phpmind.net>
Reviewed-by: Elmar Hinz <t3elmar@gmail.com>
Tested-by: Elmar Hinz <t3elmar@gmail.com>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
typo3/sysext/core/Classes/Cache/Backend/MemcachedBackend.php
typo3/sysext/core/Documentation/Changelog/master/Important-80246-MemcachedBackendMarkedTransient.rst [new file with mode: 0644]
typo3/sysext/core/Tests/Functional/Cache/Backend/MemcachedBackendTest.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Cache/Backend/MemcachedBackendTest.php [deleted file]

index 1041fd0..7dbd47a 100644 (file)
@@ -44,7 +44,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
  * This file is a backport from FLOW3 by Ingo Renner.
  * @api
  */
-class MemcachedBackend extends AbstractBackend implements TaggableBackendInterface
+class MemcachedBackend extends AbstractBackend implements TaggableBackendInterface, TransientBackendInterface
 {
     /**
      * Max bucket size, (1024*1024)-42 bytes
@@ -234,42 +234,26 @@ class MemcachedBackend extends AbstractBackend implements TaggableBackendInterfa
         if (!$this->cache instanceof FrontendInterface) {
             throw new Exception('No cache frontend has been set yet via setCache().', 1207149215);
         }
-        if (!is_string($data)) {
-            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) {
             $expiration += $GLOBALS['EXEC_TIME'];
         }
         try {
-            if (strlen($data) > self::MAX_BUCKET_SIZE) {
+            if (is_string($data) && strlen($data) > self::MAX_BUCKET_SIZE) {
                 $data = str_split($data, 1024 * 1000);
                 $success = true;
                 $chunkNumber = 1;
                 foreach ($data as $chunk) {
-                    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);
-                    }
-
+                    $success = $success && $this->setInternal($entryIdentifier . '_chunk_' . $chunkNumber, $chunk, $expiration);
                     $chunkNumber++;
                 }
-                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);
-                }
+                $success = $success && $this->setInternal($entryIdentifier, 'TYPO3*chunked:' . $chunkNumber, $expiration);
             } else {
-                if ($memcacheIsUsed) {
-                    $success = $this->memcache->set($this->identifierPrefix . $entryIdentifier, $data, $this->flags, $expiration);
-                } else {
-                    $success = $this->memcache->set($this->identifierPrefix . $entryIdentifier, $data, $expiration);
-                }
+                $success = $this->setInternal($entryIdentifier, $data, $expiration);
             }
             if ($success === true) {
                 $this->removeIdentifierFromAllTags($entryIdentifier);
@@ -283,6 +267,23 @@ class MemcachedBackend extends AbstractBackend implements TaggableBackendInterfa
     }
 
     /**
+     * Stores the actual data inside memcache/memcached
+     *
+     * @param string $entryIdentifier
+     * @param mixed $data
+     * @param int $expiration
+     * @return bool
+     */
+    protected function setInternal($entryIdentifier, $data, $expiration)
+    {
+        if ($this->usedPeclModule === 'memcache') {
+            return $this->memcache->set($this->identifierPrefix . $entryIdentifier, $data, $this->flags, $expiration);
+        } else {
+            return $this->memcache->set($this->identifierPrefix . $entryIdentifier, $data, $expiration);
+        }
+    }
+
+    /**
      * Loads data from the cache.
      *
      * @param string $entryIdentifier An identifier which describes the cache entry to load
@@ -292,7 +293,7 @@ class MemcachedBackend extends AbstractBackend implements TaggableBackendInterfa
     public function get($entryIdentifier)
     {
         $value = $this->memcache->get($this->identifierPrefix . $entryIdentifier);
-        if (substr($value, 0, 14) === 'TYPO3*chunked:') {
+        if (is_string($value) && substr($value, 0, 14) === 'TYPO3*chunked:') {
             list(, $chunkCount) = explode(':', $value);
             $value = '';
             for ($chunkNumber = 1; $chunkNumber < $chunkCount; $chunkNumber++) {
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Important-80246-MemcachedBackendMarkedTransient.rst b/typo3/sysext/core/Documentation/Changelog/master/Important-80246-MemcachedBackendMarkedTransient.rst
new file mode 100644 (file)
index 0000000..b01d646
--- /dev/null
@@ -0,0 +1,26 @@
+.. include:: ../../Includes.txt
+
+=====================================================
+Important: #80246 - MemcachedBackend marked transient
+=====================================================
+
+See :issue:`80246`
+
+Description
+===========
+
+The Memcached cache backend is marked transient. This has the following effect:
+
+* The backend now supports non-string values (Memcached serializes and compresses data internally, configured in php.ini)
+  An Exception is no longer raised if a custom cache frontend attempts to store non-strings in a Memcached backend.
+* Unnecessary serialization and unserialization is prevented, slightly improving performance.
+
+There is a single side effect: when used with a VariableFrontend and attempting to store data whose serialized and
+compressed representation exceeds the Memcached limit (~1MB), the cache operation fails silently and logs a warning.
+The system keeps operating as normal and will log such failures every time it happens.
+
+The side effect only applies to VariableFrontend and only when passing non-string values. When you pass a string bigger
+than ~1MB the backend performs chunk-split exactly as before, regardless if string was passed through a VariableFrontend.
+
+
+.. index:: PHP-API
diff --git a/typo3/sysext/core/Tests/Functional/Cache/Backend/MemcachedBackendTest.php b/typo3/sysext/core/Tests/Functional/Cache/Backend/MemcachedBackendTest.php
new file mode 100644 (file)
index 0000000..ef4823b
--- /dev/null
@@ -0,0 +1,355 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Functional\Cache\Backend;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Cache\Backend\MemcachedBackend;
+use TYPO3\CMS\Core\Cache\Exception;
+use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
+use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
+
+/**
+ * Test case
+ */
+class MemcachedBackendTest extends FunctionalTestCase
+{
+
+    /**
+     * Sets up this test case
+     */
+    protected function setUp()
+    {
+        parent::setUp();
+        if (!extension_loaded('memcache') && !extension_loaded('memcached')) {
+            $this->markTestSkipped('Neither "memcache" nor "memcached" extension was available');
+        }
+        try {
+            if (!@fsockopen('localhost', 11211)) {
+                $this->markTestSkipped('memcached not reachable');
+            }
+        } catch (\Exception $e) {
+            $this->markTestSkipped('memcached not reachable');
+        }
+    }
+
+    /**
+     * @test
+     */
+    public function setThrowsExceptionIfNoFrontEndHasBeenSet()
+    {
+        $subject = new MemcachedBackend('Testing', [ 'servers' => ['localhost:11211'] ]);
+        $subject->initializeObject();
+
+        $this->expectException(Exception::class);
+        $this->expectExceptionCode(1207149215);
+
+        $subject->set($this->getUniqueId('MyIdentifier'), 'some data');
+    }
+
+    /**
+     * @test
+     */
+    public function initializeObjectThrowsExceptionIfNoMemcacheServerIsConfigured()
+    {
+        $subject = new MemcachedBackend('Testing');
+        $this->expectException(Exception::class);
+        $this->expectExceptionCode(1213115903);
+        $subject->initializeObject();
+    }
+
+    /**
+     * @test
+     */
+    public function itIsPossibleToSetAndCheckExistenceInCache()
+    {
+        $frontendProphecy = $this->prophesize(FrontendInterface::class);
+        $frontendProphecy->getIdentifier()->willReturn('cache_pages');
+
+        $subject = new MemcachedBackend('Testing', [ 'servers' => ['localhost:11211'] ]);
+        $subject->initializeObject();
+        $subject->setCache($frontendProphecy->reveal());
+
+        $identifier = $this->getUniqueId('MyIdentifier');
+        $subject->set($identifier, 'Some data');
+        $this->assertTrue($subject->has($identifier));
+    }
+
+    /**
+     * @test
+     */
+    public function itIsPossibleToSetAndGetEntry()
+    {
+        $frontendProphecy = $this->prophesize(FrontendInterface::class);
+        $frontendProphecy->getIdentifier()->willReturn('cache_pages');
+
+        $subject = new MemcachedBackend('Testing', [ 'servers' => ['localhost:11211'] ]);
+        $subject->initializeObject();
+        $subject->setCache($frontendProphecy->reveal());
+
+        $data = 'Some data';
+        $identifier = $this->getUniqueId('MyIdentifier');
+        $subject->set($identifier, $data);
+        $this->assertEquals($data, $subject->get($identifier));
+    }
+
+    /**
+     * @test
+     */
+    public function getReturnsPreviouslySetDataWithVariousTypes()
+    {
+        $frontendProphecy = $this->prophesize(FrontendInterface::class);
+        $frontendProphecy->getIdentifier()->willReturn('cache_pages');
+
+        $subject = new MemcachedBackend('Testing', [ 'servers' => ['localhost:11211'] ]);
+        $subject->initializeObject();
+        $subject->setCache($frontendProphecy->reveal());
+
+        $data = [
+            'string' => 'Serialize a string',
+            'integer' => 0,
+            'anotherIntegerValue' => 123456,
+            'float' => 12.34,
+            'bool' => true,
+            'array' => [
+                0 => 'test',
+                1 => 'another test',
+            ],
+        ];
+
+        $subject->set('myIdentifier', $data);
+        $this->assertSame($data, $subject->get('myIdentifier'));
+    }
+
+    /**
+     * Check if we can store ~5 MB of data.
+     *
+     * @test
+     */
+    public function largeDataIsStored()
+    {
+        $frontendProphecy = $this->prophesize(FrontendInterface::class);
+        $frontendProphecy->getIdentifier()->willReturn('cache_pages');
+
+        $subject = new MemcachedBackend('Testing', [ 'servers' => ['localhost:11211'] ]);
+        $subject->initializeObject();
+        $subject->setCache($frontendProphecy->reveal());
+
+        $data = str_repeat('abcde', 1024 * 1024);
+        $subject->set('tooLargeData', $data);
+        $this->assertTrue($subject->has('tooLargeData'));
+        $this->assertEquals($subject->get('tooLargeData'), $data);
+    }
+
+    /**
+     * @test
+     */
+    public function itIsPossibleToRemoveEntryFromCache()
+    {
+        $frontendProphecy = $this->prophesize(FrontendInterface::class);
+        $frontendProphecy->getIdentifier()->willReturn('cache_pages');
+
+        $subject = new MemcachedBackend('Testing', [ 'servers' => ['localhost:11211'] ]);
+        $subject->initializeObject();
+        $subject->setCache($frontendProphecy->reveal());
+
+        $data = 'Some data';
+        $identifier = $this->getUniqueId('MyIdentifier');
+        $subject->set($identifier, $data);
+        $subject->remove($identifier);
+        $this->assertFalse($subject->has($identifier));
+    }
+
+    /**
+     * @test
+     */
+    public function itIsPossibleToOverwriteAnEntryInTheCache()
+    {
+        $frontendProphecy = $this->prophesize(FrontendInterface::class);
+        $frontendProphecy->getIdentifier()->willReturn('cache_pages');
+
+        $subject = new MemcachedBackend('Testing', [ 'servers' => ['localhost:11211'] ]);
+        $subject->initializeObject();
+        $subject->setCache($frontendProphecy->reveal());
+
+        $data = 'Some data';
+        $identifier = $this->getUniqueId('MyIdentifier');
+        $subject->set($identifier, $data);
+        $otherData = 'some other data';
+        $subject->set($identifier, $otherData);
+        $this->assertEquals($otherData, $subject->get($identifier));
+    }
+
+    /**
+     * @test
+     */
+    public function findIdentifiersByTagFindsCacheEntriesWithSpecifiedTag()
+    {
+        $frontendProphecy = $this->prophesize(FrontendInterface::class);
+        $frontendProphecy->getIdentifier()->willReturn('cache_pages');
+
+        $subject = new MemcachedBackend('Testing', [ 'servers' => ['localhost:11211'] ]);
+        $subject->initializeObject();
+        $subject->setCache($frontendProphecy->reveal());
+
+        $data = 'Some data';
+        $identifier = $this->getUniqueId('MyIdentifier');
+        $subject->set($identifier, $data, ['UnitTestTag%tag1', 'UnitTestTag%tag2']);
+        $retrieved = $subject->findIdentifiersByTag('UnitTestTag%tag1');
+        $this->assertEquals($identifier, $retrieved[0]);
+        $retrieved = $subject->findIdentifiersByTag('UnitTestTag%tag2');
+        $this->assertEquals($identifier, $retrieved[0]);
+    }
+
+    /**
+     * @test
+     */
+    public function setRemovesTagsFromPreviousSet()
+    {
+        $frontendProphecy = $this->prophesize(FrontendInterface::class);
+        $frontendProphecy->getIdentifier()->willReturn('cache_pages');
+
+        $subject = new MemcachedBackend('Testing', [ 'servers' => ['localhost:11211'] ]);
+        $subject->initializeObject();
+        $subject->setCache($frontendProphecy->reveal());
+
+        $data = 'Some data';
+        $identifier = $this->getUniqueId('MyIdentifier');
+        $subject->set($identifier, $data, ['UnitTestTag%tag1', 'UnitTestTag%tag2']);
+        $subject->set($identifier, $data, ['UnitTestTag%tag3']);
+        $this->assertEquals([], $subject->findIdentifiersByTag('UnitTestTag%tagX'));
+    }
+
+    /**
+     * @test
+     */
+    public function hasReturnsFalseIfTheEntryDoesntExist()
+    {
+        $frontendProphecy = $this->prophesize(FrontendInterface::class);
+        $frontendProphecy->getIdentifier()->willReturn('cache_pages');
+
+        $subject = new MemcachedBackend('Testing', [ 'servers' => ['localhost:11211'] ]);
+        $subject->initializeObject();
+        $subject->setCache($frontendProphecy->reveal());
+
+        $identifier = $this->getUniqueId('NonExistingIdentifier');
+        $this->assertFalse($subject->has($identifier));
+    }
+
+    /**
+     * @test
+     */
+    public function removeReturnsFalseIfTheEntryDoesntExist()
+    {
+        $frontendProphecy = $this->prophesize(FrontendInterface::class);
+        $frontendProphecy->getIdentifier()->willReturn('cache_pages');
+
+        $subject = new MemcachedBackend('Testing', [ 'servers' => ['localhost:11211'] ]);
+        $subject->initializeObject();
+        $subject->setCache($frontendProphecy->reveal());
+
+        $identifier = $this->getUniqueId('NonExistingIdentifier');
+        $this->assertFalse($subject->remove($identifier));
+    }
+
+    /**
+     * @test
+     */
+    public function flushByTagRemovesCacheEntriesWithSpecifiedTag()
+    {
+        $frontendProphecy = $this->prophesize(FrontendInterface::class);
+        $frontendProphecy->getIdentifier()->willReturn('cache_pages');
+
+        $subject = new MemcachedBackend('Testing', [ 'servers' => ['localhost:11211'] ]);
+        $subject->initializeObject();
+        $subject->setCache($frontendProphecy->reveal());
+
+        $data = 'some data' . microtime();
+        $subject->set('BackendMemcacheTest1', $data, ['UnitTestTag%test', 'UnitTestTag%boring']);
+        $subject->set('BackendMemcacheTest2', $data, ['UnitTestTag%test', 'UnitTestTag%special']);
+        $subject->set('BackendMemcacheTest3', $data, ['UnitTestTag%test']);
+        $subject->flushByTag('UnitTestTag%special');
+        $this->assertTrue($subject->has('BackendMemcacheTest1'));
+        $this->assertFalse($subject->has('BackendMemcacheTest2'));
+        $this->assertTrue($subject->has('BackendMemcacheTest3'));
+    }
+
+    /**
+     * @test
+     */
+    public function flushByTagsRemovesCacheEntriesWithSpecifiedTags()
+    {
+        $frontendProphecy = $this->prophesize(FrontendInterface::class);
+        $frontendProphecy->getIdentifier()->willReturn('cache_pages');
+
+        $subject = new MemcachedBackend('Testing', [ 'servers' => ['localhost:11211'] ]);
+        $subject->initializeObject();
+        $subject->setCache($frontendProphecy->reveal());
+
+        $data = 'some data' . microtime();
+        $subject->set('BackendMemcacheTest1', $data, ['UnitTestTag%test', 'UnitTestTag%boring']);
+        $subject->set('BackendMemcacheTest2', $data, ['UnitTestTag%test', 'UnitTestTag%special']);
+        $subject->set('BackendMemcacheTest3', $data, ['UnitTestTag%test']);
+        $subject->flushByTags(['UnitTestTag%special', 'UnitTestTag%boring']);
+        $this->assertFalse($subject->has('BackendMemcacheTest1'));
+        $this->assertFalse($subject->has('BackendMemcacheTest2'));
+        $this->assertTrue($subject->has('BackendMemcacheTest3'));
+    }
+
+    /**
+     * @test
+     */
+    public function flushRemovesAllCacheEntries()
+    {
+        $frontendProphecy = $this->prophesize(FrontendInterface::class);
+        $frontendProphecy->getIdentifier()->willReturn('cache_pages');
+
+        $subject = new MemcachedBackend('Testing', [ 'servers' => ['localhost:11211'] ]);
+        $subject->initializeObject();
+        $subject->setCache($frontendProphecy->reveal());
+
+        $data = 'some data' . microtime();
+        $subject->set('BackendMemcacheTest1', $data);
+        $subject->set('BackendMemcacheTest2', $data);
+        $subject->set('BackendMemcacheTest3', $data);
+        $subject->flush();
+        $this->assertFalse($subject->has('BackendMemcacheTest1'));
+        $this->assertFalse($subject->has('BackendMemcacheTest2'));
+        $this->assertFalse($subject->has('BackendMemcacheTest3'));
+    }
+
+    /**
+     * @test
+     */
+    public function flushRemovesOnlyOwnEntries()
+    {
+        $thisFrontendProphecy = $this->prophesize(FrontendInterface::class);
+        $thisFrontendProphecy->getIdentifier()->willReturn('thisCache');
+        $thisBackend = new MemcachedBackend('Testing', [ 'servers' => ['localhost:11211'] ]);
+        $thisBackend->initializeObject();
+        $thisBackend->setCache($thisFrontendProphecy->reveal());
+
+        $thatFrontendProphecy = $this->prophesize(FrontendInterface::class);
+        $thatFrontendProphecy->getIdentifier()->willReturn('thatCache');
+        $thatBackend = new MemcachedBackend('Testing', [ 'servers' => ['localhost:11211'] ]);
+        $thatBackend->initializeObject();
+        $thatBackend->setCache($thatFrontendProphecy->reveal());
+
+        $thisBackend->set('thisEntry', 'Hello');
+        $thatBackend->set('thatEntry', 'World!');
+        $thatBackend->flush();
+
+        $this->assertEquals('Hello', $thisBackend->get('thisEntry'));
+        $this->assertFalse($thatBackend->has('thatEntry'));
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Cache/Backend/MemcachedBackendTest.php b/typo3/sysext/core/Tests/Unit/Cache/Backend/MemcachedBackendTest.php
deleted file mode 100644 (file)
index 2fa908a..0000000
+++ /dev/null
@@ -1,313 +0,0 @@
-<?php
-namespace TYPO3\CMS\Core\Tests\Unit\Cache\Backend;
-
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-
-use TYPO3\CMS\Core\Cache\Backend\MemcachedBackend;
-
-/**
- * Testcase for the cache to memcached backend
- *
- * This file is a backport from FLOW3
- */
-class MemcachedBackendTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
-{
-    /**
-     * Sets up this testcase
-     */
-    protected function setUp()
-    {
-        if (!extension_loaded('memcache') && !extension_loaded('memcached')) {
-            $this->markTestSkipped('Neither "memcache" nor "memcached" extension was available');
-        }
-        try {
-            if (!@fsockopen('localhost', 11211)) {
-                $this->markTestSkipped('memcached not reachable');
-            }
-        } catch (\Exception $e) {
-            $this->markTestSkipped('memcached not reachable');
-        }
-    }
-
-    /**
-     * @test
-     */
-    public function setThrowsExceptionIfNoFrontEndHasBeenSet()
-    {
-        $this->expectException(\TYPO3\CMS\Core\Cache\Exception::class);
-        $this->expectExceptionCode(1207149215);
-
-        $backendOptions = ['servers' => ['localhost:11211']];
-        $backend = new MemcachedBackend('Testing', $backendOptions);
-        $backend->initializeObject();
-        $data = 'Some data';
-        $identifier = $this->getUniqueId('MyIdentifier');
-        $backend->set($identifier, $data);
-    }
-
-    /**
-     * @test
-     */
-    public function initializeObjectThrowsExceptionIfNoMemcacheServerIsConfigured()
-    {
-        $this->expectException(\TYPO3\CMS\Core\Cache\Exception::class);
-        $this->expectExceptionCode(1213115903);
-
-        $backend = new MemcachedBackend('Testing');
-        $backend->initializeObject();
-    }
-
-    /**
-     * @test
-     */
-    public function itIsPossibleToSetAndCheckExistenceInCache()
-    {
-        $backend = $this->setUpBackend();
-        $data = 'Some data';
-        $identifier = $this->getUniqueId('MyIdentifier');
-        $backend->set($identifier, $data);
-        $inCache = $backend->has($identifier);
-        $this->assertTrue($inCache, 'Memcache failed to set and check entry');
-    }
-
-    /**
-     * @test
-     */
-    public function itIsPossibleToSetAndGetEntry()
-    {
-        $backend = $this->setUpBackend();
-        $data = 'Some data';
-        $identifier = $this->getUniqueId('MyIdentifier');
-        $backend->set($identifier, $data);
-        $fetchedData = $backend->get($identifier);
-        $this->assertEquals($data, $fetchedData, 'Memcache failed to set and retrieve data');
-    }
-
-    /**
-     * @test
-     */
-    public function itIsPossibleToRemoveEntryFromCache()
-    {
-        $backend = $this->setUpBackend();
-        $data = 'Some data';
-        $identifier = $this->getUniqueId('MyIdentifier');
-        $backend->set($identifier, $data);
-        $backend->remove($identifier);
-        $inCache = $backend->has($identifier);
-        $this->assertFalse($inCache, 'Failed to set and remove data from Memcache');
-    }
-
-    /**
-     * @test
-     */
-    public function itIsPossibleToOverwriteAnEntryInTheCache()
-    {
-        $backend = $this->setUpBackend();
-        $data = 'Some data';
-        $identifier = $this->getUniqueId('MyIdentifier');
-        $backend->set($identifier, $data);
-        $otherData = 'some other data';
-        $backend->set($identifier, $otherData);
-        $fetchedData = $backend->get($identifier);
-        $this->assertEquals($otherData, $fetchedData, 'Memcache failed to overwrite and retrieve data');
-    }
-
-    /**
-     * @test
-     */
-    public function findIdentifiersByTagFindsCacheEntriesWithSpecifiedTag()
-    {
-        $backend = $this->setUpBackend();
-        $data = 'Some data';
-        $identifier = $this->getUniqueId('MyIdentifier');
-        $backend->set($identifier, $data, ['UnitTestTag%tag1', 'UnitTestTag%tag2']);
-        $retrieved = $backend->findIdentifiersByTag('UnitTestTag%tag1');
-        $this->assertEquals($identifier, $retrieved[0], 'Could not retrieve expected entry by tag.');
-        $retrieved = $backend->findIdentifiersByTag('UnitTestTag%tag2');
-        $this->assertEquals($identifier, $retrieved[0], 'Could not retrieve expected entry by tag.');
-    }
-
-    /**
-     * @test
-     */
-    public function setRemovesTagsFromPreviousSet()
-    {
-        $backend = $this->setUpBackend();
-        $data = 'Some data';
-        $identifier = $this->getUniqueId('MyIdentifier');
-        $backend->set($identifier, $data, ['UnitTestTag%tag1', 'UnitTestTag%tag2']);
-        $backend->set($identifier, $data, ['UnitTestTag%tag3']);
-        $retrieved = $backend->findIdentifiersByTag('UnitTestTag%tagX');
-        $this->assertEquals([], $retrieved, 'Found entry which should no longer exist.');
-    }
-
-    /**
-     * @test
-     */
-    public function hasReturnsFalseIfTheEntryDoesntExist()
-    {
-        $backend = $this->setUpBackend();
-        $identifier = $this->getUniqueId('NonExistingIdentifier');
-        $inCache = $backend->has($identifier);
-        $this->assertFalse($inCache, '"has" did not return FALSE when checking on non existing identifier');
-    }
-
-    /**
-     * @test
-     */
-    public function removeReturnsFalseIfTheEntryDoesntExist()
-    {
-        $backend = $this->setUpBackend();
-        $identifier = $this->getUniqueId('NonExistingIdentifier');
-        $inCache = $backend->remove($identifier);
-        $this->assertFalse($inCache, '"remove" did not return FALSE when checking on non existing identifier');
-    }
-
-    /**
-     * @test
-     */
-    public function flushByTagRemovesCacheEntriesWithSpecifiedTag()
-    {
-        $backend = $this->setUpBackend();
-        $data = 'some data' . microtime();
-        $backend->set('BackendMemcacheTest1', $data, ['UnitTestTag%test', 'UnitTestTag%boring']);
-        $backend->set('BackendMemcacheTest2', $data, ['UnitTestTag%test', 'UnitTestTag%special']);
-        $backend->set('BackendMemcacheTest3', $data, ['UnitTestTag%test']);
-        $backend->flushByTag('UnitTestTag%special');
-        $this->assertTrue($backend->has('BackendMemcacheTest1'), 'BackendMemcacheTest1');
-        $this->assertFalse($backend->has('BackendMemcacheTest2'), 'BackendMemcacheTest2');
-        $this->assertTrue($backend->has('BackendMemcacheTest3'), 'BackendMemcacheTest3');
-    }
-
-    /**
-     * @test
-     */
-    public function flushByTagsRemovesCacheEntriesWithSpecifiedTags()
-    {
-        $backend = $this->setUpBackend();
-        $data = 'some data' . microtime();
-        $backend->set('BackendMemcacheTest1', $data, ['UnitTestTag%test', 'UnitTestTag%boring']);
-        $backend->set('BackendMemcacheTest2', $data, ['UnitTestTag%test', 'UnitTestTag%special']);
-        $backend->set('BackendMemcacheTest3', $data, ['UnitTestTag%test']);
-        $backend->flushByTags(['UnitTestTag%special', 'UnitTestTag%boring']);
-        $this->assertFalse($backend->has('BackendMemcacheTest1'), 'BackendMemcacheTest1');
-        $this->assertFalse($backend->has('BackendMemcacheTest2'), 'BackendMemcacheTest2');
-        $this->assertTrue($backend->has('BackendMemcacheTest3'), 'BackendMemcacheTest3');
-    }
-
-    /**
-     * @test
-     */
-    public function flushRemovesAllCacheEntries()
-    {
-        $backend = $this->setUpBackend();
-        $data = 'some data' . microtime();
-        $backend->set('BackendMemcacheTest1', $data);
-        $backend->set('BackendMemcacheTest2', $data);
-        $backend->set('BackendMemcacheTest3', $data);
-        $backend->flush();
-        $this->assertFalse($backend->has('BackendMemcacheTest1'), 'BackendMemcacheTest1');
-        $this->assertFalse($backend->has('BackendMemcacheTest2'), 'BackendMemcacheTest2');
-        $this->assertFalse($backend->has('BackendMemcacheTest3'), 'BackendMemcacheTest3');
-    }
-
-    /**
-     * @test
-     */
-    public function flushRemovesOnlyOwnEntries()
-    {
-        $backendOptions = ['servers' => ['localhost:11211']];
-        /** @var \PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $thisCache */
-        $thisCache = $this->createMock(\TYPO3\CMS\Core\Cache\Frontend\AbstractFrontend::class);
-        $thisCache->expects($this->any())->method('getIdentifier')->will($this->returnValue('thisCache'));
-        $thisBackend = new MemcachedBackend('Testing', $backendOptions);
-        $thisBackend->setCache($thisCache);
-        $thisBackend->initializeObject();
-
-        /** @var \PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $thatCache */
-        $thatCache = $this->createMock(\TYPO3\CMS\Core\Cache\Frontend\AbstractFrontend::class);
-        $thatCache->expects($this->any())->method('getIdentifier')->will($this->returnValue('thatCache'));
-        $thatBackend = new MemcachedBackend('Testing', $backendOptions);
-        $thatBackend->setCache($thatCache);
-        $thatBackend->initializeObject();
-        $thisBackend->set('thisEntry', 'Hello');
-        $thatBackend->set('thatEntry', 'World!');
-        $thatBackend->flush();
-        $this->assertEquals('Hello', $thisBackend->get('thisEntry'));
-        $this->assertFalse($thatBackend->has('thatEntry'));
-    }
-
-    /**
-     * Check if we can store ~5 MB of data, this gives some headroom for the
-     * reflection data.
-     *
-     * @test
-     */
-    public function largeDataIsStored()
-    {
-        $backend = $this->setUpBackend();
-        $data = str_repeat('abcde', 1024 * 1024);
-        $backend->set('tooLargeData', $data);
-        $this->assertTrue($backend->has('tooLargeData'));
-        $this->assertEquals($backend->get('tooLargeData'), $data);
-    }
-
-    /**
-     * @test
-     */
-    public function setTagsOnlyOnceToIdentifier()
-    {
-        $backendOptions = ['servers' => ['localhost:11211']];
-        $identifier = $this->getUniqueId('MyIdentifier');
-        $tags = ['UnitTestTag%test', 'UnitTestTag%boring'];
-
-        $backend = $this->setUpBackend($backendOptions, true);
-        $backend->_call('addIdentifierToTags', $identifier, $tags);
-        $this->assertSame(
-            $tags,
-            $backend->_call('findTagsByIdentifier', $identifier)
-        );
-
-        $backend->_call('addIdentifierToTags', $identifier, $tags);
-        $this->assertSame(
-            $tags,
-            $backend->_call('findTagsByIdentifier', $identifier)
-        );
-    }
-
-    /**
-     * Sets up the memcached backend used for testing
-     *
-     * @param array $backendOptions Options for the memcache backend
-     * @param bool $accessible TRUE if backend should be encapsulated in accessible proxy otherwise FALSE.
-     * @return \TYPO3\TestingFramework\Core\AccessibleObjectInterface|MemcachedBackend
-     */
-    protected function setUpBackend(array $backendOptions = [], $accessible = false)
-    {
-        /** @var \PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cache */
-        $cache = $this->createMock(\TYPO3\CMS\Core\Cache\Frontend\FrontendInterface::class);
-        if ($backendOptions == []) {
-            $backendOptions = ['servers' => ['localhost:11211']];
-        }
-        if ($accessible) {
-            $accessibleClassName = $this->buildAccessibleProxy(MemcachedBackend::class);
-            $backend = new $accessibleClassName('Testing', $backendOptions);
-        } else {
-            $backend = new MemcachedBackend('Testing', $backendOptions);
-        }
-        $backend->setCache($cache);
-        $backend->initializeObject();
-        return $backend;
-    }
-}