Playing
01. Automatically separate user file uploads in Laravel

Episodes

0%
Your progress
  • Total: 17m
  • Played: 0m
  • Remaining: 17m
Join or sign in to track your progress

Transcript

00:00
So, if you're using Laravel's file system to upload files, specifically when a user signed in and they are uploading a file, it's usually a really good idea to keep these separated
00:10
in a folder per user and not just dump all of the files in one place. Now, there's a couple of reasons for that but the reason that I found this most useful is if a user maybe leaves your system, you may want to delete that entire folder just to free up some space, particularly if you have large file sizes.
00:27
There are other reasons as well, security reasons, but we won't dive too much into that. It's just a nice organizational thing to do. Now how are we actually going to do this? Well, I'm going to demo this out with Amazon S3 but this works in exactly the same way
00:40
for other file system storage as well. So let's just go ahead and set up S3 together just so we can see how this works. I'm going to go ahead and add a user just in here and let's go ahead and give these programmatic access to S3 and let's give them full access.
00:56
So if we just head over here and say S3 full access, go ahead and tick that, click review and that should give us, once we've created that user, the access key and the secret key. Now if we come over to config and file systems, we're going to be using S3 here and this is really important because we're going to be modifying this config a little bit later to
01:17
programmatically set a route inside of here depending on the user that we are working with. So let's head over to EMV. I'm actually going to go ahead and copy this over from a previous EMV so I don't have to
01:29
paste everything in, just makes it a little bit easier. So let's go ahead and down here pull all of them environment variables as well as the file system driver as well. That just lives over here so we can switch that up depending on what we want to use.
01:44
And let's go ahead and replace in these old keys just here. So let's grab the access key ID and let's show and copy and paste the secret key across here and the region is still US East 1. The bucket name is example.codecourse.com and we've given the AWS URL to that bucket
02:01
as well. So now that is set up, we need to go ahead and just pull in, again I'm going to copy and paste this over, the S3 fly system package and that's going to go ahead and allow us to connect to S3 so we'll wait for this to finish.
02:18
And now that's done, we should be pretty much ready to go to start to upload something. So if we just close this off, like I said I've created my bucket just over here, no special permissions or anything on this and let's go ahead and modify the upload controller to upload this file.
02:33
So I've just created a really simple upload controller inside of here and I've hooked this through with a URL and I'm going to create a store method inside of here, let's bring in our request object and really simply all we need to do here is grab the upload from the request and we just use the file helper for that and I've called this file.
02:52
We just head over to home.blade.php, this is just the form that we've seen, really, really straightforward and input with a type of file. And then we just go ahead and use the storage facade. We can choose the disk if we want to, we're not going to do that just yet, but we can
03:07
do something like put file, choose the location and then go ahead and pass in the upload and then just return redirect the user back. So let's go ahead and pull in the namespace for the storage facade or just use storage on its own and we should be able to upload now.
03:22
So let's go ahead and pick a file just from the desktop maybe, let's grab this and click upload and sure enough that's redirected us back and we should have that file just floating around in here. Laravel will automatically give this a key as well as you can see the name.
03:38
So if we just go ahead and delete this out, usually what we would do if we wanted to put this in a specific location based on a user would be to grab this out from here. So we would say slash and we'd go ahead and say maybe request user ID, something like that and then that would go ahead and place that in the correct place.
03:58
But doing this everywhere can get a little bit annoying so we're going to come up with a much better solution to do this. So it just happens automatically so we don't have to keep doing that over and over again. Now to see how this works, we're going to go over to the file system manager and let's
04:16
go down to create and you can see here we have a method for each of the types that Laravel supports. So in our case, we were calling create S3 driver when we were booting this up because of course our config said that we wanted to use S3, same for rack space and all that
04:34
kind of stuff as well as local for the local file storage. So what we really want to do is create an entirely new file system entry, a new disk if you like, calling this method and passing through our own config into here and then setting the route to the user's ID.
04:54
Now of course what we could do over in our config and file systems is try and cleverly modify this and add the user ID in here as the route. So for example, if I was user one, the route would be one which would be the folder one but we don't really want to do that because we want the ability to switch out from this
05:11
if we need to upload to anywhere else. So really importantly, we want to do this only when the user is signed in and when we use that specific disk. Now how do we do this?
05:20
So let's load everything off and just go ahead and generate some middleware. So let's go ahead and make some middleware and let's call this file system middleware. Now we could potentially do this in a service provider but we can't access the currently authenticated user in a service provider.
05:38
I'm putting this inside of middleware, there are other ways that you can do this as well. So for example, inside of middleware you could fire an event to say that the user is signed in and then your listener could deal with setting that new disk. So there's a load of other ways that you could do this but just popping it in middleware
05:53
is probably the easiest way to do this for now. So let's go ahead and open up our file system middleware. Now of course the first thing we want to do, we don't want to go ahead and create a new file system disk if the user isn't signed in.
06:05
So first of all, I'm just going to say, well, if the user is not signed in, I'm just going to go ahead and return the next request inside of here and we just won't bother adding this file system in. So before we go any further, let's come over to our kernel under HTTP and let's add this
06:21
middleware into our web middleware group or over in our main middleware just here. I'm going to go ahead and add it down under web because we need the session started before this happens. And of course you can fiddle around with this to also add it to API as well.
06:35
So we want to go ahead and put in that file system middleware and we're done. So let's come over to our app, over to here, give this a refresh, make sure that it's working and that looks good to me. So the first thing that we want to do is set the file system route by grabbing the
06:50
config that we're currently using or the driver that we're currently using and then inside of the config, modifying that config to add that route in there. Now just to give you a demonstration of this, let's just come over to our config over to file systems and let's come down and just add the route in here just so we can see how
07:07
this works. Let's put ABC in there for now just to test this out and let's go ahead and upload another file just to see what happens. Okay, so that's uploaded.
07:16
Let's come over and give that a refresh and sure enough we have an ABC folder with of course that file in there. But what we want is either the user's ID or some other unique identifier. You might generate a UUID for every single user that signs up to your app.
07:33
So let's extract this out to another method. We'll say set file system route just to keep things nice and tidy. And of course since we're in a class, we have the beauty of being able to extract all of these out to different methods.
07:45
Now the way that we're going to do this is using config and set and we need to grab the file system default driver that we're using and then from this go into disks and then pick this one out. So because our file system driver is currently set to S3, we need to go into disks, S3 and
08:02
then set the route under there. So to do this really, really simple, we just say file systems, disks and then dot and we concatenate on using config again, file systems default and then we go ahead and concatenate on dot route and we want to set the value of this to the user's ID.
08:21
So we can just use auth ID really simply. So that is as easy as it gets. Setting the file system route inside of here works nicely. But the only issue with this is and you can just leave it like this if you want, what's
08:36
going to happen now is this will always upload to that specific location with that route regardless if you don't want it to. So for example, over in upload controller, we might just want to say disk user like so every time we only want to use that particular user's folder for file uploads.
08:56
So let's test this out what we've done so far. This works for most people. So this is the really simple quick solution to doing this. So let's go over to here and make sure that we over in file systems don't have a route
09:08
in here. And let's go ahead and upload a file from here and we should see that placed into a folder with my user ID. And sure enough, we do.
09:18
So we know any file that that particular user now uploads will go inside of this folder. And if they leave our platform or we need to get rid of all their files, we just have one folder to delete and we don't have to hunt through every single file. Now let's just go ahead and expand on this just to add the additional disk.
09:37
We're going to create a method down here called create driver or create disk, whatever you want to call it. The first thing that we need to do is based on the current thing that we're using. So the current driver that we're using, we need to construct the method that we want
09:53
to call on our file system manager as we saw a little bit earlier. So really, the easiest way to do this would be to go ahead and say this and again, I'm going to extract this out to another method. So create or get creation method, let's go ahead and create a get creation method down
10:16
here. And essentially all this needs to be is create and then the name of the file system. So for example, S3 and then driver. So as we saw from our file system manager a little bit earlier, we have things like
10:29
create local driver, create S3 driver, and all that good stuff. So we want to return the string create, concatenate on uppercase first of the file system's defaults that will be S3. And then we want to add on driver just at the end of that.
10:50
So that will give us the method name that we can use to create that new specific file system with that config. However at the moment, what we're doing is we're completely overwriting the config. So regardless of which disk that we use, this is always going to prepend using that folder
11:06
name. So the next thing we want to do is just create a constructor up here and go ahead and inject in our file system manager and we can do this via the file system factory. And I'm going to go ahead and call this file system.
11:19
Let's just assign this here. So this file system, assign that to file system. And let's come up here and just create that out as well. So let's go ahead and pull the namespace in for this.
11:30
This will be under file system factory and this will give us an instance of file system manager. So what we can do under create driver now, if we just come over to that file system manager again, we have set which will go ahead and add a new disk in with all the information
11:46
that we need. So what we are going to do in here is return this file system and we're going to go ahead and create this using the method that we've determined and then we're going to go ahead and pass in the config.
12:00
So we're going to reference the config under file systems, disks, and then again concatenate on config file systems default. So that will give us passing through to the method here, all of the config that we've already modified.
12:18
So what we can now do just up here is go ahead and create a new disk for this. So let's use this file system set as we just saw. We give the name of the disk that we want to create. In this case, it's just going to be something like user.
12:31
And then we say this create driver and that will return us what we get back from calling the create S3 driver method determined by the method name just here. So let's try this out. Let's just give this a refresh, make sure we didn't break anything.
12:45
And let's go ahead and switch this over. So under here, we're going to choose disk now and we're going to say user. So we are saying that we specifically want to store this file relative to the path of the user which we've chosen as the user ID.
12:59
So sure enough, if I just go ahead and upload a new file, we would expect to see the exact same result. Over in this folder, if I give that a refresh, we now see two files. Now of course, the only problem with this is what we're doing is we're actually setting
13:12
the config over here. So we're overwriting that config regardless. So what we really want to do is just say something like get file system config instead. And we want to go ahead and return from here some overridden config.
13:27
So let's just keep this fairly messy for now. What I'm going to do is use config just up here to grab out the configuration from file systems disk and then from the default. So let's pull that into there.
13:39
Let's just give that a die dump just so we can see that we're getting the right config back in there. And we can get rid of this line here as well for now. Let's come over and give that a refresh.
13:49
And sure enough, set file system root does not exist. Let's go ahead and call that correct method. And sure enough, we have the S3 details in here. Now instead of overriding this globally, what we now want to do is just say config root
14:04
and set this to the user's ID. So we're just going to say auth ID. And then we're going to go ahead and return that config. So we're just kind of keeping a local instance of this in this array.
14:14
We're not just going ahead and globally modifying it. So now that we can get the file system config, we can grab this here and instead pass this through when we're actually creating the driver. So we pass that into the create driver method.
14:29
We pass the config into here. And then we just go ahead and pass the entire config into that method when we go ahead and create that driver. So we're not globally modifying this.
14:41
So all this means now is then if I get rid of the driver just in here and I try and upload a file, let's come over and upload, say, client again and come over here, that's going to go ahead and put that in the root directory. However, now when I want to specifically upload this for a user, I can go ahead and use this.
15:02
And then over here, when I go ahead and upload a file, sure enough, if we head back over here to my user ID and give that a refresh, we see another file inside of there. So of course, you can do whatever you want with this. This is just a really rough example of how I would implement this to separate this out.
15:18
Like I said, you could put this inside of an event if you wanted to and then the listener would be responsible for setting this as long as you can inject a dependency in, e.g. the file system manager. It doesn't matter.
15:31
It's going to work in exactly the same way. Now let's just see what happens when, and I'm not going to go into too much detail with this, but let's just see what happens when we switch over the driver that we're using for our file system and then we'll go through this just to make sure it makes sense.
15:45
So we're setting the file system driver now to local. What that means is that when we go ahead and call that middleware or that middleware is called, let's come over to here, then we are going to set the user file system disk much like we did before.
16:02
But when we call create driver, this is going to go ahead and grab us create local driver which is of course a method over on our file system manager class. Now a good thing to do at this point would be to just before you create this, check that the method actually exists.
16:18
So what you could do is something like if not method exists on this file system, and of course you know the method name now, it's just the method name that we've generated, then you might want to go ahead and throw a new exception or something like that, you know, it's entirely up to you.
16:34
So that's just something you might want to do. Either way, what we're now doing is calling the create local driver method because we've switched that over and we are setting the route just here, so that's going to work in exactly the same way.
16:49
Now I won't configure this by default, this will pop this into the public directory, but go ahead and check out the Laravel docs and you can see how that works. So now that I've switched the driver over, I'm going to go ahead and upload another file, click upload and sure enough what we should see is one and then that file, perfect.
17:07
So there we have it, a really nice easy way using a piece of middleware to set the route directory based on the currently signed in user and giving you that additional disk as well so it makes it a lot easier to switch between uploading files that you need for your app and having user specific uploads.
1 episode 17 mins

Overview

Setting up Laravel's filesystem to automatically separate user's uploads into their own directory.

Alex Garrett-Smith
Alex Garrett-Smith
Hey, I'm the founder of Codecourse!

Episode discussion

No comments, yet. Be the first!