Skip to content
This repository was archived by the owner on Jan 30, 2020. It is now read-only.

Commit 863dc48

Browse files
committed
Fix loop validation input context is mutable.
Fix #16
1 parent bded51b commit 863dc48

File tree

2 files changed

+93
-74
lines changed

2 files changed

+93
-74
lines changed

src/BaseInputFilter.php

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,8 @@ protected function validateInputs(array $inputs, $data = [], $context = null)
236236
$data = $this->getRawValues();
237237
}
238238

239+
$inputContext = $context ?: (array_merge($this->getRawValues(), (array) $data));
240+
239241
$this->validInputs = [];
240242
$this->invalidInputs = [];
241243
$valid = true;
@@ -289,14 +291,7 @@ protected function validateInputs(array $inputs, $data = [], $context = null)
289291
continue;
290292
}
291293

292-
// Make sure we have a value (empty) for validation of context
293-
if (!array_key_exists($name, $data)) {
294-
$data[$name] = null;
295-
}
296-
297294
// Validate an input
298-
$inputContext = $context ?: $data;
299-
300295
if (!$input->isValid($inputContext)) {
301296
// Validation failure
302297
$this->invalidInputs[$name] = $input;

test/BaseInputFilterTest.php

Lines changed: 91 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@
1010
namespace ZendTest\InputFilter;
1111

1212
use ArrayObject;
13+
use PHPUnit_Framework_MockObject_MockObject as MockObject;
1314
use PHPUnit_Framework_TestCase as TestCase;
1415
use stdClass;
1516
use Zend\InputFilter\Input;
17+
use Zend\InputFilter\InputInterface;
1618
use Zend\InputFilter\FileInput;
1719
use Zend\InputFilter\BaseInputFilter as InputFilter;
1820
use Zend\Filter;
@@ -433,44 +435,108 @@ public function testCanGetValidationMessages()
433435
}
434436
}
435437

436-
/**
437-
* Idea for this one is that one input may only need to be validated if another input is present.
438-
*
439-
* Commenting out for now, as validation context may make this irrelevant, and unsure what API to expose.
440-
public function testCanConditionallyInvokeValidators()
438+
/*
439+
* Idea for this one is that validation may need to rely on context -- e.g., a "password confirmation"
440+
* field may need to know what the original password entered was in order to compare.
441+
*/
442+
443+
public function contextProvider()
441444
{
442-
$this->markTestIncomplete();
445+
$data = ['fooInput' => 'fooValue'];
446+
$arrayAccessData = new ArrayObject(['fooInput' => 'fooValue']);
447+
$expectedFromData = ['fooInput' => 'fooValue'];
448+
449+
return [
450+
// Description => [$data, $customContext, $expectedContext]
451+
'by default get context from data (array)' => [$data, null, $expectedFromData],
452+
'by default get context from data (ArrayAccess)' => [$arrayAccessData, null, $expectedFromData],
453+
'use custom context' => [[], 'fooContext', 'fooContext'],
454+
];
443455
}
444-
*/
445456

446457
/**
447-
* Idea for this one is that validation may need to rely on context -- e.g., a "password confirmation"
448-
* field may need to know what the original password entered was in order to compare.
458+
* @dataProvider contextProvider
459+
*
460+
* @param mixed $data
461+
* @param mixed $customContext
462+
* @param mixed $expectedContext
449463
*/
450-
public function testValidationCanUseContext()
464+
public function testValidationContext($data, $customContext, $expectedContext)
451465
{
452466
$filter = new InputFilter();
453467

454-
$store = new stdClass;
455-
$foo = new Input();
456-
$foo->getValidatorChain()->attach(new Validator\Callback(function ($value, $context) use ($store) {
457-
$store->value = $value;
458-
$store->context = $context;
459-
return true;
460-
}));
468+
/** @var InputInterface|MockObject $input */
469+
$input = $this->getMock(InputInterface::class);
470+
$input->expects($this->once())
471+
->method('isValid')
472+
->with($expectedContext)
473+
->willReturn(true)
474+
;
461475

462-
$bar = new Input();
463-
$bar->getValidatorChain()->attach(new Validator\Digits());
476+
$filter->add($input, 'fooInput');
464477

465-
$filter->add($foo, 'foo')
466-
->add($bar, 'bar');
478+
$filter->setData($data);
479+
480+
$this->assertTrue($filter->isValid($customContext), json_encode($filter->getMessages()));
481+
}
482+
483+
public function testBuildValidationContextUsingInputGetRawValue()
484+
{
485+
$data = [];
486+
$expectedContext = ['fooInput' => 'fooRawValue'];
487+
$filter = new InputFilter();
488+
489+
/** @var InputInterface|MockObject $input */
490+
$input = $this->getMock(InputInterface::class);
491+
$input->method('getRawValue')
492+
->willReturn('fooRawValue')
493+
;
494+
$input->expects($this->once())
495+
->method('isValid')
496+
->with($expectedContext)
497+
->willReturn(true)
498+
;
499+
500+
$filter->add($input, 'fooInput');
467501

468-
$data = ['foo' => 'foo', 'bar' => 123];
469502
$filter->setData($data);
470503

471-
$this->assertTrue($filter->isValid());
472-
$this->assertEquals('foo', $store->value);
473-
$this->assertEquals($data, $store->context);
504+
$this->assertTrue($filter->isValid(), json_encode($filter->getMessages()));
505+
}
506+
507+
public function testContextIsTheSameWhenARequiredInputIsGivenAndOptionalInputIsMissing()
508+
{
509+
$data = [
510+
'inputRequired' => 'inputRequiredValue',
511+
];
512+
$expectedContext = [
513+
'inputRequired' => 'inputRequiredValue',
514+
'inputOptional' => null,
515+
];
516+
$filter = new InputFilter();
517+
518+
/** @var InputInterface|MockObject $inputRequired */
519+
$inputRequired = $this->getMock(InputInterface::class);
520+
$inputRequired->expects($this->once())
521+
->method('isValid')
522+
->with($expectedContext)
523+
->willReturn(true)
524+
;
525+
526+
/** @var InputInterface|MockObject $inputOptional */
527+
$inputOptional = $this->getMock(InputInterface::class);
528+
$inputOptional->expects($this->once())
529+
->method('isValid')
530+
->with($expectedContext)
531+
->willReturn(true)
532+
;
533+
534+
$filter->add($inputRequired, 'inputRequired');
535+
$filter->add($inputOptional, 'inputOptional');
536+
537+
$filter->setData($data);
538+
539+
$this->assertTrue($filter->isValid(), json_encode($filter->getMessages()));
474540
}
475541

476542
/**
@@ -627,48 +693,6 @@ public function testValidationMarksInputInvalidWhenRequiredAndAllowEmptyFlagIsFa
627693
$this->assertFalse($filter->isValid());
628694
}
629695

630-
public static function contextDataProvider()
631-
{
632-
return [
633-
['', 'y', true],
634-
['', 'n', false],
635-
];
636-
}
637-
638-
/**
639-
* Idea here is that an empty field may or may not be valid based on
640-
* context.
641-
*/
642-
/**
643-
* @dataProvider contextDataProvider()
644-
*/
645-
// @codingStandardsIgnoreStart
646-
public function testValidationMarksInputValidWhenAllowEmptyFlagIsTrueAndContinueIfEmptyIsTrueAndContextValidatesEmptyField($allowEmpty, $blankIsValid, $valid)
647-
{
648-
// @codingStandardsIgnoreEnd
649-
$filter = new InputFilter();
650-
651-
$data = [
652-
'allowEmpty' => $allowEmpty,
653-
'blankIsValid' => $blankIsValid,
654-
];
655-
656-
$allowEmpty = new Input();
657-
$allowEmpty->setAllowEmpty(true)
658-
->setContinueIfEmpty(true);
659-
660-
$blankIsValid = new Input();
661-
$blankIsValid->getValidatorChain()->attach(new Validator\Callback(function ($value, $context) {
662-
return ('y' === $value && empty($context['allowEmpty']));
663-
}));
664-
665-
$filter->add($allowEmpty, 'allowEmpty')
666-
->add($blankIsValid, 'blankIsValid');
667-
$filter->setData($data);
668-
669-
$this->assertSame($valid, $filter->isValid());
670-
}
671-
672696
public function testCanRetrieveRawValuesIndividuallyWithoutValidating()
673697
{
674698
if (!extension_loaded('intl')) {

0 commit comments

Comments
 (0)