gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
Fun fact! Witches & wizards love writing markdown. I have no idea why... but darnit! We're going to give the people what they want! We're going to allow the question text to be written in Markdown. For now, we'll focus on this "show" page.
Open up QuestionController
and find the show()
method:
... lines 1 - 9 | |
class QuestionController extends AbstractController | |
{ | |
... lines 12 - 26 | |
/** | |
* @Route("/questions/{slug}", name="app_question_show") | |
*/ | |
public function show($slug) | |
{ | |
$answers = [ | |
'Make sure your cat is sitting purrrfectly still ?', | |
'Honestly, I like furry shoes better than MY cat', | |
'Maybe... try saying the spell backwards?', | |
]; | |
return $this->render('question/show.html.twig', [ | |
'question' => ucwords(str_replace('-', ' ', $slug)), | |
'answers' => $answers, | |
]); | |
} | |
} |
Let's see, this renders show.html.twig
... open up that template... and find the question text. Here it is:
... lines 1 - 4 | |
{% block body %} | |
<div class="container"> | |
<div class="row"> | |
<div class="col-12"> | |
... line 9 | |
<div style="box-shadow: 2px 3px 9px 4px rgba(0,0,0,0.04);"> | |
<div class="q-container-show p-4"> | |
<div class="row"> | |
... lines 13 - 15 | |
<div class="col"> | |
... line 17 | |
<div class="q-display p-3"> | |
... line 19 | |
<p class="d-inline">I've been turned into a cat, any thoughts on how to turn back? While I'm adorable, I don't really care for cat food.</p> | |
... line 21 | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
... lines 29 - 56 | |
</div> | |
{% endblock %} |
Because we don't have a database yet, the question is hardcoded. Let's move this text into our controller, so we can write some code to transform it from Markdown to HTML.
Copy the question text, delete it, and, in the controller, make a new variable: $questionText =
and paste. Pass this to the template as a new questionText
variable:
... lines 1 - 9 | |
class QuestionController extends AbstractController | |
{ | |
... lines 12 - 29 | |
public function show($slug) | |
{ | |
... lines 32 - 36 | |
$questionText = 'I\'ve been turned into a cat, any thoughts on how to turn back? While I\'m adorable, I don\'t really care for cat food.'; | |
return $this->render('question/show.html.twig', [ | |
... line 40 | |
'questionText' => $questionText, | |
... line 42 | |
]); | |
} | |
} |
Back in show.html.twig
, print that: {{ questionText }}
:
... lines 1 - 4 | |
{% block body %} | |
<div class="container"> | |
<div class="row"> | |
<div class="col-12"> | |
... line 9 | |
<div style="box-shadow: 2px 3px 9px 4px rgba(0,0,0,0.04);"> | |
<div class="q-container-show p-4"> | |
<div class="row"> | |
... lines 13 - 15 | |
<div class="col"> | |
... line 17 | |
<div class="q-display p-3"> | |
... line 19 | |
<p class="d-inline">{{ questionText }}</p> | |
... line 21 | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
... lines 29 - 56 | |
</div> | |
{% endblock %} |
Oh, and to make things a bit more interesting, let's add some markdown formatting - how about **
around "adorable":
... lines 1 - 9 | |
class QuestionController extends AbstractController | |
{ | |
... lines 12 - 29 | |
public function show($slug) | |
{ | |
... lines 32 - 36 | |
$questionText = 'I\'ve been turned into a cat, any thoughts on how to turn back? While I\'m **adorable**, I don\'t really care for cat food.'; | |
... lines 38 - 43 | |
} | |
} |
Perfect!
If we refresh the page now... no surprise - it literally prints **adorable**
.
Transforming text from Markdown into HTML is clearly "work"... and we know that all work in Symfony is done by a service. And... who knows? Maybe Symfony already has a service that parses markdown. At your terminal, let's find out. Run:
php bin/console debug:autowiring markdown
Nope! And that makes sense: Symfony starts small but makes it super easy to add more stuff. Since I don't want to write a markdown parser by hand - that would be crazy - let's find something that can help! Google for KnpMarkdownBundle
and find its GitHub page. This isn't the only bundle that can parse markdown, but it's a good one. My hope is that it will add a service to our app that can handle all the markdown parsing for us.
Copy the Composer require line, find your terminal and paste:
composer require knplabs/knp-markdown-bundle
This installs and... it configured a recipe! Run:
git status
It updated the files we expect: composer.json
, composer.lock
and symfony.lock
but it also updated config/bundles.php
! Check it out: we have a new line at the bottom that initializes the new bundle:
... lines 1 - 2 | |
return [ | |
... lines 4 - 11 | |
Knp\Bundle\MarkdownBundle\KnpMarkdownBundle::class => ['all' => true], | |
]; |
Ok, so if the main purpose of a bundle is to give us more services... then we probably have at least one new one! Find your terminal and run debug:autowiring markdown
again:
php bin/console debug:autowiring markdown
Yes! There are two services. Well actually, both of these interfaces are a way to get the same service object. See this little blue text - markdown.parser.max
? We'll talk more about this later, but each "service" in Symfony has a unique "id". This service's unique id is apparently markdown.parser.max
and we can get that service by using either type-hint.
It doesn't really matter which one we use, but if you check back on the bundle's documentation... they use MarkdownParserInterface
.
Let's do it! In QuestionController::show()
add a second argument: MarkdownParserInterface $markdownParser
:
... lines 1 - 4 | |
use Knp\Bundle\MarkdownBundle\MarkdownParserInterface; | |
... lines 6 - 10 | |
class QuestionController extends AbstractController | |
{ | |
... lines 13 - 30 | |
public function show($slug, MarkdownParserInterface $markdownParser) | |
{ | |
... lines 33 - 45 | |
} | |
} |
Down below, let's say $parsedQuestionText =
$markdownParser->... I love this: we don't even need to look at documentation to see what methods this object has. Thanks to the type-hint, PhpStorm tells us exactly what's available. Use transformMarkdown($questionText)
. Now, pass this variable into the template:
... lines 1 - 10 | |
class QuestionController extends AbstractController | |
{ | |
... lines 13 - 30 | |
public function show($slug, MarkdownParserInterface $markdownParser) | |
{ | |
... lines 33 - 37 | |
$questionText = 'I\'ve been turned into a cat, any thoughts on how to turn back? While I\'m **adorable**, I don\'t really care for cat food.'; | |
$parsedQuestionText = $markdownParser->transformMarkdown($questionText); | |
return $this->render('question/show.html.twig', [ | |
... line 42 | |
'questionText' => $parsedQuestionText, | |
... line 44 | |
]); | |
} | |
} |
Love it! Will it work? Who knows? Move over and refresh. It... sorta works! But it's dumping out the HTML tags! The reason... is awesome. If you inspect the HTML... here we go... Twig is using htmlentities
to output escape the text. Twig does that automatically for security: it protects against XSS attacks - that's when users try to enter JavaScript inside a question so that it will render & execute on your site. In this case, we do want to allow HTML because it's coming from our Markdown process. To tell Twig to not escape, we can use a special filter |raw
:
... lines 1 - 4 | |
{% block body %} | |
<div class="container"> | |
<div class="row"> | |
<div class="col-12"> | |
... line 9 | |
<div style="box-shadow: 2px 3px 9px 4px rgba(0,0,0,0.04);"> | |
<div class="q-container-show p-4"> | |
<div class="row"> | |
... lines 13 - 15 | |
<div class="col"> | |
... line 17 | |
<div class="q-display p-3"> | |
... line 19 | |
<p class="d-inline">{{ questionText|raw }}</p> | |
... line 21 | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
... lines 29 - 56 | |
</div> | |
{% endblock %} |
By the way, in a real app, because the question text will be entered by users we don't trust, we would need to do a bit more work to prevent XSS attacks. I'll mention how in a minute.
Anyways, now when we refresh... it works! It's subtle, but that word is now bold.
By the way, you can of course read the Twig documentation to learn that this raw
filter exists. But Symfony also has a command that will tell you everything Twig can do. At your terminal, run:
php bin/console debug:twig
How cool is that? This shows us the Twig "tests", filters, functions - everything Twig can do in our app. Here's the raw
filter.
And... oh! Apparently there's a filter called markdown
! If you go back to the bundle's documentation and search for |markdown
... yeah! So, in addition to the MarkdownParserInterface
service, this bundle also apparently gave us another service that added this markdown
filter. At the end of the tutorial, we'll even learn how to add our own custom filters.
This filter is immediately useful because we might also want to process the answers through Markdown. We could do that in the controller, but it would be much easier in the template. I'll add some "ticks" around the word "purrrfectly":
... lines 1 - 10 | |
class QuestionController extends AbstractController | |
{ | |
... lines 13 - 30 | |
public function show($slug, MarkdownParserInterface $markdownParser) | |
{ | |
$answers = [ | |
'Make sure your cat is sitting `purrrfectly` still ?', | |
... lines 35 - 36 | |
]; | |
... lines 38 - 45 | |
} | |
} |
Then, in show.html.twig
, scroll down to where we loop over the answers. Here, say answer|markdown
:
... lines 1 - 4 | |
{% block body %} | |
<div class="container"> | |
... lines 7 - 36 | |
<ul class="list-unstyled"> | |
{% for answer in answers %} | |
<li class="mb-4"> | |
<div class="d-flex justify-content-center"> | |
... lines 41 - 43 | |
<div class="mr-3 pt-2"> | |
{{ answer|markdown }} | |
... line 46 | |
</div> | |
... lines 48 - 52 | |
</div> | |
</li> | |
{% endfor %} | |
</ul> | |
</div> | |
{% endblock %} |
And because answers will eventually be added by users we don't trust, in a real app, I would use answer|striptags|markdown
. Cool, right? That would remove any tags HTML added by the user and then processes it through Markdown.
Anyways, let's try it! Refresh and... got it! This filter is smart enough to automatically not escape the HTML, so we don't need |raw
.
Next: I'm loving this idea of finding new tools - I mean services - and seeing what we can do with them. Let's find another service that's already in our app: a caching service. Because parsing Markdown on every request can slow things down.
Hey Antoine R.!
Ah, good catch on that! That's my fault - I was sloppy! What we should have done is change the <p class="d-inline"></div> into a
div` to avoid this problem :).
Cheers!
Hello,
I tried that, and even with a div instead, as the generated <p> tag has not the class inline, it still goes below the quote instead of going on the same line ! I think the best way to have the question on the same level as the quote is to use a englobling div with a class that gives display: inline to its child.
Hi,
I just tried it. It works well. However, the element englobing our generated html must be inline too, so I used a span instead of a div.
Hey :)
In order to get the MarkdownBundle to work, I had to reboot phpstorm. It was not recognized automatically before that. Any reason why ? I guess I missed something.
Ty. Same problem with Visual Studio Code. Autowiring MarkdownParserInterface didnt work untill I restarted VS. Before you go crazy try restarting :D
Hey Zeljko!
> Before you go crazy try restarting :D
Haha, that's my favorite, yeah! Thanks for sharing your solution with others :)
Cheers!
Hey Boris,
Well, PhpStorm caches a lot of files in your project to work faster, and most of the time it regenerate the cache on file change, but sometimes it might not I suppose, especially if you use some kind of virtualization like Docker, etc. So, rebooting may work, though it's really very rare cases I think. Or, sometimes new versions may bring some minor bugs that may cause this - that just happens, that's why keep it up to date is always a good idea :)
Anyway, thank you for sharing your solution with others!
Cheers!
Why isn't KnpMarkdownBundle one of Symfony Flex's contrib bundles? There are other bundles from knplabs on there, so I was just wondering.
Hey Jack!
Not every bundle should have a Symfony Flex recipe. Even without any recipe, the bundle will be anyway configured by Symfony Flex: installed and registered in your bundles.php. So, if the bundle does not need any required config, i.e. has good defaults that are rarely changed like in this KnpMarkdownBundle - the maintainers decide to not add a recipe for it - less files in your system, yay! But of course, those who need to configure something - still can do it easily by adding a config file manually.
I hope it's clear for you know!
Cheers!
Now, I tried following the instructions in the video, but at the end it's the same old text with no bold words. I tried to add code in this comment box but I couldn't. I wanted to continue writing simple text but couldn't get out of Code Block 🙂
No changes. What am I missing?
Hey Ashatou,
Great, I'm glad you figured the problem out :) Sometimes it might be also a cache problem, so it's a good idea to clear the cache first to see if it helps ;)
Cheers!
Hello,
- When I launch the server with "symfony serve -d" : I can't find a tutorial to solve this : " A new Symfony CLI version is available (5.4.12, currently running 5.4.8)... and then I have a warning : " [WARNING] The local web server is optimized for local development and MUST never be used in a production setup. "
-I don’t know if it’s related but when I launch the web site, symfony profiler announces me 160 deprecations, 1 error, 25 debug...
In advance, thank you
@there , hi, now that the markdown bundle from knp is abandoned, do you have any advice on whether to stick with the abandoned bundle or use the underlying michelf package and how to do that? The recommended twig replacement is not a drop-in replacement as the MarkdownParserInterface is not exposed.
Hey gazzatav!
There's no real harm in sticking with the abandoned bundle... it's SUPER stable, so I wouldn't sweat it too much. If I were to replace it, I would:
A) Install https://github.com/twigphp/markdown-extra
B) If you only have Michelf installed, then the new Twig filter should magically start using it.
C) If you're rendering markdown in PHP - and need an autowireable service - try registering one manually. I would add:
services:
# ...
Twig\Extra\Markdown\MarkdownInterface: '@twig.markdown.default'
Let me know if that helps - the markdown-extra works well, but it's also simple - so you may need to do more work if you have things customized a bit :).
Cheers!
ok quick question:
"composer require knplabs/knp-markdown-bundle" installed all this thing, but did not add that line into bundles.php
I did that manually and it's ok but maybe anyone knows why is that? I have PHP 8< and Symfony 6< versions
Hey Jan,
Hm, it should add that line, probably at some point you hit "No" when Composer asked you if you would like to execute a recipe. It might just remember your answer, depends on what you typed, and then do not ask you anymore. Well, not a big deal, adding it manually should be fine too, good catch on how to do it
Cheers!
Hello. What is the difference from markdown.parser.max and markdown.parser.light? Thanks in advance
Hey zahariastefan462
The features which are enabled by default. You can find exact configuration here https://github.com/KnpLabs/...
Cheers!
Why do not install Markdown service using Flex system? Why KnpMarkdownBundle isn't present in flex.symfony.com?
Hey Janek
Flex doesn't installs services it helps you to configure bundles, which needs some additional configuration. You can read Victor's answer above as it describes perfectly this situation =)
Cheers!
Hello! I'm facing an error while running "composer require knplabs/knp-markdown-bundle" this is what I'm getting:
Your requirements could not be resolved to an installable set of packages.
Problem 1
- knplabs/knp-markdown-bundle[1.7.0, ..., 1.7.1] require symfony/framewor
k-bundle ~2.8|~3.0|^4.0 -> found symfony/framework-bundle[v2.8.0-BETA1, ...,
2.8.x-dev, v3.0.0-BETA1, ..., 3.4.x-dev, v4.0.0-BETA1, ..., 4.4.x-dev] but it
conflicts with your root composer.json require (5.2.*).
- knplabs/knp-markdown-bundle[1.8.0, ..., 1.8.1] require php ^7.1.3 -> yo
ur php version (8.0.0) does not satisfy that requirement.
- Root composer.json requires knplabs/knp-markdown-bundle ^1.7 -> satisfi
able by knplabs/knp-markdown-bundle[1.7.0, 1.7.1, 1.8.0, 1.8.1].
Use the option --with-all-dependencies (-W) to allow upgrades, downgrades and
removals for packages currently locked to specific versions.
Installation failed, reverting ./composer.json and ./composer.lock to their o
riginal content.
Please anyone advise on how to fix this?
Hey Norris,
Did you download the course code and start from the start/ directory? Or do you follow this course on your own project? What version of Symfony do you have? Depends on your Symfony version, probably you can specify a specific version of the bundle and install it, looks like Composer unable to resolve it manually. Or as a possible solution you may do what the error message suggest you, i.e. run:
composer require knplabs/knp-markdown-bundle --with-all-dependencies
But this will upgrade your dependencies that sometimes may also require some extra work, depends on the constraints you use in your composer.json file.
Cheers!
Hi Victor, I used composer require knplabs/knp-markdown-bundle --ignore-platform-reqs and it worked. Thanks for the assistance.
Hey Norris,
Glad to hear you were able to resolve this, and thanks for sharing your solution with others! For learning purposes it should be ok, but if you want to deploy this project to production someday - it would be better to resolve this problem in a different way, ignoring platform requirements may lead to unexpected problems.
Cheers!
Great tutorial, but when using the following it rips away the markdown entered. Suggestions?
questionText|striptags|markdown
Hey Matthew,
Don't use "striptags" filter? :) You literally strip tags first, and then the text without tags you're passing though markdown filter. If it works weird for you - try to debug it e.g. like {{ dump(questionText|striptags) }} - you will see the exact string that will be processed through the markdown filter. Does it look good to you? If no, please share the string and explain what do you mean about "away the markdown entered", I probably don't understand it completely.
Cheers!
git is not installed. Are you sure we are not missing any steps? On the previous video I also got an error that it couldnt vind '.' path in ./bin/console
Hey, if I want to use my code on a production server, do I then have to install all the composer packages etc. completly new on this server?
Hey Samuel
Deploying on a production server is a big topic. But in 2 words you should deploy your code with lock files on server, and run install commands. With this lock files you will be sure that you get same package versions as you have on dev. SO it will work.
Cheers!
// composer.json
{
"require": {
"php": "^7.3.0 || ^8.0.0",
"ext-ctype": "*",
"ext-iconv": "*",
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"knplabs/knp-markdown-bundle": "^1.8", // 1.9.0
"sensio/framework-extra-bundle": "^6.0", // v6.2.1
"sentry/sentry-symfony": "^4.0", // 4.0.3
"symfony/asset": "5.0.*", // v5.0.11
"symfony/console": "5.0.*", // v5.0.11
"symfony/debug-bundle": "5.0.*", // v5.0.11
"symfony/dotenv": "5.0.*", // v5.0.11
"symfony/flex": "^1.3.1", // v1.17.5
"symfony/framework-bundle": "5.0.*", // v5.0.11
"symfony/monolog-bundle": "^3.0", // v3.6.0
"symfony/profiler-pack": "*", // v1.0.5
"symfony/routing": "5.1.*", // v5.1.11
"symfony/twig-pack": "^1.0", // v1.0.1
"symfony/var-dumper": "5.0.*", // v5.0.11
"symfony/webpack-encore-bundle": "^1.7", // v1.8.0
"symfony/yaml": "5.0.*" // v5.0.11
},
"require-dev": {
"symfony/maker-bundle": "^1.15", // v1.23.0
"symfony/profiler-pack": "^1.0" // v1.0.5
}
}
Hello, when you use Knp markdown to HTML, it embeds the text in a <p> tag. So
{{ "text"|markdown }}
gives<p>text</p>.
However, it is impossible to have one <p> tag inside another <p> tag. So in the example of this course, it first generates an empty
<p class="d-inline"></p>
, then the <p> tag with the transformed markdown.If we want to keep previous display, we would need to put the class d-inline into the generated <p> tag.