alex_hill's avatar

Validating filename is unique

How would I go about validating that an uploaded file has a unique filename (from the database column).

I currently validate the file type:

$this->validate($request, [
            'file' => 'required|mimes:pdf,doc,docx'
        ]);

I can also see the validation rule for checking uniqueness on the database column using:

'email' => 'unique:users,email_address'

but I'm not sure how to pull the name out of the file and feed that into the validation (the 'email' bit above)

0 likes
16 replies
IsaacBen's avatar

Here's what I do. I just generate a random filename before it's stored into the DB.

        $filename = Input::file('filename');
        $change = $filename->getClientOriginalExtension();
        $newfilename = Auth::id().str_random(10).'.'.$change;
        $filename->move('files', "{$newfilename}");  
        $image->filename = 'files' . '/' ."{$newfilename}";
        $image->save();

If for some reason this isn't enough for you then make it unique on the DB migration.

$table->string('filename')->unique();
2 likes
alex_hill's avatar

Thanks guys - but the random file name is not really an option for this component (I have used that technique elsewhere though). Essentially the administrator would put up a brochure for a particular shop, and the name of the file itself could be important (ie Shop A - October). After the user has downloaded it they may rely on the filename to identify the components of the file.

Now if Shop A decides they want to put up a second brochure for October, I want to be able to flag that there is already a file with that name and they will need to adjust it, or delete/overwrite the current file (actively deleting the file is a preferred method).

IsaacBen's avatar

@alex_hill So what's the problem? make it unique like I showed in the DB migration. I believe that Laravel will spit out the right error message and the user will understand that he needs to change the filename.

thomaskim's avatar

If user's are providing the file name, @IsaacBen's suggestion should work out just fine.

The only thing I would say that you would need to add in addition to that is validating the file name's uniqueness with AJAX because if the validation fails, browsers won't let you repopulate the value of the file for security reasons. So, if the file name is not unique, the user would have to re-add the file. It's only a few extra clicks, but it would be annoying having to re-add the files every time your file name wasn't unique.

mehany's avatar

I think a SQL STATE gets thrown if no validation set like such 'filename' => 'required|unique

alex_hill's avatar

I will give that a shot, and see what happens. @thomaskin, it probably wont make a big difference if they have to re-add the file considering they would need to rename the file (or navigate away and delete the previous file) and re-upload anyway

miwal's avatar

I have done what the original question asked firstly by adding the filename to the request, then using a validator in the controller as opposed to a form request. Then I refactored to use a custom validator instance (see docs) which has allowed me to write clearer code. The reason is the custom validator lets you just pass an array (with your one item: the filename) to validate as opposed to requiring a request instance. You can also easily add a custom error message at that point.

1 like
yasin10's avatar

I'm doing this by throwing custom error.

if ( File::whereName($file_content->getClientOriginalName())->first() ) {
    $error = \Illuminate\Validation\ValidationException::withMessages([
         'file' => 'File already exists'
    ]);

    throw $error;
}

Or one can create their own validation rules for it.

jjudge's avatar

You can create a custom validation rule that looks at the filename and does whatever checks you like. This example, as a closure, will check the filename length is within limits:

    function (string $attribute, mixed $value, Closure $fail) {
        $minLength = 5;
        $maxLength = 200;

        $filename = $value->getClientOriginalName();

        if (strlen($filename) > $maxLength) {
             $fail('The filename is too long. Please rename the file to a shorter name.');
        }

        if (strlen($filename) < $minLength) {
            $fail('The filename is too short.');
        }
    }

It can be used anywhere you would use a validation rule or alias. For example in livewire to ensure myFile is not too large, is a CSV, and gthe filename is not too long or too short:

$this->validate([
    'myFile' => [
        'required',
        File::types('csv', 'txt')->max(2 * 1024), // kB
        function (string $attribute, mixed $value, Closure $fail) {
            // ...as above...
        },
    ],
]);

You can put your own arbitrary checks and messages in there.

You can put the rule into a class, and accept parameters to set the limits, if you are using it in multiple places. If you want to be really fancy you could nest the validation rules so you could use any of the other validation rules against the filename, such as min, max, regex.

1 like
Snapey's avatar

@consil Its a 7 year old question for pity's sake.

Always ignore the filename the user gives you. Its of no importance.

1 like
jjudge's avatar

@Snapey It comes up in Google searches, is still a pattern people can use (for pity's sake).

We need opriginal filenames in some situations where downloads of those files, searches of those files, and listings of those files using the original filename is needed. So yeah, a seven year old question, a timeless question, and a closure-based custom validation rule that fits in with today's Laravel versions.

2 likes
johnvoncolln's avatar

@jjudge I appreciate you writing an answer! I'm here 8 years later and I'm going to keep track of the filename and no one can stop me

Snapey's avatar

@johnvoncolln store the original filename in the database and give your file in the filesystem a random name like a uuid. Store the file system name in the database alongside the original name.

1 like

Please or to participate in this conversation.