diff --git a/CHANGELOG.md b/CHANGELOG.md index 9860a368..e8aa54e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,17 @@ All notable changes to this project will be documented in this file, in reverse ### Added -- Nothing. +- [#170](https://github.com/zendframework/zend-inputfilter/pull/170) adds the ability to set a "required" message on a `CollectionInputFilter`. + By default, such instances will lazy-load a `NotEmpty` validator, and use its + messages to report that the collection was empty if it is marked as required. + If you wish to set a different message, you have two options: + + - provide a custom `NotEmpty` validator via the new method + `setNotEmptyValidator()`. + + - if using a factory, provide the key `required_message` as a sibling to + `required`, containing the custom message. This will replace the typical + `IS_EMPTY` message. ### Changed diff --git a/src/CollectionInputFilter.php b/src/CollectionInputFilter.php index 33b28c84..005525fc 100644 --- a/src/CollectionInputFilter.php +++ b/src/CollectionInputFilter.php @@ -10,6 +10,7 @@ namespace Zend\InputFilter; use Traversable; +use Zend\Validator\NotEmpty; class CollectionInputFilter extends InputFilter { @@ -43,6 +44,11 @@ class CollectionInputFilter extends InputFilter */ protected $inputFilter; + /** + * @var NotEmpty + */ + protected $notEmptyValidator; + /** * Set the input filter to use when looping the data * @@ -164,6 +170,39 @@ public function setData($data) return $this; } + /** + * Retrieve the NotEmpty validator to use for failed "required" validations. + * + * This validator will be used to produce a validation failure message in + * cases where the collection is empty but required. + * + * @return NotEmpty + */ + public function getNotEmptyValidator() + { + if ($this->notEmptyValidator === null) { + $this->notEmptyValidator = new NotEmpty(); + } + + return $this->notEmptyValidator; + } + + /** + * Set the NotEmpty validator to use for failed "required" validations. + * + * This validator will be used to produce a validation failure message in + * cases where the collection is empty but required. + * + * @param NotEmpty $notEmptyValidator + * @return $this + */ + public function setNotEmptyValidator(NotEmpty $notEmptyValidator) + { + $this->notEmptyValidator = $notEmptyValidator; + + return $this; + } + /** * {@inheritdoc} * @param mixed $context Ignored, but present to retain signature compatibility. @@ -174,10 +213,9 @@ public function isValid($context = null) $inputFilter = $this->getInputFilter(); $valid = true; - if ($this->getCount() < 1) { - if ($this->isRequired) { - $valid = false; - } + if ($this->getCount() < 1 && $this->isRequired) { + $this->collectionMessages[] = $this->prepareRequiredValidationFailureMessage(); + $valid = false; } if (count($this->data) < $this->getCount()) { @@ -295,4 +333,21 @@ public function getUnknown() return $unknownInputs; } + + /** + * @return array + */ + protected function prepareRequiredValidationFailureMessage() + { + $notEmptyValidator = $this->getNotEmptyValidator(); + $templates = $notEmptyValidator->getOption('messageTemplates'); + $message = $templates[NotEmpty::IS_EMPTY]; + $translator = $notEmptyValidator->getTranslator(); + + return [ + NotEmpty::IS_EMPTY => $translator + ? $translator->translate($message, $notEmptyValidator->getTranslatorTextDomain()) + : $message, + ]; + } } diff --git a/src/Factory.php b/src/Factory.php index 01f299e4..53459320 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -319,6 +319,9 @@ public function createInputFilter($inputFilterSpecification) if (isset($inputFilterSpecification['required'])) { $inputFilter->setIsRequired($inputFilterSpecification['required']); } + if (isset($inputFilterSpecification['required_message'])) { + $inputFilter->getNotEmptyValidator()->setMessage($inputFilterSpecification['required_message']); + } return $inputFilter; } diff --git a/test/CollectionInputFilterTest.php b/test/CollectionInputFilterTest.php index 72eace01..e09a6e12 100644 --- a/test/CollectionInputFilterTest.php +++ b/test/CollectionInputFilterTest.php @@ -171,7 +171,7 @@ public function dataVsValidProvider() 'Required: F, Count: N, Valid: T' => [!$isRequired, null, $colRaw, $validIF , $colRaw, $colFiltered, true , []], 'Required: F, Count: N, Valid: F' => [!$isRequired, null, $colRaw, $invalidIF, $colRaw, $colFiltered, false, $colMessages], 'Required: F, Count: +1, Valid: F' => [!$isRequired, 2, $colRaw, $invalidIF, $colRaw, $colFiltered, false, $colMessages], - 'Required: T, Data: [], Valid: X' => [ $isRequired, null, [] , $noValidIF, [] , [] , false, []], + 'Required: T, Data: [], Valid: X' => [ $isRequired, null, [] , $noValidIF, [] , [] , false, [['isEmpty' => 'Value is required and can\'t be empty']]], 'Required: F, Data: [], Valid: X' => [!$isRequired, null, [] , $noValidIF, [] , [] , true , []], ]; // @codingStandardsIgnoreEnd @@ -735,4 +735,34 @@ public function testDuplicatedErrorMessages() ], ], $inputFilter->getMessages()); } + + public function testLazyLoadsANotEmptyValidatorWhenNoneProvided() + { + $this->assertInstanceOf(NotEmpty::class, $this->inputFilter->getNotEmptyValidator()); + } + + public function testAllowsComposingANotEmptyValidator() + { + $notEmptyValidator = new NotEmpty(); + $this->inputFilter->setNotEmptyValidator($notEmptyValidator); + $this->assertSame($notEmptyValidator, $this->inputFilter->getNotEmptyValidator()); + } + + public function testUsesMessageFromComposedNotEmptyValidatorWhenRequiredButCollectionIsEmpty() + { + $message = 'this is the validation message'; + $notEmptyValidator = new NotEmpty(); + $notEmptyValidator->setMessage($message); + + $this->inputFilter->setIsRequired(true); + $this->inputFilter->setNotEmptyValidator($notEmptyValidator); + + $this->inputFilter->setData([]); + + $this->assertFalse($this->inputFilter->isValid()); + + $this->assertEquals([ + [NotEmpty::IS_EMPTY => $message], + ], $this->inputFilter->getMessages()); + } } diff --git a/test/FactoryTest.php b/test/FactoryTest.php index 228cd0f8..610b0592 100644 --- a/test/FactoryTest.php +++ b/test/FactoryTest.php @@ -1038,6 +1038,28 @@ public function testWhenCreateInputPullsInputFromThePluginManagerItMustNotOverwr $this->assertSame($input->reveal(), $factory->createInput($spec)); } + public function testFactoryCanCreateCollectionInputFilterWithRequiredMessage() + { + $factory = $this->createDefaultFactory(); + $message = 'this is the validation message'; + + /** @var CollectionInputFilter $inputFilter */ + $inputFilter = $factory->createInputFilter([ + 'type' => CollectionInputFilter::class, + 'required' => true, + 'required_message' => $message, + 'inputfilter' => new InputFilter(), + 'count' => 3, + ]); + + $this->assertInstanceOf(CollectionInputFilter::class, $inputFilter); + + $notEmptyValidator = $inputFilter->getNotEmptyValidator(); + $messageTemplates = $notEmptyValidator->getMessageTemplates(); + $this->assertArrayHasKey(Validator\NotEmpty::IS_EMPTY, $messageTemplates); + $this->assertSame($message, $messageTemplates[Validator\NotEmpty::IS_EMPTY]); + } + /** * @return Factory */