Skip to content

Instantly share code, notes, and snippets.

@bakura10
Last active August 29, 2015 14:01
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bakura10/e35887640ef00942476c to your computer and use it in GitHub Desktop.
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' => 'test@example.com', 'first_name' => ' Michaël '],
'result_raw_data' => ['email' => 'test@example.com', 'first_name' => ' Michaël '],
'result_filtered_data' => ['email' => 'test@example.com', 'first_name' => 'Michaël'],
'result_unknown_data' => [],
'is_valid' => true
],
// Validate all with first_name not given
[
'validation_group' => InputCollection::VALIDATE_ALL,
'data' => ['email' => 'test@example.com'],
'result_raw_data' => ['email' => 'test@example.com'],
'result_filtered_data' => ['email' => 'test@example.com'],
'result_unknown_data' => [],
'is_valid' => true
],
// Validate all with unknown value
[
'validation_group' => InputCollection::VALIDATE_ALL,
'data' => ['email' => 'test@example.com', 'unknown' => 'value'],
'result_raw_data' => ['email' => 'test@example.com'],
'result_filtered_data' => ['email' => 'test@example.com'],
'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' => 'test@example.com', 'first_name' => 'Michaël'],
'result_raw_data' => ['email' => 'test@example.com'],
'result_filtered_data' => ['email' => 'test@example.com'],
'result_unknown_data' => [],
'is_valid' => true
],
// Validate only one field with a nested input collection
[
'validation_group' => InputCollection::VALIDATE_ALL,
'data' => ['email' => 'test@example.com', 'address' => ['city' => 'a']],
'result_raw_data' => ['email' => 'test@example.com', 'address' => ['city' => 'a']],
'result_filtered_data' => ['email' => 'test@example.com'],
'result_unknown_data' => [],
'is_valid' => false
],
// Validate two fields with a nested input collection
[
'validation_group' => InputCollection::VALIDATE_ALL,
'data' => ['email' => 'test@example.com', 'address' => ['city' => ' abc ']],
'result_raw_data' => ['email' => 'test@example.com', 'address' => ['city' => ' abc ']],
'result_filtered_data' => ['email' => 'test@example.com', 'address' => ['city' => 'abc']],
'result_unknown_data' => [],
'is_valid' => true
],
// Assert can have unknown field in nested inputs
[
'validation_group' => InputCollection::VALIDATE_ALL,
'data' => ['email' => 'test@example.com', 'address' => ['unknown' => 'abc']],
'result_raw_data' => ['email' => 'test@example.com'],
'result_filtered_data' => ['email' => 'test@example.com'],
'result_unknown_data' => ['address' => ['unknown' => 'abc']],
'is_valid' => true
],
// Assert that validation group applies to nested inputs
[
'validation_group' => ['address' => ['city']],
'data' => ['email' => 'test@example.com', '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());
}
@maglnet
Copy link

maglnet commented May 20, 2014

Hi

i do not have an expectation on this but my thoughts about the 4 cases:

  1. pro: if you would like to use $values to unset some values, e.g. when writing to a database table, you would like to set NULL to the columns example and bar
  2. contra: a filter should remove unknown input, so unknown should be removed
  3. contra: it could be hard to decide if you need to set bar to NULL or leave it as it is (see 1)
  4. contra: combination of 2 and 3 :)

So my favorite behavior would be #1, but you wouldn't ask if there was no use case for #3 :)

Matthias

@bakura10
Copy link
Author

My idea is that if you want to unset values, you need to explicitly give those values. With #3:

$inputFilter->setData(['example' => null, 'foo' => 'myValue', 'unknown' => 'lolzor']);

You will receive:

['foo' => 'myValue', 'example' => null]

The issue I'm experiencing with current behavior (#1), is that it will return you values for ALL the inputs you created, with null for non given input. This is very problematic for me and I'm just trying to understand if I'm the only one.

@maglnet
Copy link

maglnet commented May 20, 2014

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.

@asgrim
Copy link

asgrim commented May 21, 2014

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.

@DennisDobslaf
Copy link

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.

@juriansluiman
Copy link

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.

@bakura10
Copy link
Author

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.

@juriansluiman
Copy link

@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.

@bakura10
Copy link
Author

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.

@juriansluiman
Copy link

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.

@bakura10
Copy link
Author

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?

@juriansluiman
Copy link

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 :)

@texdc
Copy link

texdc commented May 27, 2014

Correct :)

👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment