347 lines
14 KiB
PHP
347 lines
14 KiB
PHP
|
<?php
|
||
|
|
||
|
/**
|
||
|
* PDO Cache Handler
|
||
|
* Allows you to store Smarty Cache files into your db.
|
||
|
* Example table :
|
||
|
* CREATE TABLE `smarty_cache` (
|
||
|
* `id` char(40) NOT NULL COMMENT 'sha1 hash',
|
||
|
* `name` varchar(250) NOT NULL,
|
||
|
* `cache_id` varchar(250) DEFAULT NULL,
|
||
|
* `compile_id` varchar(250) DEFAULT NULL,
|
||
|
* `modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
|
* `expire` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||
|
* `content` mediumblob NOT NULL,
|
||
|
* PRIMARY KEY (`id`),
|
||
|
* KEY `name` (`name`),
|
||
|
* KEY `cache_id` (`cache_id`),
|
||
|
* KEY `compile_id` (`compile_id`),
|
||
|
* KEY `modified` (`modified`),
|
||
|
* KEY `expire` (`expire`)
|
||
|
* ) ENGINE=InnoDB
|
||
|
* Example usage :
|
||
|
* $cnx = new PDO("mysql:host=localhost;dbname=mydb", "username", "password");
|
||
|
* $smarty->setCachingType('pdo');
|
||
|
* $smarty->loadPlugin('Smarty_CacheResource_Pdo');
|
||
|
* $smarty->registerCacheResource('pdo', new Smarty_CacheResource_Pdo($cnx, 'smarty_cache'));
|
||
|
*
|
||
|
* @author Beno!t POLASZEK - 2014
|
||
|
*/
|
||
|
class Smarty_CacheResource_Pdo extends Smarty_CacheResource_Custom
|
||
|
{
|
||
|
/**
|
||
|
* @var string[]
|
||
|
*/
|
||
|
protected $fetchStatements = array('default' => 'SELECT %2$s
|
||
|
FROM %1$s
|
||
|
WHERE 1
|
||
|
AND id = :id
|
||
|
AND cache_id IS NULL
|
||
|
AND compile_id IS NULL',
|
||
|
'withCacheId' => 'SELECT %2$s
|
||
|
FROM %1$s
|
||
|
WHERE 1
|
||
|
AND id = :id
|
||
|
AND cache_id = :cache_id
|
||
|
AND compile_id IS NULL',
|
||
|
'withCompileId' => 'SELECT %2$s
|
||
|
FROM %1$s
|
||
|
WHERE 1
|
||
|
AND id = :id
|
||
|
AND compile_id = :compile_id
|
||
|
AND cache_id IS NULL',
|
||
|
'withCacheIdAndCompileId' => 'SELECT %2$s
|
||
|
FROM %1$s
|
||
|
WHERE 1
|
||
|
AND id = :id
|
||
|
AND cache_id = :cache_id
|
||
|
AND compile_id = :compile_id');
|
||
|
|
||
|
/**
|
||
|
* @var string
|
||
|
*/
|
||
|
protected $insertStatement = 'INSERT INTO %s
|
||
|
|
||
|
SET id = :id,
|
||
|
name = :name,
|
||
|
cache_id = :cache_id,
|
||
|
compile_id = :compile_id,
|
||
|
modified = CURRENT_TIMESTAMP,
|
||
|
expire = DATE_ADD(CURRENT_TIMESTAMP, INTERVAL :expire SECOND),
|
||
|
content = :content
|
||
|
|
||
|
ON DUPLICATE KEY UPDATE
|
||
|
name = :name,
|
||
|
cache_id = :cache_id,
|
||
|
compile_id = :compile_id,
|
||
|
modified = CURRENT_TIMESTAMP,
|
||
|
expire = DATE_ADD(CURRENT_TIMESTAMP, INTERVAL :expire SECOND),
|
||
|
content = :content';
|
||
|
|
||
|
/**
|
||
|
* @var string
|
||
|
*/
|
||
|
protected $deleteStatement = 'DELETE FROM %1$s WHERE %2$s';
|
||
|
|
||
|
/**
|
||
|
* @var string
|
||
|
*/
|
||
|
protected $truncateStatement = 'TRUNCATE TABLE %s';
|
||
|
|
||
|
/**
|
||
|
* @var string
|
||
|
*/
|
||
|
protected $fetchColumns = 'modified, content';
|
||
|
|
||
|
/**
|
||
|
* @var string
|
||
|
*/
|
||
|
protected $fetchTimestampColumns = 'modified';
|
||
|
|
||
|
/**
|
||
|
* @var \PDO
|
||
|
*/
|
||
|
protected $pdo;
|
||
|
|
||
|
/**
|
||
|
* @var
|
||
|
*/
|
||
|
protected $table;
|
||
|
|
||
|
/**
|
||
|
* @var null
|
||
|
*/
|
||
|
protected $database;
|
||
|
|
||
|
/**
|
||
|
* Constructor
|
||
|
*
|
||
|
* @param PDO $pdo PDO : active connection
|
||
|
* @param string $table : table (or view) name
|
||
|
* @param string $database : optional - if table is located in another db
|
||
|
*
|
||
|
* @throws \SmartyException
|
||
|
*/
|
||
|
public function __construct(PDO $pdo, $table, $database = null)
|
||
|
{
|
||
|
if (is_null($table)) {
|
||
|
throw new SmartyException("Table name for caching can't be null");
|
||
|
}
|
||
|
$this->pdo = $pdo;
|
||
|
$this->table = $table;
|
||
|
$this->database = $database;
|
||
|
$this->fillStatementsWithTableName();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Fills the table name into the statements.
|
||
|
*
|
||
|
* @return $this Current Instance
|
||
|
* @access protected
|
||
|
*/
|
||
|
protected function fillStatementsWithTableName()
|
||
|
{
|
||
|
foreach ($this->fetchStatements as &$statement) {
|
||
|
$statement = sprintf($statement, $this->getTableName(), '%s');
|
||
|
}
|
||
|
$this->insertStatement = sprintf($this->insertStatement, $this->getTableName());
|
||
|
$this->deleteStatement = sprintf($this->deleteStatement, $this->getTableName(), '%s');
|
||
|
$this->truncateStatement = sprintf($this->truncateStatement, $this->getTableName());
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the fetch statement, depending on what you specify
|
||
|
*
|
||
|
* @param string $columns : the column(s) name(s) you want to retrieve from the database
|
||
|
* @param string $id unique cache content identifier
|
||
|
* @param string|null $cache_id cache id
|
||
|
* @param string|null $compile_id compile id
|
||
|
*
|
||
|
* @access protected
|
||
|
* @return \PDOStatement
|
||
|
*/
|
||
|
protected function getFetchStatement($columns, $id, $cache_id = null, $compile_id = null)
|
||
|
{
|
||
|
$args = array();
|
||
|
if (!is_null($cache_id) && !is_null($compile_id)) {
|
||
|
$query = $this->fetchStatements[ 'withCacheIdAndCompileId' ] and
|
||
|
$args = array('id' => $id, 'cache_id' => $cache_id, 'compile_id' => $compile_id);
|
||
|
} elseif (is_null($cache_id) && !is_null($compile_id)) {
|
||
|
$query = $this->fetchStatements[ 'withCompileId' ] and
|
||
|
$args = array('id' => $id, 'compile_id' => $compile_id);
|
||
|
} elseif (!is_null($cache_id) && is_null($compile_id)) {
|
||
|
$query = $this->fetchStatements[ 'withCacheId' ] and $args = array('id' => $id, 'cache_id' => $cache_id);
|
||
|
} else {
|
||
|
$query = $this->fetchStatements[ 'default' ] and $args = array('id' => $id);
|
||
|
}
|
||
|
$query = sprintf($query, $columns);
|
||
|
$stmt = $this->pdo->prepare($query);
|
||
|
foreach ($args as $key => $value) {
|
||
|
$stmt->bindValue($key, $value);
|
||
|
}
|
||
|
return $stmt;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* fetch cached content and its modification time from data source
|
||
|
*
|
||
|
* @param string $id unique cache content identifier
|
||
|
* @param string $name template name
|
||
|
* @param string|null $cache_id cache id
|
||
|
* @param string|null $compile_id compile id
|
||
|
* @param string $content cached content
|
||
|
* @param integer $mtime cache modification timestamp (epoch)
|
||
|
*
|
||
|
* @return void
|
||
|
* @access protected
|
||
|
*/
|
||
|
protected function fetch($id, $name, $cache_id, $compile_id, &$content, &$mtime)
|
||
|
{
|
||
|
$stmt = $this->getFetchStatement($this->fetchColumns, $id, $cache_id, $compile_id);
|
||
|
$stmt->execute();
|
||
|
$row = $stmt->fetch();
|
||
|
$stmt->closeCursor();
|
||
|
if ($row) {
|
||
|
$content = $this->outputContent($row[ 'content' ]);
|
||
|
$mtime = strtotime($row[ 'modified' ]);
|
||
|
} else {
|
||
|
$content = null;
|
||
|
$mtime = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Fetch cached content's modification timestamp from data source
|
||
|
* {@internal implementing this method is optional.
|
||
|
* Only implement it if modification times can be accessed faster than loading the complete cached content.}}
|
||
|
*
|
||
|
* @param string $id unique cache content identifier
|
||
|
* @param string $name template name
|
||
|
* @param string|null $cache_id cache id
|
||
|
* @param string|null $compile_id compile id
|
||
|
*
|
||
|
* @return integer|boolean timestamp (epoch) the template was modified, or false if not found
|
||
|
* @access protected
|
||
|
*/
|
||
|
// protected function fetchTimestamp($id, $name, $cache_id = null, $compile_id = null) {
|
||
|
// $stmt = $this->getFetchStatement($this->fetchTimestampColumns, $id, $cache_id, $compile_id);
|
||
|
// $stmt -> execute();
|
||
|
// $mtime = strtotime($stmt->fetchColumn());
|
||
|
// $stmt -> closeCursor();
|
||
|
// return $mtime;
|
||
|
// }
|
||
|
/**
|
||
|
* Save content to cache
|
||
|
*
|
||
|
* @param string $id unique cache content identifier
|
||
|
* @param string $name template name
|
||
|
* @param string|null $cache_id cache id
|
||
|
* @param string|null $compile_id compile id
|
||
|
* @param integer|null $exp_time seconds till expiration time in seconds or null
|
||
|
* @param string $content content to cache
|
||
|
*
|
||
|
* @return boolean success
|
||
|
* @access protected
|
||
|
*/
|
||
|
protected function save($id, $name, $cache_id, $compile_id, $exp_time, $content)
|
||
|
{
|
||
|
$stmt = $this->pdo->prepare($this->insertStatement);
|
||
|
$stmt->bindValue('id', $id);
|
||
|
$stmt->bindValue('name', $name);
|
||
|
$stmt->bindValue('cache_id', $cache_id, (is_null($cache_id)) ? PDO::PARAM_NULL : PDO::PARAM_STR);
|
||
|
$stmt->bindValue('compile_id', $compile_id, (is_null($compile_id)) ? PDO::PARAM_NULL : PDO::PARAM_STR);
|
||
|
$stmt->bindValue('expire', (int)$exp_time, PDO::PARAM_INT);
|
||
|
$stmt->bindValue('content', $this->inputContent($content));
|
||
|
$stmt->execute();
|
||
|
return !!$stmt->rowCount();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Encodes the content before saving to database
|
||
|
*
|
||
|
* @param string $content
|
||
|
*
|
||
|
* @return string $content
|
||
|
* @access protected
|
||
|
*/
|
||
|
protected function inputContent($content)
|
||
|
{
|
||
|
return $content;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Decodes the content before saving to database
|
||
|
*
|
||
|
* @param string $content
|
||
|
*
|
||
|
* @return string $content
|
||
|
* @access protected
|
||
|
*/
|
||
|
protected function outputContent($content)
|
||
|
{
|
||
|
return $content;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Delete content from cache
|
||
|
*
|
||
|
* @param string|null $name template name
|
||
|
* @param string|null $cache_id cache id
|
||
|
* @param string|null $compile_id compile id
|
||
|
* @param integer|null|-1 $exp_time seconds till expiration or null
|
||
|
*
|
||
|
* @return integer number of deleted caches
|
||
|
* @access protected
|
||
|
*/
|
||
|
protected function delete($name = null, $cache_id = null, $compile_id = null, $exp_time = null)
|
||
|
{
|
||
|
// delete the whole cache
|
||
|
if ($name === null && $cache_id === null && $compile_id === null && $exp_time === null) {
|
||
|
// returning the number of deleted caches would require a second query to count them
|
||
|
$this->pdo->query($this->truncateStatement);
|
||
|
return -1;
|
||
|
}
|
||
|
// build the filter
|
||
|
$where = array();
|
||
|
// equal test name
|
||
|
if ($name !== null) {
|
||
|
$where[] = 'name = ' . $this->pdo->quote($name);
|
||
|
}
|
||
|
// equal test cache_id and match sub-groups
|
||
|
if ($cache_id !== null) {
|
||
|
$where[] =
|
||
|
'(cache_id = ' .
|
||
|
$this->pdo->quote($cache_id) .
|
||
|
' OR cache_id LIKE ' .
|
||
|
$this->pdo->quote($cache_id . '|%') .
|
||
|
')';
|
||
|
}
|
||
|
// equal test compile_id
|
||
|
if ($compile_id !== null) {
|
||
|
$where[] = 'compile_id = ' . $this->pdo->quote($compile_id);
|
||
|
}
|
||
|
// for clearing expired caches
|
||
|
if ($exp_time === Smarty::CLEAR_EXPIRED) {
|
||
|
$where[] = 'expire < CURRENT_TIMESTAMP';
|
||
|
} // range test expiration time
|
||
|
elseif ($exp_time !== null) {
|
||
|
$where[] = 'modified < DATE_SUB(NOW(), INTERVAL ' . intval($exp_time) . ' SECOND)';
|
||
|
}
|
||
|
// run delete query
|
||
|
$query = $this->pdo->query(sprintf($this->deleteStatement, join(' AND ', $where)));
|
||
|
return $query->rowCount();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the formatted table name
|
||
|
*
|
||
|
* @return string
|
||
|
* @access protected
|
||
|
*/
|
||
|
protected function getTableName()
|
||
|
{
|
||
|
return (is_null($this->database)) ? "`{$this->table}`" : "`{$this->database}`.`{$this->table}`";
|
||
|
}
|
||
|
}
|