Buy

Adding a Twig Extension + DI Tags

Because the $funFact needs to be parsed through markdown, we have to pass it as an independent variable into the template. You know what would be way cooler? If we could just say genus.funFact|markdown. Ok, actually we can do this already: the KnpMarkdownBundle comes with a filter called markdown:

42 lines app/Resources/views/genus/show.html.twig
... lines 1 - 4
{% block body %}
<h2 class="genus-name">{{ genus.name }}</h2>
<div class="sea-creature-container">
<div class="genus-photo"></div>
<div class="genus-details">
<dl class="genus-details-list">
... lines 12 - 15
<dt>Fun Fact:</dt>
<dd>{{ genus.funFact|markdown }}</dd>
... lines 18 - 19
</dl>
</div>
</div>
<div id="js-notes-wrapper"></div>
{% endblock %}
... lines 25 - 42

Remove the app.php from the URL and refresh. VoilĂ ! This parses the string to markdown. Well, it doesn't look like much - but if you view the HTML, there's a p tag from the process.

Let's make this a little more obvious while we're working. Open up the Genus entity and find getFunFact(). Temporarily hack in some bold markdown code:

115 lines src/AppBundle/Entity/Genus.php
... lines 1 - 11
class Genus
{
... lines 14 - 86
public function getFunFact()
{
return '**TEST** '.$this->funFact;
}
... lines 91 - 113
}

Ok, try it again.

Nice! The bold TEST tells us that the markdown filter from KnpMarkdownBundle is working.

Ready for me to complicate things? I always do. The markdown filter uses the markdown.parser service from KnpMarkdownBundle - it does not use our app.markdown_transformer. And this means that it's not using our caching system. Instead, let's create our own Twig filter.

Creating a Twig Extension

To do that, you need a Twig "extension" - that's basically Twig's plugin system. Create a new directory called Twig - and nope, that name is not important. Inside, create a new php class - MarkdownExtension:

24 lines src/AppBundle/Twig/MarkdownExtension.php
... line 1
namespace AppBundle\Twig;
class MarkdownExtension extends \Twig_Extension
{
... lines 7 - 22
}

Remember: Twig is its own, independent library. If you read Twig's documentation about creating a Twig extension, it will tell you to create a class, make it extend \Twig_Extension and then fill in some methods.

Use the "Code"->"Generate" menu - or cmd+n - and select "Implement Methods". The one method you must have is called getName(). It's also the most boring: just make it return any unique string - like app_markdown:

24 lines src/AppBundle/Twig/MarkdownExtension.php
... lines 1 - 4
class MarkdownExtension extends \Twig_Extension
{
... lines 7 - 18
public function getName()
{
return 'app_markdown';
}
}

To add a new filter, go back to the "Code"->"Generate" menu and select "Override Methods". Choose getFilters():

24 lines src/AppBundle/Twig/MarkdownExtension.php
... lines 1 - 4
class MarkdownExtension extends \Twig_Extension
{
public function getFilters()
{
... lines 9 - 11
}
... lines 13 - 22
}

Here, you'll return an array of new filters: each is described by a new \Twig_SimpleFilter object. The first argument will be the filter name - how about markdownify - that sounds fun. Then, point to a function in this class that should be called when that filter is used: parseMarkdown:

24 lines src/AppBundle/Twig/MarkdownExtension.php
... lines 1 - 4
class MarkdownExtension extends \Twig_Extension
{
public function getFilters()
{
return [
new \Twig_SimpleFilter('markdownify', array($this, 'parseMarkdown'))
];
}
... lines 13 - 22
}

Create the new public function parseMarkdown() with a $str argument. For now, just strtoupper() that guy to start:

24 lines src/AppBundle/Twig/MarkdownExtension.php
... lines 1 - 4
class MarkdownExtension extends \Twig_Extension
{
... lines 7 - 13
public function parseMarkdown($str)
{
return strtoupper($str);
}
... lines 18 - 22
}

Cool? Update the Twig template to use this:

42 lines app/Resources/views/genus/show.html.twig
... lines 1 - 4
{% block body %}
<h2 class="genus-name">{{ genus.name }}</h2>
<div class="sea-creature-container">
<div class="genus-photo"></div>
<div class="genus-details">
<dl class="genus-details-list">
... lines 12 - 15
<dt>Fun Fact:</dt>
<dd>{{ genus.funFact|markdownify }}</dd>
... lines 18 - 19
</dl>
</div>
</div>
<div id="js-notes-wrapper"></div>
{% endblock %}
... lines 25 - 42

Our Twig extension is perfect... but it will not work yet. Refresh. Huge error:

Unknown markdownify filter.

Twig doesn't automatically find and load our extension. Somehow, we need to say:

Hey Symfony? How's it going? Oh, I'm good. Listen, when you load the twig service, can you add my MarkdownExtension to it?

How are we going to do this? With tags.

Leave a comment!

  • 2016-11-14 Victor Bocharsky

    Ah, that's weird. If you add vendor dir to excluded folders - I think it may cause this issue too. But if not, I can suggest you to re-index your project. AFAIK, you need to choose "File" -> "Invalidate Caches". I hope it will help.

    Cheers!

  • 2016-11-14 Tin Pham

    Hi Victor, Thank you for your reply but I still didn't have that getName() method from the "Implement methods" in "Generated" menu.
    On the other hand, the override methods is fine.
    My PhpStorm version is up to date and I did try to restart it.
    I decided to ignore this small bug and move on!

  • 2016-11-14 Victor Bocharsky

    Hey Tin,

    No, it should be available out-of-the-box. Do you choose the "Implement methods" option in "Generate" menu? Please, also double check that your `MarkdownExtension` extends `\Twig_Extension` class. Do you use the latest PhpStorm version? Probably you just need to restart PhpStorm - it should help in most cases.

    Cheers!

  • 2016-11-14 Tin Pham

    Hi Ryan,
    in my PHPStorm, I can not see the Twig_ExtensionInterface in the implemented methods (Cmd+N menu). Do I need to install any plugin to see it?