June 26, 2024Profile photoTony Masek

Easily validate max file size based on your settings in php.ini

In several projects, I've noticed that it can be useful to validate max filesize against the `upload_max_filesize` in php.ini. Let's take a look at how to write a validation rule to achieve this.

On a few projects I've worked on, I've noticed, that quite often we want to allow the maximum file size to be the same as our setting inside php.ini. Over time the value inside php.ini might change due to different requirements, but the validation rules usually stay the same until a user hits the rule and is prevented from proceeding.

Thanks to this rule the maximum file size is automatically loaded based on the current php.ini setting, ensuring it stays in sync.

Validation logic

The validation logic to make this work is actually quite simple. The main thing we need to get is the proper value from php.ini, and then we can leverage the existing max validation rule for files.

Maximum file size configuration

Let’s start with a bit of theory. To control the maximum allowed file size, we can tweak these two values in php.ini: upload_max_filesize and post_max_size. The value for these settings can be either an integer (in which case the unit is bytes) or a string using shorthand notation. To disable any limit we can set the number to 0 or lower.

Since PHP version 5.3.4 the post_max_size = 0 will not disable the limit when the content type is application/x-www-form-urlencoded or is not registered with PHP.

Strictly speaking, we should also take into account memory_limit. These three values should follow this rule: memory_limit >= post_max_size >= upload_max_filesize. In the case of post_max_size >= upload_max_filesize it not only should be this way it must be this way. Lastly, for the sake of this rule/article, we will focus only on post_max_size and upload_max_filesize.

Getting the value from php.ini in bytes

To get values from php.ini we can use ini_get function. However, as we mentioned before, the value can be an int in bytes or a string using shorthand notation. To ensure we always get the value in bytes, we can leverage the ini_parse_quantity function, which will handle it for us.

The validation rule

We now know, how to get values as bytes from php.ini and understand the relationship between post_max_size and upload_max_filesize. With this knowledge, the rule itself is quite simple:

class MaxUploadSizeRule implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
		// Limit is disabled
		if (($maxUploadSizeInBytes = $this->getMaxUploadSize()) < 0) {
            return;
        }

		// Set appropriate max size and leverage existing max rule
        $rule = Rule::file()->max($maxUploadSizeInBytes / 1024);

        $validator = Validator::make(
            [$attribute => $value],
            [$attribute => $rule]
        );

        if ($validator->fails()) {
            $fail(Arr::first($rule->message()));
        }
    }

	private function getMaxUploadSize(): int
    {
		// We know, that `upload_max_filesize` has to have a lower value
		// than `post_max_size`. So in case there is a limit set
		// we should use this because it can't be higher than
		// `post_max_size`
        $uploadMaxSize = ini_parse_quantity(ini_get('upload_max_filesize'));
        if ($uploadMaxSize > 0) {
            return $uploadMaxSize;
        }

		// In case `upload_max_filesize` is 0 or lower, it means, that
		// the limit is disabled. In that case, check for the value of
		// `post_max_size`
        $postMaxSize = ini_parse_quantity(ini_get('post_max_size'));
        if ($postMaxSize > 0) {
            return $postMaxSize;
        }

		// Limit is disabled
        return -1;
    }

}

And just like that, you can rest assured, that your rule will always be in sync with your current setting in php.ini.

Files larger than max_upload_size

Please note, that if the file is larger than max_upload_size, you will receive the following error message: "The file failed to upload." The reason is that even if you try to inspect the uploaded file, you will find that it has an error and is not uploaded because it exceeds the limit.

Files larger than post_max_size

In this case, Laravel will throw an exception. In production, you might encounter a "413 Content Too Large" error, and in debug mode, you would see "PostTooLargeException".

Why use the rule then

As mentioned at the beginning of the article, this approach keeps the maximum allowed file size in sync with the php.ini setting. Essentially, it means you are using the existing max rule, but it always uses the current setting based on php.ini. In all other ways, it behaves exactly like the max rule.

Package

I find myself using this rule quite often. So to make it easy to reuse, I’ve written a package laravel-max-upload-size which you can use.

Usage

To use this rule you can either use the fluent syntax or use the rule directly:

Validator::make(['file', $file], [Rule::file()->maxUploadSize()]);
Validator::make(['file', $file], [new MaxUploadSizeRule()]);

Any suggestions or PRs are welcomed :)

Until next time,
Tony