Caching with slim


#1

Hello,
is there known way to cache page or template fragment with slim ?

  1. Entire page cache: is there a way with a middleware to grab the entire response, store it into cache, then serve it from cache on the subsequent call ?

  2. Fragment caching: I found this that works with twig: https://github.com/asm89/twig-cache-extension . Not sure if it works with slim + twig. Or are there alternative, or easier way to do it with other template engine than twig ?

Thanks


#2

For my case, I’ve set up a middleware for cache. It uses a PSR-6 cache library based on filesystem.
How it works?

  1. Check if your config is allows (can be configured on the route, setArgument() … )
  2. Compute cache key
  3. Check in the FilesystemCachePool if the item “isHit()”
  4. If true, return the response with the content of the item as body, else, go to the next middleware
  5. If statusCode of the response == 200, store the response, else return the response directly

Hope it helps.

Url : https://github.com/php-cache/filesystem-adapter.


#3

Hi @claude.samuelson, I’m trying to cache my Slim responses in the same way you suggest (doing it in the middleware), however, I’m having trouble due to the way Slim responses use a TEMP stream. My response object caches fine but after retrieving the cached response and attempting to display it the stream reference no longer points to a valid stream and no body is returned. I was wondering if you may have an idea on how to address this as your post makes it seem like you’ve gotten this method to work once already.


#4

By using the cache pool, you can do:

To Save

    $body = $response->getBody();
    $body->rewind();

    $item
        ->set([
            'body' => $body->getContents(),
            'headers' => $response->getHeaders(),
        ])
        ->expiresAt($cacheDuration);

    return $this->cachePool->save($item);

To retrieve:

    $data = $item->get();

    $body = new Body(fopen('php://temp', 'r+'));
    $body->write($data['body']);

    $cachedResponse = (new Response())->withBody($body);

    foreach ($data['headers'] as $name => $header) {
        $cachedResponse = $cachedResponse->withHeader($name, $header);
    }

#5

Thanks for the reply @claude.samuelson but I already figured it out and it turns out to be very close to your suggestion. Here’s my solution:

<?php

namespace App\Middleware;

use Slim\Http\Request;
use Slim\Http\Response;
use Slim\Http\Headers;

class CacheMiddleware
{
    /** @var \Slim\Container $container The Slim application container */
    protected $container;
    
    /**
     * Create a Middleware object.
     *
     * @param \Slim\Container $container The Slim application container
     */
    public function __construct(Container $container)
    {
        $this->container = $container;
    }
    
    /**
     * Cache responses to speed up page loading.
     *
     * @param \Slim\Http\Request  $request  Incoming request object
     * @param \Slim\Http\Response $response Outgoing response object
     * @param callable            $next     The next middleware
     *
     * @return \Psr\Http\Message\ResponseInterface
     */
    public function __invoke(Request $request, Response $response, callable $next)
    {
        if (! $this->container->config->get('cache.enabled', false)) {
            return $next($request, $response);
        }

        $key = $request->getUri()->getPath();

        if ($this->container->cache->has($key)) {
            [$response, $body] = $this->container->cache->get($key);

            $headers = new Headers;
            foreach ($response->getHeaders() as $header => $value) {
                $headers->set($header, $value);
            }

            return (
                new Response($response->getStatusCode(), $headers)
            )->write($body);
        }

        $response = $next($request, $response);

        if ($response->isOk()) {
            $this->container->cache->forever($key, [$response, (string) $response->getBody()]);
        }

        return $response;
    }
}

One of the key differences is that my solution caches the whole response so I can use other cached properties like the status code. Also, instead of creating a new Body object I just use Response::write() to write the cached body to the newly created Response object.


#6

The Twig template loader can cache the compiled templates on the filesystem for future reuse. It speeds up Twig a lot as templates are only compiled once. The performance boost is extreme, thanks to the PHP OPcache. See the cache and auto_reload options of Twig_Environment for more information.

Here is an example how to enable the Twig “OPCache”:


// composer require slim/views
use Slim\Views\Twig;

$container['view'] = function (Container $container) {
    $settings = $container->get('settings');
    $viewPath = $settings['twig']['path'];

    // To enable the cache, just pass the cache path
    // To disable the cache set the cache value to false.
    $twig = new Twig($viewPath, [
        'cache' => $settings['twig']['cache_enabled'] ? $settings['twig']['cache_path'] : false
    ]);

    // ...

    return $twig;
};

#7

@odan Having the PSR6 cache pool as a middleware is much better and very speed than using the cache of twig.


#8

That’s great information @odan and I’ll definitely take it into consideration as I’m about to start working with Twig but for my specific purposes I’m caching rendered images and not Twig templates or HTML. Therefor caching the raw response is a much better approach.


#9

Images are usually just linked resources within a html document.

<img src="image.jpg">

In this case the browser is responsible for caching and response caching (on the server side) would not gain very much, right?

Or do you cache and deliver the whole image inline as base64 encoded resource?

For example:

<img src="data:image/png;base64,iVBORw0KGgoAAAANS…


#10

@odan I’m actually generating/manipulating images in PHP then rendering them so I need the caching to be handled by PHP on the server.