Building A Blog In Laravel Vapor: Part 2
Sep 3rd, 2022 By Taylor Perkins Full Stack Developer
We’re going to dive into part 2 of this blog series, but first, I wanted to go over real quick an adjustment I made to this application’s architecture. I am excited to announce that I solved the dynamic content situation with Torchlights’ blade component. In this part of the series, we will be wrapping up our discussion on using Filament as our admin backend for our posts and users. We will also look at some Laravel Vapor specific configurations and get everything hooked up to s3 and tested locally. If we have time, I will take a look at some of the open source Spatie packages that have reduced my development time dramatically. Let’s first take a look at the exciting architectural changes!
Rendering A Blade Component With Dynamic Content
It was only one week after I wrote my first post with static content that I approached this architecture problem again. I am glad that I started simple first though. I personally tend to do this thing where I approach a challenging problem, and don’t allow myself to solve it with a simple solution first. This typically turns into me procrastinating actually finishing anything or pushing it off because it becomes this HUGE task I dread tackling. I really do like the iteration approach, start small and build on it. This relieves stress and I think it also gave me the time to reapproach this problem with a clear head. Once I took a look at this problem again it was only a matter of an hour or two before I solved it. So let’s take a look at the details.
First, we need to analyze what the content looks like that's being saved. To refresh our memories, we’re using Filament’s RichEditor for our post content.
1RichEditor::make('content') 2 ->toolbarButtons([ 3 'attachFiles', 4 'blockquote', 5 'bold', 6 'bulletList', 7 'codeBlock', 8 'h2', 9 'h3',10 'italic',11 'link',12 'orderedList',13 'redo',14 'strike',15 'undo',16 'alignment'17 ])18 ->fileAttachmentsDisk('s3')19 ->fileAttachmentsDirectory('attachments')20 ->fileAttachmentsVisibility('public')21 ->columnSpan(2)
We need to understand how this editor works and how the data is saved, before we can really understand how I solved the issue. Here is a little sample snippet of the data that gets saved.
1<h2>Simple Heading</h2>2<p>Simple Paragraph to show content and how it's saved.</p>3<pre><x-torchlight-code language="php">4echo 'test code snippet';5</x-torchlight-code></pre>
What I have done here is just a simple heading example and paragraph example. As you can see, everything not in a code block here is saved as standard html. However, taking a closer look at the blade component I used in the code block, you can see that the editor encoded the html. So a < for instance is encoded and saved as <. This is important to understand because rendering a blade component dynamically is definitely expecting an html string, that’s not encoded.
Now that we understand the format the content is in, we can take a look at how I was able to implement rendering this dynamic content. Remember, that one of my requirements for the project was having quality code highlighting, which Torchlight provides. However, the simplest form of using their code highlighting is their blade component (I am certain if we dug deep enough, we could figure out how to render code snippets without using their blade component). This leads us to the final solution for rendering dynamic content. Since we are using blade in this project, we can simply take the post content, and first decode it using php’s html decode function.
1htmlspecialchars_decode($post->content)
You can probably see where I am going with this by now. It turns out that laravel has exposed a Blade Facade that allows us to render blade components. So we can take the decoded string content, and pass it over to the render method.
1Blade::render('html string here with clade components')
Since our content now is a valid HTML string, it’s going to parse that string and render any blade components, just as if you were placing a blade component directly into a blade file. As you can see in the api documentation, the return value is a string, but its HTML. Which means we need to render that string literally. We can use standard blade syntax for this to render our HTML content as HTML. Here’s what the final solution looks like (unfortunately, I can't actually show any blade syntax since I am using blade. Since I now know I can render blade components dynamically, I am going to look into getting this converted to Vue and simply pass the blade rendered content as a prop).
1$content = Blade::render(htmlspecialchars_decode($post->content));
Using SpatieMediaLibraryFileUpload Component With Filament
Now that we’ve covered the architectural changes, let’s get back to where we left off on the last post. I wanted to dig further into this section, as it's not completely necessary to use the SpatieMediaLibraryFileUpload component. However, once again I found another open-source package that dramatically reduced development time. I want to give a huge shoutout to Spatie, they are well experienced in Laravel and have loads of open-source packages for Laravel with solutions to common problems that every Laravel developer runs into. The SpatieMediaLibraryFileUpload Filament component is essentially just an extension of the FileUpload Filament component that integrates seamlessly with Spaties’ open-source package. Let’s take a deeper look.
First, let's backtrack a little bit, and discuss why I went with this package. Before I even found Filament, I had already actually implemented a super simple media upload feature. When I started reading the Filament documentation is when I found the laravel-medialibrary package and quickly decided I would switch. It handles just about every media feature you could think of out of the box. It’s as simple as implementing an interface and using a trait, and you have a totally robust media collection, optimization, and conversion functionality with any Eloquent Model you desire. Just take a gander at their documentation and you’ll see what I mean.
The conversion and collections Spaties' laravel-medialibrary offers is what attracted me. I have actually implemented a really janky solution like this in a custom e-commerce application before. While my solution worked, it was just that, working. It was not great code :). It also took me a lot more effort to implement than I thought. In this project, I didn’t have any initial plans to have media conversions, but thought in the long run it could definitely be beneficial to use. Just for a little comparison, it could be thought of as equivalent to how WordPress handles media, they always have multiple sizes for images you upload. This I believe is potentially for SEO and optimization purposes. Then you have the collections. So say you wanted a featured image for your blog post. But what about any and all other images that may be associated with that post? Maybe an SEO friendly version, or images in the post itself. Whatever your requirements, this package will help you solve it.
Now let’s take a look at the SpatieMediaLibraryFileUpload component. This component basically functions the same as the standard Filament FileUpload component. Here is what that looks like in the PostResource.
1SpatieMediaLibraryFileUpload::make('featured_image')2 ->disk('s3')3 ->visibility('public')4 ->collection('featured_image')
The component itself is as simple as that. You should now have a file upload field within your post/create view in Filament. Let’s break this down a bit. First, you have a disk defined. Of course in local development this isn’t all that important. However, we’re trying to get this application setup for Laravel Vapor, and the storage requirements are a bit picky. We then set the visibility as public, since these images will be displayed publicly. Then finally we’re connecting the dots with defining the media collection this field belongs to. This is where Spaties’ laravel-medialibrary comes into play. You’re defining the collection on this model to save to. In my case, I'm saving to the featured_image collection. One last thing I would like to mention on the laravel-medialibrary package, is defining a single file collection such as a featured image.
1public function registerMediaCollections(): void2{3 $this4 ->addMediaCollection('featured_image')5 ->singleFile();6}
Now let’s take a closer look at getting all the kinks worked out for s3 public storage with Laravel Vapor.
Storing Public Images With Laravel Vapor And Filament
There are a few minor details that, if missed, everytime you try to upload a file to s3, local or in Vapor, you won’t be able to retrieve it publicly. Meaning you can’t just pass an image URL and expect it to show the image. First, you already saw the visibility method on the FileUpload component for Filament. Now let’s take a look at the AWS s3 configuration required. This one actually stumped me for a bit, as I thought I was clearly defining public visibility. But you actually have to configure your filesystem driver to be visible as well.
1's3' => [ 2 'driver' => 's3', 3 'key' => env('AWS_ACCESS_KEY_ID'), 4 'secret' => env('AWS_SECRET_ACCESS_KEY'), 5 'region' => env('AWS_DEFAULT_REGION'), 6 'bucket' => env('AWS_BUCKET'), 7 'url' => env('AWS_URL'), 8 'endpoint' => env('AWS_ENDPOINT'), 9 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),10 'visibility' => 'public', 11 ],
Now you should be able to test this locally, and see that your uploaded files are stored publicly. Laravel Vapor also provides easy staging environments, so feel free to push your code up to staging if you want to make sure it's working properly.
Concluding Part Two
At this point, we basically have everything needed to manage posts with dynamic content and Torchlight code highlighting. As well as storing images publicly with Laravel Vapor. Now that the core functionality is out of the way, next post we can start to look at our posts index as well as take a look at managing our User model with Filament. I would also like to take a look at handling creating/updating events in PostObserver and if time we can discuss Bouncer permissions to allow for only admins to be able to edit our resources. Thanks for reading, until next time.
Please sign in or create an account to join the conversation.