TYPO3 API  SVNRelease
class.t3lib_cache_backend_pdobackend.php
Go to the documentation of this file.
00001 <?php
00002 /***************************************************************
00003  *  Copyright notice
00004  *
00005  *  (c) 2010-2011 Christian Kuhn <lolli@schwarzbu.ch>
00006  *  All rights reserved
00007  *
00008  *  This script is part of the TYPO3 project. The TYPO3 project is
00009  *  free software; you can redistribute it and/or modify
00010  *  it under the terms of the GNU General Public License as published by
00011  *  the Free Software Foundation; either version 2 of the License, or
00012  *  (at your option) any later version.
00013  *
00014  *  The GNU General Public License can be found at
00015  *  http://www.gnu.org/copyleft/gpl.html.
00016  *
00017  *  This script is distributed in the hope that it will be useful,
00018  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00019  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00020  *  GNU General Public License for more details.
00021  *
00022  *  This copyright notice MUST APPEAR in all copies of the script!
00023  ***************************************************************/
00024 
00025 /**
00026  * A PDO database cache backend
00027  *
00028  * @package TYPO3
00029  * @subpackage t3lib_cache
00030  * @api
00031  * @scope prototype
00032  * @author Christian Kuhn <lolli@schwarzbu.ch>
00033  * @version $Id$
00034  */
00035 class t3lib_cache_backend_PdoBackend extends t3lib_cache_backend_AbstractBackend {
00036 
00037     /**
00038      * @var string
00039      */
00040     protected $dataSourceName;
00041 
00042     /**
00043      * @var string
00044      */
00045     protected $username;
00046 
00047     /**
00048      * @var string
00049      */
00050     protected $password;
00051 
00052     /**
00053      * Used to seperate stored data by user, SAPI, context, ...
00054      * @var string
00055      */
00056     protected $scope;
00057 
00058     /**
00059      * @var PDO
00060      */
00061     protected $databaseHandle;
00062 
00063     /**
00064      * @var string
00065      */
00066     protected $pdoDriver;
00067 
00068     /**
00069      * Constructs this backend
00070      *
00071      * @param array $options Configuration options - depends on the actual backend
00072      * @author Christian Kuhn <lolli@schwarzbu.ch>
00073      */
00074     public function __construct(array $options = array()) {
00075         parent::__construct($options);
00076 
00077         $this->connect();
00078     }
00079 
00080     /**
00081      * Sets the DSN to use
00082      *
00083      * @param string $DSN The DSN to use for connecting to the DB
00084      * @return void
00085      * @author Karsten Dambekalns <karsten@typo3.org>
00086      * @api
00087      */
00088     public function setDataSourceName($DSN) {
00089         $this->dataSourceName = $DSN;
00090     }
00091 
00092     /**
00093      * Sets the username to use
00094      *
00095      * @param string $username The username to use for connecting to the DB
00096      * @return void
00097      * @author Karsten Dambekalns <karsten@typo3.org>
00098      * @api
00099      */
00100     public function setUsername($username) {
00101         $this->username = $username;
00102     }
00103 
00104     /**
00105      * Sets the password to use
00106      *
00107      * @param string $password The password to use for connecting to the DB
00108      * @return void
00109      * @author Karsten Dambekalns <karsten@typo3.org>
00110      * @api
00111      */
00112     public function setPassword($password) {
00113         $this->password = $password;
00114     }
00115 
00116     /**
00117      * Initializes the identifier prefix when setting the cache.
00118      *
00119      * @param t3lib_cache_frontend_Frontend $cache
00120      * @return void
00121      * @author Robert Lemke <robert@typo3.org>
00122      * @author Karsten Dambekalns <karsten@typo3.org>
00123      */
00124     public function setCache(t3lib_cache_frontend_Frontend $cache) {
00125         parent::setCache($cache);
00126         $processUser = extension_loaded('posix') ? posix_getpwuid(posix_geteuid()) : array('name' => 'default');
00127         $this->scope = t3lib_div::shortMD5(PATH_site . $processUser['name'], 12);
00128     }
00129 
00130     /**
00131      * Saves data in the cache.
00132      *
00133      * @param string $entryIdentifier An identifier for this specific cache entry
00134      * @param string $data The data to be stored
00135      * @param array $tags Tags to associate with this cache entry
00136      * @param integer $lifetime Lifetime of this cache entry in seconds. If NULL is specified, the default lifetime is used. "0" means unlimited liftime.
00137      * @return void
00138      * @throws t3lib_cache_Exception if no cache frontend has been set.
00139      * @throws t3lib_cache_exception_InvalidData if $data is not a string
00140      * @author Karsten Dambekalns <karsten@typo3.org>
00141      * @api
00142      */
00143     public function set($entryIdentifier, $data, array $tags = array(), $lifetime = NULL) {
00144         if (!$this->cache instanceof t3lib_cache_frontend_Frontend) {
00145             throw new t3lib_cache_Exception(
00146                 'No cache frontend has been set yet via setCache().',
00147                 1259515600
00148             );
00149         }
00150 
00151         if (!is_string($data)) {
00152             throw new t3lib_cache_exception_InvalidData(
00153                 'The specified data is of type "' . gettype($data) . '" but a string is expected.',
00154                 1259515601
00155             );
00156         }
00157 
00158         if ($this->has($entryIdentifier)) {
00159             $this->remove($entryIdentifier);
00160         }
00161 
00162         $lifetime = ($lifetime === NULL) ? $this->defaultLifetime : $lifetime;
00163 
00164         $statementHandle = $this->databaseHandle->prepare(
00165             'INSERT INTO "cache" ("identifier", "scope", "cache", "created", "lifetime", "content") VALUES (?, ?, ?, ?, ?, ?)'
00166         );
00167         $result = $statementHandle->execute(
00168             array($entryIdentifier, $this->scope, $this->cacheIdentifier, $GLOBALS['EXEC_TIME'], $lifetime, $data)
00169         );
00170 
00171         if ($result === FALSE) {
00172             throw new t3lib_cache_Exception(
00173                 'The cache entry "' . $entryIdentifier . '" could not be written.',
00174                 1259530791
00175             );
00176         }
00177 
00178         $statementHandle = $this->databaseHandle->prepare(
00179             'INSERT INTO "tags" ("identifier", "scope", "cache", "tag") VALUES (?, ?, ?, ?)'
00180         );
00181 
00182         foreach ($tags as $tag) {
00183             $result = $statementHandle->execute(
00184                 array($entryIdentifier, $this->scope, $this->cacheIdentifier, $tag)
00185             );
00186             if ($result === FALSE) {
00187                 throw new t3lib_cache_Exception(
00188                     'The tag "' . $tag . ' for cache entry "' . $entryIdentifier . '" could not be written.',
00189                     1259530751
00190                 );
00191             }
00192         }
00193     }
00194 
00195     /**
00196      * Loads data from the cache.
00197      *
00198      * @param string $entryIdentifier An identifier which describes the cache entry to load
00199      * @return mixed The cache entry's content as a string or FALSE if the cache entry could not be loaded
00200      * @author Karsten Dambekalns <karsten@typo3.org>
00201      * @api
00202      */
00203     public function get($entryIdentifier) {
00204         $statementHandle = $this->databaseHandle->prepare(
00205             'SELECT "content" FROM "cache" WHERE "identifier"=? AND "scope"=? AND "cache"=?' . $this->getNotExpiredStatement()
00206         );
00207         $statementHandle->execute(
00208             array($entryIdentifier, $this->scope, $this->cacheIdentifier)
00209         );
00210         return $statementHandle->fetchColumn();
00211     }
00212 
00213     /**
00214      * Checks if a cache entry with the specified identifier exists.
00215      *
00216      * @param string $entryIdentifier An identifier specifying the cache entry
00217      * @return boolean TRUE if such an entry exists, FALSE if not
00218      * @author Karsten Dambekalns <karsten@typo3.org>
00219      * @api
00220      */
00221     public function has($entryIdentifier) {
00222         $statementHandle = $this->databaseHandle->prepare(
00223             'SELECT COUNT("identifier") FROM "cache" WHERE "identifier"=? AND "scope"=? AND "cache"=?' . $this->getNotExpiredStatement()
00224         );
00225         $statementHandle->execute(
00226             array($entryIdentifier, $this->scope, $this->cacheIdentifier)
00227         );
00228         return ($statementHandle->fetchColumn() > 0);
00229     }
00230 
00231     /**
00232      * Removes all cache entries matching the specified identifier.
00233      * Usually this only affects one entry but if - for what reason ever -
00234      * old entries for the identifier still exist, they are removed as well.
00235      *
00236      * @param string $entryIdentifier Specifies the cache entry to remove
00237      * @return boolean TRUE if (at least) an entry could be removed or FALSE if no entry was found
00238      * @author Karsten Dambekalns <karsten@typo3.org>
00239      * @api
00240      */
00241     public function remove($entryIdentifier) {
00242         $statementHandle = $this->databaseHandle->prepare(
00243             'DELETE FROM "tags" WHERE "identifier"=? AND "scope"=? AND "cache"=?'
00244         );
00245         $statementHandle->execute(
00246             array($entryIdentifier, $this->scope, $this->cacheIdentifier)
00247         );
00248 
00249         $statementHandle = $this->databaseHandle->prepare(
00250             'DELETE FROM "cache" WHERE "identifier"=? AND "scope"=? AND "cache"=?'
00251         );
00252         $statementHandle->execute(
00253             array($entryIdentifier, $this->scope, $this->cacheIdentifier)
00254         );
00255 
00256         return ($statementHandle->rowCount() > 0);
00257     }
00258 
00259     /**
00260      * Removes all cache entries of this cache.
00261      *
00262      * @return void
00263      * @author Karsten Dambekalns <karsten@typo3.org>
00264      * @api
00265      */
00266     public function flush() {
00267         $statementHandle = $this->databaseHandle->prepare(
00268             'DELETE FROM "tags" WHERE "scope"=? AND "cache"=?'
00269         );
00270         $statementHandle->execute(
00271             array($this->scope, $this->cacheIdentifier)
00272         );
00273 
00274         $statementHandle = $this->databaseHandle->prepare(
00275             'DELETE FROM "cache" WHERE "scope"=? AND "cache"=?'
00276         );
00277         $statementHandle->execute(
00278             array($this->scope, $this->cacheIdentifier)
00279         );
00280     }
00281 
00282     /**
00283      * Removes all cache entries of this cache which are tagged by the specified tag.
00284      *
00285      * @param string $tag The tag the entries must have
00286      * @return void
00287      * @author Robert Lemke <robert@typo3.org>
00288      * @api
00289      */
00290     public function flushByTag($tag) {
00291         $statementHandle = $this->databaseHandle->prepare(
00292             'DELETE FROM "cache" WHERE "scope"=? AND "cache"=? AND "identifier" IN (SELECT "identifier" FROM "tags" WHERE "scope"=? AND "cache"=? AND "tag"=?)'
00293         );
00294         $statementHandle->execute(
00295             array($this->scope, $this->cacheIdentifier, $this->scope, $this->cacheIdentifier, $tag)
00296         );
00297 
00298         $statementHandle = $this->databaseHandle->prepare(
00299             'DELETE FROM "tags" WHERE "scope"=? AND "cache"=? AND "tag"=?'
00300         );
00301         $statementHandle->execute(
00302             array($this->scope, $this->cacheIdentifier, $tag)
00303         );
00304     }
00305 
00306     /**
00307      * Removes all cache entries of this cache which are tagged by the specified tags.
00308      * This method doesn't exist in FLOW3, but is mandatory for TYPO3v4.
00309      *
00310      * @TODO: Make smarter
00311      * @param array $tags The tags the entries must have
00312      * @return void
00313      * @author Christian Kuhn <lolli@schwarzbu.ch>
00314      */
00315     public function flushBytags(array $tags) {
00316         foreach ($tags as $tag) {
00317             $this->flushByTag($tag);
00318         }
00319     }
00320 
00321     /**
00322      * Finds and returns all cache entry identifiers which are tagged by the
00323      * specified tag.
00324      *
00325      * @param string $tag The tag to search for
00326      * @return array An array with identifiers of all matching entries. An empty array if no entries matched
00327      * @author Karsten Dambekalns <karsten@typo3.org>
00328      * @api
00329      */
00330     public function findIdentifiersByTag($tag) {
00331         $statementHandle = $this->databaseHandle->prepare(
00332             'SELECT "identifier" FROM "tags" WHERE "scope"=?  AND "cache"=? AND "tag"=?'
00333         );
00334         $statementHandle->execute(
00335             array($this->scope, $this->cacheIdentifier, $tag)
00336         );
00337         return $statementHandle->fetchAll(PDO::FETCH_COLUMN);
00338     }
00339 
00340     /**
00341      * Finds and returns all cache entry identifiers which are tagged with
00342      * all of the specified tags.
00343      * This method doesn't exist in FLOW3, but is mandatory for TYPO3v4.
00344      *
00345      * @TODO: Make smarter
00346      * @param array $tags Tags to search for
00347      * @return array An array with identifiers of all matching entries. An empty array if no entries matched
00348      * @author Christian Kuhn <lolli@schwarzbu.ch>
00349      */
00350     public function findIdentifiersByTags(array $tags) {
00351         $taggedEntries = array();
00352         $foundEntries = array();
00353 
00354         foreach ($tags as $tag) {
00355             $taggedEntries[$tag] = $this->findIdentifiersByTag($tag);
00356         }
00357 
00358         $intersectedTaggedEntries = call_user_func_array('array_intersect', $taggedEntries);
00359 
00360         foreach ($intersectedTaggedEntries as $entryIdentifier) {
00361             if ($this->has($entryIdentifier)) {
00362                 $foundEntries[$entryIdentifier] = $entryIdentifier;
00363             }
00364         }
00365 
00366         return $foundEntries;
00367     }
00368 
00369     /**
00370      * Does garbage collection
00371      *
00372      * @return void
00373      * @author Karsten Dambekalns <karsten@typo3.org>
00374      * @api
00375      */
00376     public function collectGarbage() {
00377         $statementHandle = $this->databaseHandle->prepare(
00378             'DELETE FROM "tags" WHERE "scope"=? AND "cache"=? AND "identifier" IN ' .
00379             '(SELECT "identifier" FROM "cache" WHERE "scope"=? AND "cache"=? AND "lifetime" > 0 AND "created" + "lifetime" < ' . $GLOBALS['EXEC_TIME'] . ')'
00380         );
00381         $statementHandle->execute(
00382             array($this->scope, $this->cacheIdentifier, $this->scope, $this->cacheIdentifier)
00383         );
00384 
00385         $statementHandle = $this->databaseHandle->prepare(
00386             'DELETE FROM "cache" WHERE "scope"=? AND "cache"=? AND "lifetime" > 0 AND "created" + "lifetime" < ' . $GLOBALS['EXEC_TIME']
00387         );
00388         $statementHandle->execute(
00389             array($this->scope, $this->cacheIdentifier)
00390         );
00391     }
00392 
00393     /**
00394      * Returns an SQL statement that evaluates to true if the entry is not expired.
00395      *
00396      * @return string
00397      * @author Karsten Dambekalns <karsten@typo3.org>
00398      */
00399     protected function getNotExpiredStatement() {
00400         return ' AND ("lifetime" = 0 OR "created" + "lifetime" >= ' . $GLOBALS['EXEC_TIME'] . ')';
00401     }
00402 
00403     /**
00404      * Connect to the database
00405      *
00406      * @return void
00407      * @author Karsten Dambekalns <karsten@typo3.org>
00408      */
00409     protected function connect() {
00410         try {
00411             $splitdsn = explode(':', $this->dataSourceName, 2);
00412             $this->pdoDriver = $splitdsn[0];
00413 
00414             if ($this->pdoDriver === 'sqlite' && !file_exists($splitdsn[1])) {
00415                 $this->databaseHandle = t3lib_div::makeInstance('PDO', $this->dataSourceName, $this->username, $this->password);
00416                 $this->createCacheTables();
00417             } else {
00418                 $this->databaseHandle = t3lib_div::makeInstance('PDO', $this->dataSourceName, $this->username, $this->password);
00419             }
00420 
00421             $this->databaseHandle->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
00422 
00423             if ($this->pdoDriver === 'mysql') {
00424                 $this->databaseHandle->exec('SET SESSION sql_mode=\'ANSI\';');
00425             }
00426         } catch (PDOException $e) {
00427         }
00428     }
00429 
00430     /**
00431      * Creates the tables needed for the cache backend.
00432      *
00433      * @return void
00434      * @throws RuntimeException if something goes wrong
00435      * @author Karsten Dambekalns <karsten@typo3.org>
00436      */
00437     protected function createCacheTables() {
00438         try {
00439             t3lib_PdoHelper::importSql($this->databaseHandle, $this->pdoDriver, PATH_t3lib . 'cache/backend/resources/ddl.sql');
00440         } catch (PDOException $e) {
00441             throw new RuntimeException(
00442                 'Could not create cache tables with DSN "' . $this->dataSourceName . '". PDO error: ' . $e->getMessage(),
00443                 1259576985
00444             );
00445         }
00446     }
00447 }
00448 
00449 if (defined('TYPO3_MODE') && $GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/cache/backend/class.t3lib_cache_backend_pdobackend.php']) {
00450     include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/cache/backend/class.t3lib_cache_backend_pdobackend.php']);
00451 }
00452 ?>