Skip to content

Commit 73f1c92

Browse files
Merge pull request #38 from Nyholm/support_multiple_upload_files
Support multiple upload files
2 parents 61f3755 + 4b4dcf9 commit 73f1c92

File tree

2 files changed

+180
-11
lines changed

2 files changed

+180
-11
lines changed

src/Http/Request.php

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -166,13 +166,7 @@ private function parseMultipartFormData($stream, $boundary) {
166166
} elseif ($fieldType === 'file' && $filename) {
167167
$tmpPath = tempnam($this->getUploadDir(), 'fastcgi_upload');
168168
$err = file_put_contents($tmpPath, $buffer);
169-
$files[$fieldName] = [
170-
'type' => $mimeType ?: 'application/octet-stream',
171-
'name' => $filename,
172-
'tmp_name' => $tmpPath,
173-
'error' => ($err === false) ? true : 0,
174-
'size' => filesize($tmpPath),
175-
];
169+
$this->addFile($files, $fieldName, $filename, $tmpPath, $mimeType, false === $err);
176170
$filename = $mimeType = null;
177171
}
178172
$fieldName = $fieldType = null;
@@ -248,10 +242,8 @@ public function getServerRequest()
248242
$uri = ServerRequestFactory::marshalUriFromServer($server, $headers);
249243
$method = ServerRequestFactory::get('REQUEST_METHOD', $server, 'GET');
250244

251-
$files = [];
252-
foreach ($this->uploadedFiles as $file) {
253-
$files[] = createUploadedFile($file);
254-
}
245+
$files = $this->uploadedFiles;
246+
$this->preparePsr7UploadedFiles($files);
255247

256248
$request = new ServerRequest($server, $files, $uri, $method, $this->stdin, $headers);
257249

@@ -276,4 +268,50 @@ public function getHttpFoundationRequest()
276268

277269
return new HttpFoundationRequest($query, $post, [], $cookies, $this->uploadedFiles, $this->params, $this->stdin);
278270
}
271+
272+
/**
273+
* Add a file to the $files array
274+
*/
275+
private function addFile(array &$files, string $fieldName, string $filename, string $tmpPath, string $mimeType, bool $err): void
276+
{
277+
$data = [
278+
'type' => $mimeType ?: 'application/octet-stream',
279+
'name' => $filename,
280+
'tmp_name' => $tmpPath,
281+
'error' => $err ? UPLOAD_ERR_CANT_WRITE : UPLOAD_ERR_OK,
282+
'size' => filesize($tmpPath),
283+
];
284+
285+
$parts = preg_split('|(\[[^\]]*\])|', $fieldName, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
286+
$count = count($parts);
287+
if (1 === $count) {
288+
$files[$fieldName] = $data;
289+
} else {
290+
$current = &$files;
291+
foreach ($parts as $i => $part) {
292+
if ($part === '[]') {
293+
$current[] = $data;
294+
continue;
295+
}
296+
297+
$trimmedMatch = trim($part, '[]');
298+
if ($i === $count -1) {
299+
$current[$trimmedMatch] = $data;
300+
} else {
301+
$current = &$current[$trimmedMatch];
302+
}
303+
}
304+
}
305+
}
306+
307+
private function preparePsr7UploadedFiles(array &$files)
308+
{
309+
if (isset($files['tmp_name'])) {
310+
$files = createUploadedFile($files);
311+
} else {
312+
foreach ($files as &$file) {
313+
$this->preparePsr7UploadedFiles($file);
314+
}
315+
}
316+
}
279317
}

test/Http/RequestTest.php

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,4 +147,135 @@ public function testUploadDir()
147147
Request::setUploadDir('/foo/bar');
148148
$this->assertEquals('/foo/bar', Request::getUploadDir());
149149
}
150+
151+
public function testMultipartContentWithMultipleFiles()
152+
{
153+
$expectedPost = ['foo' => 'A normal stream', 'baz' => 'string'];
154+
155+
// Set up FastCGI params and content
156+
$params = [
157+
'SERVER_PROTOCOL' => 'HTTP/1.1',
158+
'REQUEST_METHOD' => 'POST',
159+
'content_type' => 'multipart/form-data; boundary="578de3b0e3c46.2334ba3"',
160+
'REQUEST_URI' => '/my-page',
161+
];
162+
163+
// Set up the FastCGI stdin data stream resource
164+
$content = <<<HTTP
165+
--578de3b0e3c46.2334ba3
166+
Content-Disposition: form-data; name="foo"
167+
Content-Length: 15
168+
169+
A normal stream
170+
--578de3b0e3c46.2334ba3
171+
Content-Disposition: form-data; name="one[]"; filename="foo.png"
172+
Content-Length: 71
173+
Content-Type: image/png
174+
175+
?PNG
176+

177+
???
178+
IHDR??? ??? ?????? ???? IDATxc???51?)?:??????IEND?B`?
179+
--578de3b0e3c46.2334ba3
180+
--578de3b0e3c46.2334ba3
181+
Content-Disposition: form-data; name="one[]"; filename="bar.png"
182+
Content-Length: 71
183+
Content-Type: image/png
184+
185+
?PNG
186+

187+
???
188+
IHDR??? ??? ?????? ???? IDATxc???51?)?:??????IEND?B`?
189+
--578de3b0e3c46.2334ba3
190+
Content-Disposition: form-data; name="two[item-a]"; filename="bar.png"
191+
Content-Length: 71
192+
Content-Type: image/png
193+
194+
?PNG
195+

196+
???
197+
IHDR??? ??? ?????? ???? IDATxc???51?)?:??????IEND?B`?
198+
--578de3b0e3c46.2334ba3
199+
Content-Disposition: form-data; name="three[item][]"; filename="foo.png"
200+
Content-Length: 71
201+
Content-Type: image/png
202+
203+
?PNG
204+

205+
???
206+
IHDR??? ??? ?????? ???? IDATxc???51?)?:??????IEND?B`?
207+
--578de3b0e3c46.2334ba3
208+
Content-Disposition: form-data; name="three[item][]"; filename="bar.png"
209+
Content-Length: 71
210+
Content-Type: image/png
211+
212+
?PNG
213+

214+
???
215+
IHDR??? ??? ?????? ???? IDATxc???51?)?:??????IEND?B`?
216+
--578de3b0e3c46.2334ba3
217+
Content-Disposition: form-data; name="four[item_a][item_b][]"; filename="foo.png"
218+
Content-Length: 71
219+
Content-Type: image/png
220+
221+
?PNG
222+

223+
???
224+
IHDR??? ??? ?????? ???? IDATxc???51?)?:??????IEND?B`?
225+
--578de3b0e3c46.2334ba3
226+
Content-Disposition: form-data; name="four[item_a][item_b][]"; filename="bar.png"
227+
Content-Length: 71
228+
Content-Type: image/png
229+
230+
?PNG
231+

232+
???
233+
IHDR??? ??? ?????? ???? IDATxc???51?)?:??????IEND?B`?
234+
--578de3b0e3c46.2334ba3
235+
Content-Type: text/plain
236+
Content-Disposition: form-data; name="baz"
237+
Content-Length: 6
238+
239+
string
240+
--578de3b0e3c46.2334ba3--
241+
HTTP;
242+
243+
$stream = fopen('php://memory', 'r+');
244+
fwrite($stream, $content);
245+
246+
// Create the request
247+
$request = new Request($params, $stream);
248+
249+
// Check request object
250+
$this->assertEquals($expectedPost, $request->getPost());
251+
$this->assertEquals($stream, $request->getStdin());
252+
253+
// Check the PSR server request
254+
rewind($stream);
255+
$serverRequest = $request->getServerRequest();
256+
$this->assertEquals($expectedPost, $serverRequest->getParsedBody());
257+
$files = $serverRequest->getUploadedFiles();
258+
$this->assertNotEmpty($files['one']);
259+
$this->assertCount(2, $files['one']);
260+
261+
$this->assertNotEmpty($files['two']);
262+
$this->assertCount(1, $files['two']);
263+
$this->assertNotEmpty($files['two']['item-a']);
264+
265+
$this->assertNotEmpty($files['three']);
266+
$this->assertCount(1, $files['three']);
267+
$this->assertCount(2, $files['three']['item']);
268+
269+
$this->assertNotEmpty($files['four']);
270+
$this->assertNotEmpty($files['four']['item_a']);
271+
$this->assertNotEmpty($files['four']['item_a']['item_b']);
272+
$this->assertCount(2, $files['four']['item_a']['item_b']);
273+
274+
// Check the HttpFoundation request
275+
rewind($stream);
276+
$httpFoundationRequest = $request->getHttpFoundationRequest();
277+
$this->assertEquals($expectedPost, $httpFoundationRequest->request->all());
278+
$this->assertCount(4, $httpFoundationRequest->files->all());
279+
280+
}
150281
}

0 commit comments

Comments
 (0)