Skip to content

Commit

Permalink
Input::getMultipleFile() (#201)
Browse files Browse the repository at this point in the history
* Isolate bug #195

* feature: implement type-safe multiple file uploads
for #195, for #185
  • Loading branch information
g105b authored Mar 18, 2022
1 parent 2970a23 commit 95136bc
Show file tree
Hide file tree
Showing 8 changed files with 254 additions and 13 deletions.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"license": "MIT",

"require": {
"php": ">=8.0",
"phpgt/http": "^v1.1"
},

Expand Down
6 changes: 4 additions & 2 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

107 changes: 107 additions & 0 deletions example/02-multiple-inputs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<?php
/**
* This example shows how to work with a multiple file upload. For this to work,
* files must be uploaded through a form with enctype="multipart/formdata" using
* a file input type. The file input must be named with square brackets,
* indicating that multiple values can be present.
*
* The value of $_FILES in this script is hard-coded to what PHP will be
* provided when the user enters three images into a form such as the one shown
* below.
*
* Example form:
* <!doctype html>
* <form method="post" enctype="multipart/form-data">
* <label>
* <span>Your name:</span>
* <input name="name" />
* </label> <br />
* <label>
* <input type="checkbox" name="colour[]" value="red" /> Red
* </label><br />
* <label>
* <input type="checkbox" name="colour[]" value="green" /> Green
* </label><br />
* <label>
* <input type="checkbox" name="colour[]" value="blue" /> Blue
* </label><br />
* <input type="file" multiple name="upload[]" />
* <button name="do" value="upload">Upload!</button>
* </form>
*/
use Gt\Input\Input;
use Gt\Input\InputData\Datum\FailedFileUpload;

require(__DIR__ . "/../vendor/autoload.php");

$_GET = [];
$_POST = [
"do" => "upload",
"name" => "Greg",
"colour" => [
"red",
"blue",
],
];
$_FILES = [
"upload" => [
"name" => [
"front.jpg",
"back.jpg",
"description.txt",
],
"full_path" => [
"front.jpg",
"back.jpg",
"description.txt",
],
"type" => [
"image/jpeg",
"image/jpeg",
"text/plain",
],
"tmp_name" => [
"/tmp/phpkLgfwE",
"/tmp/phpiZKQf6",
"/tmp/php9UtO5A",
],
"error" => [
0,
0,
0,
],
"size" => [
123891,
165103,
915,
],
]
];

$input = new Input($_GET, $_POST, $_FILES);

echo "Your name: " . $input->getString("name"), PHP_EOL;

if(!$input->contains("colour")) {
echo "No colours chosen...", PHP_EOL;
exit;
}
foreach($input->getMultipleString("colour") as $colour) {
echo "Colour chosen: $colour", PHP_EOL;
}

if(!$input->contains("upload")) {
echo "Nothing uploaded...", PHP_EOL;
exit;
}

foreach($input->getMultipleFile("upload") as $fileName => $upload) {
if($upload instanceof FailedFileUpload) {
echo "Error uploading $fileName!", PHP_EOL;
continue;
}

$newPath = "data/upload/$fileName";
$size = $upload->getSize();
echo "Uploaded to $newPath (size $size)", PHP_EOL;
}
47 changes: 47 additions & 0 deletions example/www/multiple-file-upload.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php
/**
* This example should be served and accessed within a web browser.
* To serve using PHP, open up the example directory in a terminal and run:
* php -S 0.0.0.0:8080 and then visit http://localhost:8080/02-multiple-
*/
use Gt\Input\Input;

require __DIR__ . "/../../vendor/autoload.php";

if(empty($_POST)) {
goto website;
}

ini_set("display_errors", true);
$input = new Input($_GET, $_POST, $_FILES);

echo "<pre>";
echo "Your name is: ", $input->getString("name"), PHP_EOL;

$colourString = implode(", ", $input->getMultipleString("colour"));
echo "Colours chosen: ", $colourString, PHP_EOL;

echo "Files uploaded: ", PHP_EOL;
foreach($input->getMultipleFile("upload") as $fileName => $upload) {
echo "$fileName is size: ", $upload->getSize(), PHP_EOL;
}

website:?>
<!doctype html>
<form method="post" enctype="multipart/form-data">
<label>
<span>Your name:</span>
<input name="name" />
</label><br>
<label>
<input type="checkbox" name="colour[]" value="red" /> Red
</label><br>
<label>
<input type="checkbox" name="colour[]" value="green" /> Green
</label><br>
<label>
<input type="checkbox" name="colour[]" value="blue" /> Blue
</label><br>
<input type="file" multiple name="upload[]" />
<button name="do" value="upload">Upload!</button>
</form>
7 changes: 3 additions & 4 deletions src/Input.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class Input implements ArrayAccess, Countable, Iterator {
/**
* @param array<string, string> $get
* @param array<string, string> $post
* @param array<string, array<string, string>> $files
* @param array<string, array<int|string, string|array<int|string>>> $files
* @param string $bodyPath
*/
public function __construct(
Expand Down Expand Up @@ -108,9 +108,8 @@ public function add(string $key, InputDatum $datum, string $method):void {
* Get a particular input value by its key. To specify either GET or POST variables, pass
* Input::METHOD_GET or Input::METHOD_POST as the second parameter (defaults to
* Input::METHOD_BOTH).
* @return mixed|null
*/
public function get(string $key, string $method = null) {
public function get(string $key, string $method = null):null|InputDatum|string {
if(is_null($method)) {
$method = self::DATA_COMBINED;
}
Expand Down Expand Up @@ -138,7 +137,7 @@ public function get(string $key, string $method = null) {
throw new InvalidInputMethodException($method);
}

return $data;
return $data?->getValue();
}

/**
Expand Down
4 changes: 4 additions & 0 deletions src/InputData/Datum/InputDatum.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ public function __construct(mixed $value) {
public function __toString():string {
return $this->value;
}

public function getValue():mixed {
return $this->value;
}
}
69 changes: 62 additions & 7 deletions src/InputValueGetter.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ public function getString(string $key):?string {
return $this->get($key);
}

/** @return array<string> */
public function getMultipleString(string $key):array {
return $this->getTypedArray($key, "string");
}

public function getInt(string $key):?int {
$value = $this->getString($key);
if(is_null($value) || strlen($value) === 0) {
Expand All @@ -30,6 +35,11 @@ public function getInt(string $key):?int {
return (int)$value;
}

/** @return array<int> */
public function getMultipleInt(string $key):array {
return $this->getTypedArray($key, "int");
}

public function getFloat(string $key):?float {
$value = $this->getString($key);
if(is_null($value) || strlen($value) === 0) {
Expand All @@ -39,6 +49,11 @@ public function getFloat(string $key):?float {
return (float)$value;
}

/** @return array<float> */
public function getMultipleFloat(string $key):array {
return $this->getTypedArray($key, "float");
}

public function getBool(string $key):?bool {
$value = $this->getString($key);
if(is_null($value) || strlen($value) === 0) {
Expand All @@ -48,12 +63,17 @@ public function getBool(string $key):?bool {
return (bool)$value;
}

/** @return array<bool> */
public function getMultipleBool(string $key):array {
return $this->getTypedArray($key, "bool");
}


public function getFile(string $key):FileUpload {
/** @var FileUploadInputData|InputDatum[] $params */
$params = $this->fileUploadParameters ?? $this->parameters;

try {

/** @var MultipleInputDatum|FileUpload $file */
$file = $params[$key];

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

return $file;
}
catch(TypeError $exception) {
catch(TypeError) {
throw new DataNotFileUploadException($key);
}
}

/** @return MultipleInputDatum<FileUpload> */
public function getMultipleFile(string $key):MultipleInputDatum {
return $this->get($key);
/** @return array<FileUpload> */
public function getMultipleFile(string $key):array {
$multipleFileUpload = $this->get($key);
if(!$multipleFileUpload instanceof MultipleInputDatum) {
throw new InputException("Parameter '$key' is not a multiple file input.");
}

$array = [];

/** @var FileUpload $file */
foreach($multipleFileUpload as $file) {
$name = $file->getClientFilename();
$array[$name] = $file;
}

return $array;
}

public function getDateTime(
Expand Down Expand Up @@ -101,8 +134,30 @@ public function getDateTime(
return $dateTime;
}

/** @return MultipleInputDatum<DateTime> */
public function getMultipleDateTime(string $key):MultipleInputDatum {
/** @return array<DateTimeInterface> */
public function getMultipleDateTime(string $key):array {
return $this->get($key);
}

private function getTypedArray(string $key, string $typeName):array {
$array = [];
$datum = $this->get($key);

if(is_null($datum)) {
return [];
}

foreach($datum as $item) {
$cast = match($typeName) {
"int" => (int)$item,
"float" => (float)$item,
"bool" => (bool)$item,
default => (string)$item,
};

array_push($array, $cast);
}

return $array;
}
}
26 changes: 26 additions & 0 deletions test/phpunit/InputTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Gt\Input\DataNotCompatibleFormatException;
use Gt\Input\Input;
use Gt\Input\InputData\Datum\FileUpload;
use Gt\Input\InputData\Datum\InputDatum;
use Gt\Input\InputData\InputData;
use Gt\Input\InvalidInputMethodException;
use Gt\Input\MissingInputParameterException;
Expand Down Expand Up @@ -632,6 +633,31 @@ public function testContainsThrowsExceptionOnIncorrectType($get, $post) {
$input->contains("anything", "invalid-method");
}

public function testGetMultipleFile():void {
$get = [];
$post = ["do" => "upload"];
$files = [
"uploads" => [
"name" => ["one.txt", "two.txt"],
"type" => ["plain/text", "plain/text"],
"size" => [123, 321],
"tmp_name" => ["/tmp/aaaaa", "/tmp/bbbbb"],
"error" => [0, 0],
"full_path" => ["one.txt", "two.txt"],
]
];
$sut = new Input($get, $post, $files);
$multipleFiles = $sut->getMultipleFile("uploads");
self::assertCount(count($files["uploads"]["name"]), $multipleFiles);

$i = 0;
foreach($multipleFiles as $fileName => $file) {
self::assertSame($files["uploads"]["name"][$i], $fileName);
self::assertSame($files["uploads"]["tmp_name"][$i], $file->getRealPath());
$i++;
}
}

public function dataRandomGetPost():array {
$data = [];

Expand Down

0 comments on commit 95136bc

Please sign in to comment.