Skip to content

Commit f95a94e

Browse files
authored
Merge pull request #527 from armanist/issue/526-request-multi-file-fields
Issue/526 request multi file fields
2 parents a864bd0 + c454533 commit f95a94e

3 files changed

Lines changed: 75 additions & 21 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ Upgrade guide: https://github.com/softberg/quantum-php-docs/blob/master/v3.0/upg
4545
- Updated CI pipeline to test against PHP 7.4, 8.0, and 8.1
4646
- CI now fails on PHP warnings and deprecations for stricter quality control
4747
- Added `declare(strict_types=1)` to all Exception classes for improved type safety
48+
- Static analysis baseline is now PHPStan level 7
4849

4950
- **BREAKING:** Refactored routing system internals:
5051
- Routes are now represented as first-class objects (`Route`, `RouteCollection`, `MatchedRoute`)
@@ -84,6 +85,7 @@ Upgrade guide: https://github.com/softberg/quantum-php-docs/blob/master/v3.0/upg
8485
- Standardized `defineValidationRules(Request $request): void` across DemoWeb and DemoApi middleware templates
8586
- Fixed OpenAPI installer route generation to return `Response` objects via `response()->...` helpers and avoid undefined response-variable usage (#520)
8687
- Standardized `defineValidationRules(Request $request): void` in Toolkit middleware templates
88+
- Fixed request uploaded-file parsing to preserve multiple top-level multipart file fields in both real and internal request flows (#526)
8789

8890
### Added
8991
- `AppContext` class representing the runtime identity of a single application execution

src/Http/Traits/Request/File.php

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
use Quantum\Storage\Exceptions\FileUploadException;
2020
use Quantum\App\Exceptions\BaseException;
2121
use Quantum\Storage\UploadedFile;
22-
use ReflectionException;
2322

2423
/**
2524
* Trait File
@@ -76,37 +75,60 @@ public function getFile(string $key)
7675
* Handle files
7776
* @param array<string, mixed> $files
7877
* @return array<string, UploadedFile|array<UploadedFile>>
79-
* @throws BaseException
80-
* @throws ReflectionException
8178
*/
8279
public function handleFiles(array $files): array
8380
{
84-
if (!count($files)) {
85-
return [];
86-
}
81+
$formatted = [];
8782

88-
$key = key($files);
83+
foreach ($files as $key => $file) {
84+
if (!is_array($file) || !isset($file['name'])) {
85+
continue;
86+
}
8987

90-
if (!$key) {
91-
return [];
92-
}
88+
if (!is_array($file['name'])) {
89+
$formatted[$key] = new UploadedFile($file);
90+
continue;
91+
}
92+
93+
if (!$this->isMultiFilePayload($file)) {
94+
continue;
95+
}
9396

94-
if (!is_array($files[$key]['name'])) {
95-
return [$key => new UploadedFile($files[$key])];
96-
} else {
97-
$formatted = [];
97+
$types = $file['type'];
98+
$tmpNames = $file['tmp_name'];
99+
$errors = $file['error'];
100+
$sizes = $file['size'];
101+
$multiFiles = [];
98102

99-
foreach ($files[$key]['name'] as $index => $name) {
100-
$formatted[$key][$index] = new UploadedFile([
103+
foreach ($file['name'] as $index => $name) {
104+
if (!isset($types[$index], $tmpNames[$index], $errors[$index], $sizes[$index])) {
105+
continue;
106+
}
107+
108+
$multiFiles[$index] = new UploadedFile([
101109
'name' => $name,
102-
'type' => $files[$key]['type'][$index],
103-
'tmp_name' => $files[$key]['tmp_name'][$index],
104-
'error' => $files[$key]['error'][$index],
105-
'size' => $files[$key]['size'][$index],
110+
'type' => $types[$index],
111+
'tmp_name' => $tmpNames[$index],
112+
'error' => $errors[$index],
113+
'size' => $sizes[$index],
106114
]);
107115
}
108116

109-
return $formatted;
117+
$formatted[$key] = $multiFiles;
110118
}
119+
120+
return $formatted;
121+
}
122+
123+
/**
124+
* @param array<string, mixed> $file
125+
*/
126+
private function isMultiFilePayload(array $file): bool
127+
{
128+
return isset($file['type'], $file['tmp_name'], $file['error'], $file['size'])
129+
&& is_array($file['type'])
130+
&& is_array($file['tmp_name'])
131+
&& is_array($file['error'])
132+
&& is_array($file['size']);
111133
}
112134
}

tests/Unit/Http/Traits/Request/HttpRequestFileTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,34 @@ public function testGetMultipleFiles(): void
7979

8080
$this->assertEquals('bar.png', $image[1]->getNameWithExtension());
8181
}
82+
83+
public function testCreateWithMultipleTopLevelFileFields(): void
84+
{
85+
$request = request();
86+
87+
$files = [
88+
'avatar' => [
89+
'size' => 500,
90+
'name' => 'avatar.jpg',
91+
'tmp_name' => '/tmp/php8fe2.tmp',
92+
'type' => 'image/jpg',
93+
'error' => 0,
94+
],
95+
'resume' => [
96+
'size' => 300,
97+
'name' => 'resume.pdf',
98+
'tmp_name' => '/tmp/php8fe3.tmp',
99+
'type' => 'application/pdf',
100+
'error' => 0,
101+
],
102+
];
103+
104+
$request->create('POST', '/upload', [], [], $files);
105+
106+
$this->assertTrue($request->hasFile('avatar'));
107+
$this->assertTrue($request->hasFile('resume'));
108+
109+
$this->assertInstanceOf(UploadedFile::class, $request->getFile('avatar'));
110+
$this->assertInstanceOf(UploadedFile::class, $request->getFile('resume'));
111+
}
82112
}

0 commit comments

Comments
 (0)