Rendering Markdown to HTML0:01
There are a few different methods we could use to convert the markdown here into HTML. For one, we could do it in the frontend. So there are Vue packages that convert markdown to HTML, but I think that's a bit of an unnecessary overhead for the user. We should do it in php instead. The simplest way to do it in php would probably be to go to the Post resource, and we have the body here, so let's introduce a HTML element underneath it. We'll wrap the body in the string helper, and then we'll call markdown. And markdown is going to convert markdown into HTML. It's built right into Laravel.
And markdown is going to convert markdown into HTML. It's built right into Laravel. And I imagine now, if we go to our post show page, here we have our article. Let's set the v-html to post.html. Remove this from the middle and refresh. And yeah, it's not looking great, but that is the HTML. Let's fix it by adding the prose helper and the prose-sm helper, and let's set max width of none. And look at that.
Avoiding Repeated Conversion1:05
and let's set max width of none. And look at that. Here we go. That is beautiful. It's right there. It's in front of our eyes, outputted HTML. Obviously, it would work for all of the Posts, and it already looks great. However, what that means is that each and every time we hit the post show page, we're going to be converting markdown to HTML. Every single User will make the server convert the same markdown to HTML.
we're going to be converting markdown to HTML. Every single User will make the server convert the same markdown to HTML. It's just not very efficient. And, of course, we could cache this. You could use the Cache facade or the cache helper to cache it for a certain amount of time. Then you've got to think about busting the cache when somebody updates the Post or risk having outdated content. Why don't we just have a column on the post table that would store the HTML? And when you create the post or update the post, that column gets updated.
Adding Stored HTML Column1:53
Why don't we just have a column on the post table that would store the HTML? And when you create the post or update the post, that column gets updated. I think that makes much more sense. So seeing as we haven't actually published our application yet, I'm going to edit the original post table migration. We will add a new longText column called HTML right next to the body column. And then once we have that, we can start thinking about how to fill it out. One way we could fill it out is in the PostController. So in the PostController, here we have store, and you have your body that comes through.
So in the PostController, here we have store, and you have your body that comes through. So here we could say, well, HTML is equal to string wrapping data body, and then we'll call markdown. And, again, this would work fine. However, we are going to duplicate ourselves and not only do we have to do that here, we have to do it in the update method when we come to implement that, along with any other place where the Post might be updated. It would make much more sense if we just have this conversion happen
Testing Model-Level Generation2:50
along with any other place where the Post might be updated. It would make much more sense if we just have this conversion happen automatically at a model level. We could do that with attributes, right? So let's remove this, and I'm actually going to jump into the PostTest, so the model test that we created, and let's say it generates the HTML. And to do that, well, we'll have a post, so we'll say PostFactory, and rather than calling create, let's call make, and perhaps in here we could just set the body to something like hashtag space
and rather than calling create, let's call make, and perhaps in here we could just set the body to something like # or ## hello world, and then down here we go ahead and save that post, so $post->save(), and I would expect the post HTML to equal a string wrapping the post->body calling the markdown helper. Let's run it. Obviously it fails. We can jump into the Post model, and down here where we have our title attribute,
We can jump into the Post model, and down here where we have our title attribute, why don't we introduce another attribute, and we're going to call this one body, okay? Bear with me. body returns an attribute, and this says return attribute set, so very similar to what we've got for title. It's going to receive the value that has been set for the body, but instead of returning just a plain string, I'm going to return an associative array.
but instead of returning just a plain string, I'm going to return an associative array. The associative array is going to set the body to whatever you've passed, but it's also going to set HTML at the same time, and it will set HTML to, you've got it, string wrapping the value and then calling the markdown method. Run the test again, and now you'll see that it passes, which means that we could come back to our PostResource, and rather than doing this work here, we can just output this HTML, get rid of the markdown method call,
and rather than doing this work here, we can just output this HTML, get rid of the markdown method call, and if we run php artisan migrate:fresh --seed, we shouldn't need to even update our factories because this read only column will be generated automatically, and if we go back to posts, everything should still work exactly as it did before. Wonderful. However, we're still not quite done here, and that's because if you think about it,
However, we're still not quite done here, and that's because if you think about it, any time we update the body property on a Post model, this is going to be executed, so we will call the markdown method every single time we update the body property. Now imagine we update the body property five different times in different places in our code base in a single request. That means the markdown method is going to be called five times. It just isn't necessary to do that each and every time.
That means the markdown method is going to be called five times. It just isn't necessary to do that each and every time. It's only really necessary to update the HTML right before it gets persisted to the database. Again, this is a micro-optimization, but I think it's important just to stop and think about the best way, the best implementation for the solution you're trying to achieve, and in this case, it would make much more sense to listen for a particular event on the model.
Generating HTML on Saving5:57
and in this case, it would make much more sense to listen for a particular event on the model. Let's remove this attribute entirely, which obviously is going to cause our test to fail again, but don't worry, it'll pass in just a moment, and we'll come to the top of the model, and I'm going to override the booted method. So the booted method is a little hook you can make use of in a model to allow you to set up anything you want the model to do.
you can make use of saving in a model to allow you to set up anything you want the model to do each and every time. It's a configuration option, and in here, I'm going to refer to the static saving method. Now, this is a callback, and the callback will receive an instance of the Post model, so we'll call this post, and this method, this callback, is going to be executed every single time your post is about to be saved to the database,
and this method, this callback, is going to be executed every single time your Post is about to be saved to the database, whether that's being created or whether it's being updated, which is perfect because all we have to do at this point is say, well, actually, I want to fill the post, and I want to fill the post's HTML column with, you've got it, string wrapping post body calling the markdown method, and let's actually type in this so that we have some nice autocomplete as well in our IDE.
and let's actually type in this so that we have some nice autocomplete as well in our IDE. Now, we'll rerun that test that was failing. Note that it's passing again, so this is a successful refactor. We can prove that by rerunning php artisan migrate:fresh --seed. The seed completes successfully, and posts should be nicely filled once again with beautiful HTML output for each post. We have our links. We have our quotes.
Security and Comments Next7:28
with beautiful HTML output for each Post. We have our links. We have our quotes. We have bullet points. We have headings, all the rest of it. It all just works, but now we have a very efficient way of creating the HTML because the only time this markdown method will be called is when the Post is being updated or created in the database. All right, before we wrap this chapter up, there are a couple of considerations. First of all, what if someone tries to create unsafe markdown?
there are a couple of considerations. First of all, what if someone tries to create unsafe markdown? That could prove risky. We need to make sure that, for example, you can't embed HTML inside your markdown. Thankfully, Laravel makes that fairly simple, and then the other thing we just need to consider is the fact that we want to be able to support markdown in our comments as well, so we need to migrate our editor over to the comment field. Let's tackle both of those points in our next episode.
so we need to migrate our editor over to the comment field. Let's tackle both of those points in our next episode.
