Feature #24073 ยป 16410-01.diff
tests/t3lib/cache/backend/t3lib_cache_backend_redisbackendTest.php (revision 0) | ||
---|---|---|
<?php
|
||
/***************************************************************
|
||
* Copyright notice
|
||
*
|
||
* (c) 2010 Christian Kuhn <lolli@schwarzbu.ch>
|
||
* 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 cache to redis backend
|
||
*
|
||
* This class has functional tests as well as implementation tests:
|
||
* - The functional tests make API calls to the backend and check expected behaviour
|
||
* - The implementation tests make additional calls with an own redis instance to
|
||
* check stored data structures in the redis server, which can not be checked
|
||
* by functional tests alone. Those tests will fail if any changes
|
||
* to the internal data structure are done.
|
||
*
|
||
* Warning:
|
||
* The unit tests use and flush redis database numbers 0 and 1!
|
||
*
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
* @package TYPO3
|
||
* @subpackage tests
|
||
*/
|
||
class t3lib_cache_backend_RedisBackendTest extends tx_phpunit_testcase {
|
||
/**
|
||
* @var boolean
|
||
*/
|
||
protected $backupGlobals = TRUE;
|
||
/**
|
||
* If set, the tearDown() method will flush the cache used by this unit test.
|
||
*
|
||
* @var t3lib_cache_backend_RedisBackend
|
||
*/
|
||
protected $backend = NULL;
|
||
/**
|
||
* Own redis instance used in implementation tests
|
||
*
|
||
* @var Redis
|
||
*/
|
||
protected $redis = NULL;
|
||
/**
|
||
* Set up this testcase
|
||
*
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function setUp() {
|
||
if (!extension_loaded('redis')) {
|
||
$this->markTestSkipped('redis extension was not available');
|
||
}
|
||
try {
|
||
if (!fsockopen('127.0.0.1', 6379)) {
|
||
$this->markTestSkipped('redis server not reachable');
|
||
}
|
||
} catch (Exception $e) {
|
||
$this->markTestSkipped('redis server not reachable');
|
||
}
|
||
$this->backupGlobals = TRUE;
|
||
}
|
||
/**
|
||
* Sets up the redis backend used for testing
|
||
*
|
||
* @param array Options for the redis backend
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
protected function setUpBackend(array $backendOptions = array()) {
|
||
$mockCache = $this->getMock('t3lib_cache_frontend_Frontend', array(), array(), '', FALSE);
|
||
$mockCache->expects($this->any())->method('getIdentifier')->will($this->returnValue('TestCache'));
|
||
$this->backend = new t3lib_cache_backend_RedisBackend($backendOptions);
|
||
$this->backend->setCache($mockCache);
|
||
}
|
||
/**
|
||
* Sets up an own redis instance for implementation tests
|
||
*
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
protected function setUpRedis() {
|
||
$this->redis = new Redis();
|
||
$this->redis->connect('127.0.0.1', 6379);
|
||
}
|
||
/**
|
||
* Tear down this testcase
|
||
*
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function tearDown() {
|
||
if ($this->backend instanceof t3lib_cache_backend_RedisBackend) {
|
||
$this->backend->flush();
|
||
}
|
||
}
|
||
/**
|
||
* @test Functional
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function constructorThrowsNoExceptionIfPasswordOptionIsSet() {
|
||
try {
|
||
$this->setUpBackend(array('password' => 'foo'));
|
||
} catch (Exception $e) {
|
||
$this->assertTrue();
|
||
}
|
||
}
|
||
/**
|
||
* @test Functional
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function constructorThrowsNoExceptionIfGivenDatabaseWasSuccessfullySelected() {
|
||
try {
|
||
$this->setUpBackend(array('database' => 1));
|
||
} catch (Exception $e) {
|
||
$this->assertTrue();
|
||
}
|
||
}
|
||
/**
|
||
* @test Functional
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
* @expectedException InvalidArgumentException
|
||
*/
|
||
public function setDatabaseThrowsExceptionIfGivenDatabaseNumberIsNotAnInteger() {
|
||
$this->setUpBackend(array('database' => 'foo'));
|
||
}
|
||
/**
|
||
* @test Functional
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
* @expectedException InvalidArgumentException
|
||
*/
|
||
public function setDatabaseThrowsExceptionIfGivenDatabaseNumberIsNegative() {
|
||
$this->setUpBackend(array('database' => -1));
|
||
}
|
||
/**
|
||
* @test Functional
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
* @expectedException InvalidArgumentException
|
||
*/
|
||
public function setCompressionThrowsExceptionIfCompressionParameterIsNotOfTypeBoolean() {
|
||
$this->setUpBackend(array('compression' => 'foo'));
|
||
}
|
||
/**
|
||
* @test Functional
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
* @expectedException InvalidArgumentException
|
||
*/
|
||
public function setCompressionLevelThrowsExceptionIfCompressionLevelIsNotInteger() {
|
||
$this->setUpBackend(array('compressionLevel' => 'foo'));
|
||
}
|
||
/**
|
||
* @test Functional
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
* @expectedException InvalidArgumentException
|
||
*/
|
||
public function setCompressionLevelThrowsExceptionIfCompressionLevelIsNotBetweenMinusOneAndNine() {
|
||
$this->setUpBackend(array('compressionLevel' => 11));
|
||
}
|
||
/**
|
||
* @test Functional
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
* @expectedException InvalidArgumentException
|
||
*/
|
||
public function setThrowsExceptionIfIdentifierIsNotAString() {
|
||
$this->setUpBackend();
|
||
$this->backend->set(array(), 'data');
|
||
}
|
||
/**
|
||
* @test Functional
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
* @expectedException t3lib_cache_Exception_InvalidData
|
||
*/
|
||
public function setThrowsExceptionIfDataIsNotAString() {
|
||
$this->setUpBackend();
|
||
$this->backend->set('identifier' . uniqid(), array());
|
||
}
|
||
/**
|
||
* @test Functional
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
* @expectedException InvalidArgumentException
|
||
*/
|
||
public function setThrowsExceptionIfLifetimeIsNegative() {
|
||
$this->setUpBackend();
|
||
$this->backend->set('identifier' . uniqid(), 'data', array(), -42);
|
||
}
|
||
/**
|
||
* @test Functional
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
* @expectedException InvalidArgumentException
|
||
*/
|
||
public function setThrowsExceptionIfLifetimeIsNotNullOrAnInteger() {
|
||
$this->setUpBackend();
|
||
$this->backend->set('identifier' . uniqid(), 'data', array(), array());
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function setStoresEntriesInSelectedDatabase() {
|
||
$this->setUpRedis();
|
||
$this->redis->select(1);
|
||
$this->setUpBackend(array('database' => 1));
|
||
$identifier = 'identifier' . uniqid();
|
||
$this->backend->set($identifier, 'data');
|
||
$this->assertTrue($this->redis->exists('identData:' . $identifier));
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function setSavesStringDataTypeForIdentifierToDataEntry() {
|
||
$this->setUpBackend();
|
||
$this->setUpRedis();
|
||
$identifier = 'identifier' . uniqid();
|
||
$this->backend->set($identifier, 'data');
|
||
$this->assertSame(Redis::REDIS_STRING, $this->redis->type('identData:' . $identifier));
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function setSavesEntryWithDefaultLifeTime() {
|
||
$this->setUpBackend();
|
||
$this->setUpRedis();
|
||
$identifier = 'identifier' . uniqid();
|
||
$defaultLifetime = 42;
|
||
$this->backend->setDefaultLifetime($defaultLifetime);
|
||
$this->backend->set($identifier, 'data');
|
||
$lifetimeRegisteredInBackend = $this->redis->ttl('identData:' . $identifier);
|
||
$this->assertSame($defaultLifetime, $lifetimeRegisteredInBackend);
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function setSavesEntryWithSpecifiedLifeTime() {
|
||
$this->setUpBackend();
|
||
$this->setUpRedis();
|
||
$identifier = 'identifier' . uniqid();
|
||
$lifetime = 43;
|
||
$this->backend->set($identifier, 'data', array(), $lifetime);
|
||
$lifetimeRegisteredInBackend = $this->redis->ttl('identData:' . $identifier);
|
||
$this->assertSame($lifetime, $lifetimeRegisteredInBackend);
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function setSavesEntryWithUnlimitedLifeTime() {
|
||
$this->setUpBackend();
|
||
$this->setUpRedis();
|
||
$identifier = 'identifier' . uniqid();
|
||
$this->backend->set($identifier, 'data', array(), 0);
|
||
$lifetimeRegisteredInBackend = $this->redis->ttl('identData:' . $identifier);
|
||
$this->assertSame(31536000, $lifetimeRegisteredInBackend);
|
||
}
|
||
/**
|
||
* @test Functional
|
||
* @author Christian Jul Jensen <julle@typo3.org>
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function setOverwritesExistingEntryWithNewData() {
|
||
$this->setUpBackend();
|
||
$data = 'data 1';
|
||
$identifier = 'identifier' . uniqid();
|
||
$this->backend->set($identifier, $data);
|
||
$otherData = 'data 2';
|
||
$this->backend->set($identifier, $otherData);
|
||
$fetchedData = $this->backend->get($identifier);
|
||
$this->assertSame($otherData, $fetchedData);
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function setOverwritesExistingEntryWithSpecifiedLifetime() {
|
||
$this->setUpBackend();
|
||
$this->setUpRedis();
|
||
$data = 'data';
|
||
$identifier = 'identifier' . uniqid();
|
||
$this->backend->set($identifier, $data);
|
||
$lifetime = 42;
|
||
$this->backend->set($identifier, $data, array(), $lifetime);
|
||
$lifetimeRegisteredInBackend = $this->redis->ttl('identData:' . $identifier);
|
||
$this->assertSame($lifetime, $lifetimeRegisteredInBackend);
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function setOverwritesExistingEntryWithNewDefaultLifetime() {
|
||
$this->setUpBackend();
|
||
$this->setUpRedis();
|
||
$data = 'data';
|
||
$identifier = 'identifier' . uniqid();
|
||
$lifetime = 42;
|
||
$this->backend->set($identifier, $data, array(), $lifetime);
|
||
$newDefaultLifetime = 43;
|
||
$this->backend->setDefaultLifetime($newDefaultLifetime);
|
||
$this->backend->set($identifier, $data, array(), $newDefaultLifetime);
|
||
$lifetimeRegisteredInBackend = $this->redis->ttl('identData:' . $identifier);
|
||
$this->assertSame($newDefaultLifetime, $lifetimeRegisteredInBackend);
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function setOverwritesExistingEntryWithNewUnlimitedLifetime() {
|
||
$this->setUpBackend();
|
||
$this->setUpRedis();
|
||
$data = 'data';
|
||
$identifier = 'identifier' . uniqid();
|
||
$lifetime = 42;
|
||
$this->backend->set($identifier, $data, array(), $lifetime);
|
||
$this->backend->set($identifier, $data, array(), 0);
|
||
$lifetimeRegisteredInBackend = $this->redis->ttl('identData:' . $identifier);
|
||
$this->assertSame(31536000, $lifetimeRegisteredInBackend);
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function setSavesSetDataTypeForIdentifierToTagsSet() {
|
||
$this->setUpBackend();
|
||
$this->setUpRedis();
|
||
$identifier = 'identifier' . uniqid();
|
||
$this->backend->set($identifier, 'data', array('tag'));
|
||
$this->assertSame(Redis::REDIS_SET, $this->redis->type('identTags:' . $identifier));
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function setSavesSpecifiedTagsInIdentifierToTagsSet() {
|
||
$this->setUpBackend();
|
||
$this->setUpRedis();
|
||
$identifier = 'identifier' . uniqid();
|
||
$tags = array('thatTag', 'thisTag');
|
||
$this->backend->set($identifier, 'data', $tags);
|
||
$savedTags = $this->redis->sMembers('identTags:' . $identifier);
|
||
sort($savedTags);
|
||
$this->assertSame($tags, $savedTags);
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function setRemovesAllPreviouslySetTagsFromIdentifierToTagsSet() {
|
||
$this->setUpBackend();
|
||
$this->setUpRedis();
|
||
$identifier = 'identifier' . uniqid();
|
||
$tags = array('fooTag', 'barTag');
|
||
$this->backend->set($identifier, 'data', $tags);
|
||
$this->backend->set($identifier, 'data', array());
|
||
$this->assertSame(array(), $this->redis->sMembers('identTags:' . $identifier));
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function setRemovesMultiplePreviouslySetTagsFromIdentifierToTagsSet() {
|
||
$this->setUpBackend();
|
||
$this->setUpRedis();
|
||
$identifier = 'identifier' . uniqid();
|
||
$firstTagSet = array('tag1', 'tag2', 'tag3', 'tag4');
|
||
$this->backend->set($identifier, 'data', $firstTagSet);
|
||
$secondTagSet = array('tag1', 'tag3');
|
||
$this->backend->set($identifier, 'data', $secondTagSet);
|
||
$actualTagSet = $this->redis->sMembers('identTags:' . $identifier);
|
||
sort($actualTagSet);
|
||
$this->assertSame($secondTagSet, $actualTagSet);
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function setSavesSetDataTypeForTagToIdentifiersSet() {
|
||
$this->setUpBackend();
|
||
$this->setUpRedis();
|
||
$identifier = 'identifier' . uniqid();
|
||
$tag = 'tag';
|
||
$this->backend->set($identifier, 'data', array($tag));
|
||
$this->assertSame(Redis::REDIS_SET, $this->redis->type('tagIdents:' . $tag));
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function setSavesIdentifierInTagToIdentifiersSetOfSpecifiedTag() {
|
||
$this->setUpBackend();
|
||
$this->setUpRedis();
|
||
$identifier = 'identifier' . uniqid();
|
||
$tag = 'thisTag';
|
||
$this->backend->set($identifier, 'data', array($tag));
|
||
$savedTagToIdentifiersMemberArray = $this->redis->sMembers('tagIdents:' . $tag);
|
||
$this->assertSame(array($identifier), $savedTagToIdentifiersMemberArray);
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function setAppendsSecondIdentifierInTagToIdentifiersEntry() {
|
||
$this->setUpBackend();
|
||
$this->setUpRedis();
|
||
$firstIdentifier = 'identifier' . uniqid();
|
||
$tag = 'thisTag';
|
||
$this->backend->set($firstIdentifier, 'data', array($tag));
|
||
$secondIdentifier = 'identifier' . uniqid();
|
||
$this->backend->set($secondIdentifier, 'data', array($tag));
|
||
$savedTagToIdentifiersMemberArray = $this->redis->sMembers('tagIdents:' . $tag);
|
||
sort($savedTagToIdentifiersMemberArray);
|
||
$identifierArray = array($firstIdentifier, $secondIdentifier);
|
||
sort($identifierArray);
|
||
$this->assertSame(array($firstIdentifier, $secondIdentifier), $savedTagToIdentifiersMemberArray);
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function setRemovesIdentifierFromTagToIdentifiersEntryIfTagIsOmittedOnConsecutiveSet() {
|
||
$this->setUpBackend();
|
||
$this->setUpRedis();
|
||
$identifier = 'identifier' . uniqid();
|
||
$tag = 'thisTag';
|
||
$this->backend->set($identifier, 'data', array($tag));
|
||
$this->backend->set($identifier, 'data', array());
|
||
$savedTagToIdentifiersMemberArray = $this->redis->sMembers('tagIdents:' . $tag);
|
||
$this->assertSame(array(), $savedTagToIdentifiersMemberArray);
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function setAddsIdentifierInTagToIdentifiersEntryIfTagIsAddedOnConsecutiveSet() {
|
||
$this->setUpBackend();
|
||
$this->setUpRedis();
|
||
$identifier = 'identifier' . uniqid();
|
||
$this->backend->set($identifier, 'data');
|
||
$tag = 'thisTag';
|
||
$this->backend->set($identifier, 'data', array($tag));
|
||
$savedTagToIdentifiersMemberArray = $this->redis->sMembers('tagIdents:' . $tag);
|
||
$this->assertSame(array($identifier), $savedTagToIdentifiersMemberArray);
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function setSavesCompressedDataWithEnabledCompression() {
|
||
$this->setUpBackend(
|
||
array(
|
||
'compression' => TRUE,
|
||
)
|
||
);
|
||
$this->setUpRedis();
|
||
$identifier = 'identifier' . uniqid();
|
||
$data = 'some data ' . microtime();
|
||
$this->backend->set($identifier, $data);
|
||
$uncompresedStoredData = '';
|
||
try {
|
||
$uncompresedStoredData = @gzuncompress($this->redis->get('identData:' . $identifier));
|
||
} catch (Exception $e) {
|
||
}
|
||
$this->assertEquals($data, $uncompresedStoredData, 'Original and compressed data don\'t match');
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function setSavesPlaintextDataWithEnabledCompressionAndCompressionLevel0() {
|
||
$this->setUpBackend(
|
||
array(
|
||
'compression' => TRUE,
|
||
'compressionLevel' => 0,
|
||
)
|
||
);
|
||
$this->setUpRedis();
|
||
$identifier = 'identifier' . uniqid();
|
||
$data = 'some data ' . microtime();
|
||
$this->backend->set($identifier, $data);
|
||
$this->assertGreaterThan(0, substr_count($this->redis->get('identData:' . $identifier), $data), 'Plaintext data not found');
|
||
}
|
||
/**
|
||
* @test Functional
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
* @expectedException InvalidArgumentException
|
||
*/
|
||
public function hasThrowsExceptionIfIdentifierIsNotAString() {
|
||
$this->setUpBackend();
|
||
$this->backend->has(array());
|
||
}
|
||
/**
|
||
* @test Functional
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function hasReturnsFalseForNotExistingEntry() {
|
||
$this->setUpBackend();
|
||
$identifier = 'identifier' . uniqid();
|
||
$this->assertFalse($this->backend->has($identifier));
|
||
}
|
||
/**
|
||
* @test Functional
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function hasReturnsTrueForPreviouslySetEntry() {
|
||
$this->setUpBackend();
|
||
$identifier = 'identifier' . uniqid();
|
||
$this->backend->set($identifier, 'data');
|
||
$this->assertTrue($this->backend->has($identifier));
|
||
}
|
||
/**
|
||
* @test Functional
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
* @expectedException InvalidArgumentException
|
||
*/
|
||
public function getThrowsExceptionIfIdentifierIsNotAString() {
|
||
$this->setUpBackend();
|
||
$this->backend->get(array());
|
||
}
|
||
/**
|
||
* @test Functional
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function getReturnsPreviouslyCompressedSetEntry() {
|
||
$this->setUpBackend(
|
||
array(
|
||
'compression' => TRUE,
|
||
)
|
||
);
|
||
$data = 'data';
|
||
$identifier = 'identifier' . uniqid();
|
||
$this->backend->set($identifier, $data);
|
||
$fetchedData = $this->backend->get($identifier);
|
||
$this->assertSame($data, $fetchedData);
|
||
}
|
||
/**
|
||
* @test Functional
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function getReturnsPreviouslySetEntry() {
|
||
$this->setUpBackend();
|
||
$data = 'data';
|
||
$identifier = 'identifier' . uniqid();
|
||
$this->backend->set($identifier, $data);
|
||
$fetchedData = $this->backend->get($identifier);
|
||
$this->assertSame($data, $fetchedData);
|
||
}
|
||
/**
|
||
* @test Functional
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
* @expectedException InvalidArgumentException
|
||
*/
|
||
public function removeThrowsExceptionIfIdentifierIsNotAString() {
|
||
$this->setUpBackend();
|
||
$this->backend->remove(array());
|
||
}
|
||
/**
|
||
* @test Functional
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function removeReturnsFalseIfNoEntryWasDeleted() {
|
||
$this->setUpBackend();
|
||
$this->assertFalse($this->backend->remove('identifier' . uniqid()));
|
||
}
|
||
/**
|
||
* @test Functional
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function removeReturnsTrueIfAnEntryWasDeleted() {
|
||
$this->setUpBackend();
|
||
$identifier = 'identifier' . uniqid();
|
||
$this->backend->set($identifier, 'data');
|
||
$this->assertTrue($this->backend->remove($identifier));
|
||
}
|
||
/**
|
||
* @test Functional
|
||
* @author Christian Jul Jensen <julle@typo3.org>
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function removeDeletesEntryFromCache() {
|
||
$this->setUpBackend();
|
||
$identifier = 'identifier' . uniqid();
|
||
$this->backend->set($identifier, 'data');
|
||
$this->backend->remove($identifier);
|
||
$this->assertFalse($this->backend->has($identifier));
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function removeDeletesIdentifierToTagEntry() {
|
||
$this->setUpBackend();
|
||
$this->setUpRedis();
|
||
$identifier = 'identifier' . uniqid();
|
||
$tag = 'thisTag';
|
||
$this->backend->set($identifier, 'data', array($tag));
|
||
$this->backend->remove($identifier);
|
||
$this->assertFalse($this->redis->exists('identTags:' . $identifier));
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function removeDeletesIdentifierFromTagToIdentifiersSet() {
|
||
$this->setUpBackend();
|
||
$this->setUpRedis();
|
||
$identifier = 'identifier' . uniqid();
|
||
$tag = 'thisTag';
|
||
$this->backend->set($identifier, 'data', array($tag));
|
||
$this->backend->remove($identifier);
|
||
$tagToIdentifiersMemberArray = $this->redis->sMembers('tagIdents:' . $tag);
|
||
$this->assertSame(array(), $tagToIdentifiersMemberArray);
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function removeDeletesIdentifierFromTagToIdentifiersSetWithMultipleEntries() {
|
||
$this->setUpBackend();
|
||
$this->setUpRedis();
|
||
$firstIdentifier = 'identifier' . uniqid();
|
||
$secondIdentifier = 'identifier' . uniqid();
|
||
$tag = 'thisTag';
|
||
$this->backend->set($firstIdentifier, 'data', array($tag));
|
||
$this->backend->set($secondIdentifier, 'data', array($tag));
|
||
$this->backend->remove($firstIdentifier);
|
||
$tagToIdentifiersMemberArray = $this->redis->sMembers('tagIdents:' . $tag);
|
||
$this->assertSame(array($secondIdentifier), $tagToIdentifiersMemberArray);
|
||
}
|
||
/**
|
||
* @test Functional
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
* @expectedException InvalidArgumentException
|
||
*/
|
||
public function findIdentifiersByTagThrowsExceptionIfTagIsNotAString() {
|
||
$this->setUpBackend();
|
||
$this->backend->findIdentifiersByTag(array());
|
||
}
|
||
/**
|
||
* @test Functional
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function findIdentifiersByTagReturnsEmptyArrayForNotExistingTag() {
|
||
$this->setUpBackend();
|
||
$this->assertSame(array(), $this->backend->findIdentifiersByTag('thisTag'));
|
||
}
|
||
/**
|
||
* @test Functional
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function findIdentifiersByTagReturnsAllIdentifiersTagedWithSpecifiedTag() {
|
||
$this->setUpBackend();
|
||
$firstIdentifier = 'identifier' . uniqid();
|
||
$secondIdentifier = 'identifier' . uniqid();
|
||
$thirdIdentifier = 'identifier' . uniqid();
|
||
$tagsForFirstIdentifier = array('thisTag');
|
||
$tagsForSecondIdentifier = array('thatTag');
|
||
$tagsForThirdIdentifier = array('thisTag', 'thatTag');
|
||
$this->backend->set($firstIdentifier, 'data', $tagsForFirstIdentifier);
|
||
$this->backend->set($secondIdentifier, 'data', $tagsForSecondIdentifier);
|
||
$this->backend->set($thirdIdentifier, 'data', $tagsForThirdIdentifier);
|
||
$expectedResult = array($firstIdentifier, $thirdIdentifier);
|
||
$actualResult = $this->backend->findIdentifiersByTag('thisTag');
|
||
sort($actualResult);
|
||
$this->assertSame($expectedResult, $actualResult);
|
||
}
|
||
/**
|
||
* @test Functional
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function findIdentifiersByTagsReturnEmptyArrayForNotExistingTag() {
|
||
$this->setUpBackend();
|
||
$this->assertEquals(array(), $this->backend->findIdentifiersByTags(array('thisTags')));
|
||
}
|
||
/**
|
||
* @test Functional
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function findIdentifiersByTagsReturnsIdentifiersTaggedWithAllSpecifiedTags() {
|
||
$this->setUpBackend();
|
||
$identifier = 'identifier' . uniqid();
|
||
$this->backend->set($identifier . 'A', 'data', array('tag1'));
|
||
$this->backend->set($identifier . 'B', 'data', array('tag2'));
|
||
$this->backend->set($identifier . 'C', 'data', array('tag1', 'tag2'));
|
||
$this->backend->set($identifier . 'D', 'data', array('tag1', 'tag2', 'tag3'));
|
||
$expectedResult = array($identifier . 'C', $identifier . 'D');
|
||
$actualResult = $this->backend->findIdentifiersByTags(array('tag1', 'tag2'));
|
||
sort($actualResult);
|
||
$this->assertEquals($expectedResult, $actualResult);
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function flushRemovesAllEntriesFromCache() {
|
||
$this->setUpBackend();
|
||
$this->setUpRedis();
|
||
$identifier = 'identifier' . uniqid();
|
||
$this->backend->set($identifier, 'data');
|
||
$this->backend->flush();
|
||
$this->assertSame(array(), $this->redis->getKeys('*'));
|
||
}
|
||
/**
|
||
* @test Functional
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
* @expectedException InvalidArgumentException
|
||
*/
|
||
public function flushByTagThrowsExceptionIfTagIsNotAString() {
|
||
$this->setUpBackend();
|
||
$this->backend->flushByTag(array());
|
||
}
|
||
/**
|
||
* @test Functional
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function flushByTagRemovesEntriesTaggedWithSpecifiedTag() {
|
||
$this->setUpBackend();
|
||
$identifier = 'identifier' . uniqid();
|
||
$this->backend->set($identifier . 'A', 'data', array('tag1'));
|
||
$this->backend->set($identifier . 'B', 'data', array('tag2'));
|
||
$this->backend->set($identifier . 'C', 'data', array('tag1', 'tag2'));
|
||
$this->backend->flushByTag('tag1');
|
||
$expectedResult = array(FALSE, TRUE, FALSE);
|
||
$actualResult = array(
|
||
$this->backend->has($identifier . 'A'),
|
||
$this->backend->has($identifier . 'B'),
|
||
$this->backend->has($identifier . 'C'),
|
||
);
|
||
$this->assertSame($expectedResult, $actualResult);
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function flushByTagRemovesTemporarySet() {
|
||
$this->setUpBackend();
|
||
$this->setUpRedis();
|
||
$identifier = 'identifier' . uniqid();
|
||
$this->backend->set($identifier . 'A', 'data', array('tag1'));
|
||
$this->backend->set($identifier . 'C', 'data', array('tag1', 'tag2'));
|
||
$this->backend->flushByTag('tag1');
|
||
$this->assertSame(array(), $this->redis->getKeys('temp*'));
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function flushByTagRemovesIdentifierToTagsSetOfEntryTaggedWithGivenTag() {
|
||
$this->setUpBackend();
|
||
$this->setUpRedis();
|
||
$identifier = 'identifier' . uniqid();
|
||
$tag = 'tag1';
|
||
$this->backend->set($identifier, 'data', array($tag));
|
||
$this->backend->flushByTag($tag);
|
||
$this->assertFalse($this->redis->exists('identTags:' . $identifier));
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function flushByTagDoesNotRemoveIdentifierToTagsSetOfUnrelatedEntry() {
|
||
$this->setUpBackend();
|
||
$this->setUpRedis();
|
||
$identifierToBeRemoved = 'identifier' . uniqid();
|
||
$tagToRemove = 'tag1';
|
||
$this->backend->set($identifierToBeRemoved, 'data', array($tagToRemove));
|
||
$identifierNotToBeRemoved = 'identifier' . uniqid();
|
||
$tagNotToRemove = 'tag2';
|
||
$this->backend->set($identifierNotToBeRemoved, 'data', array($tagNotToRemove));
|
||
$this->backend->flushByTag($tagToRemove);
|
||
$this->assertSame(array($tagNotToRemove), $this->redis->sMembers('identTags:' . $identifierNotToBeRemoved));
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function flushByTagRemovesTagToIdentifiersSetOfGivenTag() {
|
||
$this->setUpBackend();
|
||
$this->setUpRedis();
|
||
$identifier = 'identifier' . uniqid();
|
||
$tag = 'tag1';
|
||
$this->backend->set($identifier, 'data', array($tag));
|
||
$this->backend->flushByTag($tag);
|
||
$this->assertFalse($this->redis->exists('tagIdents:' . $tag));
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function flushByTagRemovesIdentifiersTaggedWithGivenTagFromTagToIdentifiersSets() {
|
||
$this->setUpBackend();
|
||
$this->setUpRedis();
|
||
$identifier = 'identifier' . uniqid();
|
||
$this->backend->set($identifier . 'A', 'data', array('tag1', 'tag2'));
|
||
$this->backend->set($identifier . 'B', 'data', array('tag1', 'tag2'));
|
||
$this->backend->set($identifier . 'C', 'data', array('tag2'));
|
||
$this->backend->flushByTag('tag1');
|
||
$this->assertSame(array($identifier . 'C'), $this->redis->sMembers('tagIdents:tag2'));
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function flushByTagsRemovesEntriesTaggedWithSpecifiedTags() {
|
||
$this->setUpBackend();
|
||
$identifier = 'identifier' . uniqid();
|
||
$this->backend->set($identifier . 'A', 'data', array('tag1'));
|
||
$this->backend->set($identifier . 'B', 'data', array('tag2'));
|
||
$this->backend->set($identifier . 'C', 'data', array('tag3'));
|
||
$this->backend->set($identifier . 'D', 'data', array('tag1', 'tag2'));
|
||
$this->backend->set($identifier . 'E', 'data', array('tag1', 'tag3'));
|
||
$this->backend->flushByTags(array('tag1', 'tag2'));
|
||
$expectedResult = array(FALSE, FALSE, TRUE, FALSE, FALSE);
|
||
$actualResult = array(
|
||
$this->backend->has($identifier . 'A'),
|
||
$this->backend->has($identifier . 'B'),
|
||
$this->backend->has($identifier . 'C'),
|
||
$this->backend->has($identifier . 'D'),
|
||
$this->backend->has($identifier . 'E'),
|
||
);
|
||
$this->assertEquals($expectedResult, $actualResult);
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function flushByTagsRemovesIdentifierToTagsSetOfEntriesTaggedWithGivenTags() {
|
||
$this->setUpBackend();
|
||
$this->setUpRedis();
|
||
$identifier = 'identifier' . uniqid();
|
||
$this->backend->set($identifier . 'A', 'data', array('tag1', 'tag2'));
|
||
$this->backend->set($identifier . 'B', 'data', array('tag1', 'tag3'));
|
||
$this->backend->set($identifier . 'C', 'data', array('tag2', 'tag4'));
|
||
$this->backend->set($identifier . 'D', 'data', array('tag3', 'tag4'));
|
||
$this->backend->flushByTags(array('tag1', 'tag2'));
|
||
$expectedResult = array(FALSE, FALSE, FALSE, TRUE);
|
||
$actualResult = array(
|
||
$this->redis->exists('identTags:' . $identifier . 'A'),
|
||
$this->redis->exists('identTags:' . $identifier . 'B'),
|
||
$this->redis->exists('identTags:' . $identifier . 'C'),
|
||
$this->redis->exists('identTags:' . $identifier . 'D'),
|
||
);
|
||
$this->assertEquals($expectedResult, $actualResult);
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function flushByTagsRemovesTagToIdentifiersSetsOfGivenTags() {
|
||
$this->setUpBackend();
|
||
$this->setUpRedis();
|
||
$identifier = 'identifier' . uniqid();
|
||
$this->backend->set($identifier . 'A', 'data', array('tag1', 'tag2'));
|
||
$this->backend->set($identifier . 'B', 'data', array('tag1', 'tag3'));
|
||
$this->backend->set($identifier . 'C', 'data', array('tag2', 'tag4'));
|
||
$this->backend->set($identifier . 'D', 'data', array('tag3', 'tag4'));
|
||
$this->backend->flushByTags(array('tag1', 'tag2'));
|
||
$expectedResult = array(FALSE, FALSE, TRUE, TRUE);
|
||
$actualResult = array(
|
||
$this->redis->exists('tagIdents:tag1'),
|
||
$this->redis->exists('tagIdents:tag2'),
|
||
$this->redis->exists('tagIdents:tag3'),
|
||
$this->redis->exists('tagIdents:tag4'),
|
||
);
|
||
$this->assertEquals($expectedResult, $actualResult);
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function flushByTagsRemovesIdentifiersTaggedWithGivenTagsFromTagToIdentifiersSets() {
|
||
$this->setUpBackend();
|
||
$this->setUpRedis();
|
||
$identifier = 'identifier' . uniqid();
|
||
$this->backend->set('A' . $identifier, 'data', array('tag1', 'tag2'));
|
||
$this->backend->set('B' . $identifier, 'data', array('tag1', 'tag3'));
|
||
$this->backend->set('C' . $identifier, 'data', array('tag3', 'tag4'));
|
||
$this->backend->set('D' . $identifier, 'data', array('tag3', 'tag4', 'tag5'));
|
||
$this->backend->flushByTags(array('tag1', 'tag2'));
|
||
$expectedResult = array(
|
||
array('C' . $identifier, 'D' . $identifier),
|
||
array('C' . $identifier, 'D' . $identifier),
|
||
array('D' . $identifier),
|
||
);
|
||
$tag3Identifiers = $this->redis->sMembers('tagIdents:tag3');
|
||
$tag4Identifiers = $this->redis->sMembers('tagIdents:tag4');
|
||
$tag5Identifiers = $this->redis->sMembers('tagIdents:tag5');
|
||
sort($tag3Identifiers);
|
||
sort($tag4Identifiers);
|
||
$actualResult = array(
|
||
$tag3Identifiers,
|
||
$tag4Identifiers,
|
||
$tag5Identifiers,
|
||
);
|
||
$this->assertEquals($expectedResult, $actualResult);
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function collectGarbageDoesNotRemoveNotExpiredIdentifierToDataEntry() {
|
||
$this->setUpBackend();
|
||
$this->setUpRedis();
|
||
$identifier = 'identifier' . uniqid();
|
||
$this->backend->set($identifier . 'A', 'data', array('tag'));
|
||
$this->backend->set($identifier . 'B', 'data', array('tag'));
|
||
$this->redis->delete('identData:' . $identifier . 'A');
|
||
$this->backend->collectGarbage();
|
||
$this->assertTrue($this->redis->exists('identData:' . $identifier . 'B'));
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function collectGarbageRemovesLeftOverIdentifierToTagsSet() {
|
||
$this->setUpBackend();
|
||
$this->setUpRedis();
|
||
$identifier = 'identifier' . uniqid();
|
||
$this->backend->set($identifier . 'A', 'data', array('tag'));
|
||
$this->backend->set($identifier . 'B', 'data', array('tag'));
|
||
$this->redis->delete('identData:' . $identifier . 'A');
|
||
$this->backend->collectGarbage();
|
||
$expectedResult = array(FALSE, TRUE);
|
||
$actualResult = array(
|
||
$this->redis->exists('identTags:' . $identifier . 'A'),
|
||
$this->redis->exists('identTags:' . $identifier . 'B'),
|
||
);
|
||
$this->assertSame($expectedResult, $actualResult);
|
||
}
|
||
/**
|
||
* @test Implementation
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function collectGarbageRemovesExpiredIdentifierFromTagsToIdentifierSet() {
|
||
$this->setUpBackend();
|
||
$this->setUpRedis();
|
||
$identifier = 'identifier' . uniqid();
|
||
$this->backend->set($identifier . 'A', 'data', array('tag1', 'tag2'));
|
||
$this->backend->set($identifier . 'B', 'data', array('tag2'));
|
||
$this->redis->delete('identData:' . $identifier . 'A');
|
||
$this->backend->collectGarbage();
|
||
$expectedResult = array(
|
||
array(),
|
||
array($identifier . 'B')
|
||
);
|
||
$actualResult = array(
|
||
$this->redis->sMembers('tagIdents:tag1'),
|
||
$this->redis->sMembers('tagIdents:tag2'),
|
||
);
|
||
$this->assertSame($expectedResult, $actualResult);
|
||
}
|
||
}
|
||
?>
|
t3lib/config_default.php (working copy) | ||
---|---|---|
't3lib_cache_backend_GlobalsBackend' => 't3lib_cache_backend_GlobalsBackend',
|
||
't3lib_cache_backend_MemcachedBackend' => 't3lib_cache_backend_MemcachedBackend',
|
||
't3lib_cache_backend_PdoBackend' => 't3lib_cache_backend_PdoBackend',
|
||
't3lib_cache_backend_RedisBackend' => 't3lib_cache_backend_RedisBackend',
|
||
't3lib_cache_backend_ApcBackend' => 't3lib_cache_backend_ApcBackend',
|
||
't3lib_cache_backend_NullBackend' => 't3lib_cache_backend_NullBackend',
|
||
't3lib_cache_backend_TransientMemoryBackend' => 't3lib_cache_backend_TransientMemoryBackend',
|
t3lib/cache/backend/class.t3lib_cache_backend_redisbackend.php (revision 0) | ||
---|---|---|
<?php
|
||
/***************************************************************
|
||
* Copyright notice
|
||
*
|
||
* (c) 2010 Christian Kuhn <lolli@schwarzbu.ch>
|
||
* 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 Redis with phpredis
|
||
* PHP module. Redis is a noSQL database with very good scaling characteristics
|
||
* in proportion to the amount of entries and data size.
|
||
*
|
||
* @see http://code.google.com/p/redis/
|
||
* @see http://github.com/owlient/phpredis
|
||
*
|
||
* Warning:
|
||
* Redis and phpredis are young projects with very high development speed.
|
||
* This implementation should be considered as experimental for now,
|
||
* internals might break or change while the dependent projects mature.
|
||
*
|
||
* Successfully tested with:
|
||
* - redis
|
||
* version 2.0.0-rc2, version 1.2.0 does not work
|
||
* git version 9fd01051bf8400babcca73a76a67dfc1847633ff from 2010-11-12
|
||
* - phpredis
|
||
* git version 0abb9e5ec07b8a8c20b5 from 2010-07-18
|
||
* git version 12769b03c8ec17b25573e0453003712011bba241 from 2010-11-08
|
||
*
|
||
* Implementation based on ext:rediscache by Christopher Hlubek - networkteam GmbH
|
||
*
|
||
* This backend uses the following types of redis keys:
|
||
* - identData:xxx, value type "string", volatile, expires after given lifetime
|
||
* xxx is the given identifier name, value is the cache data
|
||
* - identTags:xxx, value type "set"
|
||
* xxx is the given identifier name, value is a set of associated tags.
|
||
* This is a "reverse" tag index. It provides quick access for all tags
|
||
* associated with this identifier and is used when removing the identifier.
|
||
* - tagIdents:xxx, value type "set"
|
||
* xxx is a tag name, value is a set of associated identifiers.
|
||
* This is "forward" tag index. It is mainly used for flushing content by tag.
|
||
* - temp:xxx, value type "set"
|
||
* xxx is a unique id, value is a set of identifiers. Used as temporary key
|
||
* used in flushByTag() and flushByTags(), removed after usage again.
|
||
*
|
||
* Each cache using this backend should use an own redis database to
|
||
* avoid namespace problems. By default redis has 16 databases which are
|
||
* identified with numbers 0 .. 15. setDatabase() can be used to select one.
|
||
* The unit tests use and flush database numbers 0 and 1, production use should start from 2.
|
||
*
|
||
* @package TYPO3
|
||
* @subpackage t3lib_cache
|
||
* @api
|
||
* @scope prototype
|
||
*/
|
||
class t3lib_cache_backend_RedisBackend extends t3lib_cache_backend_AbstractBackend {
|
||
/**
|
||
* Faked unlimited lifetime = 31536000 (1 Year).
|
||
* In redis an entry does not have a lifetime by default (it's not "volatile").
|
||
* Entries can be made volatile either with EXPIRE after it has been SET,
|
||
* or with SETEX, which is a combined SET and EXPIRE command.
|
||
* But an entry can not be made "unvolatile" again. To set a volatile entry to
|
||
* not volatile again, it must be DELeted and SET without a following EXPIRE.
|
||
* To save these additional calls on every set(),
|
||
* we just make every entry volatile and treat a high number as "unlimited"
|
||
*
|
||
* @see http://code.google.com/p/redis/wiki/ExpireCommand
|
||
* @var integer Faked unlimited lifetime
|
||
*/
|
||
const FAKED_UNLIMITED_LIFETIME = 31536000;
|
||
/**
|
||
* @var string Key prefix for identifier->data entries
|
||
*/
|
||
const IDENTIFIER_DATA_PREFIX = 'identData:';
|
||
/**
|
||
* @var string Key prefix for identifier->tags sets
|
||
*/
|
||
const IDENTIFIER_TAGS_PREFIX = 'identTags:';
|
||
/**
|
||
* @var string Key prefix for tag->identifiers sets
|
||
*/
|
||
const TAG_IDENTIFIERS_PREFIX = 'tagIdents:';
|
||
/**
|
||
* @var Redis Instance of the PHP redis class
|
||
*/
|
||
protected $redis;
|
||
/**
|
||
* @var boolean Indicates wether the server is connected
|
||
*/
|
||
protected $connected = FALSE;
|
||
/**
|
||
* @var string Hostname / IP of the Redis server, defaults to 127.0.0.1.
|
||
*/
|
||
protected $hostname = '127.0.0.1';
|
||
/**
|
||
* @var integer Port of the Redis server, defaults to 6379
|
||
*/
|
||
protected $port = 6379;
|
||
/**
|
||
* @var integer Number of selected database, defaults to 0
|
||
*/
|
||
protected $database = 0;
|
||
/**
|
||
* @var string Password for redis authentication
|
||
*/
|
||
protected $password = '';
|
||
/**
|
||
* @var boolean Indicates wether data is compressed or not (requires php zlib)
|
||
*/
|
||
protected $compression = FALSE;
|
||
/**
|
||
* @var integer -1 to 9, indicates zlib compression level: -1 = default level 6, 0 = no compression, 9 maximum compression
|
||
*/
|
||
protected $compressionLevel = -1;
|
||
/**
|
||
* Construct this backend
|
||
*
|
||
* @param array Configuration options
|
||
* @throws t3lib_cache_Exception if php redis module is not loaded
|
||
* @author Christopher Hlubek <hlubek@networkteam.com>
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
public function __construct(array $options = array()) {
|
||
if (!extension_loaded('redis')) {
|
||
throw new t3lib_cache_Exception(
|
||
'The PHP extension "redis" must be installed and loaded in order to use the redis backend.',
|
||
1279462933
|
||
);
|
||
}
|
||
parent::__construct($options);
|
||
$this->initializeObject();
|
||
}
|
||
/**
|
||
* Initializes the redis backend
|
||
*
|
||
* @param array Configuration options
|
||
* @return void
|
||
* @throws t3lib_cache_Exception if access to redis with password is denied or if database selection fails
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
*/
|
||
protected function initializeObject() {
|
||
$this->redis = new Redis();
|
||
try {
|
||
$this->connected = $this->redis->connect($this->hostname, $this->port);
|
||
} catch (Exception $e) {
|
||
t3lib_div::sysLog('Unable to connect to redis server.', 'core', 3);
|
||
}
|
||
if ($this->connected) {
|
||
if (strlen($this->password)) {
|
||
$success = $this->redis->auth($this->password);
|
||
if (!$success) {
|
||
throw new t3lib_cache_Exception(
|
||
'The given password was not accepted by the redis server.',
|
||
1279765134
|
||
);
|
||
}
|
||
}
|
||
if ($this->database > 0) {
|
||
$success = $this->redis->select($this->database);
|
||
if (!$success) {
|
||
throw new t3lib_cache_Exception(
|
||
'The given database "' . $this->database . '" could not be selected.',
|
||
1279765144
|
||
);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
/**
|
||
* Setter for server hostname
|
||
*
|
||
* @param string Hostname
|
||
* @return void
|
||
* @author Christopher Hlubek <hlubek@networkteam.com>
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
* @api
|
||
*/
|
||
public function setHostname($hostname) {
|
||
$this->hostname = $hostname;
|
||
}
|
||
/**
|
||
* Setter for server port
|
||
*
|
||
* @param integer Port
|
||
* @return void
|
||
* @author Christopher Hlubek <hlubek@networkteam.com>
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
* @api
|
||
*/
|
||
public function setPort($port) {
|
||
$this->port = $port;
|
||
}
|
||
/**
|
||
* Setter for database number
|
||
*
|
||
* @param integer Database
|
||
* @return void
|
||
* @throws InvalidArgumentException if database number is not valid
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
* @api
|
||
*/
|
||
public function setDatabase($database) {
|
||
if (!is_integer($database)) {
|
||
throw new InvalidArgumentException(
|
||
'The specified database number is of type "' . gettype($database) . '" but an integer is expected.',
|
||
1279763057
|
||
);
|
||
}
|
||
if ($database < 0) {
|
||
throw new InvalidArgumentException(
|
||
'The specified database "' . $database. '" must be greater or equal than zero.',
|
||
1279763534
|
||
);
|
||
}
|
||
$this->database = $database;
|
||
}
|
||
/**
|
||
* Setter for authentication password
|
||
*
|
||
* @param string Password
|
||
* @return void
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
* @api
|
||
*/
|
||
public function setPassword($password) {
|
||
$this->password = $password;
|
||
}
|
||
/**
|
||
* Enable data compression
|
||
*
|
||
* @param boolean TRUE to enable compression
|
||
* @return void
|
||
* @throws InvalidArgumentException if compression parameter is not of type boolean
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
* @api
|
||
*/
|
||
public function setCompression($compression) {
|
||
if (!is_bool($compression)) {
|
||
throw new InvalidArgumentException(
|
||
'The specified compression of type "' . gettype($compression) . '" but a boolean is expected.',
|
||
1289679153
|
||
);
|
||
}
|
||
$this->compression = $compression;
|
||
}
|
||
/**
|
||
* Set data compression level.
|
||
* If compression is enabled and this is not set,
|
||
* gzcompress default level will be used.
|
||
*
|
||
* @param integer -1 to 9: Compression level
|
||
* @return void
|
||
* @throws InvalidArgumentException if compressionLevel parameter is not within allowed bounds
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
* @api
|
||
*/
|
||
public function setCompressionLevel($compressionLevel) {
|
||
if (!is_integer($compressionLevel)) {
|
||
throw new InvalidArgumentException(
|
||
'The specified compression of type "' . gettype($compressionLevel) . '" but an integer is expected.',
|
||
1289679154
|
||
);
|
||
}
|
||
if ($compressionLevel >= -1 && $compressionLevel <= 9) {
|
||
$this->compressionLevel = $compressionLevel;
|
||
} else {
|
||
throw new InvalidArgumentException(
|
||
'The specified compression level must be an integer between -1 and 9.',
|
||
1289679155
|
||
);
|
||
}
|
||
}
|
||
/**
|
||
* Save data in the cache
|
||
*
|
||
* Scales O(1) with number of cache entries
|
||
* Scales O(n) with number of tags
|
||
*
|
||
* @param string Identifier for this specific cache entry
|
||
* @param string Data to be stored
|
||
* @param array Tags to associate with this cache entry
|
||
* @param integer Lifetime of this cache entry in seconds. If NULL is specified, default lifetime is used. "0" means unlimited lifetime.
|
||
* @return void
|
||
* @throws InvalidArgumentException if identifier is not valid
|
||
* @throws t3lib_cache_Exception_InvalidData if data is not a string
|
||
* @author Christopher Hlubek <hlubek@networkteam.com>
|
||
* @author Christian Kuhn <lolli@schwarzbu.ch>
|
||
* @api
|
||
*/
|
||
public function set($entryIdentifier, $data, array $tags = array(), $lifetime = NULL) {
|
||
if (!is_string($entryIdentifier)) {
|
||
throw new InvalidArgumentException(
|
||
'The specified identifier is of type "' . gettype($entryIdentifier) . '" but a string is expected.',
|
||
1279470252
|
||
);
|
||
}
|
||
if (!is_string($data)) {
|