diff --git a/typo3/sysext/core/Classes/Cache/Backend/MemcachedBackend.php b/typo3/sysext/core/Classes/Cache/Backend/MemcachedBackend.php index 1041fd04ce8ff62f654ee6062189842ea54dbc70..7dbd47aa793e426698765bf369cc7e767027b59d 100644 --- a/typo3/sysext/core/Classes/Cache/Backend/MemcachedBackend.php +++ b/typo3/sysext/core/Classes/Cache/Backend/MemcachedBackend.php @@ -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); @@ -282,6 +266,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. * @@ -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 index 0000000000000000000000000000000000000000..b01d646e9253f5eaeb1e2a10ed9a83c6507941d3 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/master/Important-80246-MemcachedBackendMarkedTransient.rst @@ -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 index 0000000000000000000000000000000000000000..ef4823bcfb45fd62bccf9a11495cacbed7bdb59e --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Cache/Backend/MemcachedBackendTest.php @@ -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 index 2fa908a724c8be4509057f9bbb16a925028ab20d..0000000000000000000000000000000000000000 --- a/typo3/sysext/core/Tests/Unit/Cache/Backend/MemcachedBackendTest.php +++ /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; - } -}