Index: tests/t3lib/cache/backend/t3lib_cache_backend_pdobackendtestcase.php =================================================================== --- tests/t3lib/cache/backend/t3lib_cache_backend_pdobackendtestcase.php (revision 0) +++ tests/t3lib/cache/backend/t3lib_cache_backend_pdobackendtestcase.php (revision 0) @@ -0,0 +1,385 @@ + +* 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 PDO cache backend + * + * @author Christian Kuhn + * @package TYPO3 + * @subpackage tests + * @version $Id$ + */ +class t3lib_cache_backend_PdoBackendTestCase extends tx_phpunit_testcase { + + /** + * Backup of global variable EXEC_TIME + * + * @var array + */ + protected $backupGlobalVariables; + + /** + * @var string + */ + protected $fixtureFolder; + + /** + * @var string + */ + protected $fixtureDB; + + /** + * Sets up this testcase + * + * @author Christian Kuhn + */ + public function setUp() { + if (!extension_loaded('pdo_sqlite')) { + $this->markTestSkipped('pdo_sqlite extension was not available'); + } + + $this->backupGlobalVariables = array( + 'EXEC_TIME' => $GLOBALS['EXEC_TIME'], + ); + } + + /** + * @test + * @author Karsten Dambekalns + * @expectedException t3lib_cache_Exception + */ + public function setThrowsExceptionIfNoFrontEndHasBeenSet() { + $backend = t3lib_div::makeInstance('t3lib_cache_backend_PdoBackend'); + $data = 'Some data'; + $identifier = 'MyIdentifier'; + $backend->set($identifier, $data); + } + + /** + * @test + * @author Christian Jul Jensen + */ + public function itIsPossibleToSetAndCheckExistenceInCache() { + $backend = $this->setUpBackend(); + $data = 'Some data'; + $identifier = 'MyIdentifier'; + $backend->set($identifier, $data); + $this->assertTrue($backend->has($identifier)); + } + + /** + * @test + * @author Christian Jul Jensen + */ + public function itIsPossibleToSetAndGetEntry() { + $backend = $this->setUpBackend(); + $data = 'Some data'; + $identifier = 'MyIdentifier'; + $backend->set($identifier, $data); + $fetchedData = $backend->get($identifier); + $this->assertEquals($data, $fetchedData); + } + + /** + * @test + * @author Christian Jul Jensen + */ + public function itIsPossibleToRemoveEntryFromCache() { + $backend = $this->setUpBackend(); + $data = 'Some data'; + $identifier = 'MyIdentifier'; + $backend->set($identifier, $data); + $backend->remove($identifier); + $this->assertFalse($backend->has($identifier)); + } + + /** + * @test + * @author Christian Jul Jensen + */ + public function itIsPossibleToOverwriteAnEntryInTheCache() { + $backend = $this->setUpBackend(); + $data = 'Some data'; + $identifier = 'MyIdentifier'; + $backend->set($identifier, $data); + $otherData = 'some other data'; + $backend->set($identifier, $otherData); + $fetchedData = $backend->get($identifier); + $this->assertEquals($otherData, $fetchedData); + } + + /** + * @test + * @author Karsten Dambekalns + */ + public function findIdentifiersByTagFindsSetEntries() { + $backend = $this->setUpBackend(); + + $data = 'Some data'; + $entryIdentifier = 'MyIdentifier'; + $backend->set($entryIdentifier, $data, array('UnitTestTag%tag1', 'UnitTestTag%tag2')); + + $retrieved = $backend->findIdentifiersByTag('UnitTestTag%tag1'); + $this->assertEquals($entryIdentifier, $retrieved[0]); + + $retrieved = $backend->findIdentifiersByTag('UnitTestTag%tag2'); + $this->assertEquals($entryIdentifier, $retrieved[0]); + } + + /** + * @test + * @author Christian Kuhn + */ + public function findIdentifiersByTagsFindsSetEntries() { + $backend = $this->setUpBackend(); + + $data = 'Some data'; + $entryIdentifier = 'MyIdentifier'; + $backend->set($entryIdentifier . 'A', $data, array('UnitTestTag%tag1', 'UnitTestTag%tag2')); + $backend->set($entryIdentifier . 'B', $data, array('UnitTestTag%tag1', 'UnitTestTag%tag3')); + $backend->set($entryIdentifier . 'C', $data, array('UnitTestTag%tag2')); + $backend->set($entryIdentifier . 'D', $data, array('UnitTestTag%tag3')); + + $retrieved = $backend->findIdentifiersByTags(array('UnitTestTag%tag1', 'UnitTestTag%tag2')); + $this->assertEquals($entryIdentifier . 'A', $retrieved[0]); + $this->assertEquals($entryIdentifier . 'B', $retrieved[1]); + $this->assertEquals($entryIdentifier . 'C', $retrieved[2]); + $this->assertFalse(array_key_exists(3, $retrieved)); + } + + /** + * @test + * @author Karsten Dambekalns + */ + public function setRemovesTagsFromPreviousSet() { + $backend = $this->setUpBackend(); + + $data = 'Some data'; + $entryIdentifier = 'MyIdentifier'; + $backend->set($entryIdentifier, $data, array('UnitTestTag%tag1', 'UnitTestTag%tag2')); + $backend->set($entryIdentifier, $data, array('UnitTestTag%tag3')); + + $retrieved = $backend->findIdentifiersByTag('UnitTestTag%tag2'); + $this->assertEquals(array(), $retrieved); + } + + /** + * @test + * @author Christian Jul Jensen + */ + public function hasReturnsFalseIfTheEntryDoesntExist() { + $backend = $this->setUpBackend(); + $identifier = 'NonExistingIdentifier'; + $this->assertFalse($backend->has($identifier)); + } + + /** + * @test + * @author Christian Jul Jensen + */ + public function removeReturnsFalseIfTheEntryDoesntExist() { + $backend = $this->setUpBackend(); + $identifier = 'NonExistingIdentifier'; + $this->assertFalse($backend->remove($identifier)); + } + + /** + * @test + * @author Robert Lemke + * @author Karsten Dambekalns + */ + public function flushByTagRemovesCacheEntriesWithSpecifiedTag() { + $backend = $this->setUpBackend(); + + $data = 'some data' . microtime(); + $backend->set('PdoBackendTest1', $data, array('UnitTestTag%test', 'UnitTestTag%boring')); + $backend->set('PdoBackendTest2', $data, array('UnitTestTag%test', 'UnitTestTag%special')); + $backend->set('PdoBackendTest3', $data, array('UnitTestTag%test')); + + $backend->flushByTag('UnitTestTag%special'); + + $this->assertTrue($backend->has('PdoBackendTest1'), 'PdoBackendTest1'); + $this->assertFalse($backend->has('PdoBackendTest2'), 'PdoBackendTest2'); + $this->assertTrue($backend->has('PdoBackendTest3'), 'PdoBackendTest3'); + } + + /** + * @test + * @author Robert Lemke + * @author Karsten Dambekalns + */ + public function flushByTagsRemovesCacheEntriesWithSpecifiedTags() { + $backend = $this->setUpBackend(); + + $data = 'some data' . microtime(); + $backend->set('PdoBackendTest1', $data, array('UnitTestTag%test', 'UnitTestTag%boring')); + $backend->set('PdoBackendTest2', $data, array('UnitTestTag%test', 'UnitTestTag%special1')); + $backend->set('PdoBackendTest3', $data, array('UnitTestTag%test', 'UnitTestTag%special2')); + $backend->set('PdoBackendTest4', $data, array('UnitTestTag%test', 'UnitTestTag%special2')); + + $backend->flushByTags(array('UnitTestTag%special1','UnitTestTag%special2')); + + $this->assertTrue($backend->has('PdoBackendTest1')); + $this->assertFalse($backend->has('PdoBackendTest2')); + $this->assertFalse($backend->has('PdoBackendTest3')); + $this->assertFalse($backend->has('PdoBackendTest4')); + } + + /** + * @test + * @author Karsten Dambekalns + */ + public function flushRemovesAllCacheEntries() { + $backend = $this->setUpBackend(); + + $data = 'some data' . microtime(); + $backend->set('PdoBackendTest1', $data); + $backend->set('PdoBackendTest2', $data); + $backend->set('PdoBackendTest3', $data); + + $backend->flush(); + + $this->assertFalse($backend->has('PdoBackendTest1'), 'PdoBackendTest1'); + $this->assertFalse($backend->has('PdoBackendTest2'), 'PdoBackendTest2'); + $this->assertFalse($backend->has('PdoBackendTest3'), 'PdoBackendTest3'); + } + + /** + * @test + * @author Karsten Dambekalns + */ + public function flushRemovesOnlyOwnEntries() { + $thisCache = $this->getMock('t3lib_cache_frontend_Frontend', array(), array(), '', FALSE); + $thisCache->expects($this->any())->method('getIdentifier')->will($this->returnValue('thisCache')); + $thisBackend = $this->setUpBackend(); + $thisBackend->setCache($thisCache); + + $thatCache = $this->getMock('t3lib_cache_frontend_Frontend', array(), array(), '', FALSE); + $thatCache->expects($this->any())->method('getIdentifier')->will($this->returnValue('thatCache')); + $thatBackend = $this->setUpBackend(); + $thatBackend->setCache($thatCache); + + $thisBackend->set('thisEntry', 'Hello'); + $thatBackend->set('thatEntry', 'World!'); + $thatBackend->flush(); + + $this->assertEquals('Hello', $thisBackend->get('thisEntry')); + $this->assertFalse($thatBackend->has('thatEntry')); + } + + /** + * @test + * @author Ingo Renner + * @author Christian Kuhn + */ + public function collectGarbageReallyRemovesAnExpiredCacheEntry() { + $backend = $this->setUpBackend(); + + $data = 'some data' . microtime(); + $entryIdentifier = 'BackendPDORemovalTest'; + $backend->set($entryIdentifier, $data, array(), 1); + + $this->assertTrue($backend->has($entryIdentifier)); + + $GLOBALS['EXEC_TIME'] += 2; + $backend->collectGarbage(); + + $this->assertFalse($backend->has($entryIdentifier)); + } + + /** + * @test + * @author Ingo Renner + * @author Christian Kuhn + */ + public function collectGarbageReallyRemovesAllExpiredCacheEntries() { + $backend = $this->setUpBackend(); + + $data = 'some data' . microtime(); + $entryIdentifier = 'BackendPDORemovalTest'; + + $backend->set($entryIdentifier . 'A', $data, array(), NULL); + $backend->set($entryIdentifier . 'B', $data, array(), 10); + $backend->set($entryIdentifier . 'C', $data, array(), 1); + $backend->set($entryIdentifier . 'D', $data, array(), 1); + + $this->assertTrue($backend->has($entryIdentifier . 'A')); + $this->assertTrue($backend->has($entryIdentifier . 'B')); + $this->assertTrue($backend->has($entryIdentifier . 'C')); + $this->assertTrue($backend->has($entryIdentifier . 'D')); + + $GLOBALS['EXEC_TIME'] += 2; + $backend->collectGarbage(); + + $this->assertTrue($backend->has($entryIdentifier . 'A')); + $this->assertTrue($backend->has($entryIdentifier . 'B')); + $this->assertFalse($backend->has($entryIdentifier . 'C')); + $this->assertFalse($backend->has($entryIdentifier . 'D')); + } + + /** + * Sets up the PDO backend used for testing + * + * @return t3lib_cache_backend_PdoBackend + * @author Karsten Dambekalns + */ + protected function setUpBackend() { + $this->fixtureFolder = sys_get_temp_dir() . '/' . 'typo3pdobackendtest/'; + t3lib_div::mkdir_deep(sys_get_temp_dir() . '/', 'typo3pdobackendtest/'); + $this->fixtureDB = uniqid('Cache') . '.db'; + + $pdoHelper = t3lib_div::makeInstance('t3lib_PdoHelper', 'sqlite:' . $this->fixtureFolder . $this->fixtureDB, '', ''); + $pdoHelper->importSql(PATH_t3lib . 'cache/backend/resources/ddl.sql'); + + $mockCache = $this->getMock('t3lib_cache_frontend_Frontend', array(), array(), '', FALSE); + $mockCache->expects($this->any())->method('getIdentifier')->will($this->returnValue('TestCache')); + + $backendOptions = array( + 'dataSourceName' => 'sqlite:' . $this->fixtureFolder . $this->fixtureDB, + 'username' => '', + 'password' => '', + ); + $backend = t3lib_div::makeInstance('t3lib_cache_backend_PdoBackend', $backendOptions); + $backend->setCache($mockCache); + + return $backend; + } + + /** + * Clean up after the tests + * + * @return void + * @author Karsten Dambekalns + */ + public function tearDown() { + if ($this->fixtureDB) { + t3lib_div::rmdir($this->fixtureFolder, TRUE); + } + foreach ($this->backupGlobalVariables as $key => $data) { + $GLOBALS[$key] = $data; + } + } +} + +?> Index: t3lib/config_default.php =================================================================== --- t3lib/config_default.php (revision 7828) +++ t3lib/config_default.php (working copy) @@ -117,6 +117,7 @@ 't3lib_cache_backend_FileBackend' => 't3lib/cache/backend/class.t3lib_cache_backend_filebackend.php:t3lib_cache_backend_FileBackend', 't3lib_cache_backend_GlobalsBackend' => 't3lib/cache/backend/class.t3lib_cache_backend_globalsbackend.php:t3lib_cache_backend_GlobalsBackend', 't3lib_cache_backend_MemcachedBackend' => 't3lib/cache/backend/class.t3lib_cache_backend_memcachedbackend.php:t3lib_cache_backend_MemcachedBackend', + 't3lib_cache_backend_PdoBackend' => 't3lib/cache/backend/class.t3lib_cache_backend_pdobackend.php:t3lib_cache_backend_PdoBackend', 't3lib_cache_backend_ApcBackend' => 't3lib/cache/backend/class.t3lib_cache_backend_apcbackend.php:t3lib_cache_backend_ApcBackend', 't3lib_cache_backend_NullBackend' => 't3lib/cache/backend/class.t3lib_cache_backend_nullbackend.php:t3lib_cache_backend_NullBackend' ), Index: t3lib/cache/backend/resources/ddl.sql =================================================================== --- t3lib/cache/backend/resources/ddl.sql (revision 0) +++ t3lib/cache/backend/resources/ddl.sql (revision 0) @@ -0,0 +1,22 @@ +BEGIN; + +CREATE TABLE "cache" ( + "identifier" VARCHAR(250) NOT NULL, + "cache" VARCHAR(250) NOT NULL, + "scope" CHAR(12) NOT NULL, + "created" INTEGER UNSIGNED NOT NULL, + "lifetime" INTEGER UNSIGNED DEFAULT '0' NOT NULL, + "content" TEXT, + PRIMARY KEY ("identifier", "cache", "scope") +); + +CREATE TABLE "tags" ( + "identifier" VARCHAR(250) NOT NULL, + "cache" VARCHAR(250) NOT NULL, + "scope" CHAR(12) NOT NULL, + "tag" VARCHAR(250) NOT NULL +); +CREATE INDEX "identifier" ON "tags" ("identifier", "cache", "scope"); +CREATE INDEX "tag" ON "tags" ("tag"); + +COMMIT; Index: t3lib/cache/backend/class.t3lib_cache_backend_pdobackend.php =================================================================== --- t3lib/cache/backend/class.t3lib_cache_backend_pdobackend.php (revision 0) +++ t3lib/cache/backend/class.t3lib_cache_backend_pdobackend.php (revision 0) @@ -0,0 +1,440 @@ + +* 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 PDO database cache backend + * + * @package TYPO3 + * @subpackage t3lib_cache + * @api + * @scope prototype + * @author Christian Kuhn + * @version $Id$ + */ +class t3lib_cache_backend_PdoBackend extends t3lib_cache_backend_AbstractBackend { + /** + * @var string + */ + protected $dataSourceName; + + /** + * @var string + */ + protected $username; + + /** + * @var string + */ + protected $password; + + /** + * Used to seperate stored data by user, SAPI, context, ... + * @var string + */ + protected $scope; + + /** + * @var PDO + */ + protected $databaseHandle; + + /** + * @var string + */ + protected $pdoDriver; + + /** + * Constructs this backend + * + * @param mixed $options Configuration options - depends on the actual backend + * @author Christian Kuhn + */ + public function __construct(array $options = array()) { + parent::__construct($options); + + $this->connect(); + } + + /** + * Sets the DSN to use + * + * @param string $DSN The DSN to use for connecting to the DB + * @return void + * @author Karsten Dambekalns + * @api + */ + public function setDataSourceName($DSN) { + $this->dataSourceName = $DSN; + } + + /** + * Sets the username to use + * + * @param string $username The username to use for connecting to the DB + * @return void + * @author Karsten Dambekalns + * @api + */ + public function setUsername($username) { + $this->username = $username; + } + + /** + * Sets the password to use + * + * @param string $password The password to use for connecting to the DB + * @return void + * @author Karsten Dambekalns + * @api + */ + public function setPassword($password) { + $this->password = $password; + } + + /** + * Initializes the identifier prefix when setting the cache. + * + * @param t3lib_cache_frontend_Frontend $cache + * @return void + * @author Robert Lemke + * @author Karsten Dambekalns + */ + public function setCache(t3lib_cache_frontend_Frontend $cache) { + parent::setCache($cache); + $processUser = extension_loaded('posix') ? posix_getpwuid(posix_geteuid()) : array('name' => 'default'); + $this->scope = t3lib_div::shortMD5(PATH_site . $processUser['name'], 12); + } + + /** + * 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 t3lib_cache_exception_InvalidData if $data is not a string + * @author Karsten Dambekalns + * @api + */ + 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().', + 1259515600 + ); + } + + if (!is_string($data)) { + throw new t3lib_cache_exception_InvalidData( + 'The specified data is of type "' . gettype($data) . '" but a string is expected.', + 1259515601 + ); + } + + if ($this->has($entryIdentifier)) { + $this->remove($entryIdentifier); + } + + $lifetime = ($lifetime === NULL) ? $this->defaultLifetime : $lifetime; + + $statementHandle = $this->databaseHandle->prepare( + 'INSERT INTO "cache" ("identifier", "scope", "cache", "created", "lifetime", "content") VALUES (?, ?, ?, ?, ?, ?)' + ); + $result = $statementHandle->execute( + array($entryIdentifier, $this->scope, $this->cacheIdentifier, $GLOBALS['EXEC_TIME'], $lifetime, $data) + ); + + if ($result === FALSE) { + throw new t3lib_cache_Exception( + 'The cache entry "' . $entryIdentifier . '" could not be written.', + 1259530791 + ); + } + + $statementHandle = $this->databaseHandle->prepare( + 'INSERT INTO "tags" ("identifier", "scope", "cache", "tag") VALUES (?, ?, ?, ?)' + ); + + foreach ($tags as $tag) { + $result = $statementHandle->execute( + array($entryIdentifier, $this->scope, $this->cacheIdentifier, $tag) + ); + if ($result === FALSE) { + throw new t3lib_cache_Exception( + 'The tag "' . $tag . ' for cache entry "' . $entryIdentifier . '" could not be written.', + 1259530751 + ); + } + } + } + + /** + * 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 + * @author Karsten Dambekalns + * @api + */ + public function get($entryIdentifier) { + $statementHandle = $this->databaseHandle->prepare( + 'SELECT "content" FROM "cache" WHERE "identifier"=? AND "scope"=? AND "cache"=?' . $this->getNotExpiredStatement() + ); + $statementHandle->execute( + array($entryIdentifier, $this->scope, $this->cacheIdentifier) + ); + return $statementHandle->fetchColumn(); + } + + /** + * 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 + * @author Karsten Dambekalns + * @api + */ + public function has($entryIdentifier) { + $statementHandle = $this->databaseHandle->prepare( + 'SELECT COUNT("identifier") FROM "cache" WHERE "identifier"=? AND "scope"=? AND "cache"=?' . $this->getNotExpiredStatement() + ); + $statementHandle->execute( + array($entryIdentifier, $this->scope, $this->cacheIdentifier) + ); + return ($statementHandle->fetchColumn() > 0); + } + + /** + * 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 + * @author Karsten Dambekalns + * @api + */ + public function remove($entryIdentifier) { + $statementHandle = $this->databaseHandle->prepare( + 'DELETE FROM "tags" WHERE "identifier"=? AND "scope"=? AND "cache"=?' + ); + $statementHandle->execute( + array($entryIdentifier, $this->scope, $this->cacheIdentifier) + ); + + $statementHandle = $this->databaseHandle->prepare( + 'DELETE FROM "cache" WHERE "identifier"=? AND "scope"=? AND "cache"=?' + ); + $statementHandle->execute( + array($entryIdentifier, $this->scope, $this->cacheIdentifier) + ); + + return ($statementHandle->rowCount() > 0); + } + + /** + * Removes all cache entries of this cache. + * + * @return void + * @author Karsten Dambekalns + * @api + */ + public function flush() { + $statementHandle = $this->databaseHandle->prepare( + 'DELETE FROM "tags" WHERE "scope"=? AND "cache"=?' + ); + $statementHandle->execute( + array($this->scope, $this->cacheIdentifier) + ); + + $statementHandle = $this->databaseHandle->prepare( + 'DELETE FROM "cache" WHERE "scope"=? AND "cache"=?' + ); + $statementHandle->execute( + array($this->scope, $this->cacheIdentifier) + ); + } + + /** + * 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 + * @author Robert Lemke + * @api + */ + public function flushByTag($tag) { + $statementHandle = $this->databaseHandle->prepare( + 'DELETE FROM "cache" WHERE "scope"=? AND "cache"=? AND "identifier" IN (SELECT "identifier" FROM "tags" WHERE "scope"=? AND "cache"=? AND "tag"=?)' + ); + $statementHandle->execute( + array($this->scope, $this->cacheIdentifier,$this->scope, $this->cacheIdentifier, $tag) + ); + + $statementHandle = $this->databaseHandle->prepare( + 'DELETE FROM "tags" WHERE "scope"=? AND "cache"=? AND "tag"=?' + ); + $statementHandle->execute( + array($this->scope, $this->cacheIdentifier, $tag) + ); + } + + /** + * Removes all cache entries of this cache which are tagged by the specified tags. + * This method doesn't exist in FLOW3, but is mandatory for TYPO3v4. + * + * @TODO: Make smarter + * @param array $tags The tags the entries must have + * @return void + * @author Christian Kuhn + */ + public function flushBytags(array $tags) { + foreach($tags as $tag) { + $this->flushByTag($tag); + } + } + + /** + * 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 + * @author Karsten Dambekalns + * @api + */ + public function findIdentifiersByTag($tag) { + $statementHandle = $this->databaseHandle->prepare( + 'SELECT "identifier" FROM "tags" WHERE "scope"=? AND "cache"=? AND "tag"=?' + ); + $statementHandle->execute( + array($this->scope, $this->cacheIdentifier, $tag) + ); + return $statementHandle->fetchAll(PDO::FETCH_COLUMN); + } + + /** + * Finds and returns all cache entry identifiers which are tagged by the + * specified tags. + * This method doesn't exist in FLOW3, but is mandatory for TYPO3v4. + * + * @TODO: Make smarter + * @param array $tags Tags to search for + * @return array An array with identifiers of all matching entries. An empty array if no entries matched + * @author Christian Kuhn + */ + public function findIdentifiersByTags(array $tags) { + $cacheEntryIdentifiers = array(); + foreach($tags as $tag) { + $cacheEntryIdentifiers = array_unique(array_merge($cacheEntryIdentifiers, $this->findIdentifiersByTag($tag))); + } + return array_values($cacheEntryIdentifiers); + } + + /** + * Does garbage collection + * + * @return void + * @author Karsten Dambekalns + * @api + */ + public function collectGarbage() { + $statementHandle = $this->databaseHandle->prepare( + 'DELETE FROM "tags" WHERE "scope"=? AND "cache"=? AND "identifier" IN ' . + '(SELECT "identifier" FROM "cache" WHERE "scope"=? AND "cache"=? AND "lifetime" > 0 AND "created" + "lifetime" < ' . $GLOBALS['EXEC_TIME'] . ')' + ); + $statementHandle->execute( + array($this->scope, $this->cacheIdentifier, $this->scope, $this->cacheIdentifier) + ); + + $statementHandle = $this->databaseHandle->prepare( + 'DELETE FROM "cache" WHERE "scope"=? AND "cache"=? AND "lifetime" > 0 AND "created" + "lifetime" < ' . $GLOBALS['EXEC_TIME'] + ); + $statementHandle->execute( + array($this->scope, $this->cacheIdentifier) + ); + } + + /** + * Returns an SQL statement that evaluates to true if the entry is not expired. + * + * @return string + * @author Karsten Dambekalns + */ + protected function getNotExpiredStatement() { + return ' AND ("lifetime" = 0 OR "created" + "lifetime" >= ' . $GLOBALS['EXEC_TIME'] . ')'; + } + + /** + * Connect to the database + * + * @return void + * @author Karsten Dambekalns + */ + protected function connect() { + try { + $splitdsn = explode(':', $this->dataSourceName, 2); + $this->pdoDriver = $splitdsn[0]; + + if ($this->pdoDriver === 'sqlite' && !file_exists($splitdsn[1])) { + $this->createCacheTables(); + } + + $this->databaseHandle = t3lib_div::makeInstance('PDO', $this->dataSourceName, $this->username, $this->password); + $this->databaseHandle->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + if ($this->pdoDriver === 'mysql') { + $this->databaseHandle->exec('SET SESSION sql_mode=\'ANSI\';'); + } + } catch (PDOException $e) { +# $this->createCacheTables(); + } + } + + /** + * Creates the tables needed for the cache backend. + * + * @return void + * @throws \RuntimeException if something goes wrong + * @author Karsten Dambekalns + */ + protected function createCacheTables() { + try { + $pdoHelper = t3lib_div::makeInstance('t3lib_PdoHelper', $this->dataSourceName, $this->username, $this->password); + $pdoHelper->importSql(PATH_t3lib . 'cache/backend/resources/ddl.sql'); + } catch (PDOException $e) { + throw new RuntimeException( + 'Could not create cache tables with DSN "' . $this->dataSourceName . '". PDO error: ' . $e->getMessage(), + 1259576985 + ); + } + } +} + +if (defined('TYPO3_MODE') && $GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/cache/backend/class.t3lib_cache_backend_pdobackend.php']) { + include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/cache/backend/class.t3lib_cache_backend_pdobackend.php']); +} +?> Index: t3lib/core_autoload.php =================================================================== --- t3lib/core_autoload.php (revision 7828) +++ t3lib/core_autoload.php (working copy) @@ -91,6 +91,7 @@ 't3lib_cache_backend_filebackend' => PATH_t3lib . 'cache/backend/class.t3lib_cache_backend_filebackend.php', 't3lib_cache_backend_memcachedbackend' => PATH_t3lib . 'cache/backend/class.t3lib_cache_backend_memcachedbackend.php', 't3lib_cache_backend_nullbackend' => PATH_t3lib . 'cache/backend/class.t3lib_cache_backend_nullbackend.php', + 't3lib_cache_backend_pdobackend' => PATH_t3lib . 'cache/backend/class.t3lib_cache_backend_pdobackend.php', 't3lib_cache_backend_transientmemorybackend' => PATH_t3lib . 'cache/backend/class.t3lib_cache_backend_transientmemorybackend.php', 't3lib_cache_backend_backend' => PATH_t3lib . 'cache/backend/interfaces/interface.t3lib_cache_backend_backend.php', 't3lib_cache_exception_classalreadyloaded' => PATH_t3lib . 'cache/exception/class.t3lib_cache_exception_classalreadyloaded.php', @@ -121,6 +122,7 @@ 't3lib_matchcondition_abstract' => PATH_t3lib . 'matchcondition/class.t3lib_matchcondition_abstract.php', 't3lib_matchcondition_backend' => PATH_t3lib . 'matchcondition/class.t3lib_matchcondition_backend.php', 't3lib_matchcondition_frontend' => PATH_t3lib . 'matchcondition/class.t3lib_matchcondition_frontend.php', + 't3lib_pdohelper' => PATH_t3lib . 'class.t3lib_pdohelper.php', 't3lib_tceforms_suggest' => PATH_t3lib . 'tceforms/class.t3lib_tceforms_suggest.php', 't3lib_tceforms_suggest_defaultreceiver' => PATH_t3lib . 'tceforms/class.t3lib_tceforms_suggest_defaultreceiver.php', 't3lib_utility_client' => PATH_t3lib . 'utility/class.t3lib_utility_client.php', Index: t3lib/class.t3lib_pdohelper.php =================================================================== --- t3lib/class.t3lib_pdohelper.php (revision 0) +++ t3lib/class.t3lib_pdohelper.php (revision 0) @@ -0,0 +1,103 @@ + + * 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. + * A copy is found in the textfile GPL.txt and important notices to the license + * from the author is found in LICENSE.txt distributed with these scripts. + * + * + * 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 helper class for handling PDO databases + * Backport of FLOW3 class PdoHelper, last synced version: 3528 + * + * @author Karsten Dambekalns + * @package TYPO3 + * @subpackage t3lib + * @version $Id$ + * @scope prototype + */ +class t3lib_PdoHelper { + + /** + * @var PDO + */ + protected $databaseHandle; + + /** + * @var string + */ + protected $pdoDriver; + + /** + * Construct the helper instance and set up PDO connection. + * + * @param string $dataSourceName + * @param string $user + * @param string $password + * @author Karsten Dambekalns + */ + public function __construct($dataSourceName, $user, $password) { + $splitdsn = explode(':', $dataSourceName, 2); + $this->pdoDriver = $splitdsn[0]; + + $this->databaseHandle = t3lib_div::makeInstance('PDO', $dataSourceName, $user, $password); + $this->databaseHandle->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + if ($this->pdoDriver === 'mysql') { + $this->databaseHandle->exec('SET SESSION sql_mode=\'ANSI_QUOTES\';'); + } + } + + /** + * Pumps the SQL into the database. Use for DDL only. + * + * Important: key definitions with length specifiers (needed for MySQL) must + * be given as "field"(xyz) - no space between double quote and parenthesis - + * so they can be removed automatically. + * + * @param string $pathAndFilename + * @return void + * @author Karsten Dambekalns + */ + public function importSql($pathAndFilename) { + $sql = file($pathAndFilename, FILE_IGNORE_NEW_LINES & FILE_SKIP_EMPTY_LINES); + + // Remove MySQL style key length delimiters (yuck!) if we are not setting up a MySQL db + if ($this->pdoDriver !== 'mysql') { + $sql = preg_replace('/"\([0-9]+\)/', '"', $sql); + } + + $statement = ''; + foreach ($sql as $line) { + $statement .= ' ' . trim($line); + if (substr($statement, -1) === ';') { + $this->databaseHandle->query($statement); + $statement = ''; + } + } + } +} + +if (defined('TYPO3_MODE') && $GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_pdohelper.php']) { + include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_pdohelper.php']); +} + +?>