Skip to content

Commit 95136bc

Browse files
author
Greg Bowler
authored
Input::getMultipleFile() (#201)
* Isolate bug #195 * feature: implement type-safe multiple file uploads for #195, for #185
1 parent 2970a23 commit 95136bc

File tree

8 files changed

+254
-13
lines changed

8 files changed

+254
-13
lines changed

composer.json

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"license": "MIT",
55

66
"require": {
7+
"php": ">=8.0",
78
"phpgt/http": "^v1.1"
89
},
910

composer.lock

+4-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

example/02-multiple-inputs.php

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<?php
2+
/**
3+
* This example shows how to work with a multiple file upload. For this to work,
4+
* files must be uploaded through a form with enctype="multipart/formdata" using
5+
* a file input type. The file input must be named with square brackets,
6+
* indicating that multiple values can be present.
7+
*
8+
* The value of $_FILES in this script is hard-coded to what PHP will be
9+
* provided when the user enters three images into a form such as the one shown
10+
* below.
11+
*
12+
* Example form:
13+
* <!doctype html>
14+
* <form method="post" enctype="multipart/form-data">
15+
* <label>
16+
* <span>Your name:</span>
17+
* <input name="name" />
18+
* </label> <br />
19+
* <label>
20+
* <input type="checkbox" name="colour[]" value="red" /> Red
21+
* </label><br />
22+
* <label>
23+
* <input type="checkbox" name="colour[]" value="green" /> Green
24+
* </label><br />
25+
* <label>
26+
* <input type="checkbox" name="colour[]" value="blue" /> Blue
27+
* </label><br />
28+
* <input type="file" multiple name="upload[]" />
29+
* <button name="do" value="upload">Upload!</button>
30+
* </form>
31+
*/
32+
use Gt\Input\Input;
33+
use Gt\Input\InputData\Datum\FailedFileUpload;
34+
35+
require(__DIR__ . "/../vendor/autoload.php");
36+
37+
$_GET = [];
38+
$_POST = [
39+
"do" => "upload",
40+
"name" => "Greg",
41+
"colour" => [
42+
"red",
43+
"blue",
44+
],
45+
];
46+
$_FILES = [
47+
"upload" => [
48+
"name" => [
49+
"front.jpg",
50+
"back.jpg",
51+
"description.txt",
52+
],
53+
"full_path" => [
54+
"front.jpg",
55+
"back.jpg",
56+
"description.txt",
57+
],
58+
"type" => [
59+
"image/jpeg",
60+
"image/jpeg",
61+
"text/plain",
62+
],
63+
"tmp_name" => [
64+
"/tmp/phpkLgfwE",
65+
"/tmp/phpiZKQf6",
66+
"/tmp/php9UtO5A",
67+
],
68+
"error" => [
69+
0,
70+
0,
71+
0,
72+
],
73+
"size" => [
74+
123891,
75+
165103,
76+
915,
77+
],
78+
]
79+
];
80+
81+
$input = new Input($_GET, $_POST, $_FILES);
82+
83+
echo "Your name: " . $input->getString("name"), PHP_EOL;
84+
85+
if(!$input->contains("colour")) {
86+
echo "No colours chosen...", PHP_EOL;
87+
exit;
88+
}
89+
foreach($input->getMultipleString("colour") as $colour) {
90+
echo "Colour chosen: $colour", PHP_EOL;
91+
}
92+
93+
if(!$input->contains("upload")) {
94+
echo "Nothing uploaded...", PHP_EOL;
95+
exit;
96+
}
97+
98+
foreach($input->getMultipleFile("upload") as $fileName => $upload) {
99+
if($upload instanceof FailedFileUpload) {
100+
echo "Error uploading $fileName!", PHP_EOL;
101+
continue;
102+
}
103+
104+
$newPath = "data/upload/$fileName";
105+
$size = $upload->getSize();
106+
echo "Uploaded to $newPath (size $size)", PHP_EOL;
107+
}

example/www/multiple-file-upload.php

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
/**
3+
* This example should be served and accessed within a web browser.
4+
* To serve using PHP, open up the example directory in a terminal and run:
5+
* php -S 0.0.0.0:8080 and then visit http://localhost:8080/02-multiple-
6+
*/
7+
use Gt\Input\Input;
8+
9+
require __DIR__ . "/../../vendor/autoload.php";
10+
11+
if(empty($_POST)) {
12+
goto website;
13+
}
14+
15+
ini_set("display_errors", true);
16+
$input = new Input($_GET, $_POST, $_FILES);
17+
18+
echo "<pre>";
19+
echo "Your name is: ", $input->getString("name"), PHP_EOL;
20+
21+
$colourString = implode(", ", $input->getMultipleString("colour"));
22+
echo "Colours chosen: ", $colourString, PHP_EOL;
23+
24+
echo "Files uploaded: ", PHP_EOL;
25+
foreach($input->getMultipleFile("upload") as $fileName => $upload) {
26+
echo "$fileName is size: ", $upload->getSize(), PHP_EOL;
27+
}
28+
29+
website:?>
30+
<!doctype html>
31+
<form method="post" enctype="multipart/form-data">
32+
<label>
33+
<span>Your name:</span>
34+
<input name="name" />
35+
</label><br>
36+
<label>
37+
<input type="checkbox" name="colour[]" value="red" /> Red
38+
</label><br>
39+
<label>
40+
<input type="checkbox" name="colour[]" value="green" /> Green
41+
</label><br>
42+
<label>
43+
<input type="checkbox" name="colour[]" value="blue" /> Blue
44+
</label><br>
45+
<input type="file" multiple name="upload[]" />
46+
<button name="do" value="upload">Upload!</button>
47+
</form>

src/Input.php

+3-4
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class Input implements ArrayAccess, Countable, Iterator {
3838
/**
3939
* @param array<string, string> $get
4040
* @param array<string, string> $post
41-
* @param array<string, array<string, string>> $files
41+
* @param array<string, array<int|string, string|array<int|string>>> $files
4242
* @param string $bodyPath
4343
*/
4444
public function __construct(
@@ -108,9 +108,8 @@ public function add(string $key, InputDatum $datum, string $method):void {
108108
* Get a particular input value by its key. To specify either GET or POST variables, pass
109109
* Input::METHOD_GET or Input::METHOD_POST as the second parameter (defaults to
110110
* Input::METHOD_BOTH).
111-
* @return mixed|null
112111
*/
113-
public function get(string $key, string $method = null) {
112+
public function get(string $key, string $method = null):null|InputDatum|string {
114113
if(is_null($method)) {
115114
$method = self::DATA_COMBINED;
116115
}
@@ -138,7 +137,7 @@ public function get(string $key, string $method = null) {
138137
throw new InvalidInputMethodException($method);
139138
}
140139

141-
return $data;
140+
return $data?->getValue();
142141
}
143142

144143
/**

src/InputData/Datum/InputDatum.php

+4
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,8 @@ public function __construct(mixed $value) {
1111
public function __toString():string {
1212
return $this->value;
1313
}
14+
15+
public function getValue():mixed {
16+
return $this->value;
17+
}
1418
}

src/InputValueGetter.php

+62-7
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ public function getString(string $key):?string {
2121
return $this->get($key);
2222
}
2323

24+
/** @return array<string> */
25+
public function getMultipleString(string $key):array {
26+
return $this->getTypedArray($key, "string");
27+
}
28+
2429
public function getInt(string $key):?int {
2530
$value = $this->getString($key);
2631
if(is_null($value) || strlen($value) === 0) {
@@ -30,6 +35,11 @@ public function getInt(string $key):?int {
3035
return (int)$value;
3136
}
3237

38+
/** @return array<int> */
39+
public function getMultipleInt(string $key):array {
40+
return $this->getTypedArray($key, "int");
41+
}
42+
3343
public function getFloat(string $key):?float {
3444
$value = $this->getString($key);
3545
if(is_null($value) || strlen($value) === 0) {
@@ -39,6 +49,11 @@ public function getFloat(string $key):?float {
3949
return (float)$value;
4050
}
4151

52+
/** @return array<float> */
53+
public function getMultipleFloat(string $key):array {
54+
return $this->getTypedArray($key, "float");
55+
}
56+
4257
public function getBool(string $key):?bool {
4358
$value = $this->getString($key);
4459
if(is_null($value) || strlen($value) === 0) {
@@ -48,12 +63,17 @@ public function getBool(string $key):?bool {
4863
return (bool)$value;
4964
}
5065

66+
/** @return array<bool> */
67+
public function getMultipleBool(string $key):array {
68+
return $this->getTypedArray($key, "bool");
69+
}
70+
71+
5172
public function getFile(string $key):FileUpload {
5273
/** @var FileUploadInputData|InputDatum[] $params */
5374
$params = $this->fileUploadParameters ?? $this->parameters;
5475

5576
try {
56-
5777
/** @var MultipleInputDatum|FileUpload $file */
5878
$file = $params[$key];
5979

@@ -63,14 +83,27 @@ public function getFile(string $key):FileUpload {
6383

6484
return $file;
6585
}
66-
catch(TypeError $exception) {
86+
catch(TypeError) {
6787
throw new DataNotFileUploadException($key);
6888
}
6989
}
7090

71-
/** @return MultipleInputDatum<FileUpload> */
72-
public function getMultipleFile(string $key):MultipleInputDatum {
73-
return $this->get($key);
91+
/** @return array<FileUpload> */
92+
public function getMultipleFile(string $key):array {
93+
$multipleFileUpload = $this->get($key);
94+
if(!$multipleFileUpload instanceof MultipleInputDatum) {
95+
throw new InputException("Parameter '$key' is not a multiple file input.");
96+
}
97+
98+
$array = [];
99+
100+
/** @var FileUpload $file */
101+
foreach($multipleFileUpload as $file) {
102+
$name = $file->getClientFilename();
103+
$array[$name] = $file;
104+
}
105+
106+
return $array;
74107
}
75108

76109
public function getDateTime(
@@ -101,8 +134,30 @@ public function getDateTime(
101134
return $dateTime;
102135
}
103136

104-
/** @return MultipleInputDatum<DateTime> */
105-
public function getMultipleDateTime(string $key):MultipleInputDatum {
137+
/** @return array<DateTimeInterface> */
138+
public function getMultipleDateTime(string $key):array {
106139
return $this->get($key);
107140
}
141+
142+
private function getTypedArray(string $key, string $typeName):array {
143+
$array = [];
144+
$datum = $this->get($key);
145+
146+
if(is_null($datum)) {
147+
return [];
148+
}
149+
150+
foreach($datum as $item) {
151+
$cast = match($typeName) {
152+
"int" => (int)$item,
153+
"float" => (float)$item,
154+
"bool" => (bool)$item,
155+
default => (string)$item,
156+
};
157+
158+
array_push($array, $cast);
159+
}
160+
161+
return $array;
162+
}
108163
}

test/phpunit/InputTest.php

+26
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Gt\Input\DataNotCompatibleFormatException;
66
use Gt\Input\Input;
77
use Gt\Input\InputData\Datum\FileUpload;
8+
use Gt\Input\InputData\Datum\InputDatum;
89
use Gt\Input\InputData\InputData;
910
use Gt\Input\InvalidInputMethodException;
1011
use Gt\Input\MissingInputParameterException;
@@ -632,6 +633,31 @@ public function testContainsThrowsExceptionOnIncorrectType($get, $post) {
632633
$input->contains("anything", "invalid-method");
633634
}
634635

636+
public function testGetMultipleFile():void {
637+
$get = [];
638+
$post = ["do" => "upload"];
639+
$files = [
640+
"uploads" => [
641+
"name" => ["one.txt", "two.txt"],
642+
"type" => ["plain/text", "plain/text"],
643+
"size" => [123, 321],
644+
"tmp_name" => ["/tmp/aaaaa", "/tmp/bbbbb"],
645+
"error" => [0, 0],
646+
"full_path" => ["one.txt", "two.txt"],
647+
]
648+
];
649+
$sut = new Input($get, $post, $files);
650+
$multipleFiles = $sut->getMultipleFile("uploads");
651+
self::assertCount(count($files["uploads"]["name"]), $multipleFiles);
652+
653+
$i = 0;
654+
foreach($multipleFiles as $fileName => $file) {
655+
self::assertSame($files["uploads"]["name"][$i], $fileName);
656+
self::assertSame($files["uploads"]["tmp_name"][$i], $file->getRealPath());
657+
$i++;
658+
}
659+
}
660+
635661
public function dataRandomGetPost():array {
636662
$data = [];
637663

0 commit comments

Comments
 (0)