diff --git a/psalm-baseline.xml b/psalm-baseline.xml index ff5e8e09..a5ca33e8 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -36,9 +36,9 @@ - - ! is_string($name) - + + $this->inputs + $messages diff --git a/src/BaseInputFilter.php b/src/BaseInputFilter.php index e2cde53a..a163eaa2 100644 --- a/src/BaseInputFilter.php +++ b/src/BaseInputFilter.php @@ -23,7 +23,6 @@ use function is_array; use function is_int; use function is_object; -use function is_string; use function sprintf; class BaseInputFilter implements @@ -499,7 +498,7 @@ public function getMessages() protected function validateValidationGroup(array $inputs) { foreach ($inputs as $name) { - if (! is_string($name) || ! array_key_exists($name, $this->inputs)) { + if (! array_key_exists($name, $this->inputs)) { throw new Exception\InvalidArgumentException(sprintf( 'setValidationGroup() expects a list of valid input names; "%s" was not found', (string) $name diff --git a/test/ValidationGroup/InputFilterCollectionsValidationGroupTest.php b/test/ValidationGroup/InputFilterCollectionsValidationGroupTest.php new file mode 100644 index 00000000..181d8094 --- /dev/null +++ b/test/ValidationGroup/InputFilterCollectionsValidationGroupTest.php @@ -0,0 +1,213 @@ +setIsRequired(true); + + $first = new Input('first'); + $first->setRequired(true); + $second = new Input('second'); + $second->setRequired(true); + + $nestedFilter = new InputFilter(); + $nestedFilter->add($first); + $nestedFilter->add($second); + $collection->setInputFilter($nestedFilter); + + $this->inputFilter = new InputFilter(); + $this->inputFilter->add($collection, 'stuff'); + } + + /** @return array */ + public function collectionCountProvider(): array + { + return [ + 'Collection Count: None' => [null], + 'Collection Count: One' => [1], + 'Collection Count: Two' => [2], + 'Collection Count: Three' => [3], + 'Collection Count: Four' => [4], + ]; + } + + private function setCollectionCount(?int $count): void + { + if ($count === null) { + return; + } + + $collection = $this->inputFilter->get('stuff'); + self::assertInstanceOf(CollectionInputFilter::class, $collection); + $collection->setCount($count); + } + + /** @dataProvider collectionCountProvider */ + public function testIncompleteDataFailsValidation(?int $count): void + { + $this->setCollectionCount($count); + $this->inputFilter->setData([ + 'stuff' => [ + ['first' => 'Foo'], + ], + ]); + self::assertFalse($this->inputFilter->isValid()); + } + + /** @dataProvider collectionCountProvider */ + public function testCompleteDataPassesValidation(?int $count): void + { + $this->setCollectionCount($count); + $this->inputFilter->setData([ + 'stuff' => [ + ['first' => 'Foo', 'second' => 'Bar'], + ['first' => 'Foo', 'second' => 'Bar'], + ['first' => 'Foo', 'second' => 'Bar'], + ['first' => 'Foo', 'second' => 'Bar'], + ], + ]); + + self::assertTrue($this->inputFilter->isValid()); + } + + /** @dataProvider collectionCountProvider */ + public function testValidationFailsForCollectionItemValidity(?int $count): void + { + $this->setCollectionCount($count); + $this->inputFilter->setData([ + 'stuff' => [ + ['first' => 'Foo', 'second' => 'Bar'], + ['first' => '', 'second' => 'Bar'], + ['first' => 'Foo', 'second' => ''], + ['first' => '', 'second' => ''], + ], + ]); + + self::assertFalse($this->inputFilter->isValid()); + } + + /** @dataProvider collectionCountProvider */ + public function testValidationGroupWithCollectionInputFilter(?int $count): void + { + $this->setCollectionCount($count); + $collection = $this->inputFilter->get('stuff'); + self::assertInstanceOf(CollectionInputFilter::class, $collection); + $collection->getInputFilter()->setValidationGroup('first'); + + $this->inputFilter->setData([ + 'stuff' => [ + ['first' => 'Foo'], + ['first' => 'Foo'], + ['first' => 'Foo'], + ['first' => 'Foo'], + ], + ]); + + self::assertTrue($this->inputFilter->isValid()); + } + + /** @dataProvider collectionCountProvider */ + public function testValidationGroupViaCollection(?int $count): void + { + $this->setCollectionCount($count); + $collection = $this->inputFilter->get('stuff'); + self::assertInstanceOf(CollectionInputFilter::class, $collection); + /** @psalm-suppress InvalidArgument */ + $collection->setValidationGroup([ + 0 => 'first', + 1 => 'second', + 2 => 'first', + 3 => 'first', + ]); + + $this->inputFilter->setData([ + 'stuff' => [ + ['first' => 'Foo'], + ['second' => 'Foo'], + ['first' => 'Foo'], + ['first' => 'Foo'], + ], + ]); + + self::assertTrue($this->inputFilter->isValid()); + } + + /** + * This test documents existing behaviour - the validation group must be set for elements 0 through 3 + * + * @dataProvider collectionCountProvider + */ + public function testValidationGroupViaCollectionMustSpecifyAllKeys(?int $count): void + { + $this->setCollectionCount($count); + $collection = $this->inputFilter->get('stuff'); + self::assertInstanceOf(CollectionInputFilter::class, $collection); + + /** @psalm-suppress InvalidArgument */ + $collection->setValidationGroup([ + 0 => 'first', + ]); + + $this->inputFilter->setData([ + 'stuff' => [ + ['first' => 'Foo'], + ['first' => 'Foo'], + ['first' => 'Foo'], + ['first' => 'Foo'], + ], + ]); + + if (PHP_VERSION_ID >= 80000) { + $this->expectWarning(); + $this->expectWarningMessage('Undefined array key 1'); + } else { + $this->expectNotice(); + $this->expectNoticeMessage('Undefined offset: 1'); + } + + $this->inputFilter->isValid(); + } + + /** @dataProvider collectionCountProvider */ + public function testValidationGroupViaTopLevelInputFilter(?int $count): void + { + $this->setCollectionCount($count); + /** @psalm-suppress InvalidArgument */ + $this->inputFilter->setValidationGroup([ + 'stuff' => [ + 0 => 'first', + 1 => 'second', + 2 => 'first', + 3 => 'first', + ], + ]); + + $this->inputFilter->setData([ + 'stuff' => [ + ['first' => 'Foo'], + ['second' => 'Foo'], + ['first' => 'Foo'], + ['first' => 'Foo'], + ], + ]); + + self::assertTrue($this->inputFilter->isValid()); + } +} diff --git a/test/ValidationGroup/InputFilterStringValidationGroupTest.php b/test/ValidationGroup/InputFilterStringValidationGroupTest.php new file mode 100644 index 00000000..406ce14d --- /dev/null +++ b/test/ValidationGroup/InputFilterStringValidationGroupTest.php @@ -0,0 +1,78 @@ +setRequired(true); + $first->getValidatorChain()->attach(new StringLength(['min' => 5])); + $second = new Input('second'); + $second->setRequired(true); + $second->getValidatorChain()->attach(new StringLength(['min' => 5])); + $third = new Input('third'); + $third->setRequired(true); + $third->getValidatorChain()->attach(new StringLength(['min' => 5])); + + $this->inputFilter = new InputFilter(); + $this->inputFilter->add($first); + $this->inputFilter->add($second); + $this->inputFilter->add($third); + } + + public function testValidationFailsForIncompleteInput(): void + { + $this->inputFilter->setData(['first' => 'Freddy']); + self::assertFalse($this->inputFilter->isValid()); + } + + public function testValidationSucceedsForCompleteInput(): void + { + $this->inputFilter->setData(['first' => 'Freddy', 'second' => 'Fruit Bat', 'third' => 'Muppet']); + self::assertTrue($this->inputFilter->isValid()); + } + + public function testValidationSucceedsForIncompleteInputWhenValidationGroupIsProvided(): void + { + $this->inputFilter->setValidationGroup('first'); + $this->inputFilter->setData(['first' => 'Freddy']); + + self::assertTrue($this->inputFilter->isValid()); + } + + public function testThatValidationGroupIsVariadic(): void + { + $this->inputFilter->setValidationGroup('first', 'second'); + $this->inputFilter->setData(['first' => 'Freddy', 'second' => 'Fruit Bat']); + + self::assertTrue($this->inputFilter->isValid()); + } + + public function testThatValidationGroupAcceptsListOfInputNames(): void + { + $this->inputFilter->setValidationGroup(['first', 'second']); + $this->inputFilter->setData(['first' => 'Freddy', 'second' => 'Fruit Bat']); + + self::assertTrue($this->inputFilter->isValid()); + } + + public function testValidationGroupWithUnknownInput(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('"doughnuts" was not found'); + $this->inputFilter->setValidationGroup(['doughnuts']); + } +}