简体   繁体   中英

Laravel 5.3, check if uploaded file is bigger than upload_max_filesize (optional upload)

In Laravel 5.3, I'm trying to catch if an uploaded file has a bigger file size than upload_max_filesize. The upload field is not required.

I tried this method, but it doesn't work

public function checkFile($field)
{
    if (request()->hasFile($field)){ // check if field is present
        $file = request()->file($field);
        if (!$file->isValid()){ // now check if it's valid
            return back()->with('error', $file->getErrorMessage());
        }
    }
}

I can't use just if (!$file->isValid()) because file field is optional and i get a Call to a member function isValid() on null if field is empty.

So I have to check if field is present using if (request()->hasFile($field)) , but this doesn't work for big files, since dd(request()->hasFile('picture')) returns false .

Of course I can rely on default Laravel Validator messages, but I get a dummy The picture failed to upload. that doesn't give any clue to the user.

Laravel Validation will work only if the filesize you are uploading is less than the limit set in php.ini.

If you try to upload a file larger than the limit, PHP will not forward the request to Laravel, and will error straight away. Hence, Laravel cannot do anything in this scenario.

One way to fix this is to set a much larger limit in php.ini and then validating the file size in Laravel.

You should consider using the built in Laravel form request validation system. There is a built in validation rule which lets you specify a max file size, you can check out the docs here:

https://laravel.com/docs/5.3/validation#rule-max

Your rule would look something like this:

[
    'video' => 'max:256'
]

This would fail if the file uploaded was a greater size than 256kb.

You mentioned that you didn't like Laravel's built in validation error messages. No problem! You can change them in the resources/lang/en/validation.php language file, this is the line you'd need to change:

https://github.com/laravel/laravel/blob/master/resources/lang/en/validation.php#L51

My previous answer handled the case where an uploaded file was bigger than the upload_max_filesize setting in php.ini . But failed when the size of the file made the Request be larger than the post_max_size (another php.ini setting). This case is harder to handle because the inputs (the $_POST global, if we handled plain PHP) get cleared.

I think a Middleware is a good place to do this "validation":

public function handle(Request $request, Closure $next)
{
    $post_max_size = ini_get('post_max_size') * 1024 * 1024;
    $content_length = $request->server('HTTP_CONTENT_LENGTH') ?: $request->server('CONTENT_LENGTH') ?: 0;

    $response = $next($request);

    if ($content_length > $post_max_size)
    {
        return redirect()->back()->with('errors', collect([trans('validation.max.file', ['max' => 2000])]));
    }

    return $response;
}

(As I said, this won't preserve the input.)

Server side code(In Controller):

Below function is taken from drupal by meustrus author at his stack answer and I taken here as example. Start with post_max_size

// Returns a file size limit in bytes based on the PHP upload_max_filesize
// and post_max_size
$max_size = parse_size(ini_get('post_max_size'));

// If upload_max_size is less, then reduce. Except if upload_max_size is
// zero, which indicates no limit.
$upload_max = parse_size(ini_get('upload_max_filesize'));
if ($upload_max > 0 && $upload_max < $max_size) {
  $max_size = $upload_max;
}

//Get max upload file size limit...
$file_upload_max_size = $max_size;

Public function to parse size

public function parse_size($size) {
  $unit = preg_replace('/[^bkmgtpezy]/i', '', $size); // Remove the non-unit characters from the size.
  $size = preg_replace('/[^0-9\.]/', '', $size); // Remove the non-numeric characters from the size.
  if ($unit) {
    // Find the position of the unit in the ordered string which is the power of magnitude to multiply a kilobyte by.
    return round($size * pow(1024, stripos('bkmgtpezy', $unit[0])));
  }
  else {
    return round($size);
  }
}

Set compact for send 'file_upload_max_size' value to blade

return view('YOURBLADEPATH',compact('file_upload_max_size'));

JS Validation(In Blade):

<script type="text/javascript">
document.forms[0].addEventListener('submit', function( evt ) {
    var file = document.getElementById('file').files[0];

    if(file && file.size < '{$file_upload_max_size}') { // 10 MB (this size is in bytes)
        //Submit form        
    } else {
        //Prevent default and display error
        evt.preventDefault();
    }
}, false);

In my experience, I use JavaScript to validate file sizes.

*JavaScript

function validate_size(elm) {
    var file = elm.files[0];
    var max_size = {{ env('UPLOAD_MAX_FILESIZE', 2097152) }}; // in bytes, e.g. 2 MB = 2097152 Bytes
    var size_name = {{ floor(env('UPLOAD_MAX_FILESIZE', 2097152) / 1024 / 1024) }}; // result: 2

    if (file && file.size < max_size) {
        // CONTINUE
    } else {
        // PREVENT AND DISPLAY ERROR
        alert('File size cannot be greater than ' + size_name + ' MB');
        // RESET INPUT FILE VALUE
        elm.value = '';
    }
}

*HTML/Laravel Blade

...
<input type="file" name="photo" onchange="validate_size(this)">
...

Then it will alert the user if they select a file with a size greater than the allowed size.

The default behaviour for Laravel file validator is to reject the file if the upload was not ok, for whatever reason. Validation rules then are not applied, so a "max" rule can't help you here. You clearly want in this case a custom message for this type of error (max file size exceeded). I think extending the Validator class is an elegant solution.

use Illuminate\Http\UploadedFile;
use Illuminate\Validation\Validator;

class UploadSizeValidator extends Validator
{
    protected function validateAttribute($attribute, $rule)
    {
        $value = $this->getValue($attribute);

        if ($value instanceof UploadedFile && $value->getError() != UPLOAD_ERR_OK) {
            switch ($value->getError()) {
                case UPLOAD_ERR_INI_SIZE:
                    return $this->addFailure($attribute, 'max_file_size_exceeded', []);
                // check additional UPLOAD_ERR_XXX constants if you want to handle other errors
            }
        }

        return parent::validateAttribute($attribute, $rule);
    }
}

Now, how do you tell the framework to use your validator instead of the default one? You can set a resolver function on the Validator Factory:

// do this in a 'boot' method of a ServiceProvider
use Illuminate\Support\Facades\Validator;

Validator::resolver(function($translator, $data, $rules, $messages, $customAttributes) {
    return new UploadSizeValidator($translator, $data, $rules, $messages, $customAttributes);
});

Finally set an appropriate message for the key 'max_file_size_exceeded' in the validation.php lang file.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM