Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

The Cache Service

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

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.

Tip

In recent versions of Symfony, a Symfony\Contracts\Cache\CacheInterface can also be used to autowire the cache service and it's the preferred type-hint to use.

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:

... 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):

... 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()):

... 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):

... 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:

... 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:

... 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!

32
Login or Register to join the conversation
Jose carlos C. Avatar
Jose carlos C. Avatar Jose carlos C. | posted 4 years ago | edited

Hello and sorry for the doubts I have, can be very newbie although I hope not.

1 - What is the difference between the <b>"isHit", "has" and "contains"</b> methods in the Cache Adapters?

2 - When we are talking about a <b>Cache System or Filesystem Adapter</b> we are talking about what is being stored in the <b>physical disk</b> , right? That's why I don't understand why it says in the <b>Symfony</b> documentation that you have to be careful with the memory, that also says in <b>Php Array Cache Adapter and Php Files Cache Adapter</b>, which says it uses <b>Opcache</b>, but in reality we are giving it an directory on the disk, please could you explain this. Or I'm confusing myself with the term "FileSystem."

3- In the QueryBuilder from Doctrine I see that we have the options of being able to use Cache with the methods:


->useQueryCache(true)
->useResultCache(true, 3600, 'cache_my_key')

Are these methods necessary? I mean this in <b>production</b> automatically creates a cache, and it is not necessary to use those methods, unless we want to change the Cache Adapter, right?

Sorry for my english === null

2 Reply

Hey Jose carlos C.!

Excellent questions!

1 - What is the difference between the "isHit", "has" and "contains" methods in the Cache Adapters?

Where are you seeing "has" and "contains()"? I know of a "has()" method in the cache adapters, so I CAN explain that at least. Basically, the PHP Fig created 2 separate caching interfaces - PSR-6 (which we're using here) and PSR-16. Both are interfaces that a cache object can have, and Symfony supports both of these. In this chapter, we are fetching a cache object that implements the more complex (but more powerful) PSR-6 interface. In this system, you need to get the item first and then call isHit() on the "CacheItem" to see if it's actually in the cache. But, in Symfony 4.1, you can type-hint Psr\SimpleCache\CacheInterface and receive a cache object that implements the simpler PSR-16 interface. With this object, there is no idea of a "CacheItem" - you just say if ($cache->has('cache_my_key')) or $cache->get('cache_my_key') to get the cached value (or null if it's not cached).

Let me know if that answers your question - I'm not sure about contains(), so I could be missing something :).

2 - When we are talking about a Cache System or Filesystem Adapter we are talking about what is being stored in the physical disk , right?

Hmm. Could you point me to where you're reading this in the docs? The quick answer (and I'm happy to continue this conversation and elaborate further!) is that there are 2 caches in Symfony - their service ids are:

  • cache.system: An internal cache object used by Symfony for caching things like configuration. It IS ok (in all situations) for this to be stored in the filesystem and, by default, it does that, but tries to use some fancy "Php Array Cache Adapter" and Opcache tricks to do this. Even I don't know a ton about this - but these adapters are just fancy, super-fast ways to save to the filesystem - faster than just the FilesystemAdapter

  • cache.app: THIS is the object that we're supposed to use to store things at runtime - it's what we're getting in this chapter via autowiring. By default, this also stores on the filesystem via the FilesystemAdapter. That's ok... but in a perfect world, you would change this to something like APCu or (better) Redis. This is for 2 reasons. First, by changing to something that is not the filesystem, when you deploy, you don't need to worry about your var/cache directory being writable - because the cache is being stored somewhere else. All the stuff in cache.system is pre-stored when you run the bin/console cache:warmup command on deploy. So basically, during deploy, your var/cache directory needs to be writable, but once the deploy actually finishes, it does not need to be writable. This makes your app much easier to work with on almost all hosting platforms because, for example, you don't need to try to make your var/cache directory writable by your web server user.

3- In the QueryBuilder from Doctrine I see that we have the options of being able to use Cache with the methods:

The ->useQueryCache(true) I believe is redundant because it's already activated via your prod config - https://github.com/symfony/recipes/blob/230169dc8a6ab89be7473fbb6a823f2336caa72b/doctrine/doctrine-bundle/1.6/config/packages/prod/doctrine.yaml#L7. That just defines the adapter that should be used, but I believe that this setting is on by default.

The useResultCache() IS necessary... IF you want to use this feature (I personally don't). The "query" cache is easy - this basically tells Doctrine to query the transformation from DQL to SQL - something that never changes, and so, of course, cache it! But the "result cache" tells Doctrine to query the results of a query... and if you turn it on, it means that you need to worry about getting "out of date" results or invalidating the cache. So, you need to opt into it.

But in both cases, what you're doing inside of the QueryBuilder is simply saying "yes I want to cache" - but which adapters should be used is stored in your doctrine.yaml file - and you probably won't need to change those, because they're good defaults (they say to use cache.app and cache.system).

Let me know if that makes sense! If you still have questions, let me know - these were big questions :).

Cheers!

3 Reply
Jose carlos C. Avatar
Jose carlos C. Avatar Jose carlos C. | weaverryan | posted 4 years ago | edited

Hello weaverryan

1- Perfect, all very clear. The method "contains" seems that I invented it, lol.

2- I hadn't seen it anywhere, I thought that "FilesystemCache" was like the "Filesystem" component, that wrote it on the disk directly without influencing memory (I think I made a mess with so many adapters, I have to digest it now little by little), and I also thought it was faster than memory and database. But now seeing your answer and reading more information I have it more clear, I mean that for example I had created a service "Menu" that saved the Main menu and Footer menu of a website, of course this Entity uses the Nested system with Doctrine Extensions, which makes it slower. In this example I give you, I had done it very simple:


 public function getMenu($root, $override = false)
    {
        $cache = new FilesystemCache();

        if (!$cache->has('menu-'.$root)) {
            $name           = $override ? $override : $root;
            $functionOption = "options".ucfirst($root);
            $rootMenu       = $this->getMenuRepository()->findBySlug($name);
            $links          = $this->getMenuRepository()->findMenus($rootMenu)->getQuery()->getArrayResult();
            $menu           = $this->getMenuRepository()->buildTree($links, $this->{$functionOption}());
            if (!$menu) {
                throw new \Exception('Ningun Menu econtrado con root ' . $root);
            }

            $cache->set('menu-'.$root, $menu);
        }


        return $cache->get('menu-'.$root);

    }

But now watching your video and your aswer I should have used Cache\Adapter\AdapterInterface, but to make it more perfect I'm going to use the APCu Adapter, what do you think?

3 - Perfect, all very clear.

And really thank you very much for your answer and for your great videos, I'm learning a lot.

1 Reply

Hey Charles!

Excellent! It sounds like you have a much better handle on things now :). The great thing about the cache system is that you get that cache.app service (the one that AdapterInterface gives you) automatically - and then you can just choose to use something like apc or redis for more performance (btw, redis is useful if you have multiple servers, as the servers will share the cache instead of all maintaining their own - both apcu and filesystem will mean one cache per server).

So, yes, the advantage to using AdapterInterface instead of instantiating the FilesystemAdapter is that you’re able to use the pre-configured service - no need to create it yourself. And also, you can use this in 10 places, then with one line of config, change to use apcu instead of the filesystem. The official cache docs aren’t the best (it’s on my list) and I think it’s easy to find the “component” documentation (the docs for using the cache component outside of the Symfony framework), and these show how you can manually instantiate the adapter, which is not necessary in the framework.

Phew! Anyways, let me know how it all works!

Cheers!

1 Reply
Jose carlos C. Avatar
Jose carlos C. Avatar Jose carlos C. | weaverryan | posted 4 years ago

Thank you very much.

Reply
Default user avatar

Currently symfony recommend using Symfony\Contracts\Cache\CacheInterface with 'get' methods with callback rather than AdaperInterface, will this docs be updated to avoid confusions and keeping it to the PSR standard?

Reply

Hey @Piotr!

You're absolutely right :). We show the new way in the new Symfony 5 tutorial. I especially like the DX of the new CacheInterface - much nicer!

Cheers!

Reply
Default user avatar

It looks like officially symfony says to use Symfony\Contracts\Cache\CacheInterface with callbacks instead of using adapters. Will this cast be updated?

Reply
Default user avatar

Hello, I have about 1k user contacts. When I use cache it's reduces the loading time when I want to display the user contact, however, I seems to lose data. What I mean by that is that let's say I had the column name, surname and telephone number. I'll have every data except ones relating to telephone number. Before the data was cached I had everything

Reply

Hey @Joel

It seems to me that you are caching your user data too early, probably before setting the telephone data on it. I would recommend you to invalidate your cache every time the user data changes.

Cheers!

Reply
Richard Avatar
Richard Avatar Richard | posted 3 years ago

Hang on. How can they be exactly the same objects with the same ID alias cache.app yet PHPStorm wont complete the SAME methods. You're right. It is VERY confusing. And where are AdapterInterface implementing cache objects/classes defined? How do I navigate to them? Why do I feel we should be calling something to get the helper object using "cache.app" and not an arbitrarily picked interface definition returned from console debug:autowiring?

Reply

Hey Maxii,

Haha, those are just aliases to the same service, you can create as many aliases as you want! But the idea behind of these 2 is simple, one of them is from Symfony and another one from PSR standard. As you may know Symfony implemented PSR cache standard after it was released, but before Symfony already had their own implementation and interface. So, as outcome we have two aliases, one low level from the PSR and another one from Symfony that actually extends that PSR interface. You can see it in the code :) https://github.com/symfony/...

I hope it makes sense for you now!

Cheers!

Reply
Xuan-hung L. Avatar
Xuan-hung L. Avatar Xuan-hung L. | Victor | posted 3 years ago | edited

Hello Victor,
I have this error message with intelephense in VSCode, but there is no error with the project's in Browser.

And as you sad, Symfony extends PSR interface.
Do you think that is a bug of intelephense ?

Error at the $item in the line : $this->cache->save($item);`<br />Expected type 'Psr\Cache\CacheItemInterface'. Found 'Symfony\Component\Cache\CacheItem'. Intelepense(1006) [38,32]
<a href="https://imgshare.io/image/J6ZFl&quot;&gt;https://imgshare.io/image/J6ZFl&lt;/a&gt;

Reply

Hey LE Xuan Hung,

Looks like so :) You can see it by yourself in the code: "Symfony\Component\Cache\CacheItem" implements "Symfony\Contracts\Cache\CacheItemInterface" which in turn extends "Psr\Cache\CacheItemInterface". I'm looking at the final code in master in https://github.com/symfony/... . So, instance of CacheItem should be valid for "Psr\Cache\CacheItemInterface" typehint

Cheers!

Reply
Richard Avatar
Richard Avatar Richard | posted 3 years ago

"Thanks to the 7 bundles installed in our app" : at this stage there are 8...

Reply

Wooops! Maybe Ryan didn't count the framework bundle :p

Reply
Anton S. Avatar
Anton S. Avatar Anton S. | posted 3 years ago

The question is:
What is better performance? Creating an md5 hash each time to check the integrity of the content or rely on the "updated_ad" field in the database, combining key with date string?

Or better:
If you have to retrieve the contents of the article from the database (already preprocessed by some wysiwyg keyboard) there's no sense caching it at all, because the longest procedure in that process it's the actual loading from the database.

Reply

Hey Anton!

Cool question :). I'll answer in reverse :p

> If you have to retrieve the contents of the article from the database (already preprocessed by some wysiwyg keyboard) there's no sense caching it at all, because the longest procedure in that process it's the actual loading from the database.

Yep! That's really a form of caching - "eager" caching in a sense. In this model, each time the "content" updated, you would have some code to effectively "build" the cache so that it never needs to be built at runtime. For the sake of argument, you could also do the same thing but actually store the processed content in a real cache: i.e. on update of the "content", process through Markdown and store the HTML in a "cache". Then, at runtime, the cached HTML would always be available. Performance differences would probably be negligible - I'm just making a conceptual point :).

> What is better performance? Creating an md5 hash each time to check the integrity of the content or rely on the "updated_ad" field in the database, combining key with date string?

I'm not sure! Well, what I mean is, this is probably one of those situations where they are both good... and picking one and testing it under real conditions (using Blackfire) is probably best. It's like that both are *easily* good enough - because both are ultimately using caching. But, in theory, in general, when you have a "validation caching" situation like this (where you need to check to see if some cache is valid before returning it), the general strategy is to "determine if the cache is valid with as little work as possible". In that model, you could make a query for *just* the "updated_at" field from the database (and no other data) then use that to determine if the cache is valid. If it is, return it. If it's not, make a second query for *all* the data, process it, and store it in cache. But... this sounds like possible premature optimization ;).

Cheers!

Reply
mehdi Avatar

Hello,
I have a question about the meaning of Pools.
In the PSR 6 documentation https://www.php-fig.org/psr..., they said: The Pool represents a collection of items in a caching system

And in this chapter you said : Pools are different cache systems.
Is there any differences between this two definitions ?

Reply

Hey Mehdi,

Actually, both are valid. Really, every "pool" represent a collection of items in a caching system. And every pool can be different cache system: one of them might represent collection in Redis cache, another might represent collection in filesystem cache, etc.

I hope this clearer for you now.

Cheers!

Reply
Tom Avatar

I cant find the answer to this anywhere. Is caching enabled by default in all symfony projects?

Reply
MolloKhan Avatar MolloKhan | SFCASTS | Tom | posted 4 years ago | edited

Hey Tom

As far as I know, Symfony caches a lot of things required for its internal functionality (Like compiling the container), but it won't cache anything for you, you have to explicitly cache your things.

Reply
Mike P. Avatar
Mike P. Avatar Mike P. | posted 4 years ago

Does it make sense to cache every article content even if the Markdown Bundle is not used?

By example:
A Blog with 100.000 Articles in a Database

Caching each article would save us maybe a query, but it will use disc space.
Whats your opinion of when to use the cache system? Only on "intense" data operations/modifications or if needed also on "normal" querys (even if there are thousands of articles to potentially cache?)?

Reply

Hey Mike,

Good question! And the answer is simple - it depends! Depends on your server, website load, etc. Moreover, there're many cache types: you can use filesystem cache, Redis, Memcached, etc. and it depends on your case what to cache and what tools to use for it. My advice is do not worry about cache too much at the development stage. Most probably you won't need the cache and your website could handle all the requests perfectly. Also, you can use such tools as Blackfire.io to find bottle neck in your project, and when you found it - then you can think about caching things to speed up the website. But fairly speaking SQL databases could handle 100k rows without any problems, especially if you use indexes on those fields that are used in queries, like slug field if you search articles by slug, etc.

In short, if you release a new website and it's not too popular in the beginning - don't use cache, just monitor it, see how things go, and if you see some potential problems at rush hours when the load on it is max - start think about how to solve it, but don't overcomplicate things during development too early :)

P.S. You can also enable caching for Doctrine queries in production, so you don't need to make up a custom caching, see metadata_cache_driver, query_cache_driver, and result_cache_driver in doctrine.orm config path.

Cheers!

Reply
Tobias I. Avatar
Tobias I. Avatar Tobias I. | posted 4 years ago

Hey there :) I have a quick question regarding the caching system and its use cases. I have a project similar to the spacebar where people can leave comments on articles that I want to be rendered through markdown just like the articles in this course. My project can potentially have thousands of comments and rendering even, lets say, 20 per page will slow down the page to a crawl, so I thought about caching. I am not sure if this would be a good solution, or if it would be better to save a parsed version to the database for later use in a temlate.
Also, I want my comments to be searchable, but I don't want any tags or syntax in it. Sorry if this sounds a bit confusing, ill try to give an example. Lets say a user searches for "# Something", I dont want any results to be comments that happen to use the markdown heading syntax. So I thought about saving a version to the database with all the markup strippen, along the original comment and the one that is parsed to html. But this seems also a bit silly? What would be the best solution to this problem?
Kind regards :)

Reply

Hey Tobias I.!

About your first question (caching). If you already know that rendering all those comments are slowing your page, then you can cache the markdown result using the "Symfony Cache" component (or any other caching system you like), the only downside I can see is when a user updates a comment, but probably you can add some extra logic to restore the cache of that specific comment. Another option might be to load the comments in an async way, i.e. via AJAX call

And about your second question there are many ways to provide a search functionality to your users, the technology we use here in SymfonyCasts is called "ElasticSearch", it's a great tool for indexing and searching data but it can be complex to implement. In your case you may not want to do that kind of investment (or maybe you do?) so probably storing both versions or your data might be easier. Or probably you can only store the raw version of the comment and then cache the rendered version, give it a try and check what fits better to your application.

Cheers!

Reply
Tomasz J. Avatar
Tomasz J. Avatar Tomasz J. | posted 4 years ago

Hi, first of all huge thank you for a great tutorial.

To make a long story short:
Imagine I have my application made of modules and each separte module requires a different set of configuration files ordered within the same way as the within main application. Let's say:
...
src
----Module1
--------config
------------packages
----------------dev
----------------prod
----------------test
------------routes
(etc.)
--------controller
----Module2
(etc.)
...
Now. Through my application config files I can easily call individual module config. For example in src/config/services.yaml:
imports:
- { resource: ../src/Module1/config/services.yaml }
- { resource: ../src/Module2/config/services.yaml }
...
As you can see I am forced to import all possible configurations, (no matter which module controller action is fired) because there is not
implemented any dynamic way to distinguish configs to load depending on engaged module. Briefly if the request trigers some module1 controller's action, I need to load only configs from src/module/config directory.
My question is: how I can fix this.

Thanks in advance

Reply

Hey Constantin,

Hm, so if I understand you right - you want dynamically import configuration depending on what module is triggered. Then the question is how would you know what exactly module is triggered and is NOT triggered in advance? :) If you can programmatically determine when to load this or that config file - you can do it. For example, in Symfony 4 there's src/AppKernel.php file that loads configs files and routes, so you can write your tricky rules there to dynamically include configs.

I hope this helps.

Cheers!

1 Reply
Marc C. Avatar
Marc C. Avatar Marc C. | posted 5 years ago

When I uncomment app: cache.adapter.apcu
- it gives an error
"(1/1) CacheException
APCu is not enabled"

- I install brew apcupsd successfully. Is this what he mean install Apcu Php Extension?
Please help!!!!

Reply

Hey Marc,

Not at all, apcupsd is APC UPS Daemon, see their website: http://www.apcupsd.org/ - that sounds like something different . Try to google how to install APCu for your PHP version on your OS. I see you use Mac... Unfortunately, brew deprecates https://github.com/Homebrew... repo, that's why now you can install only php with some enabled-out-of-the-box extensions with brew, but to install more extensions that does not come with php by default you need to use pecl. I suppose now you can do something like "pecl install apcu" to install it on your Mac. If it does not work for you, try to google alternative ways.

Cheers!

Reply
Ahmed C. Avatar
Ahmed C. Avatar Ahmed C. | posted 5 years ago

Hello!
I got problem undefined MarkdownInterface , how to fix it?

Reply

Hey Ahmed,

Hm, what exactly namespace do you have? Or is it just MarkdownInterface? Then, you need to use its namespace in the beginning of your file. The fully qualified class name should be: \Michelf\MarkdownInterface , so if you use this namespace above you probably won't see this error:


use Michelf\MarkdownInterface;

PhpStorm can do it for you if you try to re-type this class, choose the correct one from the list of autocompletion and press enter.

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-iconv": "*",
        "knplabs/knp-markdown-bundle": "^1.7", // 1.7.0
        "nexylan/slack-bundle": "^2.0,<2.2.0", // v2.0.0
        "php-http/guzzle6-adapter": "^1.1", // v1.1.1
        "sensio/framework-extra-bundle": "^5.1", // v5.1.4
        "symfony/asset": "^4.0", // v4.0.4
        "symfony/console": "^4.0", // v4.0.14
        "symfony/flex": "^1.0", // v1.17.6
        "symfony/framework-bundle": "^4.0", // v4.0.14
        "symfony/lts": "^4@dev", // dev-master
        "symfony/twig-bundle": "^4.0", // v4.0.4
        "symfony/web-server-bundle": "^4.0", // v4.0.4
        "symfony/yaml": "^4.0" // v4.0.14
    },
    "require-dev": {
        "easycorp/easy-log-handler": "^1.0.2", // v1.0.4
        "symfony/debug-bundle": "^3.3|^4.0", // v4.0.4
        "symfony/dotenv": "^4.0", // v4.0.14
        "symfony/maker-bundle": "^1.0", // v1.0.2
        "symfony/monolog-bundle": "^3.0", // v3.1.2
        "symfony/phpunit-bridge": "^3.3|^4.0", // v4.0.4
        "symfony/profiler-pack": "^1.0", // v1.0.3
        "symfony/var-dumper": "^3.3|^4.0" // v4.0.4
    }
}
userVoice