Buy

Thanks to the 7 bundles installed in our app, we already have a bunch of useful services. In fact, Symfony ships with a killer cache system out of the box! Run:

./bin/console debug:autowiring

Scroll to the top. Ah! Check out CacheItemPoolInterface. Notice it's an alias to cache.app. And, further below, there's another called AdapterInterface that's an alias to that same key.

Understanding Autowiring Types & Aliases

Honestly, this can be confusing at first. Internally, each service has a unique name, or "id", just like routes. The internal id for Symfony's cache service is cache.app. That's not very important yet... except that, if you see two entries that are both aliases to the same service, it means that you can use either type hint to get the exact same object. Yep, both CacheItemPoolInterface and AdapterInterface will cause the exact same object to be passed to you.

So... which one should we use? The docs will recommend one, but it technically does not matter. The only difference is that PhpStorm may auto-complete different methods for you based on the interface or class you choose. So if it doesn't auto-complete the method you're looking for, try the other interface.

Using Symfony's Cache

Let's use the AdapterInterface. Go back to our controller. Here's our next mission: to cache the markdown transformation: there's no reason to do that on every request! At the top of the method, add AdapterInterface $cache:

81 lines src/Controller/ArticleController.php
... lines 1 - 8
use Symfony\Component\Cache\Adapter\AdapterInterface;
... lines 10 - 13
class ArticleController extends AbstractController
{
... lines 16 - 26
public function show($slug, MarkdownInterface $markdown, AdapterInterface $cache)
{
... lines 29 - 66
}
... lines 68 - 79
}

Cool! Let's go use it! Symfony's cache service implements the PHP-standard cache interface, called PSR-6... in case you want Google it and geek-out over the details. But, you probably shouldn't care about this... it just means better interoperability between libraries. So... I guess... yay!

But... there's a downside.... a dark side. The standard is very powerful... but kinda weird to use at first. So, watch closely.

Start with $item = $cache->getItem(). We need to pass this a cache key. Use markdown_ and then md5($articleContent):

81 lines src/Controller/ArticleController.php
... lines 1 - 8
use Symfony\Component\Cache\Adapter\AdapterInterface;
... lines 10 - 13
class ArticleController extends AbstractController
{
... lines 16 - 26
public function show($slug, MarkdownInterface $markdown, AdapterInterface $cache)
{
... lines 29 - 34
$articleContent = <<<EOF
... lines 36 - 51
EOF;
$item = $cache->getItem('markdown_'.md5($articleContent));
... lines 55 - 66
}
... lines 68 - 79
}

Excellent! Different markdown content will have a different key. Now, when we call getItem() this does not actually go and fetch that from the cache. Nope, it just creates a CacheItem object in memory that can help us fetch and save to the cache.

For example, to check if this key is not already cached, use if (!$item->isHit()):

81 lines src/Controller/ArticleController.php
... lines 1 - 8
use Symfony\Component\Cache\Adapter\AdapterInterface;
... lines 10 - 13
class ArticleController extends AbstractController
{
... lines 16 - 26
public function show($slug, MarkdownInterface $markdown, AdapterInterface $cache)
{
... lines 29 - 53
$item = $cache->getItem('markdown_'.md5($articleContent));
if (!$item->isHit()) {
... lines 56 - 57
}
... lines 59 - 66
}
... lines 68 - 79
}

Inside we need to put the item into cache. That's a two-step process. Step 1: $item->set() and then the value, which is $markdown->transform($articleContent). Step 2: $cache->save($item):

81 lines src/Controller/ArticleController.php
... lines 1 - 8
use Symfony\Component\Cache\Adapter\AdapterInterface;
... lines 10 - 13
class ArticleController extends AbstractController
{
... lines 16 - 26
public function show($slug, MarkdownInterface $markdown, AdapterInterface $cache)
{
... lines 29 - 53
$item = $cache->getItem('markdown_'.md5($articleContent));
if (!$item->isHit()) {
$item->set($markdown->transform($articleContent));
$cache->save($item);
}
... lines 59 - 66
}
... lines 68 - 79
}

I know, I know - it smells a bit over-engineered... but it's crazy powerful and insanely quick.

Tip

In Symfony 4.1, you will be able to use the Psr\SimpleCache\CacheInterface type-hint to get a "simpler" (but less powerful) cache object.

After all of this, add $articleContent = $item->get() to fetch the value from cache:

81 lines src/Controller/ArticleController.php
... lines 1 - 8
use Symfony\Component\Cache\Adapter\AdapterInterface;
... lines 10 - 13
class ArticleController extends AbstractController
{
... lines 16 - 26
public function show($slug, MarkdownInterface $markdown, AdapterInterface $cache)
{
... lines 29 - 53
$item = $cache->getItem('markdown_'.md5($articleContent));
if (!$item->isHit()) {
$item->set($markdown->transform($articleContent));
$cache->save($item);
}
$articleContent = $item->get();
... lines 60 - 66
}
... lines 68 - 79
}

Debugging the Cache

Ok, let's do this! Find your browser and refresh! Check this out: remember that we have a web debug toolbar icon for the cache! I'll click and open that in a new tab.

Hmm. There are a number of things called "pools". Pools are different cache systems and most are used internally by Symfony. The one we're using is called cache.app. And, cool! We had a cache "miss" and two calls: we wrote to the cache and then read from it.

Refresh the page again... and re-open the cache profiler. This time we hit the cache. Yes!

And just to make sure we did our job correctly, go back to the markdown content. Let's emphasize "turkey" with two asterisks:

81 lines src/Controller/ArticleController.php
... lines 1 - 8
use Symfony\Component\Cache\Adapter\AdapterInterface;
... lines 10 - 13
class ArticleController extends AbstractController
{
... lines 16 - 26
public function show($slug, MarkdownInterface $markdown, AdapterInterface $cache)
{
... lines 29 - 34
$articleContent = <<<EOF
... lines 36 - 38
**turkey** shank eu pork belly meatball non cupim.
... lines 40 - 51
EOF;
$item = $cache->getItem('markdown_'.md5($articleContent));
if (!$item->isHit()) {
$item->set($markdown->transform($articleContent));
$cache->save($item);
}
$articleContent = $item->get();
... lines 60 - 66
}
... lines 68 - 79
}

Refresh again! Yes! The change does show up thanks to the new cache key. And this time, in the profiler, we had another miss and write on cache.app.

Check you out! You just learned Symfony's cache service! Add that to your toolkit!

But this leaves some questions: it's great that Symfony gives us a cache service... but where is it saving the cache files? And more importantly, what if I need to change the cache service to save the cache somewhere else, like Redis? That's next!

Leave a comment!

  • 2018-02-19 Victor Bocharsky

    Hey AdFlorin,

    I agree, Doctrine is very popular in Symfony applications if we're talking about database, but sometimes database is not a requirement for an application. Anyway, we do not cover database and Doctrine in particular in this course on purpose - we're going to talk about this complex stuff in the next course, thanks for your patience!

    Cheers!

  • 2018-02-19 Victor Bocharsky

    Hey AdFlorin,

    We're going to talk about Doctrine in the next course on purpose and that course will be all about Doctrine. But for now I can give you a quick example:


    public function show($slug, MarkdownInterface $markdown, AdapterInterface $cache)
    {
    $em = $this->getDoctrine()->getManager();
    $article = $em->getRepository(Article::class)->findOneBySlug($slug);
    $articleContent = $article->getContent();
    }

    So as you can see it's easy, you need to fetch a proper Article entity from the repository, in our case we need to use $slug to do so. Of course, you need to have Article entity and it should have "slug" and "content" fields at least. If you used to work with Doctrine - I think you'll nail it. If no, please, wait for the Doctrine course for the Symfony 4 track or check out our https://knpuniversity.com/s... from Symfony 3 track to get more information how to work with database using Doctrine.

    Cheers!

  • 2018-02-19 AdFlorin

    you should have examples on how to do that with doctrine ... because no one will use symfony without doctrine ...

  • 2018-02-19 AdFlorin

    how do we transform fields from entities ( markdown && cache )
    i'm using doctrine to fetch the "$articleContent" from mysql
    how do i tranform only the "content" field ?

  • 2018-01-31 Jérôme 

    Wow, I wasn't expecting this additional information. Thanks! I'll take a look at those two links, they may be useful. I'm not sure I'll use this for the project I'm currently working on, but maybe I'll use it in a future update. Thanks again! :)

  • 2018-01-31 Victor Bocharsky

    Hey Jerome,

    Yeah, don't hesitate to ask questions later when you have questions. Btw, you can totally use this cache component on Symfony 3, I mean you don't need to upgrade to Symfony 4 first to start using it ;) Unfortunately, we do not have a separate screencast about caching, but these docs may be useful for you:
    https://symfony.com/doc/cur...

    P.S. There's another type of caching which called HTTP cache: https://symfony.com/doc/cur... - it allows you to cache the whole page instead of a separate things, but this one is not so great for pages where you have user-related dynamic data, like pages where you show users avatar and name in the header when they are logged in, because due to it you can't cache the whole page (well, you can, but you probably don't want to show all users the same cached page with someones avatar and name on it :) ). Though Edge Side Includes (ESI) could help to solve this problem.

    Cheers!

  • 2018-01-31 Jérôme 

    Hi Victor,

    Thanks for answering my question. It's very kind of you. Now that I read your comment, I think I'll need to practice more, but only on side projects first. SF4 is great, and I'll probably need some time to adapt, because I'm still using SF3 for my current projects.

    I'll contact you again if I have more questions about the course in the future. Have a good day!

  • 2018-01-31 Victor Bocharsky

    Hi Jérôme,

    Well, cache always brings more complexity in your code, so if your website is simple and your server resources are enough on highest traffic and you're happy with page's response time - I'd say you can ignore caching :) But in case you really need it, or, maybe you just want to play with Symfony cache system, let's find out what is cache.

    Yes, you're right, in short cache allows your app return response faster to your users, but it depends on implementation. Rationally to use cache when you want to avoid some time-consuming processes, for example, like in this screencast, when we convert markdown into HTML - no reasons to do this operation again and again for each user who want to see this page, we can convert it only once, then cache the result and use already converted HTML for all further users, i.e. we avoid resource-consuming operation from N time to only 1 and save some response time. Or, for example, you have some queries to the DB that are really slow so you can cache the result for some time to reduce server load. But in some cases you should think about cache invalidation and cache expiration time, you probably won't cache result of DB queries forever because results may cache with time, so sometimes make sense to invalidate the cache manually on some event, like adding new post, etc. or for example set expiration time, so after some time like 1 hour, 1 day, etc. you will do same operations again to get a new cache result for another 1 hour, 1 day, etc. And of course, sometimes it means that some users will see a bit outdated *cached* information. If it's not a bit deal for you - go for it! But if it is, then you should avoid caching those things that are changed with time or manually invalidate cache on some events to recalculate data again.

    So, it depends on your website implementation, but I think you know it best and can find some bottle necks where you use many resources, or repeat the same operations/calculations for each user which give you the same result.

    Hope this helps you ;)

    Cheers!

  • 2018-01-30 Jérôme 

    Hello,

    Thanks for the great tutorial. Unfortunately, I'm not very familiar with the cache. I'm currently working on a website for a sport association which allows the admin to post news and create "events" (posts with title, starting & ending date, starting & ending time, and description). I'm wondering if I should use this cache service anywhere. I think I understand what it does: it allows the app to load faster (I guess...), but could you give me a better explanation please?

    Thanks!