Skip to content

Instantly share code, notes, and snippets.

@vanqard
Last active August 29, 2015 14:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vanqard/7c167c7ac4a4f14ad4ce to your computer and use it in GitHub Desktop.
Save vanqard/7c167c7ac4a4f14ad4ce to your computer and use it in GitHub Desktop.
Provides a very simple mechanism for writing generated pages to a file system so that they can be served in future instead of executing the Slim app.
<?php
/**
* Simplistic page cacheing middleware class to assist with the
* writing of page content to static files, bypassing the Slim
* application for pages that can be written out to the filesystem
* and served statically
*
* @author Thunder Raven-Stoker
* @link http://phpbrilliance.com
* @copyright 2015 Thunder Raven-Stoker
*/
/**
* Class definition for the PageCache class
*/
class CustomPageCache extends \Slim\Middleware
{
/**
* Define the name of the HTTP HEADER to use
*/
const SLIM_PAGE_CACHE_HEADER = "X-Slim-PageCache";
/**
* Provide a 'cache enabled' value
*/
const SLIM_PAGE_CACHE_ENABLED = "build";
/**
* Provide a 'cache disabled' value
*/
const SLIM_PAGE_CACHE_DISABLED = "no-build";
/**
* Provide the file extension to use for cached pages
*/
const SLIM_CACHE_FILE_EXTENSION = ".html";
/**
* Flag for the status of the current request
* @var boolean
*/
private $pageCacheEnabled = false;
/**
* Satisfies the interface for the \Slim\Middleware class.
* This implementation tests for a custom HTTP header to determine
* whether page cacheing is permitted on this request.
*
* For a fully static site, you could set the custom header at the
* very start, and then just change the header value to self::SLIM_PAGE_CACHE_DISABLED
* for requests that shouldn't be cached.
*
* @see \Slim\Middleware::call()
*/
public function call()
{
$this->resolveCacheHeader();
if(!$this->pageCacheEnabled) {
$this->next->call();
return;
}
$cacheFilePath = $this->getFilePathFromRequest();
if (!$this->serveCachedPage($cacheFilePath)) {
// Let slim build the page content first
$this->next->call();
$this->writePageToCache($cacheFilePath);
}
}
/**
* Determines whether to enabled the page cache or not
*
* @return boolean
*/
private function resolveCacheHeader()
{
// has the page set a no-build header? If yes, ignore the build process
$cacheHeader = $this->app->response->headers->get(self::SLIM_PAGE_CACHE_HEADER);
$this->pageCacheEnabled = ($cacheHeader == self::SLIM_PAGE_CACHE_ENABLED);
return $this->pageCacheEnabled;
}
/**
* Returns the full system path to the cache directory
*
* You can choose how you specify this, this could be a
* class constant here or a config setting applied directly
* to the Slim\Slim app instance
*
* Examples:
* 1. As a class constant at the top of this class
* const SLIM_PAGE_CACHE_DIR = "/path/to/cache/dir";
*
* 2. As a setting on the Slim $app instance after you've instantiated it
* // $app = new Slim\Slim()
* $app->config(array('page.cache.dir' => "/path/to/cache/dir"));
*
* This method then returns the value in accordance to how you set it
*
* @author Thunder Raven-Stoker
* @return string
*/
private function getCacheDir()
{
return realpath($this->app->config('page.cache.dir'));
}
/**
* Resolves the cache filename based on the incoming request path. Slashes
* are converted to underscores and any character that's not numeric or alphabetic
* will be stripped.
*
*
* @author Thunder Raven-Stoker
* @param string $requestPath
* @return string
*/
private function getFilePathFromRequest()
{
$requestPath = $this->transformRequestPath();
$buildDir = $this->getCacheDir();
$cachedPath = $buildDir . DIRECTORY_SEPARATOR . $requestPath;
return $cachedPath;
}
/**
* Retrieves the request path from the Slim application's request object
* and transforms it according to these rules
*
* 1. remove leading slash
* 2. replace forward slashes with underscores
* 3. strip any non alphanumeric characters (excluding underscores)
* 4. Add self::SLIM_CACHE_FILE_EXTENSION as a file extension
* (or provides 'index' stem for '/' requests)
*
* Example:
* /blog/2015/03/28/this-is-my-first-post
*
* would be converted to
*
* blog_2015_03_28_this-is-my-first-post.html
*
* @return string
*/
private function transformRequestPath()
{
$requestPath = $this->app->request->getPath();
$requestPath = ltrim($requestPath, '/');
$requestPath = str_replace('/','_', $requestPath);
$requestPath = preg_replace('/[^a-zA-Z0-9\-_]+/', '', $requestPath);
$requestPath .= (empty($requestPath) ? 'index' . self::SLIM_CACHE_FILE_EXTENSION : self::SLIM_CACHE_FILE_EXTENSION);
return $requestPath;
}
/**
* Attempts to write the page content to the file identified by the path param
*
* @param string $cacheFilePath
* @return number $numbytes written
*/
private function writePageToCache($cacheFilePath)
{
$response = $this->app->response;
if (!$response->isSuccessful()) {
// Bail - this isn't a 2xx result
return;
}
$pageContent = $response->getBody();
try {
return file_put_contents($cacheFilePath, $pageContent);
} catch (\Exception $e) {
// Do nothing - fail silently
// You could attach a logger here though
}
}
/**
* Attempts to deliver the file identified by the path param.
* This will only succeed when the page has already been previously cached
*
* @param string $cacheFilePath
* @return boolean
*/
private function serveCachedPage($cacheFilePath)
{
if (!file_exists($cacheFilePath) || !is_readable($cacheFilePath)) {
return false;
}
// Open and deliver the page content
$fh = fopen($cacheFilePath, 'r');
fpassthru($fh);
return true;
}
}
@vanqard
Copy link
Author

vanqard commented Mar 29, 2015

This is an implementation of Slim middleware. To use it, you register it just as you would any other Slim middleware.

$app->add(new CustomPageCache());

You'll will also probably want a default value for whether the page cache is to be enabled or disabled. You can do this with:

$app->response->headers->set(
    CustomPageCache::SLIM_PAGE_CACHE_HEADER,
    CustomPageCache::SLIM_PAGE_CACHE_ENABLED
);

Then in your routing callbacks, you can switch page caching on and off at will by calling the set() method again and using the appropriate constant value.

The last thing to note is that pages that are not in the 2XX http response code range are not cached by default. You will need to edit line 173 if you want to change this behaviour.

I used this class in a static site generator that I built with Slim for PHPBrilliance. Dogfooding++ ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment