-
-
Save bakura10/e35887640ef00942476c to your computer and use it in GitHub Desktop.
<?php | |
class MyInputFilter extends InputFilter | |
{ | |
public function __construct() | |
{ | |
$this->add(['name' => 'example', 'required' => false]); | |
$this->add(['name' => 'foo', 'required' => false]); | |
$this->add(['name' => 'bar', 'required' => false]); | |
} | |
} | |
$inputFilter = new MyInputFilter(); | |
$inputFilter->setData(['foo' => 'myValue', 'unknown' => 'lolzor']); | |
if ($inputFilter->isValid()) { | |
$values = $inputFilter->getValues(); | |
// Do you expect (1) ['example' => null, 'foo' => 'myValue', 'bar' => null] | |
// OR (2) : ['example' => null, 'foo' => 'myValue', 'bar' => null, 'unknown' => 'lolzor'] | |
// OR (3) : ['foo' => 'myValue'] | |
// OR (4) : ['foo' => 'myValue', 'unknown' => 'lolzor'] | |
} | |
// Currently, ZF2 does (1), I personally expect it to do (3), with getUnknown returning unknown fields |
<?php | |
public function dataProvider() | |
{ | |
return [ | |
// Validate none | |
[ | |
'validation_group' => InputCollection::VALIDATE_NONE, | |
'data' => [], | |
'result_raw_data' => [], | |
'result_filtered_data' => [], | |
'result_unknown_data' => [], | |
'is_valid' => true | |
], | |
// Validate all | |
[ | |
'validation_group' => InputCollection::VALIDATE_ALL, | |
'data' => ['email' => '[email protected]', 'first_name' => ' Michaël '], | |
'result_raw_data' => ['email' => '[email protected]', 'first_name' => ' Michaël '], | |
'result_filtered_data' => ['email' => '[email protected]', 'first_name' => 'Michaël'], | |
'result_unknown_data' => [], | |
'is_valid' => true | |
], | |
// Validate all with first_name not given | |
[ | |
'validation_group' => InputCollection::VALIDATE_ALL, | |
'data' => ['email' => '[email protected]'], | |
'result_raw_data' => ['email' => '[email protected]'], | |
'result_filtered_data' => ['email' => '[email protected]'], | |
'result_unknown_data' => [], | |
'is_valid' => true | |
], | |
// Validate all with unknown value | |
[ | |
'validation_group' => InputCollection::VALIDATE_ALL, | |
'data' => ['email' => '[email protected]', 'unknown' => 'value'], | |
'result_raw_data' => ['email' => '[email protected]'], | |
'result_filtered_data' => ['email' => '[email protected]'], | |
'result_unknown_data' => ['unknown' => 'value'], | |
'is_valid' => true | |
], | |
// Assert fails if required input is not present | |
[ | |
'validation_group' => InputCollection::VALIDATE_ALL, | |
'data' => ['first_name' => 'Michaël'], | |
'result_raw_data' => ['first_name' => 'Michaël'], | |
'result_filtered_data' => ['first_name' => 'Michaël'], | |
'result_unknown_data' => [], | |
'is_valid' => false | |
], | |
// Validate only one field (with validation group) without unknown fields | |
[ | |
'validation_group' => ['email'], | |
'data' => ['email' => '[email protected]', 'first_name' => 'Michaël'], | |
'result_raw_data' => ['email' => '[email protected]'], | |
'result_filtered_data' => ['email' => '[email protected]'], | |
'result_unknown_data' => [], | |
'is_valid' => true | |
], | |
// Validate only one field with a nested input collection | |
[ | |
'validation_group' => InputCollection::VALIDATE_ALL, | |
'data' => ['email' => '[email protected]', 'address' => ['city' => 'a']], | |
'result_raw_data' => ['email' => '[email protected]', 'address' => ['city' => 'a']], | |
'result_filtered_data' => ['email' => '[email protected]'], | |
'result_unknown_data' => [], | |
'is_valid' => false | |
], | |
// Validate two fields with a nested input collection | |
[ | |
'validation_group' => InputCollection::VALIDATE_ALL, | |
'data' => ['email' => '[email protected]', 'address' => ['city' => ' abc ']], | |
'result_raw_data' => ['email' => '[email protected]', 'address' => ['city' => ' abc ']], | |
'result_filtered_data' => ['email' => '[email protected]', 'address' => ['city' => 'abc']], | |
'result_unknown_data' => [], | |
'is_valid' => true | |
], | |
// Assert can have unknown field in nested inputs | |
[ | |
'validation_group' => InputCollection::VALIDATE_ALL, | |
'data' => ['email' => '[email protected]', 'address' => ['unknown' => 'abc']], | |
'result_raw_data' => ['email' => '[email protected]'], | |
'result_filtered_data' => ['email' => '[email protected]'], | |
'result_unknown_data' => ['address' => ['unknown' => 'abc']], | |
'is_valid' => true | |
], | |
// Assert that validation group applies to nested inputs | |
[ | |
'validation_group' => ['address' => ['city']], | |
'data' => ['email' => '[email protected]', 'address' => ['city' => ' abc ']], | |
'result_raw_data' => ['address' => ['city' => ' abc ']], | |
'result_filtered_data' => ['address' => ['city' => 'abc']], | |
'result_unknown_data' => [], | |
'is_valid' => true | |
] | |
]; | |
} | |
/** | |
* @dataProvider dataProvider | |
*/ | |
public function testBehaviour( | |
$validationGroup, | |
array $data, | |
array $resultRawData, | |
array $resultFilteredData, | |
array $resultUnknownData, | |
$isValid | |
) { | |
$inputCollection = new InputCollection(); | |
$inputCollection->setName('user'); | |
// We add one input that is required, one that is optional, and a nested input collection | |
$input1 = new Input(); | |
$input1->setName('email'); | |
$input1->setRequired(true); | |
$input2 = new Input(); | |
$input2->setName('first_name'); | |
$input2->getFilterChain()->attachByName(StringTrim::class); | |
$addressInputCollection = new InputCollection(); | |
$addressInputCollection->setName('address'); | |
$input3 = new Input(); | |
$input3->setName('city'); | |
$input3->getFilterChain()->attachByName(StringTrim::class); | |
$input3->getValidatorChain()->attachByName(StringLength::class, ['min' => 2]); | |
$addressInputCollection->addInput($input3); | |
$inputCollection->addInput($input1); | |
$inputCollection->addInput($input2); | |
$inputCollection->addInput($addressInputCollection); | |
$inputCollection->setValidationGroup($validationGroup); | |
$result = $inputCollection->runAgainst($data); | |
$this->assertEquals($isValid, $result->isValid()); | |
$this->assertEquals($resultRawData, $result->getRawData()); | |
$this->assertEquals($resultFilteredData, $result->getData()); | |
$this->assertEquals($resultUnknownData, $result->getUnknownData()); | |
} |
As long as a form's resulting values represents #1 then this wouldn't affect our business.
Personally, #1 makes sense (e.g. from a checkbox element perspective as already mentioned), but 2 and 4 don't make much sense at all. 3 is ok I guess, but it just feels like you're missing what you asked for (i.e. give me a validated XYZ, but I only get back a validated Y). Again, if Zend\Form still compensates for this logic, then I think it is acceptable either way.
After a couple minutes of re-thinking this, I would expect #3.
If you have non required fields in a form, the value is empty (null, default or whatever). So it doesn't matter if you pass a null value with $form->setValue()
or even nothing (no array key). With that in mind, the InputFilter is completly standalone. And therefore I suggest only filtering (and returning) the given data and not return some data you don't need or want.
The scenario is indeed #1 but when I started using input filters I expected #3. I guess #3 seems reasonable for most developers, but perhaps you could have a flag includeMissing
in the input filter which returns all fields which aren't set as well. Therefore, you can always use the feature as #1 if you expect it to be that way. By the way, #3 is also way easier if you directly hydrate the dataset with Zend\Stdlib\Hydrator
.
I would never use #2 or #4. If you need the data which did't came through the filter, you could use array_diff_assoc
with the request data and the filtered data.
I think we all agree that #2 and #4 are wrong.
Regarding the checkbox, thanks that's a very good input. Actually, I think that this should be handled by Zend\Form component to validate one value for each of its fields. But when using input filter on its own, I definitely think that #3 is more logical! Actually, I'm using input filters in REST context and I was always limited by current behavior of Input Filter. You cannot actually implement any PATCHing using it.
I'll therefore based my refactor on that.
@bakura10 regarding PATCH, with the Restful controller's I usually do this:
public function patch($id, $data)
{
$group = array_keys($data);
$filter = $this->getInputFilter();
$filter->setValidationGroup($group);
return $this->update($id, $data);
}
And therefore reusing the actual PUT logic from the controller. This has helped me a lot, with only two drawback: the input filter must be injected into the controller (a good thing) but then holds state between the patch()
and update()
call. But well, I didn't consider that much of a waste. Furthermore, it breaks when you send more fields than defined in the input filter. But I guess you could diff it against all input filter elements.
I have updated this file with a few unit tests that show my expected behavior. Does everyone agree with that?
@juriansluiman > That is a nice trick, actually.
The only thing I wonder about is why you want to return the "unknown" data. Is there a use case for it?
Also, for the scenario "// Validate only one field (with validation group) without unknown fields": you provide a field email
and first_name
. The validation group is set to email
and the unknown fields are null. I have no real scenario for the unkown fields, but I'd rather argue that if you provide such feature, it would also return first_name
in that case.
So: $result->getUnknownData()
will return all data which didn't came through the filter. This is including input filters which are excluded from the collection by its validation group.
first_name is not an unknown field because it is indeed set in the input filter. I have few cases for unknown data but I know that this is something that was asked by a lot of people in ZF and was added to ZF2.1 iirc, so I suppose there is a use case!
So: $result->getUnknownData() will return all data which didn't came through the filter. This is including input filters which are excluded from the collection by its validation group.
That makes sense. What you mean is that, once you set a validation group, everything that DO NOT belong to the validation group (even if it has been set as an input collection element) should be considered like an unknown field?
That makes sense. What you mean is that, once you set a validation group, everything that DO NOT belong to the validation group (even if it has been set as an input collection element) should be considered like an unknown field?
Correct :)
Correct :)
👍
I was thinking about checkboxes:
if a checkbox is not checked, it will not be sent within the request, so you need to know that the checkbox exists to unset it.
But if the form (with an attached input filter) would internally call the input filter like within your example, i think #3 would also be fine.
I'm not familiar with ZF2 internals, so perhaps this already happens.