diff --git a/.travis.yml b/.travis.yml index af1558b78..e1faebc8f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ branches: cache: directories: - - $HOME/.composer/cache + - $HOME/.composer/ env: global: @@ -50,17 +50,17 @@ matrix: - php: 7.1 env: - DEPS=latest - - php: hhvm + - php: nightly env: - DEPS=lowest - - php: hhvm + - php: nightly env: - DEPS=locked - - php: hhvm + - php: nightly env: - DEPS=latest allow_failures: - - php: hhvm + - php: nightly before_install: - if [[ $TEST_COVERAGE != 'true' ]]; then phpenv config-rm xdebug.ini || return 0 ; fi @@ -81,7 +81,7 @@ script: - if [[ $EXECUTE_HOSTNAME_CHECK == "true" && $TRAVIS_PULL_REQUEST == "false" ]]; then php bin/update_hostname_validator.php --check-only; fi after_script: - - if [[ $TEST_COVERAGE == 'true' ]]; then composer upload-coverage ; fi + - if [[ $TEST_COVERAGE == 'true' ]]; then travis_retry composer upload-coverage ; fi notifications: email: false diff --git a/CHANGELOG.md b/CHANGELOG.md index 38975104b..c04abd6e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,38 @@ All notable changes to this project will be documented in this file, in reverse chronological order by release. +## 2.10.0 - TBD + +### Added + +- [#175](https://github.com/zendframework/zend-validator/pull/175) adds support + for PHP 7.2 (conditionally, as PHP 7.2 is currently in beta1). + +### Changed + +- [#169](https://github.com/zendframework/zend-validator/pull/169) modifies how + the various `File` validators check for readable files. Previously, they used + `stream_resolve_include_path`, which led to false negative checks when the + files did not exist within an `include_path` (which is often the case within a + web application). These now use `is_readable()` instead. + +- [#185](https://github.com/zendframework/zend-validator/pull/185) updates the + zend-session requirement (during development, and in the suggestions) to 2.8+, + to ensure compatibility with the upcoming PHP 7.2 release. + +### Deprecated + +- Nothing. + +### Removed + +- [#175](https://github.com/zendframework/zend-validator/pull/175) removes + support for HHVM. + +### Fixed + +- Nothing. + ## 2.9.2 - 2017-07-20 ### Added diff --git a/bin/update_hostname_validator.php b/bin/update_hostname_validator.php index fab52c24e..69043d681 100644 --- a/bin/update_hostname_validator.php +++ b/bin/update_hostname_validator.php @@ -177,7 +177,9 @@ function getNewValidTlds($string) function getPunycodeDecoder() { if (function_exists('idn_to_utf8')) { - return 'idn_to_utf8'; + return function ($domain) { + return idn_to_utf8($domain, 0, INTL_IDNA_VARIANT_UTS46); + }; } $hostnameValidator = new Hostname(); diff --git a/composer.json b/composer.json index 560ed56bb..6b52eca70 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "zendframework/zend-i18n": "^2.6", "zendframework/zend-math": "^2.6", "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-session": "^2.6.2", + "zendframework/zend-session": "^2.8", "zendframework/zend-uri": "^2.5", "phpunit/PHPUnit": "^6.0.8 || ^5.7.15", "zendframework/zend-coding-standard": "~1.0.0" @@ -38,7 +38,7 @@ "zendframework/zend-math": "Zend\\Math component, required by the Csrf validator", "zendframework/zend-i18n-resources": "Translations of validator messages", "zendframework/zend-servicemanager": "Zend\\ServiceManager component to allow using the ValidatorPluginManager and validator chains", - "zendframework/zend-session": "Zend\\Session component, required by the Csrf validator", + "zendframework/zend-session": "Zend\\Session component, ^2.8; required by the Csrf validator", "zendframework/zend-uri": "Zend\\Uri component, required by the Uri and Sitemap\\Loc validators" }, "minimum-stability": "dev", diff --git a/composer.lock b/composer.lock index dca2092e7..e47398518 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "6926a47b4f62c328d7c9d74767654cb9", + "content-hash": "592dc52b9037dcdb0de561eced941e30", "packages": [ { "name": "container-interop/container-interop", @@ -2169,29 +2169,29 @@ }, { "name": "zendframework/zend-session", - "version": "2.7.3", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/zendframework/zend-session.git", - "reference": "346e9709657b81a5d53d70ce754730a26d1f02f2" + "reference": "b1486c382decc241de8b1c7778eaf2f0a884f67d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-session/zipball/346e9709657b81a5d53d70ce754730a26d1f02f2", - "reference": "346e9709657b81a5d53d70ce754730a26d1f02f2", + "url": "https://api.github.com/repos/zendframework/zend-session/zipball/b1486c382decc241de8b1c7778eaf2f0a884f67d", + "reference": "b1486c382decc241de8b1c7778eaf2f0a884f67d", "shasum": "" }, "require": { - "php": "^5.5 || ^7.0", + "php": "^7.0 || ^5.6", "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", "zendframework/zend-stdlib": "^2.7 || ^3.0" }, "require-dev": { "container-interop/container-interop": "^1.1", - "fabpot/php-cs-fixer": "1.7.*", "mongodb/mongodb": "^1.0.1", - "phpunit/phpunit": "~4.0", + "phpunit/phpunit": "^6.0.8 || ^5.7.15", "zendframework/zend-cache": "^2.6.1", + "zendframework/zend-coding-standard": "~1.0.0", "zendframework/zend-db": "^2.7", "zendframework/zend-http": "^2.5.4", "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", @@ -2208,8 +2208,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev", - "dev-develop": "2.8-dev" + "dev-master": "2.8-dev", + "dev-develop": "2.9-dev" }, "zf": { "component": "Zend\\Session", @@ -2231,7 +2231,7 @@ "session", "zf2" ], - "time": "2016-07-05T18:32:50+00:00" + "time": "2017-06-19T21:31:39+00:00" }, { "name": "zendframework/zend-uri", diff --git a/doc/book/validators/is-countable.md b/doc/book/validators/is-countable.md new file mode 100644 index 000000000..bfd66c123 --- /dev/null +++ b/doc/book/validators/is-countable.md @@ -0,0 +1,107 @@ +# IsCountable Validator + +- **Since 2.10.0** + +`Zend\Validator\IsCountable` allows you to validate that a value can be counted +(i.e., it's an array or implements `Countable`), and, optionally: + +- the exact count of the value +- the minimum count of the value +- the maximum count of the value + +Specifying either of the latter two is inconsistent with the first, and, as +such, the validator does not allow setting both a count and a minimum or maximum +value. You may, however specify both minimum and maximum values, in which case +the validator operates similar to the [Between validator](between.md). + +## Supported options + +The following options are supported for `Zend\Validator\IsCountable`: + +- `count`: Defines if the validation should look for a specific, exact count for + the value provided. +- `max`: Sets the maximum value for the validation; if the count of the value is + greater than the maximum, validation fails.. +- `min`: Sets the minimum value for the validation; if the count of the value is + lower than the minimum, validation fails. + +## Default behaviour + +Given no options, the validator simply tests to see that the value may be +counted (i.e., it's an array or `Countable` instance): + +```php +$validator = new Zend\Validator\IsCountable(); + +$validator->isValid(10); // false; not an array or Countable +$validator->isValid([10]); // true; value is an array +$validator->isValid(new ArrayObject([10])); // true; value is Countable +$validator->isValid(new stdClass); // false; value is not Countable +``` + +## Specifying an exact count + +You can also specify an exact count; if the value is countable, and its count +matches, the the value is valid. + +```php +$validator = new Zend\Validator\IsCountable(['count' => 3]); + +$validator->isValid([1, 2, 3]); // true; countable, and count is 3 +$validator->isValid(new ArrayObject([1, 2, 3])); // true; countable, and count is 3 +$validator->isValid([1]); // false; countable, but count is 1 +$validator->isValid(new ArrayObject([1])); // false; countable, but count is 1 +``` + +## Specifying a minimum count + +You may specify a minimum count. When you do, the value must be countable, and +greater than or equal to the minimum count you specify in order to be valid. + +```php +$validator = new Zend\Validator\IsCountable(['min' => 2]); + +$validator->isValid([1, 2, 3]); // true; countable, and count is 3 +$validator->isValid(new ArrayObject([1, 2, 3])); // true; countable, and count is 3 +$validator->isValid([1, 2]); // true; countable, and count is 2 +$validator->isValid(new ArrayObject([1, 2])); // true; countable, and count is 2 +$validator->isValid([1]); // false; countable, but count is 1 +$validator->isValid(new ArrayObject([1])); // false; countable, but count is 1 +``` + +## Specifying a maximum count + +You may specify a maximum count. When you do, the value must be countable, and +less than or equal to the maximum count you specify in order to be valid. + +```php +$validator = new Zend\Validator\IsCountable(['max' => 2]); + +$validator->isValid([1, 2, 3]); // false; countable, but count is 3 +$validator->isValid(new ArrayObject([1, 2, 3])); // false; countable, but count is 3 +$validator->isValid([1, 2]); // true; countable, and count is 2 +$validator->isValid(new ArrayObject([1, 2])); // true; countable, and count is 2 +$validator->isValid([1]); // true; countable, and count is 1 +$validator->isValid(new ArrayObject([1])); // true; countable, and count is 1 +``` + +## Specifying both minimum and maximum + +If you specify both a minimum and maximum, the count must be _between_ the two, +inclusively (i.e., it may be the minimum or maximum, and any value between). + +```php +$validator = new Zend\Validator\IsCountable([ + 'min' => 3, + 'max' => 5, +]); + +$validator->isValid([1, 2, 3]); // true; countable, and count is 3 +$validator->isValid(new ArrayObject([1, 2, 3])); // true; countable, and count is 3 +$validator->isValid(range(1, 5)); // true; countable, and count is 5 +$validator->isValid(new ArrayObject(range(1, 5))); // true; countable, and count is 5 +$validator->isValid([1, 2]); // false; countable, and count is 2 +$validator->isValid(new ArrayObject([1, 2])); // false; countable, and count is 2 +$validator->isValid(range(1, 6)); // false; countable, and count is 6 +$validator->isValid(new ArrayObject(range(1, 6))); // false; countable, and count is 6 +``` diff --git a/mkdocs.yml b/mkdocs.yml index f72bf9afb..277caf7ff 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -25,6 +25,7 @@ pages: - InArray: validators/in-array.md - Ip: validators/ip.md - Isbn: validators/isbn.md + - IsCountable: validators/is-countable.md - IsInstanceOf: validators/isinstanceof.md - LessThan: validators/less-than.md - NotEmpty: validators/not-empty.md diff --git a/src/EmailAddress.php b/src/EmailAddress.php index d089f84c0..55c450eef 100644 --- a/src/EmailAddress.php +++ b/src/EmailAddress.php @@ -530,7 +530,7 @@ public function isValid($value) protected function idnToAscii($email) { if (extension_loaded('intl')) { - return (idn_to_ascii($email) ?: $email); + return (idn_to_ascii($email, 0, INTL_IDNA_VARIANT_UTS46) ?: $email); } return $email; } @@ -553,7 +553,7 @@ protected function idnToUtf8($email) // the source string in those cases. // But not when the source string is long enough. // Thus we default to source string ourselves. - return idn_to_utf8($email) ?: $email; + return idn_to_utf8($email, 0, INTL_IDNA_VARIANT_UTS46) ?: $email; } return $email; } diff --git a/src/File/Crc32.php b/src/File/Crc32.php index db8aac806..126d63d56 100644 --- a/src/File/Crc32.php +++ b/src/File/Crc32.php @@ -56,7 +56,7 @@ public function getCrc32() * Sets the crc32 hash for one or multiple files * * @param string|array $options - * @return Crc32 Provides a fluent interface + * @return self Provides a fluent interface */ public function setCrc32($options) { @@ -68,7 +68,7 @@ public function setCrc32($options) * Adds the crc32 hash for one or multiple files * * @param string|array $options - * @return Crc32 Provides a fluent interface + * @return self Provides a fluent interface */ public function addCrc32($options) { @@ -104,12 +104,12 @@ public function isValid($value, $file = null) $this->setValue($filename); // Is file readable ? - if (empty($file) || false === stream_resolve_include_path($file)) { + if (empty($file) || false === is_readable($file)) { $this->error(self::NOT_FOUND); return false; } - $hashes = array_unique(array_keys($this->getHash())); + $hashes = array_unique(array_keys($this->getHash())); $filehash = hash_file('crc32', $file); if ($filehash === false) { $this->error(self::NOT_DETECTED); diff --git a/src/File/ExcludeExtension.php b/src/File/ExcludeExtension.php index e06830788..1d1cb4218 100644 --- a/src/File/ExcludeExtension.php +++ b/src/File/ExcludeExtension.php @@ -59,7 +59,7 @@ public function isValid($value, $file = null) $this->setValue($filename); // Is file readable ? - if (empty($file) || false === stream_resolve_include_path($file)) { + if (empty($file) || false === is_readable($file)) { $this->error(self::NOT_FOUND); return false; } diff --git a/src/File/ExcludeMimeType.php b/src/File/ExcludeMimeType.php index 2f6b5771e..66c8eadb6 100644 --- a/src/File/ExcludeMimeType.php +++ b/src/File/ExcludeMimeType.php @@ -63,7 +63,7 @@ public function isValid($value, $file = null) $this->setValue($filename); // Is file readable ? - if (empty($file) || false === stream_resolve_include_path($file)) { + if (empty($file) || false === is_readable($file)) { $this->error(self::NOT_READABLE); return false; } diff --git a/src/File/Extension.php b/src/File/Extension.php index 6f2287840..e502dc993 100644 --- a/src/File/Extension.php +++ b/src/File/Extension.php @@ -100,7 +100,7 @@ public function getCase() * Sets the case to use * * @param bool $case - * @return Extension Provides a fluent interface + * @return self Provides a fluent interface */ public function setCase($case) { @@ -124,7 +124,7 @@ public function getExtension() * Sets the file extensions * * @param string|array $extension The extensions to validate - * @return Extension Provides a fluent interface + * @return self Provides a fluent interface */ public function setExtension($extension) { @@ -137,7 +137,7 @@ public function setExtension($extension) * Adds the file extensions * * @param string|array $extension The extensions to add for validation - * @return Extension Provides a fluent interface + * @return self Provides a fluent interface */ public function addExtension($extension) { @@ -196,7 +196,7 @@ public function isValid($value, $file = null) $this->setValue($filename); // Is file readable ? - if (empty($file) || false === stream_resolve_include_path($file)) { + if (empty($file) || false === is_readable($file)) { $this->error(self::NOT_FOUND); return false; } diff --git a/src/File/FilesSize.php b/src/File/FilesSize.php index 237f417cd..079c7a291 100644 --- a/src/File/FilesSize.php +++ b/src/File/FilesSize.php @@ -103,12 +103,12 @@ public function isValid($value, $file = null) 'Value array must be in $_FILES format' ); } - $file = $files; + $file = $files; $files = $files['tmp_name']; } // Is file readable ? - if (empty($files) || false === stream_resolve_include_path($files)) { + if (empty($files) || false === is_readable($files)) { $this->throwError($file, self::NOT_READABLE); continue; } @@ -128,10 +128,10 @@ public function isValid($value, $file = null) if (($max !== null) && ($max < $size)) { if ($this->getByteString()) { $this->options['max'] = $this->toByteString($max); - $this->size = $this->toByteString($size); + $this->size = $this->toByteString($size); $this->throwError($file, self::TOO_BIG); $this->options['max'] = $max; - $this->size = $size; + $this->size = $size; } else { $this->throwError($file, self::TOO_BIG); } @@ -142,10 +142,10 @@ public function isValid($value, $file = null) if (($min !== null) && ($size < $min)) { if ($this->getByteString()) { $this->options['min'] = $this->toByteString($min); - $this->size = $this->toByteString($size); + $this->size = $this->toByteString($size); $this->throwError($file, self::TOO_SMALL); $this->options['min'] = $min; - $this->size = $size; + $this->size = $size; } else { $this->throwError($file, self::TOO_SMALL); } diff --git a/src/File/Hash.php b/src/File/Hash.php index e28e8afb6..f48c2c921 100644 --- a/src/File/Hash.php +++ b/src/File/Hash.php @@ -76,7 +76,7 @@ public function getHash() * Sets the hash for one or multiple files * * @param string|array $options - * @return Hash Provides a fluent interface + * @return self Provides a fluent interface */ public function setHash($options) { @@ -90,8 +90,8 @@ public function setHash($options) * Adds the hash for one or multiple files * * @param string|array $options - * @return Hash Provides a fluent interface * @throws Exception\InvalidArgumentException + * @return self Provides a fluent interface */ public function addHash($options) { @@ -148,7 +148,7 @@ public function isValid($value, $file = null) $this->setValue($filename); // Is file readable ? - if (empty($file) || false === stream_resolve_include_path($file)) { + if (empty($file) || false === is_readable($file)) { $this->error(self::NOT_FOUND); return false; } diff --git a/src/File/ImageSize.php b/src/File/ImageSize.php index 023a86b40..62e1d1f7a 100644 --- a/src/File/ImageSize.php +++ b/src/File/ImageSize.php @@ -124,8 +124,8 @@ public function getMinWidth() * Sets the minimum allowed width * * @param int $minWidth - * @return ImageSize Provides a fluid interface * @throws Exception\InvalidArgumentException When minwidth is greater than maxwidth + * @return self Provides a fluid interface */ public function setMinWidth($minWidth) { @@ -154,8 +154,8 @@ public function getMaxWidth() * Sets the maximum allowed width * * @param int $maxWidth - * @return ImageSize Provides a fluid interface * @throws Exception\InvalidArgumentException When maxwidth is less than minwidth + * @return self Provides a fluid interface */ public function setMaxWidth($maxWidth) { @@ -184,8 +184,8 @@ public function getMinHeight() * Sets the minimum allowed height * * @param int $minHeight - * @return ImageSize Provides a fluid interface * @throws Exception\InvalidArgumentException When minheight is greater than maxheight + * @return self Provides a fluid interface */ public function setMinHeight($minHeight) { @@ -214,8 +214,8 @@ public function getMaxHeight() * Sets the maximum allowed height * * @param int $maxHeight - * @return ImageSize Provides a fluid interface * @throws Exception\InvalidArgumentException When maxheight is less than minheight + * @return self Provides a fluid interface */ public function setMaxHeight($maxHeight) { @@ -274,7 +274,7 @@ public function getImageHeight() * Sets the minimum image size * * @param array $options The minimum image dimensions - * @return ImageSize Provides a fluent interface + * @return self Provides a fluent interface */ public function setImageMin($options) { @@ -286,7 +286,7 @@ public function setImageMin($options) * Sets the maximum image size * * @param array|\Traversable $options The maximum image dimensions - * @return ImageSize Provides a fluent interface + * @return self Provides a fluent interface */ public function setImageMax($options) { @@ -298,7 +298,7 @@ public function setImageMax($options) * Sets the minimum and maximum image width * * @param array $options The image width dimensions - * @return ImageSize Provides a fluent interface + * @return self Provides a fluent interface */ public function setImageWidth($options) { @@ -312,7 +312,7 @@ public function setImageWidth($options) * Sets the minimum and maximum image height * * @param array $options The image height dimensions - * @return ImageSize Provides a fluent interface + * @return self Provides a fluent interface */ public function setImageHeight($options) { @@ -351,7 +351,7 @@ public function isValid($value, $file = null) $this->setValue($filename); // Is file readable ? - if (empty($file) || false === stream_resolve_include_path($file)) { + if (empty($file) || false === is_readable($file)) { $this->error(self::NOT_READABLE); return false; } diff --git a/src/File/Md5.php b/src/File/Md5.php index 2a88e4084..c36b8a457 100644 --- a/src/File/Md5.php +++ b/src/File/Md5.php @@ -104,12 +104,12 @@ public function isValid($value, $file = null) $this->setValue($filename); // Is file readable ? - if (empty($file) || false === stream_resolve_include_path($file)) { + if (empty($file) || false === is_readable($file)) { $this->error(self::NOT_FOUND); return false; } - $hashes = array_unique(array_keys($this->getHash())); + $hashes = array_unique(array_keys($this->getHash())); $filehash = hash_file('md5', $file); if ($filehash === false) { $this->error(self::NOT_DETECTED); diff --git a/src/File/MimeType.php b/src/File/MimeType.php index b479e8015..3a9d42e6f 100644 --- a/src/File/MimeType.php +++ b/src/File/MimeType.php @@ -176,10 +176,10 @@ public function getMagicFile() * if false, the default MAGIC file from PHP will be used * * @param string $file - * @return MimeType Provides fluid interface * @throws Exception\RuntimeException When finfo can not read the magicfile * @throws Exception\InvalidArgumentException * @throws Exception\InvalidMagicMimeFileException + * @return self Provides fluid interface */ public function setMagicFile($file) { @@ -216,7 +216,7 @@ public function setMagicFile($file) * Disables usage of MagicFile * * @param $disable boolean False disables usage of magic file - * @return MimeType Provides fluid interface + * @return self Provides fluid interface */ public function disableMagicFile($disable) { @@ -249,7 +249,7 @@ public function getHeaderCheck() * Note that this is unsafe and therefor the default value is false * * @param bool $headerCheck - * @return MimeType Provides fluid interface + * @return self Provides fluid interface */ public function enableHeaderCheck($headerCheck = true) { @@ -278,7 +278,7 @@ public function getMimeType($asArray = false) * Sets the mimetypes * * @param string|array $mimetype The mimetypes to validate - * @return MimeType Provides a fluent interface + * @return self Provides a fluent interface */ public function setMimeType($mimetype) { @@ -291,8 +291,8 @@ public function setMimeType($mimetype) * Adds the mimetypes * * @param string|array $mimetype The mimetypes to add for validation - * @return MimeType Provides a fluent interface * @throws Exception\InvalidArgumentException + * @return self Provides a fluent interface */ public function addMimeType($mimetype) { @@ -363,7 +363,7 @@ public function isValid($value, $file = null) $this->setValue($filename); // Is file readable ? - if (empty($file) || false === stream_resolve_include_path($file)) { + if (empty($file) || false === is_readable($file)) { $this->error(static::NOT_READABLE); return false; } diff --git a/src/File/Sha1.php b/src/File/Sha1.php index 2a59039c2..bc3929d1f 100644 --- a/src/File/Sha1.php +++ b/src/File/Sha1.php @@ -104,12 +104,12 @@ public function isValid($value, $file = null) $this->setValue($filename); // Is file readable ? - if (empty($file) || false === stream_resolve_include_path($file)) { + if (empty($file) || false === is_readable($file)) { $this->error(self::NOT_FOUND); return false; } - $hashes = array_unique(array_keys($this->getHash())); + $hashes = array_unique(array_keys($this->getHash())); $filehash = hash_file('sha1', $file); if ($filehash === false) { $this->error(self::NOT_DETECTED); diff --git a/src/File/Size.php b/src/File/Size.php index d8ac7481b..80a9701b0 100644 --- a/src/File/Size.php +++ b/src/File/Size.php @@ -136,8 +136,8 @@ public function getMin($raw = false) * For example: 2000, 2MB, 0.2GB * * @param int|string $min The minimum file size - * @return Size Provides a fluent interface * @throws Exception\InvalidArgumentException When min is greater than max + * @return self Provides a fluent interface */ public function setMin($min) { @@ -181,8 +181,8 @@ public function getMax($raw = false) * For example: 2000, 2MB, 0.2GB * * @param int|string $max The maximum file size - * @return Size Provides a fluent interface * @throws Exception\InvalidArgumentException When max is smaller than min + * @return self Provides a fluent interface */ public function setMax($max) { @@ -216,7 +216,7 @@ protected function getSize() * Set current size * * @param int $size - * @return Size + * @return self */ protected function setSize($size) { @@ -253,7 +253,7 @@ public function isValid($value, $file = null) $this->setValue($filename); // Is file readable ? - if (empty($file) || false === stream_resolve_include_path($file)) { + if (empty($file) || false === is_readable($file)) { $this->error(self::NOT_FOUND); return false; } @@ -270,10 +270,10 @@ public function isValid($value, $file = null) if (($min !== null) && ($size < $min)) { if ($this->getByteString()) { $this->options['min'] = $this->toByteString($min); - $this->size = $this->toByteString($size); + $this->size = $this->toByteString($size); $this->error(self::TOO_SMALL); $this->options['min'] = $min; - $this->size = $size; + $this->size = $size; } else { $this->error(self::TOO_SMALL); } @@ -283,10 +283,10 @@ public function isValid($value, $file = null) if (($max !== null) && ($max < $size)) { if ($this->getByteString()) { $this->options['max'] = $this->toByteString($max); - $this->size = $this->toByteString($size); + $this->size = $this->toByteString($size); $this->error(self::TOO_BIG); $this->options['max'] = $max; - $this->size = $size; + $this->size = $size; } else { $this->error(self::TOO_BIG); } diff --git a/src/File/Upload.php b/src/File/Upload.php index 55025f633..33b664b3a 100644 --- a/src/File/Upload.php +++ b/src/File/Upload.php @@ -9,6 +9,7 @@ namespace Zend\Validator\File; +use Countable; use Zend\Validator\AbstractValidator; use Zend\Validator\Exception; @@ -109,7 +110,10 @@ public function getFiles($file = null) */ public function setFiles($files = []) { - if (count($files) === 0) { + if (null === $files + || ((is_array($files) || $files instanceof Countable) + && count($files) === 0) + ) { $this->options['files'] = $_FILES; } else { $this->options['files'] = $files; diff --git a/src/File/WordCount.php b/src/File/WordCount.php index 61145597a..cbe9ce451 100644 --- a/src/File/WordCount.php +++ b/src/File/WordCount.php @@ -28,8 +28,8 @@ class WordCount extends AbstractValidator * @var array Error message templates */ protected $messageTemplates = [ - self::TOO_MUCH => "Too many words, maximum '%max%' are allowed but '%count%' were counted", - self::TOO_LESS => "Too few words, minimum '%min%' are expected but '%count%' were counted", + self::TOO_MUCH => "Too many words, maximum '%max%' are allowed but '%count%' were counted", + self::TOO_LESS => "Too few words, minimum '%min%' are expected but '%count%' were counted", self::NOT_FOUND => "File is not readable or does not exist", ]; @@ -75,7 +75,7 @@ class WordCount extends AbstractValidator public function __construct($options = null) { if (1 < func_num_args()) { - $args = func_get_args(); + $args = func_get_args(); $options = [ 'min' => array_shift($args), 'max' => array_shift($args), @@ -103,8 +103,8 @@ public function getMin() * Sets the minimum word count * * @param int|array $min The minimum word count - * @return WordCount Provides a fluent interface * @throws Exception\InvalidArgumentException When min is greater than max + * @return self Provides a fluent interface */ public function setMin($min) { @@ -141,8 +141,8 @@ public function getMax() * Sets the maximum file count * * @param int|array $max The maximum word count - * @return WordCount Provides a fluent interface * @throws Exception\InvalidArgumentException When max is smaller than min + * @return self Provides a fluent interface */ public function setMax($max) { @@ -194,7 +194,7 @@ public function isValid($value, $file = null) $this->setValue($filename); // Is file readable ? - if (empty($file) || false === stream_resolve_include_path($file)) { + if (empty($file) || false === is_readable($file)) { $this->error(self::NOT_FOUND); return false; } diff --git a/src/Hostname.php b/src/Hostname.php index 7fedac45f..1e88b9894 100644 --- a/src/Hostname.php +++ b/src/Hostname.php @@ -69,7 +69,7 @@ class Hostname extends AbstractValidator /** * Array of valid top-level-domains - * IanaVersion 2017072000 + * IanaVersion 2017072500 * * @see ftp://data.iana.org/TLD/tlds-alpha-by-domain.txt List of all TLDs by domain * @see http://www.iana.org/domains/root/db/ Official list of supported TLDs diff --git a/src/IsCountable.php b/src/IsCountable.php new file mode 100644 index 000000000..ab553f83f --- /dev/null +++ b/src/IsCountable.php @@ -0,0 +1,197 @@ + "The input must be an array or an instance of \\Countable", + self::NOT_EQUALS => "The input count must equal '%count%'", + self::GREATER_THAN => "The input count must be less than '%max%', inclusively", + self::LESS_THAN => "The input count must be greater than '%min%', inclusively", + ]; + + /** + * Additional variables available for validation failure messages + * + * @var array + */ + protected $messageVariables = [ + 'count' => ['options' => 'count'], + 'min' => ['options' => 'min'], + 'max' => ['options' => 'max'], + ]; + + /** + * Options for the between validator + * + * @var array + */ + protected $options = [ + 'count' => null, + 'min' => null, + 'max' => null, + ]; + + public function setOptions($options = []) + { + foreach (['count', 'min', 'max'] as $option) { + if (! is_array($options) || ! isset($options[$option])) { + continue; + } + + $method = sprintf('set%s', ucfirst($option)); + $this->$method($options[$option]); + unset($options[$option]); + } + + return parent::setOptions($options); + } + + /** + * Returns true if and only if $value is countable (and the count validates against optional values). + * + * @param iterable $value + * @return bool + */ + public function isValid($value) + { + if (! (is_array($value) || $value instanceof Countable)) { + $this->error(self::NOT_COUNTABLE); + return false; + } + + $count = count($value); + + if (is_numeric($this->getCount())) { + if ($count != $this->getCount()) { + $this->error(self::NOT_EQUALS); + return false; + } + + return true; + } + + if (is_numeric($this->getMax()) && $count > $this->getMax()) { + $this->error(self::GREATER_THAN); + return false; + } + + if (is_numeric($this->getMin()) && $count < $this->getMin()) { + $this->error(self::LESS_THAN); + return false; + } + + return true; + } + + /** + * Returns the count option + * + * @return mixed + */ + public function getCount() + { + return $this->options['count']; + } + + /** + * Returns the min option + * + * @return mixed + */ + public function getMin() + { + return $this->options['min']; + } + + /** + * Returns the max option + * + * @return mixed + */ + public function getMax() + { + return $this->options['max']; + } + + /** + * @param mixed $value + * @return void + * @throws Exception\InvalidArgumentException if either a min or max option + * was previously set. + */ + private function setCount($value) + { + if (isset($this->options['min']) || isset($this->options['max'])) { + throw new Exception\InvalidArgumentException( + 'Cannot set count; conflicts with either a min or max option previously set' + ); + } + $this->options['count'] = $value; + } + + /** + * @param mixed $value + * @return void + * @throws Exception\InvalidArgumentException if either a count or max option + * was previously set. + */ + private function setMin($value) + { + if (isset($this->options['count'])) { + throw new Exception\InvalidArgumentException( + 'Cannot set count; conflicts with either a count option previously set' + ); + } + $this->options['min'] = $value; + } + + /** + * @param mixed $value + * @return void + * @throws Exception\InvalidArgumentException if either a count or min option + * was previously set. + */ + private function setMax($value) + { + if (isset($this->options['count'])) { + throw new Exception\InvalidArgumentException( + 'Cannot set count; conflicts with either a count option previously set' + ); + } + $this->options['max'] = $value; + } +} diff --git a/test/IsCountableTest.php b/test/IsCountableTest.php new file mode 100644 index 000000000..2084b52c7 --- /dev/null +++ b/test/IsCountableTest.php @@ -0,0 +1,141 @@ + [['count' => 10, 'min' => 1]], + 'count-max' => [['count' => 10, 'max' => 10]], + ]; + } + + /** + * @dataProvider conflictingOptionsProvider + */ + public function testConstructorRaisesExceptionWhenProvidedConflictingOptions(array $options) + { + $this->expectException(Exception\InvalidArgumentException::class); + $this->expectExceptionMessage('conflicts'); + new IsCountable($options); + } + + public function conflictingSecondaryOptionsProvider() + { + return [ + 'count-min' => [['count' => 10], ['min' => 1]], + 'count-max' => [['count' => 10], ['max' => 10]], + ]; + } + + /** + * @dataProvider conflictingSecondaryOptionsProvider + */ + public function testSetOptionsRaisesExceptionWhenProvidedOptionConflictingWithCurrentSettings( + array $originalOptions, + array $secondaryOptions + ) { + $validator = new IsCountable($originalOptions); + $this->expectException(Exception\InvalidArgumentException::class); + $this->expectExceptionMessage('conflicts'); + $validator->setOptions($secondaryOptions); + } + + public function testArrayIsValid() + { + $sut = new IsCountable([ + 'min' => 1, + 'max' => 10, + ]); + + $this->assertTrue($sut->isValid(['Foo']), json_encode($sut->getMessages())); + $this->assertCount(0, $sut->getMessages()); + } + + public function testIteratorIsValid() + { + $sut = new IsCountable(); + + $this->assertTrue($sut->isValid(new \SplQueue()), json_encode($sut->getMessages())); + $this->assertCount(0, $sut->getMessages()); + } + + public function testValidEquals() + { + $sut = new IsCountable([ + 'count' => 1, + ]); + + $this->assertTrue($sut->isValid(['Foo'])); + $this->assertCount(0, $sut->getMessages()); + } + + public function testValidMax() + { + $sut = new IsCountable([ + 'max' => 1, + ]); + + $this->assertTrue($sut->isValid(['Foo'])); + $this->assertCount(0, $sut->getMessages()); + } + + public function testValidMin() + { + $sut = new IsCountable([ + 'min' => 1, + ]); + + $this->assertTrue($sut->isValid(['Foo'])); + $this->assertCount(0, $sut->getMessages()); + } + + public function testInvalidNotEquals() + { + $sut = new IsCountable([ + 'count' => 2, + ]); + + $this->assertFalse($sut->isValid(['Foo'])); + $this->assertCount(1, $sut->getMessages()); + } + + public function testInvalidType() + { + $sut = new IsCountable(); + + $this->assertFalse($sut->isValid(new \stdClass())); + $this->assertCount(1, $sut->getMessages()); + } + + public function testInvalidExceedsMax() + { + $sut = new IsCountable([ + 'max' => 1, + ]); + + $this->assertFalse($sut->isValid(['Foo', 'Bar'])); + $this->assertCount(1, $sut->getMessages()); + } + + public function testInvalidExceedsMin() + { + $sut = new IsCountable([ + 'min' => 2, + ]); + + $this->assertFalse($sut->isValid(['Foo'])); + $this->assertCount(1, $sut->getMessages()); + } +}