From 95136bcbe1c50c3d8db90eb3b1944d049ec2b2a0 Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Fri, 18 Mar 2022 19:03:02 +0000 Subject: [PATCH] Input::getMultipleFile() (#201) * Isolate bug #195 * feature: implement type-safe multiple file uploads for #195, for #185 --- composer.json | 1 + composer.lock | 6 +- example/02-multiple-inputs.php | 107 +++++++++++++++++++++++++++ example/www/multiple-file-upload.php | 47 ++++++++++++ src/Input.php | 7 +- src/InputData/Datum/InputDatum.php | 4 + src/InputValueGetter.php | 69 +++++++++++++++-- test/phpunit/InputTest.php | 26 +++++++ 8 files changed, 254 insertions(+), 13 deletions(-) create mode 100644 example/02-multiple-inputs.php create mode 100644 example/www/multiple-file-upload.php diff --git a/composer.json b/composer.json index 78df854..da8749a 100644 --- a/composer.json +++ b/composer.json @@ -4,6 +4,7 @@ "license": "MIT", "require": { + "php": ">=8.0", "phpgt/http": "^v1.1" }, diff --git a/composer.lock b/composer.lock index 6525628..6f26469 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d699d19511679c984383019a9d3ca3a4", + "content-hash": "d2206c48be8ef3c502284e560cdbfd52", "packages": [ { "name": "phpgt/cookie", @@ -2364,7 +2364,9 @@ "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, - "platform": [], + "platform": { + "php": ">=8.0" + }, "platform-dev": [], "plugin-api-version": "2.2.0" } diff --git a/example/02-multiple-inputs.php b/example/02-multiple-inputs.php new file mode 100644 index 0000000..d2045da --- /dev/null +++ b/example/02-multiple-inputs.php @@ -0,0 +1,107 @@ + + *
+ *
+ *
+ *
+ *
+ * + * + *
+ */ +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; +} diff --git a/example/www/multiple-file-upload.php b/example/www/multiple-file-upload.php new file mode 100644 index 0000000..d3052de --- /dev/null +++ b/example/www/multiple-file-upload.php @@ -0,0 +1,47 @@ +"; +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:?> + +
+
+
+
+
+ + +
diff --git a/src/Input.php b/src/Input.php index 6fe3b04..7eada4b 100644 --- a/src/Input.php +++ b/src/Input.php @@ -38,7 +38,7 @@ class Input implements ArrayAccess, Countable, Iterator { /** * @param array $get * @param array $post - * @param array> $files + * @param array>> $files * @param string $bodyPath */ public function __construct( @@ -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; } @@ -138,7 +137,7 @@ public function get(string $key, string $method = null) { throw new InvalidInputMethodException($method); } - return $data; + return $data?->getValue(); } /** diff --git a/src/InputData/Datum/InputDatum.php b/src/InputData/Datum/InputDatum.php index 18d7d38..f1405e7 100644 --- a/src/InputData/Datum/InputDatum.php +++ b/src/InputData/Datum/InputDatum.php @@ -11,4 +11,8 @@ public function __construct(mixed $value) { public function __toString():string { return $this->value; } + + public function getValue():mixed { + return $this->value; + } } diff --git a/src/InputValueGetter.php b/src/InputValueGetter.php index c399505..29d828b 100644 --- a/src/InputValueGetter.php +++ b/src/InputValueGetter.php @@ -21,6 +21,11 @@ public function getString(string $key):?string { return $this->get($key); } + /** @return array */ + 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) { @@ -30,6 +35,11 @@ public function getInt(string $key):?int { return (int)$value; } + /** @return array */ + 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) { @@ -39,6 +49,11 @@ public function getFloat(string $key):?float { return (float)$value; } + /** @return array */ + 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) { @@ -48,12 +63,17 @@ public function getBool(string $key):?bool { return (bool)$value; } + /** @return array */ + 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]; @@ -63,14 +83,27 @@ public function getFile(string $key):FileUpload { return $file; } - catch(TypeError $exception) { + catch(TypeError) { throw new DataNotFileUploadException($key); } } - /** @return MultipleInputDatum */ - public function getMultipleFile(string $key):MultipleInputDatum { - return $this->get($key); + /** @return array */ + 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( @@ -101,8 +134,30 @@ public function getDateTime( return $dateTime; } - /** @return MultipleInputDatum */ - public function getMultipleDateTime(string $key):MultipleInputDatum { + /** @return array */ + 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; + } } diff --git a/test/phpunit/InputTest.php b/test/phpunit/InputTest.php index e49a5ec..9a1ddd6 100644 --- a/test/phpunit/InputTest.php +++ b/test/phpunit/InputTest.php @@ -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; @@ -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 = [];