From 55e55e07783ce60f74d4fb9cee5949db62e38122 Mon Sep 17 00:00:00 2001 From: Aleksei Khudiakov Date: Fri, 17 Mar 2017 20:22:26 +1000 Subject: [PATCH 01/20] Bumped to next dev version (2.10.0) --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cad5eaf0f..a14e811a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,24 @@ All notable changes to this project will be documented in this file, in reverse chronological order by release. +## 2.10.0 - TBD + +### Added + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- Nothing. + ## 2.9.1 - TBD ### Added From 479f57cb6c68d4481e2dca6c2001b0cf0203cc5a Mon Sep 17 00:00:00 2001 From: Tim Younger Date: Sat, 25 Mar 2017 13:32:27 -0700 Subject: [PATCH 02/20] add IsCountable validator, to enforce type and count elements. --- src/IsCountable.php | 150 +++++++++++++++++++++++++++++++++++++++ test/IsCountableTest.php | 88 +++++++++++++++++++++++ 2 files changed, 238 insertions(+) create mode 100644 src/IsCountable.php create mode 100644 test/IsCountableTest.php diff --git a/src/IsCountable.php b/src/IsCountable.php new file mode 100644 index 000000000..ea9ba81aa --- /dev/null +++ b/src/IsCountable.php @@ -0,0 +1,150 @@ + "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, + ]; + + /** + * Returns true if and only if $value is countable (and the count validates against optional values). + * + * @param string $value + * @return bool + * @throws Exception\RuntimeException + */ + public function isValid($value) + { + if (! (is_array($value) || $value instanceof \Countable)) { + throw new RuntimeException($this->messageTemplates[self::NOT_COUNTABLE]); + } + + $count = count($value); + + if (is_numeric($this->options['count'])) { + if ($count != $this->options['count']) { + $this->error(self::NOT_EQUALS); + return false; + } + + return true; + } + + if (is_numeric($this->options['max']) && $count > $this->options['max']) { + $this->error(self::GREATER_THAN); + return false; + } + + if (is_numeric($this->options['min']) && $count < $this->options['min']) { + $this->error(self::LESS_THAN); + return false; + } + + return true; + } + + /** + * Returns the count option + * + * @return mixed + */ + public function getCount() + { + return $this->options['count']; + } + + /** + * Sets the count option + * + * @param int $count + * @return self Provides a fluent interface + */ + public function setCount($count) + { + $this->options['count'] = $count; + return $this; + } + + /** + * Returns the min option + * + * @return mixed + */ + public function getMin() + { + return $this->options['min']; + } + + /** + * Sets the min option + * + * @param int $min + * @return self Provides a fluent interface + */ + public function setMin($min) + { + $this->options['min'] = $min; + return $this; + } + + /** + * Returns the max option + * + * @return mixed + */ + public function getMax() + { + return $this->options['max']; + } + + /** + * Sets the max option + * + * @param int $max + * @return self Provides a fluent interface + */ + public function setMax($max) + { + $this->options['max'] = $max; + return $this; + } +} diff --git a/test/IsCountableTest.php b/test/IsCountableTest.php new file mode 100644 index 000000000..953faf57a --- /dev/null +++ b/test/IsCountableTest.php @@ -0,0 +1,88 @@ +sut = new IsCountable(); + } + + public function testArrayIsValid() + { + $this->sut->setMin(1); + $this->sut->setMax(10); + + self::assertTrue($this->sut->isValid(['Foo']), json_encode($this->sut->getMessages())); + self::assertCount(0, $this->sut->getMessages()); + } + + public function testIteratorIsValid() + { + self::assertTrue($this->sut->isValid(new \SplQueue()), json_encode($this->sut->getMessages())); + self::assertCount(0, $this->sut->getMessages()); + } + + public function testValidEquals() + { + $this->sut->setCount(1); + + self::assertTrue($this->sut->isValid(['Foo'])); + self::assertCount(0, $this->sut->getMessages()); + } + + public function testValidMax() + { + $this->sut->setMax(1); + + self::assertTrue($this->sut->isValid(['Foo'])); + self::assertCount(0, $this->sut->getMessages()); + } + + public function testValidMin() + { + $this->sut->setMin(1); + + self::assertTrue($this->sut->isValid(['Foo'])); + self::assertCount(0, $this->sut->getMessages()); + } + + public function testInvalidNotEquals() + { + $this->sut->setCount(2); + + self::assertFalse($this->sut->isValid(['Foo'])); + self::assertCount(1, $this->sut->getMessages()); + } + + /** + * @expectedException \Zend\Validator\Exception\RuntimeException + */ + public function testInvalidType() + { + $this->sut->isValid(new \stdClass()); + } + + public function testInvalidExceedsMax() + { + $this->sut->setMax(1); + + self::assertFalse($this->sut->isValid(['Foo', 'Bar'])); + self::assertCount(1, $this->sut->getMessages()); + } + + public function testInvalidExceedsMin() + { + $this->sut->setMin(2); + + self::assertFalse($this->sut->isValid(['Foo'])); + self::assertCount(1, $this->sut->getMessages()); + } +} From 33591b684a405dd07617afbd0a86b4dcb5b6caa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Nystr=C3=B6m?= Date: Tue, 23 May 2017 14:41:09 +0200 Subject: [PATCH 03/20] Fix #165 - Use is_readable() instead of stream_resolve_include_path() --- src/File/Crc32.php | 4 ++-- src/File/ExcludeExtension.php | 2 +- src/File/ExcludeMimeType.php | 2 +- src/File/Extension.php | 2 +- src/File/FilesSize.php | 12 ++++++------ src/File/Hash.php | 4 ++-- src/File/ImageSize.php | 10 +++++----- src/File/Md5.php | 4 ++-- src/File/MimeType.php | 6 +++--- src/File/Sha1.php | 4 ++-- src/File/Size.php | 14 +++++++------- src/File/WordCount.php | 12 ++++++------ 12 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/File/Crc32.php b/src/File/Crc32.php index db8aac806..ba5d1c448 100644 --- a/src/File/Crc32.php +++ b/src/File/Crc32.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('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..0918474bf 100644 --- a/src/File/Extension.php +++ b/src/File/Extension.php @@ -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..b1a5fa484 100644 --- a/src/File/Hash.php +++ b/src/File/Hash.php @@ -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 Hash 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..bb7433766 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 ImageSize 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 ImageSize 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 ImageSize 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 ImageSize Provides a fluid interface */ public function setMaxHeight($maxHeight) { @@ -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 68e7dd8e3..4a4ed819f 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 MimeType Provides fluid interface */ public function setMagicFile($file) { @@ -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 MimeType 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..b698f8417 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 Size 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 Size Provides a fluent interface */ public function setMax($max) { @@ -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/WordCount.php b/src/File/WordCount.php index 61145597a..a725de394 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 WordCount 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 WordCount 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; } From 9d26add4d02faa225f5a328bd77df7f9503aec9b Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Fri, 23 Jun 2017 11:46:00 +0200 Subject: [PATCH 04/20] Adapt .travis.yml to recent ZF standards --- .travis.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 44b0eb45e..7b6c5acc3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ branches: cache: directories: - - $HOME/.composer/cache + - $HOME/.composer/ - $HOME/.local - zf-mkdoc-theme @@ -91,7 +91,7 @@ install: - travis_retry composer install $COMPOSER_ARGS --ignore-platform-reqs - if [[ $TRAVIS_PHP_VERSION =~ ^5.6 ]]; then travis_retry composer update $COMPOSER_ARGS --with-dependencies $LEGACY_DEPS ; fi - if [[ $DEPS == 'latest' ]]; then travis_retry composer update $COMPOSER_ARGS ; fi - - if [[ $DEPS == 'lowest' ]]; then travis_retry composer update --prefer-lowest --prefer-stable $COMPOSER_ARGS ; fi + - if [[ $DEPS == 'lowest' ]]; then travis_retry composer update $COMPOSER_ARGS --prefer-lowest --prefer-stable ; fi - if [[ $SERVICE_MANAGER_VERSION != '' ]]; then travis_retry composer require --dev --no-update $COMPOSER_ARGS "zendframework/zend-servicemanager:$SERVICE_MANAGER_VERSION" ; fi - if [[ $SERVICE_MANAGER_VERSION == '' ]]; then travis_retry composer require --dev --no-update $COMPOSER_ARGS "zendframework/zend-servicemanager:^3.0.3" ; fi - if [[ $TEST_COVERAGE == 'true' ]]; then travis_retry composer require --dev $COMPOSER_ARGS $COVERAGE_DEPS ; fi @@ -104,12 +104,12 @@ script: - if [[ $EXECUTE_HOSTNAME_CHECK == "true" && $TRAVIS_PULL_REQUEST == "false" ]]; then php bin/update_hostname_validator.php --check-only; fi - if [[ $DEPLOY_DOCS == "true" && "$TRAVIS_TEST_RESULT" == "0" ]]; then wget -O theme-installer.sh "https://raw.githubusercontent.com/zendframework/zf-mkdoc-theme/master/theme-installer.sh" ; chmod 755 theme-installer.sh ; ./theme-installer.sh ; fi -after_script: - - if [[ $TEST_COVERAGE == 'true' ]]; then composer upload-coverage ; fi - after_success: - if [[ $DEPLOY_DOCS == "true" ]]; then echo "Preparing to build and deploy documentation" ; ./zf-mkdoc-theme/deploy.sh ; echo "Completed deploying documentation" ; fi +after_script: + - if [[ $TEST_COVERAGE == 'true' ]]; then travis_retry composer upload-coverage ; fi + notifications: email: false slack: From 92035a47e70a0dc726698e0eac902653b8587256 Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Fri, 23 Jun 2017 11:46:47 +0200 Subject: [PATCH 05/20] Travis: drop HVVM & add PHP 7.2 --- .travis.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7b6c5acc3..44944de12 100644 --- a/.travis.yml +++ b/.travis.yml @@ -67,21 +67,21 @@ 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 - - php: hhvm + - php: nightly env: - DEPS=locked - SERVICE_MANAGER_VERSION="^2.7.5" allow_failures: - - php: hhvm + - php: nightly before_install: - if [[ $TEST_COVERAGE != 'true' ]]; then phpenv config-rm xdebug.ini || return 0 ; fi From 1a0717cd4f2e74ff947c3e068181718a186b37f2 Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Fri, 23 Jun 2017 11:57:39 +0200 Subject: [PATCH 06/20] Prepare to PHP 7.2 --- bin/update_hostname_validator.php | 4 +++- src/EmailAddress.php | 4 ++-- src/File/Upload.php | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/bin/update_hostname_validator.php b/bin/update_hostname_validator.php index fab52c24e..49070cbb5 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/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/Upload.php b/src/File/Upload.php index 55025f633..724a8987d 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,7 @@ public function getFiles($file = null) */ public function setFiles($files = []) { - if (count($files) === 0) { + if ((is_array($files) || $files instanceof Countable) && count($files) === 0) { $this->options['files'] = $_FILES; } else { $this->options['files'] = $files; From 407ee843496ebed629e3816f01d1c4f53f9ae230 Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Fri, 23 Jun 2017 16:49:15 +0200 Subject: [PATCH 07/20] In PHP < 7.2 `0 === count(null)` --- src/File/Upload.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/File/Upload.php b/src/File/Upload.php index 724a8987d..a721dfe3e 100644 --- a/src/File/Upload.php +++ b/src/File/Upload.php @@ -110,7 +110,7 @@ public function getFiles($file = null) */ public function setFiles($files = []) { - if ((is_array($files) || $files instanceof Countable) && count($files) === 0) { + if (null === $files || ((is_array($files) || $files instanceof Countable) && count($files) === 0)) { $this->options['files'] = $_FILES; } else { $this->options['files'] = $files; From 4416265d5b53fc6f9b5c4faf61afc212b2cb8e8b Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Tue, 25 Jul 2017 08:58:47 -0500 Subject: [PATCH 08/20] Updates `@return` annotations for fluent interfaces Should return `self` for fluent interfaces, class name when returning new instance (immutable classes). --- src/File/Crc32.php | 4 ++-- src/File/Extension.php | 6 +++--- src/File/Hash.php | 4 ++-- src/File/ImageSize.php | 16 ++++++++-------- src/File/MimeType.php | 10 +++++----- src/File/Size.php | 6 +++--- src/File/WordCount.php | 4 ++-- 7 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/File/Crc32.php b/src/File/Crc32.php index ba5d1c448..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) { diff --git a/src/File/Extension.php b/src/File/Extension.php index 0918474bf..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) { diff --git a/src/File/Hash.php b/src/File/Hash.php index b1a5fa484..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) { @@ -91,7 +91,7 @@ public function setHash($options) * * @param string|array $options * @throws Exception\InvalidArgumentException - * @return Hash Provides a fluent interface + * @return self Provides a fluent interface */ public function addHash($options) { diff --git a/src/File/ImageSize.php b/src/File/ImageSize.php index bb7433766..62e1d1f7a 100644 --- a/src/File/ImageSize.php +++ b/src/File/ImageSize.php @@ -125,7 +125,7 @@ public function getMinWidth() * * @param int $minWidth * @throws Exception\InvalidArgumentException When minwidth is greater than maxwidth - * @return ImageSize Provides a fluid interface + * @return self Provides a fluid interface */ public function setMinWidth($minWidth) { @@ -155,7 +155,7 @@ public function getMaxWidth() * * @param int $maxWidth * @throws Exception\InvalidArgumentException When maxwidth is less than minwidth - * @return ImageSize Provides a fluid interface + * @return self Provides a fluid interface */ public function setMaxWidth($maxWidth) { @@ -185,7 +185,7 @@ public function getMinHeight() * * @param int $minHeight * @throws Exception\InvalidArgumentException When minheight is greater than maxheight - * @return ImageSize Provides a fluid interface + * @return self Provides a fluid interface */ public function setMinHeight($minHeight) { @@ -215,7 +215,7 @@ public function getMaxHeight() * * @param int $maxHeight * @throws Exception\InvalidArgumentException When maxheight is less than minheight - * @return ImageSize Provides a fluid interface + * @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) { diff --git a/src/File/MimeType.php b/src/File/MimeType.php index 7bac70f7f..3a9d42e6f 100644 --- a/src/File/MimeType.php +++ b/src/File/MimeType.php @@ -179,7 +179,7 @@ public function getMagicFile() * @throws Exception\RuntimeException When finfo can not read the magicfile * @throws Exception\InvalidArgumentException * @throws Exception\InvalidMagicMimeFileException - * @return MimeType Provides fluid interface + * @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) { @@ -292,7 +292,7 @@ public function setMimeType($mimetype) * * @param string|array $mimetype The mimetypes to add for validation * @throws Exception\InvalidArgumentException - * @return MimeType Provides a fluent interface + * @return self Provides a fluent interface */ public function addMimeType($mimetype) { diff --git a/src/File/Size.php b/src/File/Size.php index b698f8417..80a9701b0 100644 --- a/src/File/Size.php +++ b/src/File/Size.php @@ -137,7 +137,7 @@ public function getMin($raw = false) * * @param int|string $min The minimum file size * @throws Exception\InvalidArgumentException When min is greater than max - * @return Size Provides a fluent interface + * @return self Provides a fluent interface */ public function setMin($min) { @@ -182,7 +182,7 @@ public function getMax($raw = false) * * @param int|string $max The maximum file size * @throws Exception\InvalidArgumentException When max is smaller than min - * @return Size Provides a fluent interface + * @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) { diff --git a/src/File/WordCount.php b/src/File/WordCount.php index a725de394..cbe9ce451 100644 --- a/src/File/WordCount.php +++ b/src/File/WordCount.php @@ -104,7 +104,7 @@ public function getMin() * * @param int|array $min The minimum word count * @throws Exception\InvalidArgumentException When min is greater than max - * @return WordCount Provides a fluent interface + * @return self Provides a fluent interface */ public function setMin($min) { @@ -142,7 +142,7 @@ public function getMax() * * @param int|array $max The maximum word count * @throws Exception\InvalidArgumentException When max is smaller than min - * @return WordCount Provides a fluent interface + * @return self Provides a fluent interface */ public function setMax($max) { From ddcb7cd779af1250b4116a148564c720a314aef1 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Tue, 25 Jul 2017 09:01:28 -0500 Subject: [PATCH 09/20] Adds CHANGELOG for #169 --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d3dfa088..597c7fbb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,14 @@ All notable changes to this project will be documented in this file, in reverse - Nothing. +### 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. + ### Deprecated - Nothing. From 7b3c9acb5575a0fcf84206eab79fcf79d3cb94b5 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Tue, 25 Jul 2017 09:02:18 -0500 Subject: [PATCH 10/20] Updated Hostname validator TLD list --- src/Hostname.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From d0316a1e0d60ef2c33a551b3cd06be1228580dfc Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Tue, 25 Jul 2017 09:07:28 -0500 Subject: [PATCH 11/20] Provides CS fix for update_hostname_validator - Whitespace following `function` keyword --- bin/update_hostname_validator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/update_hostname_validator.php b/bin/update_hostname_validator.php index 49070cbb5..69043d681 100644 --- a/bin/update_hostname_validator.php +++ b/bin/update_hostname_validator.php @@ -177,7 +177,7 @@ function getNewValidTlds($string) function getPunycodeDecoder() { if (function_exists('idn_to_utf8')) { - return function($domain) { + return function ($domain) { return idn_to_utf8($domain, 0, INTL_IDNA_VARIANT_UTS46); }; } From 612035786724cd3c4b0d9b166914ac3321dbd688 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Tue, 25 Jul 2017 09:09:03 -0500 Subject: [PATCH 12/20] Provides formatting of complex conditional - Uses newlines and indentation to group conditional statements --- src/File/Upload.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/File/Upload.php b/src/File/Upload.php index a721dfe3e..33b664b3a 100644 --- a/src/File/Upload.php +++ b/src/File/Upload.php @@ -110,7 +110,10 @@ public function getFiles($file = null) */ public function setFiles($files = []) { - if (null === $files || ((is_array($files) || $files instanceof Countable) && count($files) === 0)) { + if (null === $files + || ((is_array($files) || $files instanceof Countable) + && count($files) === 0) + ) { $this->options['files'] = $_FILES; } else { $this->options['files'] = $files; From af974c96a2ceca74ce529400702b77dee6e02d90 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Tue, 25 Jul 2017 09:10:30 -0500 Subject: [PATCH 13/20] Adds CHANGELOG for #175 --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 597c7fbb6..eaa64bf1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,8 @@ All notable changes to this project will be documented in this file, in reverse ### Added -- Nothing. +- [#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 @@ -22,7 +23,8 @@ All notable changes to this project will be documented in this file, in reverse ### Removed -- Nothing. +- [#175](https://github.com/zendframework/zend-validator/pull/175) removes + support for HHVM. ### Fixed From e7355ab1dcc9183719fb6efcb71e9ac5642d96c9 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Tue, 25 Jul 2017 10:45:20 -0500 Subject: [PATCH 14/20] Updates zend-session requirement Updates the zend-session requirement to v2.8+, to ensure compatibility with PHP 7.2. --- composer.json | 4 ++-- composer.lock | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) 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", From f527802116967f8288b35caa57cad4f7ace0526b Mon Sep 17 00:00:00 2001 From: Tim Younger Date: Tue, 25 Jul 2017 20:01:06 -0700 Subject: [PATCH 15/20] address upstream feedback: remove setters, prefer count over min and max in setOptions which is used by the constructor, use getter methods instead of options field in isValid, report NOT_COUNTABLE as a validation error rather than throwing an exception, and use `$this->` instead of `self::` for assertions in test case. --- src/IsCountable.php | 61 ++++++----------------- test/IsCountableTest.php | 103 +++++++++++++++++++++++++-------------- 2 files changed, 83 insertions(+), 81 deletions(-) diff --git a/src/IsCountable.php b/src/IsCountable.php index ea9ba81aa..07b007084 100644 --- a/src/IsCountable.php +++ b/src/IsCountable.php @@ -2,8 +2,6 @@ namespace Zend\Validator; -use Zend\Validator\Exception\RuntimeException; - class IsCountable extends AbstractValidator { const NOT_COUNTABLE = 'notCountable'; @@ -45,23 +43,32 @@ class IsCountable extends AbstractValidator 'max' => null, ]; + public function setOptions($options = []) + { + if (is_array($options) && isset($options['count'])) { + unset($options['min'], $options['max']); + } + + return parent::setOptions($options); + } + /** * Returns true if and only if $value is countable (and the count validates against optional values). * - * @param string $value + * @param iterable $value * @return bool - * @throws Exception\RuntimeException */ public function isValid($value) { if (! (is_array($value) || $value instanceof \Countable)) { - throw new RuntimeException($this->messageTemplates[self::NOT_COUNTABLE]); + $this->error(self::NOT_COUNTABLE); + return false; } $count = count($value); - if (is_numeric($this->options['count'])) { - if ($count != $this->options['count']) { + if (is_numeric($this->getCount())) { + if ($count != $this->getCount()) { $this->error(self::NOT_EQUALS); return false; } @@ -69,12 +76,12 @@ public function isValid($value) return true; } - if (is_numeric($this->options['max']) && $count > $this->options['max']) { + if (is_numeric($this->getMax()) && $count > $this->getMax()) { $this->error(self::GREATER_THAN); return false; } - if (is_numeric($this->options['min']) && $count < $this->options['min']) { + if (is_numeric($this->getMin()) && $count < $this->getMin()) { $this->error(self::LESS_THAN); return false; } @@ -92,18 +99,6 @@ public function getCount() return $this->options['count']; } - /** - * Sets the count option - * - * @param int $count - * @return self Provides a fluent interface - */ - public function setCount($count) - { - $this->options['count'] = $count; - return $this; - } - /** * Returns the min option * @@ -114,18 +109,6 @@ public function getMin() return $this->options['min']; } - /** - * Sets the min option - * - * @param int $min - * @return self Provides a fluent interface - */ - public function setMin($min) - { - $this->options['min'] = $min; - return $this; - } - /** * Returns the max option * @@ -135,16 +118,4 @@ public function getMax() { return $this->options['max']; } - - /** - * Sets the max option - * - * @param int $max - * @return self Provides a fluent interface - */ - public function setMax($max) - { - $this->options['max'] = $max; - return $this; - } } diff --git a/test/IsCountableTest.php b/test/IsCountableTest.php index 953faf57a..c69397afe 100644 --- a/test/IsCountableTest.php +++ b/test/IsCountableTest.php @@ -7,82 +7,113 @@ class IsCountableTest extends TestCase { - /** @var IsCountable */ - private $sut; - - protected function setUp() - { - $this->sut = new IsCountable(); - } - public function testArrayIsValid() { - $this->sut->setMin(1); - $this->sut->setMax(10); + $sut = new IsCountable([ + 'min' => 1, + 'max' => 10, + ]); - self::assertTrue($this->sut->isValid(['Foo']), json_encode($this->sut->getMessages())); - self::assertCount(0, $this->sut->getMessages()); + $this->assertTrue($sut->isValid(['Foo']), json_encode($sut->getMessages())); + $this->assertCount(0, $sut->getMessages()); } public function testIteratorIsValid() { - self::assertTrue($this->sut->isValid(new \SplQueue()), json_encode($this->sut->getMessages())); - self::assertCount(0, $this->sut->getMessages()); + $sut = new IsCountable(); + + $this->assertTrue($sut->isValid(new \SplQueue()), json_encode($sut->getMessages())); + $this->assertCount(0, $sut->getMessages()); } public function testValidEquals() { - $this->sut->setCount(1); + $sut = new IsCountable([ + 'count' => 1, + ]); - self::assertTrue($this->sut->isValid(['Foo'])); - self::assertCount(0, $this->sut->getMessages()); + $this->assertTrue($sut->isValid(['Foo'])); + $this->assertCount(0, $sut->getMessages()); } public function testValidMax() { - $this->sut->setMax(1); + $sut = new IsCountable([ + 'max' => 1, + ]); - self::assertTrue($this->sut->isValid(['Foo'])); - self::assertCount(0, $this->sut->getMessages()); + $this->assertTrue($sut->isValid(['Foo'])); + $this->assertCount(0, $sut->getMessages()); } public function testValidMin() { - $this->sut->setMin(1); + $sut = new IsCountable([ + 'min' => 1, + ]); - self::assertTrue($this->sut->isValid(['Foo'])); - self::assertCount(0, $this->sut->getMessages()); + $this->assertTrue($sut->isValid(['Foo'])); + $this->assertCount(0, $sut->getMessages()); } public function testInvalidNotEquals() { - $this->sut->setCount(2); + $sut = new IsCountable([ + 'count' => 2, + ]); - self::assertFalse($this->sut->isValid(['Foo'])); - self::assertCount(1, $this->sut->getMessages()); + $this->assertFalse($sut->isValid(['Foo'])); + $this->assertCount(1, $sut->getMessages()); } - /** - * @expectedException \Zend\Validator\Exception\RuntimeException - */ public function testInvalidType() { - $this->sut->isValid(new \stdClass()); + $sut = new IsCountable(); + + $this->assertFalse($sut->isValid(new \stdClass())); + $this->assertCount(1, $sut->getMessages()); } public function testInvalidExceedsMax() { - $this->sut->setMax(1); + $sut = new IsCountable([ + 'max' => 1, + ]); - self::assertFalse($this->sut->isValid(['Foo', 'Bar'])); - self::assertCount(1, $this->sut->getMessages()); + $this->assertFalse($sut->isValid(['Foo', 'Bar'])); + $this->assertCount(1, $sut->getMessages()); } public function testInvalidExceedsMin() { - $this->sut->setMin(2); + $sut = new IsCountable([ + 'min' => 2, + ]); + + $this->assertFalse($sut->isValid(['Foo'])); + $this->assertCount(1, $sut->getMessages()); + } - self::assertFalse($this->sut->isValid(['Foo'])); - self::assertCount(1, $this->sut->getMessages()); + public function testExactCountOverridesMinAndMax() + { + $sut = new IsCountable([ + 'count' => 1, + 'min' => 2, + 'max' => 3, + ]); + + $this->assertSame(1, $sut->getCount()); + $this->assertNull($sut->getMin()); + $this->assertNull($sut->getMax()); + + $sut->setOptions([ + 'count' => 4, + 'min' => 5, + 'max' => 6, + ]); + + $this->assertSame(4, $sut->getCount()); + $this->assertNull($sut->getMin()); + $this->assertNull($sut->getMax()); } } From 99247005e1c7a9ce80e37545fb30aa937abedaab Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Wed, 26 Jul 2017 10:23:08 -0500 Subject: [PATCH 16/20] Adds CHANGELOG for #185 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eaa64bf1d..c04abd6e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,10 @@ All notable changes to this project will be documented in this file, in reverse 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. From 74b4711166ca96825715558067aecd36992b9aee Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Wed, 26 Jul 2017 10:26:22 -0500 Subject: [PATCH 17/20] Adds license docblocks to new class files --- src/IsCountable.php | 5 +++++ test/IsCountableTest.php | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/IsCountable.php b/src/IsCountable.php index 07b007084..eb49e07e5 100644 --- a/src/IsCountable.php +++ b/src/IsCountable.php @@ -1,4 +1,9 @@ Date: Wed, 26 Jul 2017 10:26:59 -0500 Subject: [PATCH 18/20] Imports `Countable` into `IsCountable` class file --- src/IsCountable.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/IsCountable.php b/src/IsCountable.php index eb49e07e5..653e33444 100644 --- a/src/IsCountable.php +++ b/src/IsCountable.php @@ -7,6 +7,8 @@ namespace Zend\Validator; +use Countable; + class IsCountable extends AbstractValidator { const NOT_COUNTABLE = 'notCountable'; @@ -65,7 +67,7 @@ public function setOptions($options = []) */ public function isValid($value) { - if (! (is_array($value) || $value instanceof \Countable)) { + if (! (is_array($value) || $value instanceof Countable)) { $this->error(self::NOT_COUNTABLE); return false; } From 97d297c117bd047add23538ab5413a4cfe22f933 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Wed, 26 Jul 2017 10:49:22 -0500 Subject: [PATCH 19/20] Updates IsCountable to not allow passing both a count and a min or max value Since there is no way to clear a `count`, this is the only way to ensure that behavior remains as expected when merging options from disparate sources. --- src/IsCountable.php | 73 ++++++++++++++++++++++++++++++++++++++-- test/IsCountableTest.php | 63 +++++++++++++++++++++------------- 2 files changed, 111 insertions(+), 25 deletions(-) diff --git a/src/IsCountable.php b/src/IsCountable.php index 653e33444..ab553f83f 100644 --- a/src/IsCountable.php +++ b/src/IsCountable.php @@ -9,6 +9,21 @@ use Countable; +/** + * Validate that a value is countable and the count meets expectations. + * + * The validator has five specific behaviors: + * + * - You can determine if a value is countable only + * - You can test if the value is an exact count + * - You can test if the value is greater than a minimum count value + * - You can test if the value is greater than a maximum count value + * - You can test if the value is between the minimum and maximum count values + * + * When creating the instance or calling `setOptions()`, if you specify a + * "count" option, specifying either "min" or "max" leads to an inconsistent + * state and, as such will raise an Exception\InvalidArgumentException. + */ class IsCountable extends AbstractValidator { const NOT_COUNTABLE = 'notCountable'; @@ -52,8 +67,14 @@ class IsCountable extends AbstractValidator public function setOptions($options = []) { - if (is_array($options) && isset($options['count'])) { - unset($options['min'], $options['max']); + 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); @@ -125,4 +146,52 @@ 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 index a5d877ecb..2084b52c7 100644 --- a/test/IsCountableTest.php +++ b/test/IsCountableTest.php @@ -9,9 +9,49 @@ use PHPUnit\Framework\TestCase; use Zend\Validator\IsCountable; +use Zend\Validator\Exception; class IsCountableTest extends TestCase { + public function conflictingOptionsProvider() + { + return [ + 'count-min' => [['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([ @@ -98,27 +138,4 @@ public function testInvalidExceedsMin() $this->assertFalse($sut->isValid(['Foo'])); $this->assertCount(1, $sut->getMessages()); } - - public function testExactCountOverridesMinAndMax() - { - $sut = new IsCountable([ - 'count' => 1, - 'min' => 2, - 'max' => 3, - ]); - - $this->assertSame(1, $sut->getCount()); - $this->assertNull($sut->getMin()); - $this->assertNull($sut->getMax()); - - $sut->setOptions([ - 'count' => 4, - 'min' => 5, - 'max' => 6, - ]); - - $this->assertSame(4, $sut->getCount()); - $this->assertNull($sut->getMin()); - $this->assertNull($sut->getMax()); - } } From 03c12fd5d117bf5802f2c8b01b411f8949c518b1 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Wed, 26 Jul 2017 11:09:02 -0500 Subject: [PATCH 20/20] Adds documentation for #157 --- doc/book/validators/is-countable.md | 107 ++++++++++++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 108 insertions(+) create mode 100644 doc/book/validators/is-countable.md 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