Fix Pagination when Using Laravel Page Cache, Part II

Fix Pagination when Using Laravel Page Cache, Part II

Note: much like its predecessor, this article’s all about Laravel websites that rely on the Laravel Page Cache package for page caching. This time, though, rather than exclude certain pages—those with a question mark, i.e., a query string, in their URL—from being cached, we’re going to make page caching work for them, too.

Separately Store ‘Paged’ Requests

On its own, Laravel Page Cache simply ignores query strings. We can, however, override some of the classes and methods in the original package in order to correctly cache URLs that contain query strings, too, and update the rules for retrieval accordingly.

In a newly created app/Cache.php, add—and notice the added if statement inside getDirectoryAndFileNames():

<?php

namespace App;

use Symfony\Component\HttpFoundation\Request;
use Silber\PageCache\Cache as BaseCache;

class Cache extends BaseCache
{
  protected function getDirectoryAndFileNames($request)
  {
    $segments = explode('/', ltrim($request->getPathInfo(), '/'));

    if ($request->has('page') && ctype_digit($request->query('page'))) {
      // The current URL contains a (numeric) query string
      $file = $this->aliasFilename(array_pop($segments)).'---page---'.$request->query('page').'.html';
    } else {
      // Default behavior
      $file = $this->aliasFilename(array_pop($segments)).'.html';
    }

    return [$this->getCachePath(implode('/', $segments)), $file];
  }
}

What this does is essentially tell the rest of the package to save the page at, e.g., /articles?page=2 to a static HTML file named articles---page---2.html.

Then, in app/Http/Middleware/CacheResponse.php, add:

<?php

namespace App\Http\Middleware;

use App\Cache;
use Symfony\Component\HttpFoundation\Request;
use Silber\PageCache\Middleware\CacheResponse as BaseCacheResponse;

class CacheResponse extends BaseCacheResponse
{
  public function __construct(Cache $cache)
  {
    // Using `App\Cache` instead of `Silber\PageCache\Cache`
    $this->cache = $cache;
  }
}

Subsequently, make sure app/Http/Kernel.php points to this middleware rather than the original one:

protected $routeMiddleware = [
  …
  //'page-cache' => \Silber\PageCache\Middleware\CacheResponse::class,
  'page-cache' => \App\Http\Middleware\CacheResponse::class,
];

We also have to adapt the service provider. To do so, create a new file, app/Providers/CacheServiceProvider.php, and add:

<?php

namespace App\Providers;

use App\Cache;
use Silber\PageCache\Console\ClearCache;
use Silber\PageCache\LaravelServiceProvider as BaseServiceProvider;

class CacheServiceProvider extends BaseServiceProvider
{
  public function register()
  {
    $this->commands(ClearCache::class);
    // Using `App\Cache` instead of `Silber\PageCache\Cache`
    $this->app->singleton(Cache::class, function () {
        $instance = new Cache($this->app->make('files'));

        return $instance->setContainer($this->app);
    });
  }
}

Finally, we have to (1) register this service provider, and (2) disable auto-discovery for the original package, so that the original service provider is no longer loaded automatically.

To register the new service provider, edit config/app.php, and add App\Providers\CacheServiceProvider::class to the providers array, right below the Application Service Providers... comment.

To deregister the old service provider, open your Laravel project’s composer.json, look for the dont-discover bit, and modify it like so:

"extra": {
  "laravel": {
    "dont-discover": [
      "silber/page-cache"
    ]
  }
},

Then run composer dump-autoload. Voilà! (And: dang, that sure took a while!)

Retrieve the Correct Cached Page

NGINX

Add the following to your site’s server block. These lines pretty much tell NGINX to try and load a slightly different cached HTML file—the one we defined above!—whenever the page query string parameter is present.

error_page 418 = @paged_index;
error_page 419 = @paged_other;
recursive_error_pages on;

location = / {
  if ($arg_page) {
    return 418;
  }

  try_files /page-cache/pc__index__pc.html /index.php?$query_string;
}

location / {
  if ($arg_page) {
    return 419;
  }

  try_files $uri $uri/ /page-cache/$uri.html /index.php?$query_string;
}

location @paged_index {
  try_files /page-cache/pc__index__pc---page---$arg_page.html /index.php?$query_string;
}

location @paged_other {
  try_files $uri $uri/ /page-cache/$uri---page---$arg_page.html /index.php?$query_string;
}

Fix Console Commands

To do: to be able to clear the page cache with an Artisan command, we also have to add a couple changes.