1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-17 20:11:46 +02:00

Add new $cache->renderFile() method that works like $files->render() but caches the output according to WireCache rules. Also updated $files->render() to support a 'cache' option, which provides the same result.

This commit is contained in:
Ryan Cramer
2019-04-11 11:48:34 -04:00
parent b0f11c15c5
commit 12aede03fe
2 changed files with 177 additions and 1 deletions

View File

@@ -404,7 +404,7 @@ class WireCache extends Wire {
*
* @param string $name Name of cache, can be any string up to 255 chars
* @param string|array|PageArray $data Data that you want to cache. May be string, array of non-object values, or PageArray.
* @param int|Page $expire Lifetime of this cache, in seconds, OR one of the following:
* @param int|string|Page $expire Lifetime of this cache, in seconds, OR one of the following:
* - Specify one of the `WireCache::expire*` constants.
* - Specify the future date you want it to expire (as unix timestamp or any `strtotime()` compatible date format)
* - Provide a `Page` object to expire when any page using that template is saved.
@@ -1070,6 +1070,155 @@ class WireCache extends Wire {
return $all;
}
/**
* Render a file as a ProcessWire template file and cache the output
*
* This method is similar to the `$files->render()` method and actually delegates the file
* rendering to that method (when creating the cache). The important difference is that this
* method caches the output according to WireCache rules for the `$expire` argument, rather
* than re-rendering the file on every call.
*
* If there are any changes to the source file `$filename` the cache will be automatically
* re-created, regardless of what is specified for the `$expire` argument.
*
* ~~~~~~
* // render primary nav from site/templates/partials/primary-nav.php
* // and cache for 3600 seconds (1 hour)
* echo $cache->renderFile('partials/primary-nav.php', 3600);
* ~~~~~~
*
* @param string $filename Filename to render (typically PHP file).
* Can be full path/file, or dir/file relative to current work directory (which is typically /site/templates/).
* If providing a file relative to current dir, it should not start with "/".
* File must be somewhere within site/templates/, site/modules/ or wire/modules/, or provide your own `allowedPaths` option.
* Please note that $filename receives API variables already (you dont have to provide them).
* @param int|Page|string|null $expire Lifetime of this cache, in seconds, OR one of the following:
* - Specify one of the `WireCache::expire*` constants.
* - Specify the future date you want it to expire (as unix timestamp or any `strtotime()` compatible date format)
* - Provide a `Page` object to expire when any page using that template is saved.
* - Specify `WireCache::expireNever` to prevent expiration.
* - Specify `WireCache::expireSave` to expire when any page or template is saved.
* - Specify selector string matching pages thatwhen savedexpire the cache.
* - Omit for default value, which is `WireCache::expireDaily`.
* @param array $options Accepts all options for the `WireFileTools::render()` method, plus these additional ones:
* - `name` (string): Optionally specify a unique name for this cache, otherwise $filename will be used as the unique name. (default='')
* - `vars` (array): Optional associative array of extra variables to send to template file. (default=[])
* - `allowedPaths` (array): Array of paths that are allowed (default is anywhere within templates, core modules and site modules)
* - `throwExceptions` (bool): Throw exceptions when fatal error occurs? (default=true)
* @return string|bool Rendered template file or boolean false on fatal error (and throwExceptions disabled)
* @throws WireException if given file doesnt exist
* @see WireFileTools::render()
* @since 3.0.130
*
*/
public function renderFile($filename, $expire = null, array $options = array()) {
$defaults = array(
'name' => '',
'vars' => array(),
'throwExceptions' => true,
);
$out = null;
$paths = $this->wire('config')->paths;
$files = $this->wire('files');
$filename = $files->unixFileName($filename);
if(strpos($filename, '/') !== 0 && strpos($filename, ':') === false && strpos($filename, '//') === false) {
// make relative to current path
$currentPath = $files->currentPath();
if($files->fileInPath($filename, $currentPath)) {
$f = $currentPath . $filename;
if(file_exists($f)) $filename = $f;
}
}
$options = array_merge($defaults, $options);
$mtime = filemtime($filename);
$name = str_replace($paths->root, '', $filename);
$ns = 'cache.' . ($options['name'] ? $options['name'] : 'renderFile');
$cacheName = $this->cacheName($name, $ns);
if($mtime === false) {
if($options['throwExceptions']) throw new WireException("File not found: $filename");
return false;
}
$data = $this->get($cacheName, $expire);
// cache value is array where [ 0=created, 1='value' ]
if(!is_array($data) || $data[0] < $mtime) {
// cache does not exist or is older source file mtime
$out = $this->wire('files')->render($filename, $options['vars'], $options);
if($out === false) return false;
$data = array(time(), $out);
if($expire === null) $expire = self::expireDaily;
$this->save($cacheName, $data, $expire);
} else {
$out = $data[1];
}
return $out;
}
/**
* Make sure a cache name is of the right length and format for a cache name
*
* @param string $name Name including namespace (if applicable)
* @param bool|string $ns True to allow namespace present, false to prevent, or specify namespace to add to name if not already present.
* @return string
* @since 3.0.130
* @todo update other methods in this class to use this method
*
*
*/
protected function cacheName($name, $ns = true) {
$maxLength = 190;
$name = trim($name);
if($ns === false) {
// namespace not allowed (cache name is NAME only)
while(strpos($name, '__') !== false) $name = str_replace('__', '_', $name);
if(strlen($name) > $maxLength) $name = md5($name);
return $name;
}
if(is_string($ns) && strlen($ns)) {
// a namespace has been supplied
while(strpos($name, '__') !== false) $name = str_replace('__', '_', $name);
while(strpos($ns, '__') !== false) $ns = str_replace('__', '_', $ns);
$ns = rtrim($ns, '_') . '__';
if(strpos($name, $ns) === 0) {
// name already has this namespace
} else {
// prepend namespace to name
$name = $ns . $name;
}
}
if(strlen($name) <= $maxLength) {
// name already in bounds
return $name;
}
// at this point we have a cache name that is too long
if(strpos($name, '__') !== false) {
// has namespace
list($ns, $name) = explode('__', $name, 2);
while(strpos($name, '__') !== false) $name = str_replace('__', '_', $name);
if(strlen($name) > 32) $name = md5($name);
if(strlen($ns . '__' . $name) > $maxLength) $ns = md5($ns); // not likely
$name = $ns . '__' . $name;
} else {
// no namespace
$name = md5($name);
}
return $name;
}
/**
* Does the given string look like it might be JSON?
*

View File

@@ -861,6 +861,8 @@ class WireFileTools extends Wire {
* - `allowedPaths` (array): Array of paths that are allowed (default is templates, core modules and site modules)
* - `allowDotDot` (bool): Allow use of ".." in paths? (default=false)
* - `throwExceptions` (bool): Throw exceptions when fatal error occurs? (default=true)
* - `cache` (int|string|Page): Specify non-zero value to cache rendered result for this many seconds, or see the `WireCache::renderFile()`
* method `$expire` argument for more options you can specify here. (default=0, no cache) *Note: this option added in 3.0.130*
* @return string|bool Rendered template file or boolean false on fatal error (and throwExceptions disabled)
* @throws WireException if template file doesn't exist
* @see WireFileTools::include()
@@ -881,6 +883,7 @@ class WireFileTools extends Wire {
),
'allowDotDot' => false,
'throwExceptions' => true,
'cache' => 0,
);
$options = array_merge($defaults, $options);
@@ -923,6 +926,15 @@ class WireFileTools extends Wire {
return false;
}
}
if($options['cache']) {
/** @var WireCache $cache */
$cache = $this->wire('cache');
$o = $options;
unset($o['cache']);
$o['vars'] = $vars;
return $cache->renderFile($filename, $options['cache'], $o);
}
// render file and return output
$t = new TemplateFile();
@@ -1317,4 +1329,19 @@ class WireFileTools extends Wire {
return strpos($file, $path) === 0;
}
/**
* Get the current path / work directory
*
* This is like PHPs getcwd() function except that is in ProcessWire format as unix path with trailing slash.
*
* #pw-group-filenames
*
* @return string
* @since 3.0.130
*
*/
public function currentPath() {
return $this->unixDirName(getcwd());
}
}