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.