Buy Access to Course
02.

KnpMarkdownBundle & its Services

Share this awesome video!

|

Scroll down to the "Why do Asteroids Taste like Bacon" article and click to see it. Here's the goal: I want the article body to be processed through markdown. Of course, Symfony doesn't come with a markdown-processing service... but there's probably a bundle that does! Google for KnpMarkdownBundle and find its GitHub page.

Installing a Bundle

Let's get this installed: copy the composer require line. Then, move over to your terminal, paste and... go!

composer require "knplabs/knp-markdown-bundle:^1.9"

Notice that this is a bundle: you can see it right in the name. That means it likely contains two things: First, of course, some PHP classes. And second, some configuration that will add one or more new services to our app!

And.... installed! It executed one recipe, which made just one change:

git status

Yep! It updated bundles.php, which activates the bundle:

13 lines | config/bundles.php
// ... lines 1 - 2
return [
// ... lines 4 - 10
Knp\Bundle\MarkdownBundle\KnpMarkdownBundle::class => ['all' => true],
];

Finding the new Service

So... what's different now? Run:

./bin/console debug:autowiring

and scroll to the top. Surprise! We have a new tool! Actually, there are two interfaces you can use to get the same markdown service. How do I know these will give us the same object? And which should we use? We'll talk about those two questions in the next chapter.

But since it doesn't matter, let's use MarkdownInterface. Open ArticleController. In show(), create a new variable - $articleContent - and set it to the multiline HEREDOC syntax. I'm going to paste in some fake content. This is the same beefy content that's in the template. In the controller, let's markdownify some stuff! Add some emphasis to jalapeno bacon ands let's turn beef ribs into a link to https://baconipsum.com/:

72 lines | src/Controller/ArticleController.php
// ... lines 1 - 11
class ArticleController extends AbstractController
{
// ... lines 14 - 24
public function show($slug)
{
$comments = [
// ... lines 28 - 30
];
$articleContent = <<<EOF
Spicy **jalapeno bacon** ipsum dolor amet veniam shank in dolore. Ham hock nisi landjaeger cow,
lorem proident [beef ribs](https://baconipsum.com/) aute enim veniam ut cillum pork chuck picanha. Dolore reprehenderit
labore minim pork belly spare ribs cupim short loin in. Elit exercitation eiusmod dolore cow
turkey shank eu pork belly meatball non cupim.
Laboris beef ribs fatback fugiat eiusmod jowl kielbasa alcatra dolore velit ea ball tip. Pariatur
laboris sunt venison, et laborum dolore minim non meatball. Shankle eu flank aliqua shoulder,
capicola biltong frankfurter boudin cupim officia. Exercitation fugiat consectetur ham. Adipisicing
picanha shank et filet mignon pork belly ut ullamco. Irure velit turducken ground round doner incididunt
occaecat lorem meatball prosciutto quis strip steak.
Meatball adipisicing ribeye bacon strip steak eu. Consectetur ham hock pork hamburger enim strip steak
mollit quis officia meatloaf tri-tip swine. Cow ut reprehenderit, buffalo incididunt in filet mignon
strip steak pork belly aliquip capicola officia. Labore deserunt esse chicken lorem shoulder tail consectetur
cow est ribeye adipisicing. Pig hamburger pork belly enim. Do porchetta minim capicola irure pancetta chuck
fugiat.
EOF;
// ... lines 51 - 57
}
// ... lines 59 - 70
}

Pass this into the template as a new articleContent variable:

72 lines | src/Controller/ArticleController.php
// ... lines 1 - 11
class ArticleController extends AbstractController
{
// ... lines 14 - 24
public function show($slug)
{
// ... lines 27 - 32
$articleContent = <<<EOF
Spicy **jalapeno bacon** ipsum dolor amet veniam shank in dolore. Ham hock nisi landjaeger cow,
lorem proident [beef ribs](https://baconipsum.com/) aute enim veniam ut cillum pork chuck picanha. Dolore reprehenderit
labore minim pork belly spare ribs cupim short loin in. Elit exercitation eiusmod dolore cow
turkey shank eu pork belly meatball non cupim.
Laboris beef ribs fatback fugiat eiusmod jowl kielbasa alcatra dolore velit ea ball tip. Pariatur
laboris sunt venison, et laborum dolore minim non meatball. Shankle eu flank aliqua shoulder,
capicola biltong frankfurter boudin cupim officia. Exercitation fugiat consectetur ham. Adipisicing
picanha shank et filet mignon pork belly ut ullamco. Irure velit turducken ground round doner incididunt
occaecat lorem meatball prosciutto quis strip steak.
Meatball adipisicing ribeye bacon strip steak eu. Consectetur ham hock pork hamburger enim strip steak
mollit quis officia meatloaf tri-tip swine. Cow ut reprehenderit, buffalo incididunt in filet mignon
strip steak pork belly aliquip capicola officia. Labore deserunt esse chicken lorem shoulder tail consectetur
cow est ribeye adipisicing. Pig hamburger pork belly enim. Do porchetta minim capicola irure pancetta chuck
fugiat.
EOF;
return $this->render('article/show.html.twig', [
// ... lines 53 - 55
'articleContent' => $articleContent,
]);
}
// ... lines 59 - 70
}

And now, in the template, remove all the old stuff and just print {{ articleContent }}:

83 lines | templates/article/show.html.twig
// ... lines 1 - 4
{% block body %}
<div class="container">
<div class="row">
<div class="col-sm-12">
<div class="show-article-container p-3 mt-4">
// ... lines 11 - 25
<div class="row">
<div class="col-sm-12">
<div class="article-text">
{{ articleContent }}
</div>
</div>
</div>
// ... lines 33 - 71
</div>
</div>
</div>
</div>
{% endblock %}
// ... lines 78 - 83

Let's try it! Go back to our site and refresh! No surprise: it's the raw content. Now it's time to process this through Markdown!

Using the Markdown Service

In ArticleController, tell Symfony to pass us the markdown service by adding a type-hinted argument. Let's use MarkdownInterface: MarkdownInterface $markdown:

75 lines | src/Controller/ArticleController.php
// ... lines 1 - 4
use Michelf\MarkdownInterface;
// ... lines 6 - 12
class ArticleController extends AbstractController
{
// ... lines 15 - 25
public function show($slug, MarkdownInterface $markdown)
{
// ... lines 28 - 60
}
// ... lines 62 - 73
}

Now, below, $articleContent = $markdown-> - we never looked at the documentation to see how to use the markdown service... but thanks to PhpStorm, it's pretty self-explanatory - $markdown->transform($articleContent):

75 lines | src/Controller/ArticleController.php
// ... lines 1 - 4
use Michelf\MarkdownInterface;
// ... lines 6 - 12
class ArticleController extends AbstractController
{
// ... lines 15 - 25
public function show($slug, MarkdownInterface $markdown)
{
// ... lines 28 - 33
$articleContent = <<<EOF
// ... lines 35 - 50
EOF;
$articleContent = $markdown->transform($articleContent);
// ... lines 54 - 60
}
// ... lines 62 - 73
}

Un-escaping Raw HTML

And that's it! Refresh! It works! Um... kind of. It is transforming our markdown into HTML... but if you look at the HTML source, it's all being escaped! Bah!

Actually, this is awesome! One of Twig's super-powers - in addition to having very stylish hair - is to automatically escape any variable you render. That means you're protected from XSS attacks without doing anything.

If you do know that it's safe to print raw HTML, just add |raw:

83 lines | templates/article/show.html.twig
// ... lines 1 - 4
{% block body %}
<div class="container">
<div class="row">
<div class="col-sm-12">
<div class="show-article-container p-3 mt-4">
// ... lines 11 - 25
<div class="row">
<div class="col-sm-12">
<div class="article-text">
{{ articleContent|raw }}
</div>
</div>
</div>
// ... lines 33 - 71
</div>
</div>
</div>
</div>
{% endblock %}
// ... lines 78 - 83

Try it again! Beautiful!

So here is our first big lesson:

  1. Everything in Symfony is done by a service
  2. Bundles give us these services... and installing new bundles gives us more services.

And 3, Twig clearly gets its hair done by a professional.

Next, let's use a service that's already being added to our app by an existing bundle: the cache service.