From 5c8106542ad369bb2143f1bd074cb3bf77fad480 Mon Sep 17 00:00:00 2001 From: Maks3w Date: Thu, 20 Aug 2015 10:09:47 +0200 Subject: [PATCH] Loop validation context is mutable --- src/BaseInputFilter.php | 9 +-- test/BaseInputFilterTest.php | 153 ++++++++++++++++++++--------------- 2 files changed, 88 insertions(+), 74 deletions(-) diff --git a/src/BaseInputFilter.php b/src/BaseInputFilter.php index 0e7a3e23..aa9fc5b8 100644 --- a/src/BaseInputFilter.php +++ b/src/BaseInputFilter.php @@ -236,6 +236,8 @@ protected function validateInputs(array $inputs, $data = [], $context = null) $data = $this->getRawValues(); } + $inputContext = $context ?: (array_merge($this->getRawValues(), (array) $data)); + $this->validInputs = []; $this->invalidInputs = []; $valid = true; @@ -274,11 +276,6 @@ protected function validateInputs(array $inputs, $data = [], $context = null) continue; } - // Make sure we have a value (empty) for validation of context - if (!array_key_exists($name, $data)) { - $data[$name] = null; - } - // Validate an input filter if ($input instanceof InputFilterInterface) { if (!$input->isValid($context)) { @@ -292,8 +289,6 @@ protected function validateInputs(array $inputs, $data = [], $context = null) // Validate an input if ($input instanceof InputInterface) { - $inputContext = $context ?: $data; - if (!$input->isValid($inputContext)) { // Validation failure $this->invalidInputs[$name] = $input; diff --git a/test/BaseInputFilterTest.php b/test/BaseInputFilterTest.php index 25f259c0..836f111b 100644 --- a/test/BaseInputFilterTest.php +++ b/test/BaseInputFilterTest.php @@ -10,9 +10,11 @@ namespace ZendTest\InputFilter; use ArrayObject; +use PHPUnit_Framework_MockObject_MockObject as MockObject; use PHPUnit_Framework_TestCase as TestCase; use stdClass; use Zend\InputFilter\Input; +use Zend\InputFilter\InputInterface; use Zend\InputFilter\FileInput; use Zend\InputFilter\BaseInputFilter as InputFilter; use Zend\Filter; @@ -433,44 +435,103 @@ public function testCanGetValidationMessages() } } - /** - * Idea for this one is that one input may only need to be validated if another input is present. - * - * Commenting out for now, as validation context may make this irrelevant, and unsure what API to expose. - public function testCanConditionallyInvokeValidators() + /* + * Idea for this one is that validation may need to rely on context -- e.g., a "password confirmation" + * field may need to know what the original password entered was in order to compare. + */ + + public function contextProvider() { - $this->markTestIncomplete(); + $data = ['fooInput' => 'fooValue']; + $arrayAccessData = new ArrayObject(['fooInput' => 'fooValue']); + $expectedFromRawValues = ['fooInput' => 'fooValue']; + + return [ + // Description => [$data, $expectedContext] + 'by default get context from raw values (array)' => [$data, $expectedFromRawValues], + 'by default get context from raw values (ArrayAccess)' => [$arrayAccessData, $expectedFromRawValues], + ]; } - */ /** - * Idea for this one is that validation may need to rely on context -- e.g., a "password confirmation" - * field may need to know what the original password entered was in order to compare. + * @dataProvider contextProvider + * + * @param mixed $data + * @param mixed $expectedContext */ - public function testValidationCanUseContext() + public function testValidationUseContext($data, $expectedContext) { $filter = new InputFilter(); - $store = new stdClass; - $foo = new Input(); - $foo->getValidatorChain()->attach(new Validator\Callback(function ($value, $context) use ($store) { - $store->value = $value; - $store->context = $context; - return true; - })); + /** @var InputInterface|MockObject $input */ + $input = $this->getMock(InputInterface::class); + $input->expects($this->once()) + ->method('isValid') + ->with($expectedContext) + ->willReturn(true) + ; - $bar = new Input(); - $bar->getValidatorChain()->attach(new Validator\Digits()); + $filter->add($input, 'fooInput'); - $filter->add($foo, 'foo') - ->add($bar, 'bar'); + $filter->setData($data); + + $this->assertTrue($filter->isValid(), json_encode($filter->getMessages())); + } + + public function testValidationUseCustomContext() + { + $data = ['fooInput' => 'fooValue']; + $expectedContext = ['custom context']; + $filter = new InputFilter(); + + /** @var InputInterface|MockObject $input */ + $input = $this->getMock(InputInterface::class); + $input->expects($this->once()) + ->method('isValid') + ->with($expectedContext) + ->willReturn(true) + ; + + $filter->add($input, 'fooInput'); - $data = ['foo' => 'foo', 'bar' => 123]; $filter->setData($data); - $this->assertTrue($filter->isValid()); - $this->assertEquals('foo', $store->value); - $this->assertEquals($data, $store->context); + $this->assertTrue($filter->isValid($expectedContext), json_encode($filter->getMessages())); + } + + public function testContextIsTheSameWhenARequiredInputIsGivenAndOptionalInputIsMissing() + { + $data = [ + 'inputRequired' => 'inputRequiredValue', + ]; + $expectedContext = [ + 'inputRequired' => 'inputRequiredValue', + 'inputOptional' => null, + ]; + $filter = new InputFilter(); + + /** @var InputInterface|MockObject $inputRequired */ + $inputRequired = $this->getMock(InputInterface::class); + $inputRequired->expects($this->once()) + ->method('isValid') + ->with($expectedContext) + ->willReturn(true) + ; + + /** @var InputInterface|MockObject $inputOptional */ + $inputOptional = $this->getMock(InputInterface::class); + $inputOptional->expects($this->once()) + ->method('isValid') + ->with($expectedContext) + ->willReturn(true) + ; + + $filter->add($inputRequired, 'inputRequired'); + $filter->add($inputOptional, 'inputOptional'); + + $filter->setData($data); + + $this->assertTrue($filter->isValid(), json_encode($filter->getMessages())); } /** @@ -627,48 +688,6 @@ public function testValidationMarksInputInvalidWhenRequiredAndAllowEmptyFlagIsFa $this->assertFalse($filter->isValid()); } - public static function contextDataProvider() - { - return [ - ['', 'y', true], - ['', 'n', false], - ]; - } - - /** - * Idea here is that an empty field may or may not be valid based on - * context. - */ - /** - * @dataProvider contextDataProvider() - */ - // @codingStandardsIgnoreStart - public function testValidationMarksInputValidWhenAllowEmptyFlagIsTrueAndContinueIfEmptyIsTrueAndContextValidatesEmptyField($allowEmpty, $blankIsValid, $valid) - { - // @codingStandardsIgnoreEnd - $filter = new InputFilter(); - - $data = [ - 'allowEmpty' => $allowEmpty, - 'blankIsValid' => $blankIsValid, - ]; - - $allowEmpty = new Input(); - $allowEmpty->setAllowEmpty(true) - ->setContinueIfEmpty(true); - - $blankIsValid = new Input(); - $blankIsValid->getValidatorChain()->attach(new Validator\Callback(function ($value, $context) { - return ('y' === $value && empty($context['allowEmpty'])); - })); - - $filter->add($allowEmpty, 'allowEmpty') - ->add($blankIsValid, 'blankIsValid'); - $filter->setData($data); - - $this->assertSame($valid, $filter->isValid()); - } - public function testCanRetrieveRawValuesIndividuallyWithoutValidating() { if (!extension_loaded('intl')) {