I told the UploadedFile object to move the file into public/uploads.
And it did...
but I kinda get the feeling it wasn't trying very hard.
I mean, that is a horrible filename.
Well, to be fair, this is the temporary filename that PHP decided to use.
Fortunately, the move() method has a second argument: the name to give to the file.
The easiest name to use is: $uploadedFile->getClientOriginalName().
This is the name that the file had on my computer: it's one of the pieces of data
that is sent up on the request, along with the file contents.
Move over and resubmit the form again.
There it is: astronaut.jpg!
But there are a few problems with this.
Number one is security.
Boo security!
I know, I know, if the world were more butterflies and ice cream cones,
we wouldn't need to worry about this stuff.
But when it comes to file uploads, security concerns are real.
Right now, our upload form has no validation at all.
So even though we are intending for this to be an image, the user could upload anything.
And to make things worse, the file will then be publicly accessible.
Someone could basically use our site as a private file storage, even storing viruses
and trying to trick people into downloading it from our trusted domain.
We'll talk about validation a bit later:
it is critical that you do not allow your users to upload any file type.
Side note: no matter how you build your app or what safeguards you put it place,
you should always make sure that your web server will only parse your main public/index.php file
through PHP.
If your server is configured to execute any file ending in .php
through PHP, that is a huge security risk.
Ok, back to butterflies and ice cream.
Even after we add validation to guarantee that the uploaded file is actually an image,
the user could still successfully upload an image with a .exe or .php file extension!
Even if we validate the file type, allowing fake extensions is weird...
and could be risky.
So problem number one is security and we'll tackle part of it in a minute
and the other part when we talk about validation.
Problem number two is that the filename is not guaranteed to be unique!
If someone else uploads a file called astronaut.jpg, boom!
My schweet photo is gone!
There are a few ways to handle the unique problem - but the easiest one is just
to add some sort of unique id to the filename.
Set $newFilename to uniqid(), a '-' then $uploadedFile->getClientOriginalName().
Below, use $newFilename.
Let's try that!
Better. It's kind of an ugly hash on the beginning of the filename,
but it does solve the unique problem.
You can also use a shorter hash or, when we actually save this data to our Article object,
you could use the Article id instead of the hash.
Or, if you really want to keep the original filename exactly as it was, well...
we'll talk about that later when we upload "references" to our Article.
The other thing I want to solve is the possibility that someone uploads an image
with a totally insane file extension - like .potato.
We can fix this really nicely.
Create a new variable called $originalFilename set to pathinfo()
with $uploadedFile->getClientOriginalName() and the constant PATHINFO_FILENAME.
This will give us the original filename - astronaut.jpg -
but without the file extension: so, just astronaut.
Then, for the filename, use $originalFilename, a dash, the uniqid(), a period,
and now the real extension of the file: $uploadedFile->guessExtension().
Oh, see how there are two methods: ->guessClientExtension() and ->guessExtension()?
The difference is important: the guessExtension() method looks
at the file contents, determines the mime type, and returns the file extension for that.
But the guessClientExtension() uses the mime type the user sent...
which can't be trusted.
So, we're not validating that this is an image file yet, but no matter what they upload,
we should now get the correct file extension.
Give it a try!
Nice! We've got a .jpeg ending.
There's one last thing you might want to do...
and it's really optional.
Go back to the form.
One of my files has uppercase letters and spaces inside.
Let's try uploading that.
It works! There is no problem with storing spaces or...
most weird characters on a filesystem.
But if you want to guarantee cleaner filenames, there's an easy way to do that.
I'll use a class called Urlizer: this comes from the gedmo/doctrine-extensions library.
It has a nice method called urlize() and we can wrap our $originalFilename
in that to make it a bit cleaner.
Try that out.
Nice! So now we have a unique, normalized filename
that at least looks a bit like the original filename.
Later, we'll see how we can keep the exact original filename in all cases...
if you care.
But unless your users are downloading these files,
the exact filenames aren't usually that important.
Next: it's time to put this upload field properly into our Symfony form
and save the filename to the Article entity.