Skip to main content

File System Caching in Slim Framework 3

In the past month, I've been learning Slim 3 PHP framework through a website as a real experiment of how powerful Slim Framework is. In this post, I'm sharing a code snippet of how I implemented the Filesystem caching.

The summary is:
- Add a middleware for caching successful responses (200 OK) by saving the response in a file. To avoid long file names or deep paths, I hash the response URL.
- When receiving a request, check its hashed path in the cached files. If found, return the content. Else, continue with initializing the app, routes, and everything else. This way, there is no initialization overhead for cached requests.

Here is the beginning of my index.php:
require './lib/initializer.php';

function getCachePath($uri) {
    $webpage_handle = md5($uri);
    return ROOT."/public/cache/".$webpage_handle;
}

/* Check for cached file here before loading anything or opening DB */
if(CACHING_ENABLED) {
    $requestedURI = "http://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
    $cachedFilePath = getCachePath($requestedURI);
    if (file_exists($cachedFilePath) && (filemtime($cachedFilePath) > (time() - CACHE_SECONDS ))) {
        $cacheFile = file_get_contents($cachedFilePath);
        die($cacheFile);
    }
}

$cacheMiddleware = function ($request, $response, $next) {
    /* process the request */
    $response = $next($request, $response);

    /* cache response */
    if(CACHING_ENABLED && ($response->getStatusCode()==200) && ($request->getMethod() == 'GET')) {
        $cachedFilePath = getCachePath($request->getUri());
        $file = fopen($cachedFilePath,"w");
        fwrite($file, $response->getBody());
        fclose($file);
    }

    return $response;
};


First, I require a file where I initialize some configs and constants, like CACHE_SECONDS.

Then, I create a function for deciding the cache path for requests. To use it in both reads and writes.

Then, -if caching is enabled- I check for the existence of the requested file and the creation date. This way I override the file if it was older. (*check the end of the post for another better way for expiry)

Next, you can see the caching middleware I use with some routes I wish to cache. Here is an example:
$app->get('/', function ($request, $response, $args) {
    // some app-related code
})->setName('home')->add($cacheMiddleware);

And that's it. The file system is now implemented with minimal code. Of course there are many changes and upgrades depending on your case.

----------------------------------
Notes:
*Better than just checking for file creation date (and leaving the files to increase), you can remove the date-check part and add a cron job for removing files older than a specific duration. Here is how my cron job script looks like:
CACHE_DIR=$1;
cd $CACHE_DIR && find * -type f -cmin +5 -exec rm {} \;

It takes the path to clean in the cron job command. And removes any file older than 5 minutes.

The cron job looks like:
*/5 * * * * bash ~/path/to/cache/expiry/script.sh ~/path/to/cache/folder/ 



Comments

  1. Thank you soo much. It worked. Its is caching all the requests. We can limit it to cache only the GET requests by changing the line 23 on the first code block as


    if(CACHING_ENABLED && ($response->getStatusCode()==200) && $request->getMethod() == 'GET') {

    Thank you !

    ReplyDelete
  2. Hi! Do you have any tutorial for Slim 4?

    Thanks!

    ReplyDelete
  3. Please tell me where I can get the file: require './lib/initializer.php';

    Thanks!

    ReplyDelete
  4. if its not work you must do include the returned response inside the route
    $response->getBody()->write((string)json_encode($result));

    ReplyDelete

Post a Comment

Popular posts from this blog

Prayer Times Calculations: Pure C++ Code

[25-March-2017] Update: For brothers and sisters in Indonesia or any country below the equator, thanks to Reza's comment ,  the code now works correctly. [18-January-2012 Update: I refined the code to make it easier and more elegant to use] [06-July-2011 Update: I found this page http://praytimes.org/calculation#Implementation ] This idea came to me when a friend of mine was asking for an 'offline database' for prayer times. I searched for algorithms to make my own database and almost all search results lead to the same algorithm. And all codes are divided into two categories: a simple but approximate one, and a complicated but precise one. I tried with the complicated one to understand it but it needs more time because of its nested functions and -unfortunately- it is not a priority right now. So I went with the easy one as a start. One thing I noticed is that every person is trying to make the ultimate use of the framework he is working on (mostly dotNet). But co...

[Qt] Custom QListView delegate with word wrap

Using QListView with a custom item layout has been a very popular demand by many developers. The problem with using  a custom delegate was having some basic features like word wrap depending on the allowed list size. Here is a screenshot from a project prototype I've been working on. The text will wrap around to fit in multiple lines depending on text length and list view width. My custom delegate is about using two important methods: QFontMetrics::boundingRect() and QPainter::drawText() . The first method will return the minimum Rect needed to draw the given text using the given origin and font. The second method draws the given text within the given Rect. Note that the flag Qt::TextWordWrap is given to both methods when called. The code is simple: Get system default font. Process the font to suite your needs. In my case: I have a normal font and another bold one for the header. Create a QFontMetrics instance for each font. Use the QFontMetrics instances to cal...

Qt Folder Compression

I needed a compression module that does not depend on anything but Qt, and I mean a module to compress a complete folder with its children. This is because I wanted the compression module to work on any platform supported by Qt with no other dependencies. So I made this modules that only uses only qCompress() & qUncompress() methods plus some logic. It is mainly a recursive algorithm that serializes data into a single file, each with its relative path to the parent folder. And deserializing is just taking binary data, creating needed subfolders, and saving the file. Download: QtFolderCompressor.zip [FolderCompressor.h] #ifndef FOLDERCOMPRESSOR_H #define FOLDERCOMPRESSOR_H #include <QFile> #include <QObject> #include <QDir> class FolderCompressor : public QObject { Q_OBJECT public: explicit FolderCompressor(QObject *parent = 0); //A recursive function that scans all files inside the source folder //and serializes all files in a row of f...