Search code examples
phplaravellaravel-5caching

Laravel cache with route model binding?


I'm adding caching to my Laravel app routes. I have a function that renders a blog post on my site:

    public function show(Post $post)
    {
        SEO::setTitle($post->title);
        SEO::setDescription($post->subtitle);
        SEO::setCanonical('https://employbl.com/blog/' . $post->slug);
        SEO::opengraph()->setUrl('https://employbl.com/blog/' . $post->slug);
        SEO::opengraph()->addProperty('type', 'article');
        SEO::opengraph()->addImage($post->featured_image);
        SEO::twitter()->setSite('@Employbl_Jobs');

        $markdown = Markdown::parse($post->body);

        return view('blog.post', compact('post', 'markdown'));
    }

This is the route that calls the method: Route::get('/blog/{post}', 'PostController@show')->name('posts.show'); so that my blog renders a URL with a slug like: https://employbl.com/blog/laravel-vue-tailwindcss-single-page-application-spa

What is the best way to implement caching on this route so the page loads faster for users?

Would it be something like:

$post = Cache::rememberForever('blog-post' . $post->id, function(){
     return $post;
});

Or is caching even necessary with route model binding? Does the cache key need to be unique or can I just use "blog-post" as cache key? Would it be better to cache the $markdown variable instead of the $post variable? Both?


Solution

  • You've got a few questions in here, so I'll try to answer each. The answers may not be letter perfect as I am going from memory without any way to reference or confirm them myself at the moment.

    If you're trying to cache the final output of your view, you can effectively do it be replacing your final view call with:

    return Cache::rememberForever('blog-post' . $post->id, function() use ($post) {
    
        // Do your SEO and markdown stuff here
    
        return view('blog.post', compact('post', 'markdown'))->render();
    });
    

    The cache key needs to be unique for the post. The model routing system knows nothing about the cache system, it's just a way of passing a value to a controller which makes some assumptions about the incoming data based on the URI. So what you are doing currently is fine.

    The problem your question about should I cache the post, the markdown or both? is that it probably won't make a difference

    1) You're calling a model GET route. This has the effect of loading the Post from the DB each time, making the caching of the Post itself irrelevant. This is true even with the caching of the render view itself.

    2) Your view call requires the Post itself as a parameter [of compact()]. You'll need to load it from somewhere, so that means a database call again to retrieve the post.

    3) You're using Cache::rememberForever which means the cache will never expire. So loading the Post after the first time will be pointless, since it will never be used again (the results are cached forever!). Future edits (if any) won't work unless you invalidate the cache (which makes rememberForever kind of pointless).

    So, I recommend, for this case, that you move away from the model route and instead try a traditional id based Route

    public function show(Request $request, $id)
    {
        return Cache::remember('blog-post'.$id, ttl, function() use($id) {
          $post = Post::find($id);
          // Do SEO and markdown stuff
          return view('blog.post', compact('post', 'markdown'))->render();
        });
    }
    

    where ttl is the time for the cache to expire.