diff --git a/src/ArrayInput.php b/src/ArrayInput.php index d23b9d81..0451a600 100644 --- a/src/ArrayInput.php +++ b/src/ArrayInput.php @@ -63,6 +63,7 @@ public function isValid($context = null) $hasValue = $this->hasValue(); $required = $this->isRequired(); $hasFallback = $this->hasFallback(); + $values = $this->getValue(); if (! $hasValue && $hasFallback) { $this->setValue($this->getFallbackValue()); @@ -70,17 +71,14 @@ public function isValid($context = null) } if (! $hasValue && $required) { - if ($this->errorMessage === null) { - $this->errorMessage = $this->prepareRequiredValidationFailureMessage(); - } - return false; + $this->injectRequiredValidator(); + $values = [$this]; } if (!$this->continueIfEmpty() && !$this->allowEmpty()) { $this->injectNotEmptyValidator(); } $validator = $this->getValidatorChain(); - $values = $this->getValue(); $result = true; foreach ($values as $value) { $empty = ($value === null || $value === '' || $value === []); diff --git a/src/FileInput.php b/src/FileInput.php index b195306a..125115d5 100644 --- a/src/FileInput.php +++ b/src/FileInput.php @@ -118,16 +118,15 @@ public function isValid($context = null) $required = $this->isRequired(); $allowEmpty = $this->allowEmpty(); $continueIfEmpty = $this->continueIfEmpty(); + $validator = $this->getValidatorChain(); if (! $hasValue && ! $required) { return true; } if (! $hasValue && $required && ! $this->hasFallback()) { - if ($this->errorMessage === null) { - $this->errorMessage = $this->prepareRequiredValidationFailureMessage(); - } - return false; + $this->injectRequiredValidator(); + return $validator->isValid($this); } if ($empty && ! $required && ! $continueIfEmpty) { @@ -139,7 +138,6 @@ public function isValid($context = null) } $this->injectUploadValidator(); - $validator = $this->getValidatorChain(); //$value = $this->getValue(); // Do not run the filters yet for File uploads (see getValue()) if (!is_array($rawValue)) { diff --git a/src/Input.php b/src/Input.php index 6342040b..4b3d629f 100644 --- a/src/Input.php +++ b/src/Input.php @@ -59,6 +59,11 @@ class Input implements */ protected $notEmptyValidator = false; + /** + * @var bool If required validator is in the validator chain. + */ + protected $requiredValidator = false; + /** * @var bool */ @@ -403,10 +408,8 @@ public function isValid($context = null) } if (! $hasValue && $required) { - if ($this->errorMessage === null) { - $this->errorMessage = $this->prepareRequiredValidationFailureMessage(); - } - return false; + $this->injectRequiredValidator(); + $value = $this; } if ($empty && ! $required && ! $continueIfEmpty) { @@ -485,16 +488,27 @@ protected function injectNotEmptyValidator() } /** - * Create and return the validation failure message for required input. - * - * @return string[] + * @return void */ - protected function prepareRequiredValidationFailureMessage() + protected function injectRequiredValidator() { - $notEmpty = new NotEmpty(); - $templates = $notEmpty->getOption('messageTemplates'); - return [ - NotEmpty::IS_EMPTY => $templates[NotEmpty::IS_EMPTY], - ]; + if ($this->requiredValidator) { + return; + } + + $chain = $this->getValidatorChain(); + + // Check if Required validator is already in chain + $validators = $chain->getValidators(); + foreach ($validators as $validator) { + if ($validator['instance'] instanceof Validator\Required) { + $this->requiredValidator = true; + return; + } + } + + $this->requiredValidator = true; + + $chain->prependValidator(new Validator\Required(), true); } } diff --git a/src/Validator/Required.php b/src/Validator/Required.php new file mode 100644 index 00000000..f645c00d --- /dev/null +++ b/src/Validator/Required.php @@ -0,0 +1,44 @@ + 'Invalid type given. Zend\InputFilter\Input is required', + self::REQUIRED => 'Value is required', + ]; + + /** + * {@inheritDoc} + */ + public function isValid($value) + { + if (!($value instanceof Input)) { + $this->error(self::INVALID); + return false; + } + + $input = $value; + + if ($input->hasValue()) { // If has value then all is ok + return true; + } + + if ($input->isRequired()) { // It's Required and value was not set. + $this->error(self::REQUIRED); + return false; + } + + return true; + } +} diff --git a/test/InputTest.php b/test/InputTest.php index e7dd27ae..9d708aed 100644 --- a/test/InputTest.php +++ b/test/InputTest.php @@ -34,21 +34,6 @@ public function setUp() $this->input = new Input('foo'); } - public function assertRequiredValidationErrorMessage(Input $input, $message = '') - { - $message = $message ?: 'Expected failure message for required input'; - $message .= ';'; - - $messages = $input->getMessages(); - $this->assertInternalType('array', $messages, $message . ' non-array messages array'); - - $notEmpty = new NotEmptyValidator(); - $messageTemplates = $notEmpty->getOption('messageTemplates'); - $this->assertSame([ - NotEmptyValidator::IS_EMPTY => $messageTemplates[NotEmptyValidator::IS_EMPTY], - ], $messages, $message . ' missing NotEmpty::IS_EMPTY key and/or contains additional messages'); - } - public function testConstructorRequiresAName() { $this->assertEquals('foo', $this->input->getName()); @@ -183,59 +168,28 @@ public function testRequiredWithoutFallbackAndValueNotSetThenFail() $input = $this->input; $input->setRequired(true); - $this->assertFalse( - $input->isValid(), - 'isValid() should be return always false when no fallback value, is required, and not data is set.' - ); - $this->assertRequiredValidationErrorMessage($input); - } - - public function testRequiredWithoutFallbackAndValueNotSetThenFailReturnsCustomErrorMessageWhenSet() - { - $input = $this->input; - $input->setRequired(true); - $input->setErrorMessage('FAILED TO VALIDATE'); + $expectedMessages = [ + 'inputRequired' => 'Value is required', + ]; $this->assertFalse( $input->isValid(), 'isValid() should be return always false when no fallback value, is required, and not data is set.' ); - $this->assertSame(['FAILED TO VALIDATE'], $input->getMessages()); + $this->assertEquals($expectedMessages, $input->getMessages(), 'getMessages() value not match'); } - /** - * @group 28 - * @group 60 - */ - public function testRequiredWithoutFallbackAndValueNotSetProvidesNotEmptyValidatorIsEmptyErrorMessage() - { - $input = $this->input; - $input->setRequired(true); - - $this->assertFalse( - $input->isValid(), - 'isValid() should always return false when no fallback value is present, ' - . 'the input is required, and no data is set.' - ); - $this->assertRequiredValidationErrorMessage($input); - } - - /** - * @group 28 - * @group 60 - */ - public function testRequiredWithoutFallbackAndValueNotSetProvidesCustomErrorMessageWhenSet() + public function testRequiredWithoutFallbackAndValueNotSetThenFailWithCustomErrorMessage() { $input = $this->input; $input->setRequired(true); - $input->setErrorMessage('FAILED TO VALIDATE'); + $input->setErrorMessage('fooErrorMessage'); $this->assertFalse( $input->isValid(), - 'isValid() should always return false when no fallback value is present, ' - . 'the input is required, and no data is set.' + 'isValid() should be return always false when no fallback value, is required, and not data is set.' ); - $this->assertSame(['FAILED TO VALIDATE'], $input->getMessages()); + $this->assertEquals(['fooErrorMessage'], $input->getMessages(), 'getMessages() value not match'); } public function testNotRequiredWithoutFallbackAndValueNotSetThenIsValid() diff --git a/test/Validator/RequiredTest.php b/test/Validator/RequiredTest.php new file mode 100644 index 00000000..c072a7db --- /dev/null +++ b/test/Validator/RequiredTest.php @@ -0,0 +1,88 @@ +validator = new Required(); + } + + /** + * @dataProvider inputProvider + */ + public function testValid($input, $expectedIsValid, $expectedMessages) + { + $this->assertEquals( + $expectedIsValid, + $this->validator->isValid($input), + 'isValid() value not match. Detail: ' . json_encode($this->validator->getMessages()) + ); + + $this->assertEquals( + $expectedMessages, + $this->validator->getMessages(), + 'getMessages() value not match.' + ); + } + + public function inputProvider() + { + $requiredMsg = [ + Required::REQUIRED => 'Value is required', + ]; + $invalidMsg = [ + Required::INVALID => 'Invalid type given. Zend\InputFilter\Input is required', + ]; + + $required = true; + $hasValue = true; + + // @codingStandardsIgnoreStart + return [ + // Description => [$input, isValid, getMessages] + 'Invalid type' => [new \stdClass() , false, $invalidMsg], + 'Required: T. Value: Set' => [$this->createInputMock($required, $hasValue), true , []], + 'Required: T. Value: Not set' => [$this->createInputMock($required, !$hasValue), false, $requiredMsg], + 'Required: F. Value: set' => [$this->createInputMock(!$required, $hasValue), true , []], + 'Required: F. Value: Not set' => [$this->createInputMock(!$required, !$hasValue), true , []], + ]; + // @codingStandardsIgnoreEnd + } + + /** + * @param bool $required + * @param bool $hasValue + * + * @return Input|MockObject + */ + protected function createInputMock($required, $hasValue) + { + /** @var Input|MockObject $input */ + $input = $this->getMock(Input::class); + + $input->method('isRequired') + ->willReturn($required) + ; + + $input->method('hasValue') + ->willReturn($hasValue) + ; + + return $input; + } +}