With <input type="file" name="images[]" multiple>, PHP builds $_FILES['images'] "transposed": one array per attribute, indexed by file ($_FILES['images']['error'][1]), so every multi-file handler starts by flipping it into one array per file:
<?php
declare(strict_types=1);
function files_list(array $field): array { // images[] arrives "transposed"
if (!is_array($field['name'] ?? null)) return $field ? [$field] : [];
$list = [];
foreach ($field as $key => $values) foreach ($values as $i => $v) $list[$i][$key] = $v;
return $list;
}
header('Content-Type: text/plain');
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $_POST === [] && $_FILES === []) {
exit("rejected: {$_SERVER['CONTENT_LENGTH']} bytes exceeds post_max_size\n");
}
foreach (files_list($_FILES['images'] ?? []) as $f) {
printf("%-10s %8d %s\n", $f['name'], $f['size'], match ($f['error']) {
UPLOAD_ERR_OK => (new finfo(FILEINFO_MIME_TYPE))->file($f['tmp_name']),
UPLOAD_ERR_INI_SIZE => 'too big for upload_max_filesize=' . ini_get('upload_max_filesize'),
default => "error code {$f['error']}",
});
}$ curl -s -F 'images[]=@cover.png' -F 'images[]=@big.png' -F 'images[]=@notes.txt' \ 127.0.0.1:8215/gallery.php cover.png 2062 image/png big.png 0 too big for upload_max_filesize=1M notes.txt 14 text/plain $ curl -s -F 'images[]=@huge.bin' 127.0.0.1:8215/gallery.php rejected: 5000216 bytes exceeds post_max_size
A file over upload_max_filesize is dropped alone (error 1, size 0). A body over post_max_size is discarded whole: $_POST and $_FILES are empty and PHP only logs "POST Content-Length of 5000216 bytes exceeds the limit", so the empty-arrays test must tell the user. Ubuntu 225 's defaults are 2M per file and 8M per body; also check max_file_uploads (20 files, the rest ignored), max_input_time (60 seconds) and Apache 129 's LimitRequestBody (1 GiB since 2.4.54). memory_limit does not apply, because uploads stream to disk. A hidden MAX_FILE_SIZE field only makes PHP give up early with code 2; anyone can edit it.