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 isapplication/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