Commit 0707d0b0 authored by Tobias Burger's avatar Tobias Burger Committed by Christian Kuhn
Browse files

[FEATURE] Add wincache as a cache backend

Add wincache as a cache backend for TYPO3 installations
running on a windows machine

Change-Id: I3089939f87d2597001120d9d3831f4e9ddb913bd
Resolves: #35818
Releases: 6.0
Reviewed-on: http://review.typo3.org/10204
Reviewed-by: Wouter Wolters
Tested-by: Wouter Wolters
Reviewed-by: Christian Kuhn
Tested-by: Christian Kuhn
parent 83c7f030
<?php
/***************************************************************
* Copyright notice
*
* (c) 2009-2012 Tobias Burger <tobias_burger@hotmail.com>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
/**
* 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.
*
* @author Tobias Burger <tobias_burger@hotmail.com>
* @package TYPO3
* @subpackage t3lib_cache
*/
class t3lib_cache_backend_WincacheBackend extends t3lib_cache_backend_AbstractBackend {
/**
* A prefix to seperate stored data from other data possible stored in the wincache
*
* @var string
*/
protected $identifierPrefix;
/**
* Constructs this backend
*
* @param mixed $options Configuration options - unused here
*/
public function __construct($options = array()) {
if (!extension_loaded('wincache')) {
throw new t3lib_cache_Exception(
'The PHP extension "wincache" must be installed and loaded in order to use the wincache backend.',
1343331520
);
}
parent::__construct($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 integer $lifetime Lifetime of this cache entry in seconds. If NULL is specified, the default lifetime is used. "0" means unlimited liftime.
* @return void
* @throws t3lib_cache_Exception if no cache frontend has been set
* @throws InvalidArgumentException if the identifier is not valid
* @throws t3lib_cache_exception_InvalidData if $data is not a string
*/
public function set($entryIdentifier, $data, array $tags = array(), $lifetime = NULL) {
if (!$this->cache instanceof t3lib_cache_frontend_Frontend) {
throw new t3lib_cache_Exception(
'No cache frontend has been set yet via setCache().',
1343331521
);
}
if (!is_string($data)) {
throw new t3lib_cache_exception_InvalidData(
'The specified data is of type "' . gettype($data) . '" but a string is expected.',
1343331522
);
}
$tags[] = '%WCBE%' . $this->cache->getIdentifier();
$expiration = $lifetime !== NULL ? $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 t3lib_cache_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 boolean TRUE if such an entry exists, FALSE if not
*/
public function has($entryIdentifier) {
$success = FALSE;
wincache_ucache_get($this->identifierPrefix . $entryIdentifier, $success);
return $success;
}
/**
* 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 boolean 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 array();
} else {
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 : array());
}
/**
* Removes all cache entries of this cache
*
* @return void
*/
public function flush() {
if (!$this->cache instanceof t3lib_cache_frontend_Frontend) {
throw new t3lib_cache_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
* @return void
*/
public function flushByTag($tag) {
$identifiers = $this->findIdentifiersByTag($tag);
foreach ($identifiers as $identifier) {
$this->remove($identifier);
}
}
/**
* Removes all cache entries of this cache which are tagged by the specified tag.
*
* @param array $tags The tags the entries must have
* @return void
*/
public function flushByTags(array $tags) {
foreach ($tags as $tag) {
$this->flushByTag($tag);
}
}
/**
* Associates the identifier with the given tags
*
* @param string $entryIdentifier
* @param array $tags
* @return void
*/
protected function addIdentifierToTags($entryIdentifier, array $tags) {
foreach ($tags as $tag) {
// Update tag-to-identifier index
$identifiers = $this->findIdentifiersByTag($tag);
if (array_search($entryIdentifier, $identifiers) === FALSE) {
$identifiers[] = $entryIdentifier;
wincache_ucache_set($this->identifierPrefix . 'tag_' . $tag, $identifiers);
}
// Update identifier-to-tag index
$existingTags = $this->findTagsByIdentifier($entryIdentifier);
if (array_search($entryIdentifier, $existingTags) === false) {
wincache_ucache_set($this->identifierPrefix . 'ident_' . $entryIdentifier, array_merge($existingTags, $tags));
}
}
}
/**
* Removes association of the identifier with the given tags
*
* @param string $entryIdentifier
* @return void
*/
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 (count($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
*
* @return void
*/
public function collectGarbage() {
}
}
?>
\ No newline at end of file
<?php
/***************************************************************
* Copyright notice
*
* (c) 2009-2012 Tobias Burger <tobias_burger@hotmail.com>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
/**
* Testcase for the WinCache cache backend
*
* @author Tobias Burger <tobias_burger@hotmail.com>
* @package TYPO3
* @subpackage tests
*/
class t3lib_cache_backend_WincacheBackendTest extends tx_phpunit_testcase {
/**
* Sets up this testcase
*
* @return void
*/
public function setUp() {
if (!extension_loaded('wincache')) {
$this->markTestSkipped('WinCache extension was not available');
}
}
/**
* @test
* @expectedException t3lib_cache_Exception
*/
public function setThrowsExceptionIfNoFrontEndHasBeenSet() {
$backend = new t3lib_cache_backend_WincacheBackend();
$data = 'Some data';
$identifier = 'MyIdentifier' . md5(uniqid(mt_rand(), TRUE));
$backend->set($identifier, $data);
}
/**
* @test
*/
public function itIsPossibleToSetAndCheckExistenceInCache() {
$backend = $this->setUpBackend();
$data = 'Some data';
$identifier = 'MyIdentifier' . md5(uniqid(mt_rand(), TRUE));
$backend->set($identifier, $data);
$inCache = $backend->has($identifier);
$this->assertTrue($inCache, 'WinCache backend failed to set and check entry');
}
/**
* @test
*/
public function itIsPossibleToSetAndGetEntry() {
$backend = $this->setUpBackend();
$data = 'Some data';
$identifier = 'MyIdentifier' . md5(uniqid(mt_rand(), TRUE));
$backend->set($identifier, $data);
$fetchedData = $backend->get($identifier);
$this->assertEquals($data, $fetchedData, 'Winache backend failed to set and retrieve data');
}
/**
* @test
*/
public function itIsPossibleToRemoveEntryFromCache() {
$backend = $this->setUpBackend();
$data = 'Some data';
$identifier = 'MyIdentifier' . md5(uniqid(mt_rand(), TRUE));
$backend->set($identifier, $data);
$backend->remove($identifier);
$inCache = $backend->has($identifier);
$this->assertFalse($inCache, 'Failed to set and remove data from WinCache backend');
}
/**
* @test
*/
public function itIsPossibleToOverwriteAnEntryInTheCache() {
$backend = $this->setUpBackend();
$data = 'Some data';
$identifier = 'MyIdentifier' . md5(uniqid(mt_rand(), TRUE));
$backend->set($identifier, $data);
$otherData = 'some other data';
$backend->set($identifier, $otherData);
$fetchedData = $backend->get($identifier);
$this->assertEquals($otherData, $fetchedData, 'WinCache backend failed to overwrite and retrieve data');
}
/**
* @test
*/
public function findIdentifiersByTagFindsSetEntries() {
$backend = $this->setUpBackend();
$data = 'Some data';
$identifier = 'MyIdentifier' . md5(uniqid(mt_rand(), TRUE));
$backend->set($identifier, $data, array('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 = 'MyIdentifier' . md5(uniqid(mt_rand(), TRUE));
$backend->set($identifier, $data, array('UnitTestTag%tag1', 'UnitTestTag%tagX'));
$backend->set($identifier, $data, array('UnitTestTag%tag3'));
$retrieved = $backend->findIdentifiersByTag('UnitTestTag%tagX');
$this->assertEquals(array(), $retrieved, 'Found entry which should no longer exist.');
}
/**
* @test
*/
public function hasReturnsFalseIfTheEntryDoesntExist() {
$backend = $this->setUpBackend();
$identifier = 'NonExistingIdentifier' . md5(uniqid(mt_rand(), TRUE));
$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 = 'NonExistingIdentifier' . md5(uniqid(mt_rand(), TRUE));
$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('BackendWincacheTest1', $data, array('UnitTestTag%test', 'UnitTestTag%boring'));
$backend->set('BackendWincacheTest2', $data, array('UnitTestTag%test', 'UnitTestTag%special'));
$backend->set('BackendWincacheTest3', $data, array('UnitTestTag%test'));
$backend->flushByTag('UnitTestTag%special');
$this->assertTrue($backend->has('BackendWincacheTest1'), 'BackendWincacheTest1');
$this->assertFalse($backend->has('BackendWincacheTest2'), 'BackendWincacheTest2');
$this->assertTrue($backend->has('BackendWincacheTest3'), 'BackendWincacheTest3');
}
/**
* @test
*/
public function flushRemovesAllCacheEntries() {
$backend = $this->setUpBackend();
$data = 'some data' . microtime();
$backend->set('BackendWincacheTest1', $data);
$backend->set('BackendWincacheTest2', $data);
$backend->set('BackendWincacheTest3', $data);
$backend->flush();
$this->assertFalse($backend->has('BackendWincacheTest1'), 'BackendWincacheTest1');
$this->assertFalse($backend->has('BackendWincacheTest2'), 'BackendWincacheTest2');
$this->assertFalse($backend->has('BackendWincacheTest3'), 'BackendWincacheTest3');
}
/**
* @test
*/
public function flushRemovesOnlyOwnEntries() {
$thisCache = $this->getMock('t3lib_cache_frontend_Frontend', array(), array(), '', FALSE);
$thisCache->expects($this->any())->method('getIdentifier')->will($this->returnValue('thisCache'));
$thisBackend = new t3lib_cache_backend_WincacheBackend();
$thisBackend->setCache($thisCache);
$thatCache = $this->getMock('t3lib_cache_frontend_Frontend', array(), array(), '', FALSE);
$thatCache->expects($this->any())->method('getIdentifier')->will($this->returnValue('thatCache'));
$thatBackend = new t3lib_cache_backend_WincacheBackend();
$thatBackend->setCache($thatCache);
$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
*
* @test
*/
public function largeDataIsStored() {
$backend = $this->setUpBackend();
$data = str_repeat('abcde', 1024 * 1024);
$identifier = 'tooLargeData' . md5(uniqid(mt_rand(), TRUE));
$backend->set($identifier, $data);
$this->assertTrue($backend->has($identifier));
$this->assertEquals($backend->get($identifier), $data);
}
/**
* Sets up the WinCache backend used for testing
*
* @return t3lib_cache_backend_WincacheBackend
*/
protected function setUpBackend() {
$cache = $this->getMock('t3lib_cache_frontend_Frontend', array(), array(), '', FALSE);
$backend = new t3lib_cache_backend_WincacheBackend();
$backend->setCache($cache);
return $backend;
}
}
?>
\ No newline at end of file
Supports Markdown
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