Storing files and displaying preview thumbnails

Constantin Druc ยท 03 Jul, 2021

[00:00] So I have this drag and drop form right here that makes post requests to this media endpoint, but currently we are not doing anything; it's just an empty function. So let's make it work. The first thing I'm going to do is to create a media model and a migration for it; so I'll do php artisan make:model Media --m and then dash m to create the migration.

[00:23] Now let's open the migration and add the following columns: we'll have a name which will be a string, a file name which will also be a string, mime type size which will be an integer and, let's say, an unsigned big integer for the author id. This will be the user that uploaded the file; and let's also set up a foreign key. We'll do table foreign author id references id on the users table.

[01:11] Next up I will refresh the migrations, so I'll do php artisan migrate:fresh --seed and set guarded to an empty array on the Media model. Now let's go back to our route and add some validation. It says here that the maximum upload file size is 512 megabytes so let's go ahead and add it. We'll do file, first, because the file parameter needs to be a file, and then max 512 and three zeros because the size is expressed in kilobytes.

[01:46] Now for the validation error message, we'd like that size to be expressed in megabytes. And we can override that by passing a second array to the validate function and say "Max file cannot be larger than 512 megabytes". Moving on, let's grab the file from the request, and we can do that with request file and pass the name of the parameter - in our case it's also file. And create the media record in the database. I'll do media create and then name will be file get client original name, file name will have the same value, mime type will be file get mime type size will be file get size, and the author id will be the currently authenticated user, so auth id.

[02:51] Now, before we store the file on disk, we need to come up with a directory structure. The one I like to use is media slash year, slash month, slash day, and slash, the id of the model. to build this we can do directory equals media and then we'll grab the creator that of the media model and format it using y for the year, m for the month, and d for the day. Then we'll have slash, media id, and this will be our directory.

[03:43] Now, to store the file on disk we can do file store as, and we'll need to pass in the directory, the file name which will be media file name, and the disk, which will be the public disk. Let's try it out. I'll drop a file and check my storage app public and here it is. Media, the year, month, day, and the id. And here is the file.

[04:10] It would be nice to see a preview of the uploaded file like a little thumbnail, so let's return the url. In our endpoint we'll go here and say: return, and we'll return the id, which will be media id, and preview url. To construct this we can do url, storage, slash, directory, slash, file name.

[04:46] Before we even try this, let's make sure we have a symlink to our storage, and we can do that with php artisan storage:link. Now, let's close this, and go to our create media component, and scroll down here; will we need to add preview url, and here when the request is successful, we will receive data, and we can do media dot id equals data dot id and media preview url.

[05:26] To display the preview url, we can go in our loop here, and let's wrap it in a div,give it a width and a height, and a background; because the image will take a while to load so this will serve as a placeholder. And here we can do image source equals item dot preview url, and let's give this a full height and the full width, make it rounded, and for the alt we can do item.file.name.

[06:08] Let's refresh the page and give it a go. Let's align these items, and we can do that by removing justify between, adding some spacing, so we'll do space x 2, and we'll make this flex one so it pushes the rest of the elements to the right. So let's grab the file again, and here it is. Looks nice. Now let's create a media controller to move all this in a dedicated action. We'll do php artisan make:controller MediaController, and we'll say public function store, we'll grab all this, and here we'll have media controller, store. Let's import the controller, and paste this in. Import the media model, and make sure everything still works, and it does.

[07:40] Now let's see what happens if we drop a different kind of file. The preview thumbnail is broken because, well, the file is no longer an image. Let's make it work so our preview can return images representing different kind of files. The first thing I'm going to do is to create a preview url accessor on the media model. So I'll grab this part and replace it with media preview url, then on the media model we'll do public function get preview url attribute, and this is how you define an accessor, return url storage the directory, which we'll need to grab from here, and we'll have to replace media with this.

[08:42] Now if I go into browser and try again, it should still work, and it does. Inside this preview url accessor we can set up a collection that will serve as definition for different kind of mime types, and then use it to return the correct preview url. Here's what I mean. We'll have urls equals collect, and we'll pass in an array, and here we'll have, for example image mimes, and here we'll have all the mime types associated with the image kind of file- for example image, slash jpeg, or image slash png, and as the preview url will have what we already defined here.

[09:38] But that's for the image kind of file. If we have an audio file, we'll accept these mime types, which will be audio/mpeg or audio/wave for example. But the preview url will be a link to an asset we have in our public directory. So we'll do asset, images, file type, audio.svg, and these are just a couple of files I have in my public directory. So I have a file type for archive, audio, document, other, and video.

[10:27] So what this will be, is an entire collection of such type definitions. Let's call them. So I'll paste in the full list, and here it is. We have the image, the audio, video, document, and archive. To find the correct file type we can do urls first, and we'll pass in a function that will receive the item in the collection and return the one where in array this mime type. So the mime type of the current media file matches the ones found in item mimes.

[11:17] Finally, here instead of returning the image preview, we'll do file type of preview url but just in case we don't find the correct file type we can set a default, and we'll do images slash file type other.svg, and now if I go in the browser, refresh, and upload different kind of files, here they are. Well, except for the mp3 because, well, something is wrong. mp3 file type, video file type, audio. What's wrong here...oh there isn't enough space. To fix this we can go here, and say flex shrink zero, and this will prevent the element from shrinking. Let's give it another go, and here it is. But now this is broken, so let's do truncate, and there it is.