mirror of
https://github.com/smarty-php/smarty.git
synced 2026-07-05 16:00:55 +02:00
ff2ef3b0cb
* Redirect test temp dirs to system temp directory. Fixes #1178 Move all test-generated output (compiled templates, cache files, and temporary template sources) from per-test-directory folders inside the working tree to a parallel structure under sys_get_temp_dir()/smarty-tests/. This removes 215 boilerplate .gitignore files from the repo and ensures running the test suite leaves zero uncommitted files in the working tree. All 2296 tests continue to pass with identical behavior. * Isolate each test class in a unique temp directory getTempDir() now appends a per-class uniqid token to the temp path, so concurrent or sequential test runs never share compiled/cached output. The token is generated lazily on first use and reset in tearDownAfterClass(), giving every test class a fresh isolated directory. As a result, the Bootstrap.php pre-run cleanup of smarty-tests/ is no longer needed for correctness (stale paths are unreachable) and was harmful to concurrent runs, so it has been removed. * Remove individualFolders dead code and spurious assertTrue from cleanDirs() - Remove the never-active individualFolders code path from setUpSmarty() (the constant was always true, making the branch unreachable) - Remove define('individualFolders') from Config.php and the constructor - Remove $this->assertTrue(true) from cleanDirs(): it existed solely to make testInit() count as a passing test; now that cleanDirs() is called from setUpSmarty() and from test methods directly, the assertion was spuriously inflating assertion counts - Add tests/**/templates_c/, cache/, templates_tmp/ to .gitignore to prevent stale test output from appearing as untracked files * Clean up each test class's unique temp dir in tearDownAfterClass() Add a private static removeDir() helper and call it from tearDownAfterClass() to recursively delete the per-class unique temp directory after each test class finishes. Cleanup failures are silently ignored (@ suppression) so they never cause test failures. Set KEEP_SMARTY_TEST_ARTIFACTS=1 in the environment to skip cleanup and keep the artifacts on disk for debugging. * cleanup of unused template files, non-shared files stored in __shared folder, no longer required calls to add template folders et cetera * fixed the unit tests * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * remove useless resetting of static properties in tearDownAfterClass * changed an incorrect doc and formatted some code. * add changelog --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
348 lines
14 KiB
PHP
348 lines
14 KiB
PHP
<?php
|
|
|
|
use Smarty\Exception;
|
|
|
|
/**
|
|
* 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->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 \Smarty\Exception
|
|
*/
|
|
public function __construct(PDO $pdo, $table, $database = null)
|
|
{
|
|
if (is_null($table)) {
|
|
throw new Exception("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\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}`";
|
|
}
|
|
}
|