Commit ea710274 authored by Christian Kuhn's avatar Christian Kuhn Committed by Benni Mack
Browse files

[!!!][TASK] Remove WincacheBackend cache backend

Resolves: #96115
Related: #94665
Releases: master
Change-Id: Id7702f2328a0ca848656caae8350d6facc65068a
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/72336

Tested-by: Stefan Bürk's avatarStefan Bürk <stefan@buerk.tech>
Tested-by: Wouter Wolters's avatarWouter Wolters <typo3@wouterwolters.nl>
Tested-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Reviewed-by: Stefan Bürk's avatarStefan Bürk <stefan@buerk.tech>
Reviewed-by: Wouter Wolters's avatarWouter Wolters <typo3@wouterwolters.nl>
Reviewed-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
parent 932e35ca
<?php
/*
* 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!
*/
namespace TYPO3\CMS\Core\Cache\Backend;
use TYPO3\CMS\Core\Cache\Exception;
use TYPO3\CMS\Core\Cache\Exception\InvalidDataException;
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
/**
* A caching backend which stores cache entries by using wincache.
*
* This backend uses the following types of keys:
* - tag_xxx
* xxx is tag name, value is array of associated identifiers identifier. This
* is "forward" tag index. It is mainly used for obtaining content by tag
* (get identifier by tag -> get content by identifier)
* - ident_xxx
* xxx is identifier, value is array of associated tags. This is "reverse" tag
* index. It provides quick access for all tags associated with this identifier
* and used when removing the identifier
*
* Each key is prepended with a prefix. By default prefix consists from two parts
* separated by underscore character and ends in yet another underscore character:
* - "TYPO3"
* - MD5 of script path and filename and SAPI name
* This prefix makes sure that keys from the different installations do not
* conflict.
*
* @deprecated since v11, will be removed in v12. A substitution with similar characteristics is the ApcuBackend.
*/
class WincacheBackend extends AbstractBackend implements TaggableBackendInterface
{
/**
* A prefix to separate stored data from other data possible stored in the wincache
*
* @var string
*/
protected $identifierPrefix;
/**
* Constructs this backend
*
* @param string $context Unused, for backward compatibility only
* @param array $options Configuration options
* @throws Exception If wincache PHP extension is not loaded
*/
public function __construct($context, array $options = [])
{
if (!extension_loaded('wincache')) {
throw new Exception('The PHP extension "wincache" must be installed and loaded in order to use the wincache backend.', 1343331520);
}
trigger_error(__CLASS__ . ' will be removed in TYPO3 v12, use ApcuBackend as substitution with similar characteristics.', E_USER_DEPRECATED);
parent::__construct($context, $options);
}
/**
* Saves data in the cache
*
* @param string $entryIdentifier An identifier for this specific cache entry
* @param string $data The data to be stored
* @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.
* @throws Exception if no cache frontend has been set
* @throws \InvalidArgumentException if the identifier is not valid
* @throws InvalidDataException if $data is not a string
*/
public function set($entryIdentifier, $data, array $tags = [], $lifetime = null)
{
if (!$this->cache instanceof FrontendInterface) {
throw new Exception('No cache frontend has been set yet via setCache().', 1343331521);
}
if (!is_string($data)) {
throw new InvalidDataException('The specified data is of type "' . gettype($data) . '" but a string is expected.', 1343331522);
}
$tags[] = '%WCBE%' . $this->cache->getIdentifier();
$expiration = $lifetime ?? $this->defaultLifetime;
$success = wincache_ucache_set($this->identifierPrefix . $entryIdentifier, $data, $expiration);
if ($success === true) {
$this->removeIdentifierFromAllTags($entryIdentifier);
$this->addIdentifierToTags($entryIdentifier, $tags);
} else {
throw new Exception('Could not set value.', 1343331523);
}
}
/**
* Loads data from the cache
*
* @param string $entryIdentifier An identifier which describes the cache entry to load
* @return mixed The cache entry's content as a string or FALSE if the cache entry could not be loaded
*/
public function get($entryIdentifier)
{
$success = false;
$value = wincache_ucache_get($this->identifierPrefix . $entryIdentifier, $success);
return $success ? $value : $success;
}
/**
* Checks if a cache entry with the specified identifier exists
*
* @param string $entryIdentifier An identifier specifying the cache entry
* @return bool TRUE if such an entry exists, FALSE if not
*/
public function has($entryIdentifier)
{
return wincache_ucache_exists($this->identifierPrefix . $entryIdentifier);
}
/**
* Removes all cache entries matching the specified identifier.
* Usually this only affects one entry but if - for what reason ever -
* old entries for the identifier still exist, they are removed as well.
*
* @param string $entryIdentifier Specifies the cache entry to remove
* @return bool TRUE if (at least) an entry could be removed or FALSE if no entry was found
*/
public function remove($entryIdentifier)
{
$this->removeIdentifierFromAllTags($entryIdentifier);
return wincache_ucache_delete($this->identifierPrefix . $entryIdentifier);
}
/**
* Finds and returns all cache entry identifiers which are tagged by the
* specified tag.
*
* @param string $tag The tag to search for
* @return array An array with identifiers of all matching entries. An empty array if no entries matched
*/
public function findIdentifiersByTag($tag)
{
$success = false;
$identifiers = wincache_ucache_get($this->identifierPrefix . 'tag_' . $tag, $success);
if ($success === false) {
return [];
}
return (array)$identifiers;
}
/**
* Finds all tags for the given identifier. This function uses reverse tag
* index to search for tags.
*
* @param string $identifier Identifier to find tags by
* @return array Array with tags
*/
protected function findTagsByIdentifier($identifier)
{
$success = false;
$tags = wincache_ucache_get($this->identifierPrefix . 'ident_' . $identifier, $success);
return $success ? (array)$tags : [];
}
/**
* Removes all cache entries of this cache
*
* @throws Exception
*/
public function flush()
{
if (!$this->cache instanceof FrontendInterface) {
throw new Exception('Yet no cache frontend has been set via setCache().', 1343331524);
}
$this->flushByTag('%WCBE%' . $this->cache->getIdentifier());
}
/**
* Removes all cache entries of this cache which are tagged by the specified
* tag.
*
* @param string $tag The tag the entries must have
*/
public function flushByTag($tag)
{
$identifiers = $this->findIdentifiersByTag($tag);
foreach ($identifiers as $identifier) {
$this->remove($identifier);
}
}
/**
* Associates the identifier with the given tags
*
* @param string $entryIdentifier
* @param array $tags
*/
protected function addIdentifierToTags($entryIdentifier, array $tags)
{
// Get identifier-to-tag index to look for updates
$existingTags = $this->findTagsByIdentifier($entryIdentifier);
$existingTagsUpdated = false;
foreach ($tags as $tag) {
// Update tag-to-identifier index
$identifiers = $this->findIdentifiersByTag($tag);
if (!in_array($entryIdentifier, $identifiers, true)) {
$identifiers[] = $entryIdentifier;
wincache_ucache_set($this->identifierPrefix . 'tag_' . $tag, $identifiers);
}
// Test if identifier-to-tag index needs update
if (!in_array($tag, $existingTags, true)) {
$existingTags[] = $tag;
$existingTagsUpdated = true;
}
}
// Update identifier-to-tag index if needed
if ($existingTagsUpdated) {
wincache_ucache_set($this->identifierPrefix . 'ident_' . $entryIdentifier, $existingTags);
}
}
/**
* Removes association of the identifier with the given tags
*
* @param string $entryIdentifier
*/
protected function removeIdentifierFromAllTags($entryIdentifier)
{
// Get tags for this identifier
$tags = $this->findTagsByIdentifier($entryIdentifier);
// Deassociate tags with this identifier
foreach ($tags as $tag) {
$identifiers = $this->findIdentifiersByTag($tag);
// Formally array_search() below should never return false due to
// the behavior of findTagsByIdentifier(). But if reverse index is
// corrupted, we still can get 'false' from array_search(). This is
// not a problem because we are removing this identifier from
// anywhere.
if (($key = array_search($entryIdentifier, $identifiers)) !== false) {
unset($identifiers[$key]);
if (!empty($identifiers)) {
wincache_ucache_set($this->identifierPrefix . 'tag_' . $tag, $identifiers);
} else {
wincache_ucache_delete($this->identifierPrefix . 'tag_' . $tag);
}
}
}
// Clear reverse tag index for this identifier
wincache_ucache_delete($this->identifierPrefix . 'ident_' . $entryIdentifier);
}
/**
* Does nothing, as wincache does GC itself
*/
public function collectGarbage()
{
}
}
......@@ -12,6 +12,7 @@ Description
The following PHP classes that have previously been marked as deprecated for v11 and were now removed:
- :php:`\TYPO3\CMS\Core\Cache\Backend\PdoBackend`
- :php:`\TYPO3\CMS\Core\Cache\Backend\WincacheBackend`
- :php:`\TYPO3\CMS\Core\Database\QueryGenerator`
- :php:`\TYPO3\CMS\Core\Database\QueryView`
......
<?php
declare(strict_types=1);
/*
* 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!
*/
namespace TYPO3\CMS\Core\Tests\UnitDeprecated\Cache\Backend;
use TYPO3\CMS\Core\Cache\Backend\WincacheBackend;
use TYPO3\CMS\Core\Cache\Exception;
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
use TYPO3\CMS\Core\Utility\StringUtility;
use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
/**
* Testcase for the WinCache cache backend
*
* @requires extension wincache
*/
class WincacheBackendTest extends UnitTestCase
{
/**
* @test
*/
public function setThrowsExceptionIfNoFrontEndHasBeenSet(): void
{
$this->expectException(Exception::class);
// @todo Add exception code with wincache extension
$backend = new WincacheBackend('Testing');
$data = 'Some data';
$identifier = StringUtility::getUniqueId('MyIdentifier');
$backend->set($identifier, $data);
}
/**
* @test
*/
public function itIsPossibleToSetAndCheckExistenceInCache(): void
{
$backend = $this->setUpBackend();
$data = 'Some data';
$identifier = StringUtility::getUniqueId('MyIdentifier');
$backend->set($identifier, $data);
$inCache = $backend->has($identifier);
self::assertTrue($inCache, 'WinCache backend failed to set and check entry');
}
/**
* @test
*/
public function itIsPossibleToSetAndGetEntry(): void
{
$backend = $this->setUpBackend();
$data = 'Some data';
$identifier = StringUtility::getUniqueId('MyIdentifier');
$backend->set($identifier, $data);
$fetchedData = $backend->get($identifier);
self::assertEquals($data, $fetchedData, 'Wincache backend failed to set and retrieve data');
}
/**
* @test
*/
public function itIsPossibleToRemoveEntryFromCache(): void
{
$backend = $this->setUpBackend();
$data = 'Some data';
$identifier = StringUtility::getUniqueId('MyIdentifier');
$backend->set($identifier, $data);
$backend->remove($identifier);
$inCache = $backend->has($identifier);
self::assertFalse($inCache, 'Failed to set and remove data from WinCache backend');
}
/**
* @test
*/
public function itIsPossibleToOverwriteAnEntryInTheCache(): void
{
$backend = $this->setUpBackend();
$data = 'Some data';
$identifier = StringUtility::getUniqueId('MyIdentifier');
$backend->set($identifier, $data);
$otherData = 'some other data';
$backend->set($identifier, $otherData);
$fetchedData = $backend->get($identifier);
self::assertEquals($otherData, $fetchedData, 'WinCache backend failed to overwrite and retrieve data');
}
/**
* @test
*/
public function findIdentifiersByTagFindsSetEntries(): void
{
$backend = $this->setUpBackend();
$data = 'Some data';
$identifier = StringUtility::getUniqueId('MyIdentifier');
$backend->set($identifier, $data, ['UnitTestTag%tag1', 'UnitTestTag%tag2']);
$retrieved = $backend->findIdentifiersByTag('UnitTestTag%tag1');
self::assertEquals($identifier, $retrieved[0], 'Could not retrieve expected entry by tag.');
$retrieved = $backend->findIdentifiersByTag('UnitTestTag%tag2');
self::assertEquals($identifier, $retrieved[0], 'Could not retrieve expected entry by tag.');
}
/**
* @test
*/
public function setRemovesTagsFromPreviousSet(): void
{
$backend = $this->setUpBackend();
$data = 'Some data';
$identifier = StringUtility::getUniqueId('MyIdentifier');
$backend->set($identifier, $data, ['UnitTestTag%tag1', 'UnitTestTag%tagX']);
$backend->set($identifier, $data, ['UnitTestTag%tag3']);
$retrieved = $backend->findIdentifiersByTag('UnitTestTag%tagX');
self::assertEquals([], $retrieved, 'Found entry which should no longer exist.');
}
/**
* @test
*/
public function hasReturnsFalseIfTheEntryDoesntExist(): void
{
$backend = $this->setUpBackend();
$identifier = StringUtility::getUniqueId('NonExistingIdentifier');
$inCache = $backend->has($identifier);
self::assertFalse($inCache, '"has" did not return FALSE when checking on non existing identifier');
}
/**
* @test
*/
public function removeReturnsFalseIfTheEntryDoesntExist(): void
{
$backend = $this->setUpBackend();
$identifier = StringUtility::getUniqueId('NonExistingIdentifier');
$inCache = $backend->remove($identifier);
self::assertFalse($inCache, '"remove" did not return FALSE when checking on non existing identifier');
}
/**
* @test
*/
public function flushByTagRemovesCacheEntriesWithSpecifiedTag(): void
{
$backend = $this->setUpBackend();
$data = 'some data' . microtime();
$backend->set('BackendWincacheTest1', $data, ['UnitTestTag%test', 'UnitTestTag%boring']);
$backend->set('BackendWincacheTest2', $data, ['UnitTestTag%test', 'UnitTestTag%special']);
$backend->set('BackendWincacheTest3', $data, ['UnitTestTag%test']);
$backend->flushByTag('UnitTestTag%special');
self::assertTrue($backend->has('BackendWincacheTest1'), 'BackendWincacheTest1');
self::assertFalse($backend->has('BackendWincacheTest2'), 'BackendWincacheTest2');
self::assertTrue($backend->has('BackendWincacheTest3'), 'BackendWincacheTest3');
}
/**
* @test
*/
public function flushByTagsRemovesCacheEntriesWithSpecifiedTags(): void
{
$backend = $this->setUpBackend();
$data = 'some data' . microtime();
$backend->set('BackendWincacheTest1', $data, ['UnitTestTag%test', 'UnitTestTag%boring']);
$backend->set('BackendWincacheTest2', $data, ['UnitTestTag%test', 'UnitTestTag%special']);
$backend->set('BackendWincacheTest3', $data, ['UnitTestTag%test']);
$backend->flushByTag('UnitTestTag%special', 'UnitTestTag%boring');
self::assertTrue($backend->has('BackendWincacheTest1'), 'BackendWincacheTest1');
self::assertFalse($backend->has('BackendWincacheTest2'), 'BackendWincacheTest2');
self::assertTrue($backend->has('BackendWincacheTest3'), 'BackendWincacheTest3');
}
/**
* @test
*/
public function flushRemovesAllCacheEntries(): void
{
$backend = $this->setUpBackend();
$data = 'some data' . microtime();
$backend->set('BackendWincacheTest1', $data);
$backend->set('BackendWincacheTest2', $data);
$backend->set('BackendWincacheTest3', $data);
$backend->flush();
self::assertFalse($backend->has('BackendWincacheTest1'), 'BackendWincacheTest1');
self::assertFalse($backend->has('BackendWincacheTest2'), 'BackendWincacheTest2');
self::assertFalse($backend->has('BackendWincacheTest3'), 'BackendWincacheTest3');
}
/**
* @test
*/
public function flushRemovesOnlyOwnEntries(): void
{
/** @var \PHPUnit\Framework\MockObject\MockObject|\TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $thisCache */
$thisCache = $this->createMock(FrontendInterface::class);
$thisCache->method('getIdentifier')->willReturn('thisCache');
$thisBackend = new WincacheBackend('Testing');
$thisBackend->setCache($thisCache);
/** @var \PHPUnit\Framework\MockObject\MockObject|\TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $thatCache */
$thatCache = $this->createMock(FrontendInterface::class);
$thatCache->method('getIdentifier')->willReturn('thatCache');
$thatBackend = new WincacheBackend('Testing');
$thatBackend->setCache($thatCache);
$thisBackend->set('thisEntry', 'Hello');
$thatBackend->set('thatEntry', 'World!');
$thatBackend->flush();
self::assertEquals('Hello', $thisBackend->get('thisEntry'));
self::assertFalse($thatBackend->has('thatEntry'));
}
/**
* Check if we can store ~5 MB of data
*
* @test
*/
public function largeDataIsStored(): void
{
$backend = $this->setUpBackend();
$data = str_repeat('abcde', 1024 * 1024);
$identifier = StringUtility::getUniqueId('tooLargeData');
$backend->set($identifier, $data);
self::assertTrue($backend->has($identifier));
self::assertEquals($backend->get($identifier), $data);
}
/**
* @test
*/
public function setTagsOnlyOnceToIdentifier(): void
{
$identifier = StringUtility::getUniqueId('MyIdentifier');
$tags = ['UnitTestTag%test', 'UnitTestTag%boring'];
$backend = $this->setUpBackend(true);
$backend->_call('addIdentifierToTags', $identifier, $tags);
self::assertSame(
$tags,
$backend->_call('findTagsByIdentifier', $identifier)
);
$backend->_call('addIdentifierToTags', $identifier, $tags);
self::assertSame(
$tags,
$backend->_call('findTagsByIdentifier', $identifier)
);
}
/**
* Sets up the WinCache backend used for testing
*
* @param bool $accessible TRUE if backend should be encapsulated in accessible proxy otherwise FALSE.
* @return \TYPO3\TestingFramework\Core\AccessibleObjectInterface|WincacheBackend
*/
protected function setUpBackend(bool $accessible = false)
{
/** @var \PHPUnit\Framework\MockObject\MockObject|\TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cache */
$cache = $this->createMock(FrontendInterface::class);
if ($accessible) {
$backend = $this->getAccessibleMock(WincacheBackend::class, ['dummy'], ['Testing']);
} else {
$backend = new WincacheBackend('Testing');
}
$backend->setCache($cache);
return $backend;
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment