diff --git a/CHANGELOG.md b/CHANGELOG.md index d93950f2..cf459ad3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,46 @@ All notable changes to this project will be documented in this file, in reverse chronological order by release. +## 2.9.0 - 2018-12-17 + +### Added + +- [#172](https://github.com/zendframework/zend-inputfilter/pull/172) adds support for PSR-7 `UploadedFileInterface` to `Zend\InputFilter\FileInput`. + It adds a new interface, `Zend\InputFilter\FileInput\FileInputDecoratorInterface`, + which defines methods required for validating and filtering file uploads. It + also provides two implementations of it, one for standard SAPI file uploads, + and the other for PSR-7 uploads. The `FileInput` class does detection on the + value being tested and decorates itself using the appropriate decorator, which + then performs the work of validating and filtering the upload or uploads. + +- [#170](https://github.com/zendframework/zend-inputfilter/pull/170) adds the ability to set a "required" message on a `CollectionInputFilter`. + By default, such instances will lazy-load a `NotEmpty` validator, and use its + messages to report that the collection was empty if it is marked as required. + If you wish to set a different message, you have two options: + + - provide a custom `NotEmpty` validator via the new method + `setNotEmptyValidator()`. + + - if using a factory, provide the key `required_message` as a sibling to + `required`, containing the custom message. This will replace the typical + `IS_EMPTY` message. + +### Changed + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- Nothing. + ## 2.8.3 - 2018-12-13 ### Added diff --git a/composer.json b/composer.json index 0a78d1cb..d895fafd 100644 --- a/composer.json +++ b/composer.json @@ -12,8 +12,8 @@ }, "extra": { "branch-alias": { - "dev-master": "2.8.x-dev", - "dev-develop": "2.9.x-dev" + "dev-master": "2.9.x-dev", + "dev-develop": "2.10.x-dev" }, "zf": { "component": "Zend\\InputFilter", @@ -30,15 +30,19 @@ }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-filter": "^2.6", + "zendframework/zend-filter": "^2.9.1", + "zendframework/zend-servicemanager": "^2.7.10 || ^3.3.1", "zendframework/zend-stdlib": "^2.7 || ^3.0", - "zendframework/zend-validator": "^2.10.1", - "zendframework/zend-servicemanager": "^2.7.10 || ^3.3.1" + "zendframework/zend-validator": "^2.11" }, "require-dev": { "phpunit/phpunit": "^5.7.23 || ^6.4.3", + "psr/http-message": "^1.0", "zendframework/zend-coding-standard": "~1.0.0" }, + "suggest": { + "psr/http-message-implementation": "PSR-7 is required if you wish to validate PSR-7 UploadedFileInterface payloads" + }, "autoload": { "psr-4": { "Zend\\InputFilter\\": "src/" diff --git a/composer.lock b/composer.lock index 58d3d53d..001e4d10 100644 --- a/composer.lock +++ b/composer.lock @@ -1,10 +1,10 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ac89a57a6a22a9ec7d3063ec7eecd574", + "content-hash": "b7ceabdec2f140507b9c15f618abc0d9", "packages": [ { "name": "container-interop/container-interop", @@ -88,31 +88,36 @@ }, { "name": "zendframework/zend-filter", - "version": "2.7.2", + "version": "2.9.1", "source": { "type": "git", "url": "https://github.com/zendframework/zend-filter.git", - "reference": "b8d0ff872f126631bf63a932e33aa2d22d467175" + "reference": "1c3e6d02f9cd5f6c929c9859498f5efbe216e86f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-filter/zipball/b8d0ff872f126631bf63a932e33aa2d22d467175", - "reference": "b8d0ff872f126631bf63a932e33aa2d22d467175", + "url": "https://api.github.com/repos/zendframework/zend-filter/zipball/1c3e6d02f9cd5f6c929c9859498f5efbe216e86f", + "reference": "1c3e6d02f9cd5f6c929c9859498f5efbe216e86f", "shasum": "" }, "require": { - "php": "^5.5 || ^7.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "php": "^5.6 || ^7.0", + "zendframework/zend-stdlib": "^2.7.7 || ^3.1" + }, + "conflict": { + "zendframework/zend-validator": "<2.10.1" }, "require-dev": { - "pear/archive_tar": "^1.4", - "phpunit/phpunit": "^6.0.10 || ^5.7.17", + "pear/archive_tar": "^1.4.3", + "phpunit/phpunit": "^5.7.23 || ^6.4.3", + "psr/http-factory": "^1.0", "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-crypt": "^2.6 || ^3.0", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-uri": "^2.5" + "zendframework/zend-crypt": "^3.2.1", + "zendframework/zend-servicemanager": "^2.7.8 || ^3.3", + "zendframework/zend-uri": "^2.6" }, "suggest": { + "psr/http-factory-implementation": "psr/http-factory-implementation, for creating file upload instances when consuming PSR-7 in file upload filters", "zendframework/zend-crypt": "Zend\\Crypt component, for encryption filters", "zendframework/zend-i18n": "Zend\\I18n component for filters depending on i18n functionality", "zendframework/zend-servicemanager": "Zend\\ServiceManager component, for using the filter chain functionality", @@ -121,8 +126,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev", - "dev-develop": "2.8-dev" + "dev-master": "2.9.x-dev", + "dev-develop": "2.10.x-dev" }, "zf": { "component": "Zend\\Filter", @@ -139,25 +144,25 @@ "BSD-3-Clause" ], "description": "provides a set of commonly needed data filters", - "homepage": "https://github.com/zendframework/zend-filter", "keywords": [ + "ZendFramework", "filter", - "zf2" + "zf" ], - "time": "2017-05-17T20:56:17+00:00" + "time": "2018-12-17T16:00:04+00:00" }, { "name": "zendframework/zend-servicemanager", - "version": "3.3.1", + "version": "3.3.2", "source": { "type": "git", "url": "https://github.com/zendframework/zend-servicemanager.git", - "reference": "0fa3d3cf588dde0850fff1efa60d44a7aa3c3ab7" + "reference": "9f35a104b8d4d3b32da5f4a3b6efc0dd62e5af42" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-servicemanager/zipball/0fa3d3cf588dde0850fff1efa60d44a7aa3c3ab7", - "reference": "0fa3d3cf588dde0850fff1efa60d44a7aa3c3ab7", + "url": "https://api.github.com/repos/zendframework/zend-servicemanager/zipball/9f35a104b8d4d3b32da5f4a3b6efc0dd62e5af42", + "reference": "9f35a104b8d4d3b32da5f4a3b6efc0dd62e5af42", "shasum": "" }, "require": { @@ -171,10 +176,10 @@ "psr/container-implementation": "^1.0" }, "require-dev": { - "mikey179/vfsstream": "^1.6", + "mikey179/vfsstream": "^1.6.5", "ocramius/proxy-manager": "^1.0 || ^2.0", - "phpbench/phpbench": "^0.10.0", - "phpunit/phpunit": "^5.7 || ^6.0.6", + "phpbench/phpbench": "^0.13.0", + "phpunit/phpunit": "^5.7.25 || ^6.4.4", "zendframework/zend-coding-standard": "~1.0.0" }, "suggest": { @@ -201,41 +206,46 @@ "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-servicemanager", + "description": "Factory-Driven Dependency Injection Container", "keywords": [ + "PSR-11", + "ZendFramework", + "dependency-injection", + "di", + "dic", "service-manager", "servicemanager", "zf" ], - "time": "2017-11-27T18:11:25+00:00" + "time": "2018-01-29T16:48:37+00:00" }, { "name": "zendframework/zend-stdlib", - "version": "3.1.0", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/zendframework/zend-stdlib.git", - "reference": "debedcfc373a293f9250cc9aa03cf121428c8e78" + "reference": "66536006722aff9e62d1b331025089b7ec71c065" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-stdlib/zipball/debedcfc373a293f9250cc9aa03cf121428c8e78", - "reference": "debedcfc373a293f9250cc9aa03cf121428c8e78", + "url": "https://api.github.com/repos/zendframework/zend-stdlib/zipball/66536006722aff9e62d1b331025089b7ec71c065", + "reference": "66536006722aff9e62d1b331025089b7ec71c065", "shasum": "" }, "require": { "php": "^5.6 || ^7.0" }, "require-dev": { - "athletic/athletic": "~0.1", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "^2.6.2" + "phpbench/phpbench": "^0.13", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2", + "zendframework/zend-coding-standard": "~1.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev", - "dev-develop": "3.2-dev" + "dev-master": "3.2.x-dev", + "dev-develop": "3.3.x-dev" } }, "autoload": { @@ -247,25 +257,26 @@ "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-stdlib", + "description": "SPL extensions, array utilities, error handlers, and more", "keywords": [ + "ZendFramework", "stdlib", - "zf2" + "zf" ], - "time": "2016-09-13T14:38:50+00:00" + "time": "2018-08-28T21:34:05+00:00" }, { "name": "zendframework/zend-validator", - "version": "2.10.1", + "version": "2.11.0", "source": { "type": "git", "url": "https://github.com/zendframework/zend-validator.git", - "reference": "010084ddbd33299bf51ea6f0e07f8f4e8bd832a8" + "reference": "f0789b4c4c099afdd2ecc58cc209a26c64bd4f17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-validator/zipball/010084ddbd33299bf51ea6f0e07f8f4e8bd832a8", - "reference": "010084ddbd33299bf51ea6f0e07f8f4e8bd832a8", + "url": "https://api.github.com/repos/zendframework/zend-validator/zipball/f0789b4c4c099afdd2ecc58cc209a26c64bd4f17", + "reference": "f0789b4c4c099afdd2ecc58cc209a26c64bd4f17", "shasum": "" }, "require": { @@ -275,6 +286,7 @@ }, "require-dev": { "phpunit/phpunit": "^6.0.8 || ^5.7.15", + "psr/http-message": "^1.0", "zendframework/zend-cache": "^2.6.1", "zendframework/zend-coding-standard": "~1.0.0", "zendframework/zend-config": "^2.6", @@ -288,6 +300,7 @@ "zendframework/zend-uri": "^2.5" }, "suggest": { + "psr/http-message": "psr/http-message, required when validating PSR-7 UploadedFileInterface instances via the Upload and UploadFile validators", "zendframework/zend-db": "Zend\\Db component, required by the (No)RecordExists validator", "zendframework/zend-filter": "Zend\\Filter component, required by the Digits validator", "zendframework/zend-i18n": "Zend\\I18n component to allow translation of validation error messages", @@ -300,8 +313,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.10-dev", - "dev-develop": "2.11-dev" + "dev-master": "2.11.x-dev", + "dev-develop": "2.12.x-dev" }, "zf": { "component": "Zend\\Validator", @@ -323,7 +336,7 @@ "validator", "zf2" ], - "time": "2017-08-22T14:19:23+00:00" + "time": "2018-12-13T21:23:15+00:00" } ], "packages-dev": [ @@ -383,25 +396,28 @@ }, { "name": "myclabs/deep-copy", - "version": "1.7.0", + "version": "1.8.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" + "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", - "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", + "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^7.1" + }, + "replace": { + "myclabs/deep-copy": "self.version" }, "require-dev": { "doctrine/collections": "^1.0", "doctrine/common": "^2.6", - "phpunit/phpunit": "^4.1" + "phpunit/phpunit": "^7.1" }, "type": "library", "autoload": { @@ -424,7 +440,7 @@ "object", "object graph" ], - "time": "2017-10-19T19:58:43+00:00" + "time": "2018-06-11T23:09:50+00:00" }, { "name": "phar-io/manifest", @@ -584,16 +600,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "4.2.0", + "version": "4.3.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "66465776cfc249844bde6d117abff1d22e06c2da" + "reference": "94fd0001232e47129dd3504189fa1c7225010d08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/66465776cfc249844bde6d117abff1d22e06c2da", - "reference": "66465776cfc249844bde6d117abff1d22e06c2da", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08", + "reference": "94fd0001232e47129dd3504189fa1c7225010d08", "shasum": "" }, "require": { @@ -631,7 +647,7 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2017-11-27T17:38:31+00:00" + "time": "2017-11-30T07:14:17+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -682,33 +698,33 @@ }, { "name": "phpspec/prophecy", - "version": "1.7.3", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf" + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf", - "reference": "e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", "php": "^5.3|^7.0", "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", - "sebastian/comparator": "^1.1|^2.0", + "sebastian/comparator": "^1.1|^2.0|^3.0", "sebastian/recursion-context": "^1.0|^2.0|^3.0" }, "require-dev": { "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8.35 || ^5.7" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7.x-dev" + "dev-master": "1.8.x-dev" } }, "autoload": { @@ -741,20 +757,20 @@ "spy", "stub" ], - "time": "2017-11-24T13:59:53+00:00" + "time": "2018-08-05T17:53:17+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "5.3.0", + "version": "5.3.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "661f34d0bd3f1a7225ef491a70a020ad23a057a1" + "reference": "c89677919c5dd6d3b3852f230a663118762218ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/661f34d0bd3f1a7225ef491a70a020ad23a057a1", - "reference": "661f34d0bd3f1a7225ef491a70a020ad23a057a1", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c89677919c5dd6d3b3852f230a663118762218ac", + "reference": "c89677919c5dd6d3b3852f230a663118762218ac", "shasum": "" }, "require": { @@ -804,7 +820,7 @@ "testing", "xunit" ], - "time": "2017-12-06T09:29:45+00:00" + "time": "2018-04-06T15:36:58+00:00" }, { "name": "phpunit/php-file-iterator", @@ -994,16 +1010,16 @@ }, { "name": "phpunit/phpunit", - "version": "6.5.5", + "version": "6.5.13", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "83d27937a310f2984fd575686138597147bdc7df" + "reference": "0973426fb012359b2f18d3bd1e90ef1172839693" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/83d27937a310f2984fd575686138597147bdc7df", - "reference": "83d27937a310f2984fd575686138597147bdc7df", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0973426fb012359b2f18d3bd1e90ef1172839693", + "reference": "0973426fb012359b2f18d3bd1e90ef1172839693", "shasum": "" }, "require": { @@ -1021,7 +1037,7 @@ "phpunit/php-file-iterator": "^1.4.3", "phpunit/php-text-template": "^1.2.1", "phpunit/php-timer": "^1.0.9", - "phpunit/phpunit-mock-objects": "^5.0.5", + "phpunit/phpunit-mock-objects": "^5.0.9", "sebastian/comparator": "^2.1", "sebastian/diff": "^2.0", "sebastian/environment": "^3.1", @@ -1074,20 +1090,20 @@ "testing", "xunit" ], - "time": "2017-12-17T06:31:19+00:00" + "time": "2018-09-08T15:10:43+00:00" }, { "name": "phpunit/phpunit-mock-objects", - "version": "5.0.6", + "version": "5.0.10", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "33fd41a76e746b8fa96d00b49a23dadfa8334cdf" + "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/33fd41a76e746b8fa96d00b49a23dadfa8334cdf", - "reference": "33fd41a76e746b8fa96d00b49a23dadfa8334cdf", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/cd1cf05c553ecfec36b170070573e540b67d3f1f", + "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f", "shasum": "" }, "require": { @@ -1100,7 +1116,7 @@ "phpunit/phpunit": "<6.0" }, "require-dev": { - "phpunit/phpunit": "^6.5" + "phpunit/phpunit": "^6.5.11" }, "suggest": { "ext-soap": "*" @@ -1133,7 +1149,57 @@ "mock", "xunit" ], - "time": "2018-01-06T05:45:45+00:00" + "time": "2018-08-09T05:50:03+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06T14:39:51+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -1182,21 +1248,21 @@ }, { "name": "sebastian/comparator", - "version": "2.1.2", + "version": "2.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "11c07feade1d65453e06df3b3b90171d6d982087" + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/11c07feade1d65453e06df3b3b90171d6d982087", - "reference": "11c07feade1d65453e06df3b3b90171d6d982087", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9", + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9", "shasum": "" }, "require": { "php": "^7.0", - "sebastian/diff": "^2.0", + "sebastian/diff": "^2.0 || ^3.0", "sebastian/exporter": "^3.1" }, "require-dev": { @@ -1242,7 +1308,7 @@ "compare", "equality" ], - "time": "2018-01-12T06:34:42+00:00" + "time": "2018-02-01T13:46:46+00:00" }, { "name": "sebastian/diff", @@ -1696,16 +1762,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "2.9.1", + "version": "2.9.2", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "dcbed1074f8244661eecddfc2a675430d8d33f62" + "reference": "2acf168de78487db620ab4bc524135a13cfe6745" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/dcbed1074f8244661eecddfc2a675430d8d33f62", - "reference": "dcbed1074f8244661eecddfc2a675430d8d33f62", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/2acf168de78487db620ab4bc524135a13cfe6745", + "reference": "2acf168de78487db620ab4bc524135a13cfe6745", "shasum": "" }, "require": { @@ -1770,7 +1836,7 @@ "phpcs", "standards" ], - "time": "2017-05-22T02:43:20+00:00" + "time": "2018-11-07T22:31:41+00:00" }, { "name": "theseer/tokenizer", @@ -1814,16 +1880,16 @@ }, { "name": "webmozart/assert", - "version": "1.2.0", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/webmozart/assert.git", - "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f" + "reference": "0df1908962e7a3071564e857d86874dad1ef204a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/2db61e59ff05fe5126d152bd0655c9ea113e550f", - "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f", + "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a", + "reference": "0df1908962e7a3071564e857d86874dad1ef204a", "shasum": "" }, "require": { @@ -1860,7 +1926,7 @@ "check", "validate" ], - "time": "2016-11-23T20:04:58+00:00" + "time": "2018-01-29T19:49:41+00:00" }, { "name": "zendframework/zend-coding-standard", diff --git a/docs/book/file-input.md b/docs/book/file-input.md index e04bd025..425f837b 100644 --- a/docs/book/file-input.md +++ b/docs/book/file-input.md @@ -10,9 +10,14 @@ While `FileInput` uses the same interface as `Input`, it differs in a few ways: chapter for details on how to accomplish this. [Diactoros](https://docs.zendframework.com/zend-diactoros/) and [zend-http](https://docs.zendframework.com/zend-http/) can do this for you. + + Alternately, you may provide an array of PSR-7 uploaded file instances. + 2. The validators are run **before** the filters (which is the opposite behavior - of `Input`). This is so that any `is_uploaded_file()` validation can be run - prior to any filters that may rename/move/modify the file. + of `Input`). This is so that any validation can be run + prior to any filters that may rename/move/modify the file; we should not do + those operations if the file upload was invalid! + 3. Instead of adding a `NotEmpty` validator, it will (by default) automatically add a `Zend\Validator\File\UploadFile` validator. @@ -34,29 +39,95 @@ use Zend\Validator; // Description text input $description = new Input('description'); // Standard Input type -$description->getFilterChain() // Filters are run first w/ Input - ->attach(new Filter\StringTrim()); -$description->getValidatorChain() // Validators are run second w/ Input - ->attach(new Validator\StringLength(['max' => 140])); + +// Filters are run first w/ Input +$description + ->getFilterChain() + ->attach(new Filter\StringTrim()); + +// Validators are run second w/ Input +$description + ->getValidatorChain() + ->attach(new Validator\StringLength(['max' => 140])); // File upload input -$file = new FileInput('file'); // Special File Input type -$file->getValidatorChain() // Validators are run first w/ FileInput - ->attach(new Validator\File\UploadFile()); -$file->getFilterChain() // Filters are run second w/ FileInput - ->attach(new Filter\File\RenameUpload([ +$file = new FileInput('file'); // Special File Input type + +// Validators are run first w/ FileInput +$file + ->getValidatorChain() + ->attach(new Validator\File\UploadFile()); + +// Filters are run second w/ FileInput +$file + ->getFilterChain() + ->attach(new Filter\File\RenameUpload([ 'target' => './data/tmpuploads/file', 'randomize' => true, - ])); + ])); // Merge $_POST and $_FILES data together $request = new Request(); -$postData = array_merge_recursive($request->getPost()->toArray(), $request->getFiles()->toArray()); +$postData = array_merge_recursive( + $request->getPost()->toArray(), + $request->getFiles()->toArray() +); + +$inputFilter = new InputFilter(); +$inputFilter + ->add($description) + ->add($file) + ->setData($postData); + +if ($inputFilter->isValid()) { // FileInput validators are run, but not the filters... + echo "The form is valid\n"; + $data = $inputFilter->getValues(); // This is when the FileInput filters are run. +} else { + echo "The form is not valid\n"; + foreach ($inputFilter->getInvalidInput() as $error) { + print_r ($error->getMessages()); + } +} +``` + +## PSR-7 Support + +- Since 2.9.0 + +You may also pass an array of uploaded files from a [PSR-7 ServerRequestInterface](http://www.php-fig.org/psr/psr-7/#serverrequestinterface). + +```php +use Psr\Http\Message\ServerRequestInterface; +use Zend\Filter; +use Zend\InputFilter\InputFilter; +use Zend\InputFilter\FileInput; +use Zend\Validator; + +// File upload input +$file = new FileInput('file'); +$file + ->getValidatorChain() + ->attach(new Validator\File\UploadFile()); +$file + ->getFilterChain() + ->attach(new Filter\File\RenameUpload([ + 'target' => './data/tmpuploads/file', + 'randomize' => true, + ])); + +// Merge form and uploaded file data together +// Unlike the previous example, we get the form data from `getParsedBody()`, and +// the uploaded file data from `getUploadedFiles()`. +// @var ServerRequestInterface $request +$postData = array_merge_recursive( + $request->getParsedBody(), + $request->getUploadedFiles() +); $inputFilter = new InputFilter(); -$inputFilter->add($description) - ->add($file) - ->setData($postData); +$inputFilter + ->add($file) + ->setData($postData); if ($inputFilter->isValid()) { // FileInput validators are run, but not the filters... echo "The form is valid\n"; diff --git a/src/CollectionInputFilter.php b/src/CollectionInputFilter.php index 33b28c84..005525fc 100644 --- a/src/CollectionInputFilter.php +++ b/src/CollectionInputFilter.php @@ -10,6 +10,7 @@ namespace Zend\InputFilter; use Traversable; +use Zend\Validator\NotEmpty; class CollectionInputFilter extends InputFilter { @@ -43,6 +44,11 @@ class CollectionInputFilter extends InputFilter */ protected $inputFilter; + /** + * @var NotEmpty + */ + protected $notEmptyValidator; + /** * Set the input filter to use when looping the data * @@ -164,6 +170,39 @@ public function setData($data) return $this; } + /** + * Retrieve the NotEmpty validator to use for failed "required" validations. + * + * This validator will be used to produce a validation failure message in + * cases where the collection is empty but required. + * + * @return NotEmpty + */ + public function getNotEmptyValidator() + { + if ($this->notEmptyValidator === null) { + $this->notEmptyValidator = new NotEmpty(); + } + + return $this->notEmptyValidator; + } + + /** + * Set the NotEmpty validator to use for failed "required" validations. + * + * This validator will be used to produce a validation failure message in + * cases where the collection is empty but required. + * + * @param NotEmpty $notEmptyValidator + * @return $this + */ + public function setNotEmptyValidator(NotEmpty $notEmptyValidator) + { + $this->notEmptyValidator = $notEmptyValidator; + + return $this; + } + /** * {@inheritdoc} * @param mixed $context Ignored, but present to retain signature compatibility. @@ -174,10 +213,9 @@ public function isValid($context = null) $inputFilter = $this->getInputFilter(); $valid = true; - if ($this->getCount() < 1) { - if ($this->isRequired) { - $valid = false; - } + if ($this->getCount() < 1 && $this->isRequired) { + $this->collectionMessages[] = $this->prepareRequiredValidationFailureMessage(); + $valid = false; } if (count($this->data) < $this->getCount()) { @@ -295,4 +333,21 @@ public function getUnknown() return $unknownInputs; } + + /** + * @return array + */ + protected function prepareRequiredValidationFailureMessage() + { + $notEmptyValidator = $this->getNotEmptyValidator(); + $templates = $notEmptyValidator->getOption('messageTemplates'); + $message = $templates[NotEmpty::IS_EMPTY]; + $translator = $notEmptyValidator->getTranslator(); + + return [ + NotEmpty::IS_EMPTY => $translator + ? $translator->translate($message, $notEmptyValidator->getTranslatorTextDomain()) + : $message, + ]; + } } diff --git a/src/Factory.php b/src/Factory.php index 01f299e4..53459320 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -319,6 +319,9 @@ public function createInputFilter($inputFilterSpecification) if (isset($inputFilterSpecification['required'])) { $inputFilter->setIsRequired($inputFilterSpecification['required']); } + if (isset($inputFilterSpecification['required_message'])) { + $inputFilter->getNotEmptyValidator()->setMessage($inputFilterSpecification['required_message']); + } return $inputFilter; } diff --git a/src/FileInput.php b/src/FileInput.php index 08d6a7f3..e982185f 100644 --- a/src/FileInput.php +++ b/src/FileInput.php @@ -1,26 +1,27 @@ implementation = $this->createDecoratorImplementation($value); + parent::setValue($value); + return $this; + } + + public function resetValue() + { + $this->implementation = null; + return parent::resetValue(); + } + /** * @param bool $value Enable/Disable automatically prepending an Upload validator + * * @return FileInput */ public function setAutoPrependUploadValidator($value) @@ -60,27 +83,10 @@ public function getAutoPrependUploadValidator() */ public function getValue() { - $value = $this->value; - if ($this->isValid && is_array($value)) { - // Run filters ~after~ validation, so that is_uploaded_file() - // validation is not affected by filters. - $filter = $this->getFilterChain(); - if (isset($value['tmp_name'])) { - // Single file input - $value = $filter->filter($value); - } else { - // Multi file input (multiple attribute set) - $newValue = []; - foreach ($value as $fileData) { - if (is_array($fileData) && isset($fileData['tmp_name'])) { - $newValue[] = $filter->filter($fileData); - } - } - $value = $newValue; - } + if ($this->implementation === null) { + return $this->value; } - - return $value; + return $this->implementation->getValue(); } /** @@ -91,19 +97,19 @@ public function getValue() */ public function isEmptyFile($rawValue) { - if (! is_array($rawValue)) { - return true; + if ($rawValue instanceof UploadedFileInterface) { + return FileInput\PsrFileInputDecorator::isEmptyFileDecorator($rawValue); } - if (isset($rawValue['error']) && $rawValue['error'] === UPLOAD_ERR_NO_FILE) { - return true; - } + if (is_array($rawValue)) { + if (isset($rawValue[0]) && $rawValue[0] instanceof UploadedFileInterface) { + return FileInput\PsrFileInputDecorator::isEmptyFileDecorator($rawValue); + } - if (count($rawValue) === 1 && isset($rawValue[0])) { - return $this->isEmptyFile($rawValue[0]); + return FileInput\HttpServerFileInputDecorator::isEmptyFileDecorator($rawValue); } - return false; + return true; } /** @@ -138,58 +144,21 @@ public function isValid($context = null) return true; } - $this->injectUploadValidator(); - $validator = $this->getValidatorChain(); - //$value = $this->getValue(); // Do not run the filters yet for File uploads (see getValue()) - - if (! is_array($rawValue)) { - // This can happen in an AJAX POST, where the input comes across as a string - $rawValue = [ - 'tmp_name' => $rawValue, - 'name' => $rawValue, - 'size' => 0, - 'type' => '', - 'error' => UPLOAD_ERR_NO_FILE, - ]; - } - if (is_array($rawValue) && isset($rawValue['tmp_name'])) { - // Single file input - $this->isValid = $validator->isValid($rawValue, $context); - } elseif (is_array($rawValue) && isset($rawValue[0]['tmp_name'])) { - // Multi file input (multiple attribute set) - $this->isValid = true; - foreach ($rawValue as $value) { - if (! $validator->isValid($value, $context)) { - $this->isValid = false; - break; // Do not continue processing files if validation fails - } - } - } - - return $this->isValid; + return $this->implementation->isValid($context); } /** - * @return void + * @param InputInterface $input + * + * @return FileInput */ - protected function injectUploadValidator() + public function merge(InputInterface $input) { - if (! $this->autoPrependUploadValidator) { - return; - } - $chain = $this->getValidatorChain(); - - // Check if Upload validator is already first in chain - $validators = $chain->getValidators(); - if (isset($validators[0]['instance']) - && $validators[0]['instance'] instanceof UploadValidator - ) { - $this->autoPrependUploadValidator = false; - return; + parent::merge($input); + if ($input instanceof FileInput) { + $this->setAutoPrependUploadValidator($input->getAutoPrependUploadValidator()); } - - $chain->prependByName('fileuploadfile', [], true); - $this->autoPrependUploadValidator = false; + return $this; } /** @@ -206,15 +175,27 @@ protected function injectNotEmptyValidator() } /** - * @param InputInterface $input - * @return FileInput + * @param mixed $value + * @return FileInput\FileInputDecoratorInterface */ - public function merge(InputInterface $input) + private function createDecoratorImplementation($value) { - parent::merge($input); - if ($input instanceof FileInput) { - $this->setAutoPrependUploadValidator($input->getAutoPrependUploadValidator()); + // Single PSR-7 instance + if ($value instanceof UploadedFileInterface) { + return new FileInput\PsrFileInputDecorator($this); } - return $this; + + if (is_array($value)) { + if (isset($value[0]) && $value[0] instanceof UploadedFileInterface) { + // Array of PSR-7 instances + return new FileInput\PsrFileInputDecorator($this); + } + + // Single or multiple SAPI file upload arrays + return new FileInput\HttpServerFileInputDecorator($this); + } + + // AJAX/XHR/Fetch case + return new FileInput\HttpServerFileInputDecorator($this); } } diff --git a/src/FileInput/FileInputDecoratorInterface.php b/src/FileInput/FileInputDecoratorInterface.php new file mode 100644 index 00000000..3cda94e1 --- /dev/null +++ b/src/FileInput/FileInputDecoratorInterface.php @@ -0,0 +1,37 @@ +subject = $subject; + } + + /** + * @return mixed + */ + public function getValue() + { + $value = $this->subject->value; + + if (! $this->subject->isValid || ! is_array($value)) { + return $value; + } + + // Run filters ~after~ validation, so that is_uploaded_file() + // validation is not affected by filters. + $filter = $this->subject->getFilterChain(); + if (isset($value['tmp_name'])) { + // Single file input + $value = $filter->filter($value); + return $value; + } + + // Multi file input (multiple attribute set) + $newValue = []; + foreach ($value as $fileData) { + if (is_array($fileData) && isset($fileData['tmp_name'])) { + $newValue[] = $filter->filter($fileData); + } + } + + return $newValue; + } + + /** + * @param mixed $context Extra "context" to provide the validator + * @return bool + */ + public function isValid($context = null) + { + $rawValue = $this->subject->getRawValue(); + $validator = $this->injectUploadValidator($this->subject->getValidatorChain()); + + if (! is_array($rawValue)) { + // This can happen in an AJAX POST, where the input comes across as a string + $rawValue = [ + 'tmp_name' => $rawValue, + 'name' => $rawValue, + 'size' => 0, + 'type' => '', + 'error' => UPLOAD_ERR_NO_FILE, + ]; + } + + if (is_array($rawValue) && isset($rawValue['tmp_name'])) { + // Single file input + $this->subject->isValid = $validator->isValid($rawValue, $context); + return $this->subject->isValid; + } + + if (is_array($rawValue) && isset($rawValue[0]['tmp_name'])) { + // Multi file input (multiple attribute set) + $this->subject->isValid = true; + + foreach ($rawValue as $value) { + if (! $validator->isValid($value, $context)) { + $this->subject->isValid = false; + return false; // Do not continue processing files if validation fails + } + } + + return true; // We return early from the loop if validation fails + } + + return $this->subject->isValid; + } + + /** + * @return ValidatorChain + */ + protected function injectUploadValidator(ValidatorChain $chain) + { + if (! $this->subject->autoPrependUploadValidator) { + return $chain; + } + + // Check if Upload validator is already first in chain + $validators = $chain->getValidators(); + if (isset($validators[0]['instance']) + && $validators[0]['instance'] instanceof UploadValidator + ) { + $this->subject->autoPrependUploadValidator = false; + return $chain; + } + + $chain->prependByName(UploadValidator::class, [], true); + $this->subject->autoPrependUploadValidator = false; + + return $chain; + } +} diff --git a/src/FileInput/PsrFileInputDecorator.php b/src/FileInput/PsrFileInputDecorator.php new file mode 100644 index 00000000..4045f505 --- /dev/null +++ b/src/FileInput/PsrFileInputDecorator.php @@ -0,0 +1,133 @@ +getError() === UPLOAD_ERR_NO_FILE; + } + + public function __construct(FileInput $subject) + { + $this->subject = $subject; + } + + /** + * @return UploadedFileInterface|UploadedFileInterface[] + */ + public function getValue() + { + $value = $this->subject->value; + + // Run filters ~after~ validation, so that is_uploaded_file() + // validation is not affected by filters. + if (! $this->subject->isValid) { + return $value; + } + + $filter = $this->subject->getFilterChain(); + + if (is_array($value)) { + // Multi file input (multiple attribute set) + $newValue = []; + foreach ($value as $fileData) { + $newValue[] = $filter->filter($fileData); + } + return $newValue; + } + + // Single file input + return $filter->filter($value); + } + + /** + * @param mixed $context Extra "context" to provide the validator + * @return bool + */ + public function isValid($context = null) + { + $rawValue = $this->subject->getRawValue(); + $validator = $this->injectUploadValidator($this->subject->getValidatorChain()); + + if (is_array($rawValue)) { + // Multi file input (multiple attribute set) + $this->subject->isValid = true; + foreach ($rawValue as $value) { + if (! $validator->isValid($value, $context)) { + $this->subject->isValid = false; + return false; // Do not continue processing files if validation fails + } + } + return true; // We return early from the loop if validation fails + } + + // Single file input + $this->subject->isValid = $validator->isValid($rawValue, $context); + return $this->subject->isValid; + } + + /** + * @return ValidatorChain + */ + protected function injectUploadValidator(ValidatorChain $chain) + { + if (! $this->subject->autoPrependUploadValidator) { + return $chain; + } + + // Check if Upload validator is already first in chain + $validators = $chain->getValidators(); + if (isset($validators[0]['instance']) + && $validators[0]['instance'] instanceof UploadValidator + ) { + $this->subject->autoPrependUploadValidator = false; + return $chain; + } + + $chain->prependByName(UploadValidator::class, [], true); + $this->subject->autoPrependUploadValidator = false; + + return $chain; + } +} diff --git a/test/CollectionInputFilterTest.php b/test/CollectionInputFilterTest.php index 72eace01..e09a6e12 100644 --- a/test/CollectionInputFilterTest.php +++ b/test/CollectionInputFilterTest.php @@ -171,7 +171,7 @@ public function dataVsValidProvider() 'Required: F, Count: N, Valid: T' => [!$isRequired, null, $colRaw, $validIF , $colRaw, $colFiltered, true , []], 'Required: F, Count: N, Valid: F' => [!$isRequired, null, $colRaw, $invalidIF, $colRaw, $colFiltered, false, $colMessages], 'Required: F, Count: +1, Valid: F' => [!$isRequired, 2, $colRaw, $invalidIF, $colRaw, $colFiltered, false, $colMessages], - 'Required: T, Data: [], Valid: X' => [ $isRequired, null, [] , $noValidIF, [] , [] , false, []], + 'Required: T, Data: [], Valid: X' => [ $isRequired, null, [] , $noValidIF, [] , [] , false, [['isEmpty' => 'Value is required and can\'t be empty']]], 'Required: F, Data: [], Valid: X' => [!$isRequired, null, [] , $noValidIF, [] , [] , true , []], ]; // @codingStandardsIgnoreEnd @@ -735,4 +735,34 @@ public function testDuplicatedErrorMessages() ], ], $inputFilter->getMessages()); } + + public function testLazyLoadsANotEmptyValidatorWhenNoneProvided() + { + $this->assertInstanceOf(NotEmpty::class, $this->inputFilter->getNotEmptyValidator()); + } + + public function testAllowsComposingANotEmptyValidator() + { + $notEmptyValidator = new NotEmpty(); + $this->inputFilter->setNotEmptyValidator($notEmptyValidator); + $this->assertSame($notEmptyValidator, $this->inputFilter->getNotEmptyValidator()); + } + + public function testUsesMessageFromComposedNotEmptyValidatorWhenRequiredButCollectionIsEmpty() + { + $message = 'this is the validation message'; + $notEmptyValidator = new NotEmpty(); + $notEmptyValidator->setMessage($message); + + $this->inputFilter->setIsRequired(true); + $this->inputFilter->setNotEmptyValidator($notEmptyValidator); + + $this->inputFilter->setData([]); + + $this->assertFalse($this->inputFilter->isValid()); + + $this->assertEquals([ + [NotEmpty::IS_EMPTY => $message], + ], $this->inputFilter->getMessages()); + } } diff --git a/test/FactoryTest.php b/test/FactoryTest.php index 228cd0f8..610b0592 100644 --- a/test/FactoryTest.php +++ b/test/FactoryTest.php @@ -1038,6 +1038,28 @@ public function testWhenCreateInputPullsInputFromThePluginManagerItMustNotOverwr $this->assertSame($input->reveal(), $factory->createInput($spec)); } + public function testFactoryCanCreateCollectionInputFilterWithRequiredMessage() + { + $factory = $this->createDefaultFactory(); + $message = 'this is the validation message'; + + /** @var CollectionInputFilter $inputFilter */ + $inputFilter = $factory->createInputFilter([ + 'type' => CollectionInputFilter::class, + 'required' => true, + 'required_message' => $message, + 'inputfilter' => new InputFilter(), + 'count' => 3, + ]); + + $this->assertInstanceOf(CollectionInputFilter::class, $inputFilter); + + $notEmptyValidator = $inputFilter->getNotEmptyValidator(); + $messageTemplates = $notEmptyValidator->getMessageTemplates(); + $this->assertArrayHasKey(Validator\NotEmpty::IS_EMPTY, $messageTemplates); + $this->assertSame($message, $messageTemplates[Validator\NotEmpty::IS_EMPTY]); + } + /** * @return Factory */ diff --git a/test/FileInputTest.php b/test/FileInput/HttpServerFileInputDecoratorTest.php similarity index 94% rename from test/FileInputTest.php rename to test/FileInput/HttpServerFileInputDecoratorTest.php index be0d14ad..c3ba2bd0 100644 --- a/test/FileInputTest.php +++ b/test/FileInput/HttpServerFileInputDecoratorTest.php @@ -1,24 +1,24 @@ assertTrue($this->input->isRequired()); $this->input->setValue(['tmp_name' => 'bar']); - /** @var Validator\File\UploadFile|MockObject $uploadMock */ $uploadMock = $this->getMockBuilder(Validator\File\UploadFile::class) ->setMethods(['isValid']) ->getMock(); diff --git a/test/FileInput/PsrFileInputDecoratorTest.php b/test/FileInput/PsrFileInputDecoratorTest.php new file mode 100644 index 00000000..6eea2867 --- /dev/null +++ b/test/FileInput/PsrFileInputDecoratorTest.php @@ -0,0 +1,351 @@ +input = new FileInput('foo'); + // Upload validator does not work in CLI test environment, disable + $this->input->setAutoPrependUploadValidator(false); + } + + public function testRetrievingValueFiltersTheValue() + { + $this->markTestSkipped('Test is not enabled in PsrFileInputTest'); + } + + public function testRetrievingValueFiltersTheValueOnlyAfterValidating() + { + $upload = $this->prophesize(UploadedFileInterface::class); + $upload->getError()->willReturn(UPLOAD_ERR_OK); + + $this->input->setValue($upload->reveal()); + + $filteredUpload = $this->prophesize(UploadedFileInterface::class); + + $this->input->setFilterChain($this->createFilterChainMock([[ + $upload->reveal(), + $filteredUpload->reveal(), + ]])); + + $this->assertEquals($upload->reveal(), $this->input->getValue()); + $this->assertTrue( + $this->input->isValid(), + 'isValid() value not match. Detail . ' . json_encode($this->input->getMessages()) + ); + $this->assertEquals($filteredUpload->reveal(), $this->input->getValue()); + } + + public function testCanFilterArrayOfMultiFileData() + { + $values = []; + for ($i = 0; $i < 3; $i += 1) { + $upload = $this->prophesize(UploadedFileInterface::class); + $upload->getError()->willReturn(UPLOAD_ERR_OK); + $values[] = $upload->reveal(); + } + + $this->input->setValue($values); + + $filteredValues = []; + for ($i = 0; $i < 3; $i += 1) { + $upload = $this->prophesize(UploadedFileInterface::class); + $filteredValues[] = $upload->reveal(); + } + + $this->input->setFilterChain($this->createFilterChainMock([ + [$values[0], $filteredValues[0]], + [$values[1], $filteredValues[1]], + [$values[2], $filteredValues[2]], + ])); + + $this->assertEquals($values, $this->input->getValue()); + $this->assertTrue( + $this->input->isValid(), + 'isValid() value not match. Detail . ' . json_encode($this->input->getMessages()) + ); + $this->assertEquals( + $filteredValues, + $this->input->getValue() + ); + } + + public function testCanRetrieveRawValue() + { + $value = $this->prophesize(UploadedFileInterface::class); + $value->getError()->shouldNotBeCalled(); + + $this->input->setValue($value->reveal()); + + $filteredValue = $this->prophesize(UploadedFileInterface::class)->reveal(); + $this->input->setFilterChain($this->createFilterChainMock([[$value->reveal(), $filteredValue]])); + + $this->assertEquals($value->reveal(), $this->input->getRawValue()); + } + + public function testValidationOperatesOnFilteredValue() + { + $this->markTestSkipped('Test is not enabled in PsrFileInputTest'); + } + + public function testValidationOperatesBeforeFiltering() + { + $badValue = $this->prophesize(UploadedFileInterface::class); + $badValue->getError()->willReturn(UPLOAD_ERR_NO_FILE); + $filteredValue = $this->prophesize(UploadedFileInterface::class)->reveal(); + + $this->input->setValue($badValue->reveal()); + + $this->input->setFilterChain($this->createFilterChainMock([[$badValue->reveal(), $filteredValue]])); + $this->input->setValidatorChain($this->createValidatorChainMock([[$badValue->reveal(), null, false]])); + + $this->assertFalse($this->input->isValid()); + $this->assertEquals($badValue->reveal(), $this->input->getValue()); + } + + public function testAutoPrependUploadValidatorIsOnByDefault() + { + $input = new FileInput('foo'); + $this->assertTrue($input->getAutoPrependUploadValidator()); + } + + public function testUploadValidatorIsAddedDuringIsValidWhenAutoPrependUploadValidatorIsEnabled() + { + $this->input->setAutoPrependUploadValidator(true); + $this->assertTrue($this->input->getAutoPrependUploadValidator()); + $this->assertTrue($this->input->isRequired()); + + $uploadedFile = $this->prophesize(UploadedFileInterface::class); + $uploadedFile->getError()->willReturn(UPLOAD_ERR_CANT_WRITE); + + $this->input->setValue($uploadedFile->reveal()); + + $validatorChain = $this->input->getValidatorChain(); + $this->assertCount(0, $validatorChain->getValidators()); + + $this->assertFalse($this->input->isValid()); + $validators = $validatorChain->getValidators(); + $this->assertCount(1, $validators); + $this->assertInstanceOf(Validator\File\UploadFile::class, $validators[0]['instance']); + } + + public function testUploadValidatorIsNotAddedByDefaultDuringIsValidWhenAutoPrependUploadValidatorIsDisabled() + { + $this->assertFalse($this->input->getAutoPrependUploadValidator()); + $this->assertTrue($this->input->isRequired()); + + $uploadedFile = $this->prophesize(UploadedFileInterface::class); + $uploadedFile->getError()->willReturn(UPLOAD_ERR_OK); + + $this->input->setValue($uploadedFile->reveal()); + $validatorChain = $this->input->getValidatorChain(); + $this->assertEquals(0, count($validatorChain->getValidators())); + + $this->assertTrue( + $this->input->isValid(), + 'isValid() value not match. Detail . ' . json_encode($this->input->getMessages()) + ); + $this->assertEquals(0, count($validatorChain->getValidators())); + } + + public function testRequiredUploadValidatorValidatorNotAddedWhenOneExists() + { + $this->input->setAutoPrependUploadValidator(true); + $this->assertTrue($this->input->getAutoPrependUploadValidator()); + $this->assertTrue($this->input->isRequired()); + + $upload = $this->prophesize(UploadedFileInterface::class); + $upload->getError()->willReturn(UPLOAD_ERR_OK); + + $this->input->setValue($upload->reveal()); + + $validator = $this->prophesize(Validator\File\UploadFile::class); + $validator + ->isValid(Argument::that([$upload, 'reveal']), null) + ->willReturn(true) + ->shouldBeCalledTimes(1); + + $validatorChain = $this->input->getValidatorChain(); + $validatorChain->prependValidator($validator->reveal()); + $this->assertTrue( + $this->input->isValid(), + 'isValid() value not match. Detail . ' . json_encode($this->input->getMessages()) + ); + + $validators = $validatorChain->getValidators(); + $this->assertEquals(1, count($validators)); + $this->assertEquals($validator->reveal(), $validators[0]['instance']); + } + + public function testNotEmptyValidatorAddedWhenIsValidIsCalled($value = null) + { + $this->markTestSkipped('Test is not enabled in PsrFileInputTest'); + } + + public function testRequiredNotEmptyValidatorNotAddedWhenOneExists($value = null) + { + $this->markTestSkipped('Test is not enabled in PsrFileInputTest'); + } + + public function testFallbackValueVsIsValidRules( + $required = null, + $fallbackValue = null, + $originalValue = null, + $isValid = null, + $expectedValue = null + ) { + $this->markTestSkipped('Input::setFallbackValue is not implemented on PsrFileInput'); + } + + + public function testFallbackValueVsIsValidRulesWhenValueNotSet( + $required = null, + $fallbackValue = null + ) { + $this->markTestSkipped('Input::setFallbackValue is not implemented on PsrFileInput'); + } + + public function testIsEmptyFileUploadNoFile() + { + $upload = $this->prophesize(UploadedFileInterface::class); + $upload->getError()->willReturn(UPLOAD_ERR_NO_FILE); + $this->assertTrue($this->input->isEmptyFile($upload->reveal())); + } + + public function testIsEmptyFileOk() + { + $upload = $this->prophesize(UploadedFileInterface::class); + $upload->getError()->willReturn(UPLOAD_ERR_OK); + $this->assertFalse($this->input->isEmptyFile($upload->reveal())); + } + + public function testIsEmptyMultiFileUploadNoFile() + { + $upload = $this->prophesize(UploadedFileInterface::class); + $upload->getError()->willReturn(UPLOAD_ERR_NO_FILE); + + $rawValue = [$upload->reveal()]; + + $this->assertTrue($this->input->isEmptyFile($rawValue)); + } + + public function testIsEmptyFileMultiFileOk() + { + $rawValue = []; + for ($i = 0; $i < 2; $i += 1) { + $upload = $this->prophesize(UploadedFileInterface::class); + $upload->getError()->willReturn(UPLOAD_ERR_OK); + $rawValue[] = $upload->reveal(); + } + + $this->assertFalse($this->input->isEmptyFile($rawValue)); + } + + /** + * Specific PsrFileInput::merge extras + */ + public function testPsrFileInputMerge() + { + $source = new FileInput(); + $source->setAutoPrependUploadValidator(true); + + $target = $this->input; + $target->setAutoPrependUploadValidator(false); + + $return = $target->merge($source); + $this->assertSame($target, $return, 'merge() must return it self'); + + $this->assertEquals( + true, + $target->getAutoPrependUploadValidator(), + 'getAutoPrependUploadValidator() value not match' + ); + } + + public function isRequiredVsAllowEmptyVsContinueIfEmptyVsIsValidProvider() + { + $generator = parent::isRequiredVsAllowEmptyVsContinueIfEmptyVsIsValidProvider(); + if (! is_array($generator)) { + $generator = clone $generator; + $generator->rewind(); + } + + $toSkip = [ + 'Required: T; AEmpty: F; CIEmpty: F; Validator: X, Value: Empty / tmp_name', + 'Required: T; AEmpty: F; CIEmpty: F; Validator: X, Value: Empty / single', + 'Required: T; AEmpty: F; CIEmpty: F; Validator: X, Value: Empty / multi', + ]; + + foreach ($generator as $name => $data) { + if (in_array($name, $toSkip, true)) { + continue; + } + yield $name => $data; + } + } + + public function emptyValueProvider() + { + foreach (['single', 'multi'] as $type) { + $raw = $this->prophesize(UploadedFileInterface::class); + $raw->getError()->willReturn(UPLOAD_ERR_NO_FILE); + + $filtered = $this->prophesize(UploadedFileInterface::class); + $filtered->getError()->willReturn(UPLOAD_ERR_NO_FILE); + + yield $type => [ + 'raw' => $type === 'multi' + ? [$raw->reveal()] + : $raw->reveal(), + 'filtered' => $raw->reveal(), + ]; + } + } + + public function mixedValueProvider() + { + $fooUploadErrOk = $this->prophesize(UploadedFileInterface::class); + $fooUploadErrOk->getError()->willReturn(UPLOAD_ERR_OK); + + return [ + 'single' => [ + 'raw' => $fooUploadErrOk->reveal(), + 'filtered' => $fooUploadErrOk->reveal(), + ], + 'multi' => [ + 'raw' => [ + $fooUploadErrOk->reveal(), + ], + 'filtered' => $fooUploadErrOk->reveal(), + ], + ]; + } + + protected function getDummyValue($raw = true) + { + $upload = $this->prophesize(UploadedFileInterface::class); + $upload->getError()->willReturn(UPLOAD_ERR_OK); + return $upload->reveal(); + } +} diff --git a/test/InputTest.php b/test/InputTest.php index 16ed313b..5760593c 100644 --- a/test/InputTest.php +++ b/test/InputTest.php @@ -1,14 +1,13 @@ emptyValueProvider(); $mixedValues = $this->mixedValueProvider(); + $emptyValues = $emptyValues instanceof Iterator ? iterator_to_array($emptyValues) : $emptyValues; + $mixedValues = $mixedValues instanceof Iterator ? iterator_to_array($mixedValues) : $mixedValues; + $values = array_merge($emptyValues, $mixedValues); return $values; @@ -672,7 +674,10 @@ public function setValueProvider() public function isRequiredVsAllowEmptyVsContinueIfEmptyVsIsValidProvider() { $allValues = $this->setValueProvider(); + $emptyValues = $this->emptyValueProvider(); + $emptyValues = $emptyValues instanceof Iterator ? iterator_to_array($emptyValues) : $emptyValues; + $nonEmptyValues = array_diff_key($allValues, $emptyValues); $isRequired = true; @@ -694,7 +699,7 @@ public function isRequiredVsAllowEmptyVsContinueIfEmptyVsIsValidProvider() }; // @codingStandardsIgnoreStart - $dataTemplates=[ + $dataTemplates = [ // Description => [$isRequired, $allowEmpty, $continueIfEmpty, $validator, [$values], $expectedIsValid, $expectedMessages] 'Required: T; AEmpty: T; CIEmpty: T; Validator: T' => [ $isRequired, $aEmpty, $cIEmpty, $validatorValid , $allValues , $isValid, []], 'Required: T; AEmpty: T; CIEmpty: T; Validator: F' => [ $isRequired, $aEmpty, $cIEmpty, $validatorInvalid, $allValues , !$isValid, $validatorMsg],