Skip to content

Instantly share code, notes, and snippets.

@texdc
Last active December 19, 2015 08:59
Show Gist options
  • Save texdc/5929229 to your computer and use it in GitHub Desktop.
Save texdc/5929229 to your computer and use it in GitHub Desktop.
Notes on refactoring ZF2's "InputFilter"

A few notes from my personal gripes about what InputFilter is and does.

  1. InputFilter is a terrible name as the class mixes sanitation and validation. Rename to something far more appropriate (e.g. InputManager?).
  2. Data should only be processed, not stored - remove setData()

SoC Article

For example, if both raw and sanitized data are required:

$sanitizedData    = $this->inputManager->sanitize($data->toArray());
$validationResult = $this->inputManager->validate($sanitizedData);

if ($validationResult->isValid()) {
    // hooray!
} else {
    $messages = $validationResult->getMessages();
}

Also, if all we need is the result:

$validationResult = $this->inputManager->sanitizeAndValidate($data->toArray());

if ($validationResult->isValid()) {
    // hooray!
} else {
    $messages = $validationResult->getMessages();
}
<?php
namespace Zend\Input;
use SplPriorityQueue;
class CollectionInput implements InputInterface, CollectionInterface
{
/**
* @var SplPriorityQueue
*/
private $inputs;
private $name;
public function __construct($aName, array $anInputList = [])
{
$this->name = (string) $aName;
$this->inputs = new SplPriorityQueue();
$this->insertAll($anInputList);
}
public function sanitize($aValue)
{
foreach ($this->inputs as $input) {
$name = $input->getName();
if (isset($aValue[$name])) {
$aValue[$name] = $input->sanitize($aValue[$name]);
}
}
return $aValue;
}
public function validate($aValue)
{
$results = [];
foreach ($this->inputs as $input) {
$name = $input->getName();
if (isset($aValue[$name])) {
$results[$name] = $input->validate($aValue[$name]);
}
}
return $results;
}
public function sanitizeAndValidate($aValue)
{
return $this->validate($this->sanitize($aValue));
}
public function insert(InputInterface $anInput, $aPriority = 0)
{
$this->inputs->insert($anInput, $aPriority);
}
public function insertAll($anInputList)
{
foreach ($anInputList as $input) {
$this->insert($input);
}
}
// ...
public function getIterator()
{
return $this->inputs;
}
}
<?php
namespace Zend\Input;
interface CollectionInterface extends \InteratorAggregate
{
public function insert(InputInterface $anInput);
public function insertAll($inputs);
public function remove(InputInterface $anInput);
public function removeAll($inputs);
public function contains(InputInterface $anInput);
public function retrieve($anInputName);
}
<?php
namespace Zend\Input;
use Zend\Sanitizer\SanitizerInterface;
use Zend\Validator\ValidatorInterface;
/**
* An implementation of the {@link Zend\Input\InputInterface}
*
* @package Zend\Input
*/
class Input implements InputInterface
{
/**
* @var string
*/
protected $name;
/**
* @var SanitizerInterface
*/
protected $sanitizer;
/**
* @var ValidatorInterface
*/
protected $validator;
/**
* @param string $aName
* @param SanitizerInterface $aSanitizer
* @param ValidatorInterface $aValidator
*/
public function __construct($aName, SanitizerInterface $aSanitizer, ValidatorInterface $aValidator)
{
$this->name = (string) $aName;
$this->sanitizer = $aSanitizer;
$this->validator = $aValidator;
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* @param mixed $aValue
* @return mixed
*/
public function sanitize($aValue)
{
return $this->sanitizer->sanitize($aValue);
}
/**
* @param mixed $aValue
* @return \Zend\Validator\ResultInterface
*/
public function validate($aValue)
{
return $this->validator->validate($aValue);
}
/**
* @param mixed $aValue
* @return \Zend\Validator\ResultInterface
*/
public function sanitizeAndValidate($aValue)
{
return $this->validate($this->sanitize($aValue));
}
}
<?php
namespace Zend\Input;
interface InputInterface
{
public function getName();
public function sanitize($aValue);
public function validate($aValue);
public function sanitizeAndValidate($aValue);
}
<?php
namespace Zend\Input;
/**
* An implementation of the {@link Zend\Input\ManagerInterface}
*
* @package Zend\Input
*/
class Manager implements ManagerInterface
{
/**
* @param ContainerInterface $anInputList
* @param array|\Traversable $aValidationGroup
*/
public function __construct(CollectionInterface $anInputList, $aValidationGroup)
{
foreach ($anInputList as $name => $input) {
if (!in_array($name, $aValidationGroup)) {
$anInputList->remove($input);
}
}
$this->insertAll($anInputList);
}
/**
* @param InputInterface[] $anInputList
* @param string[] $aValidationGroup
* @return self
*/
public static function withInputs($anInputList, $aValidationGroup)
{
return new static(new Collection($anInputList), $aValidationGroup);
}
/**
* @param array
* @return array
*/
public function sanitize(array $data)
{
return $this->inputs->sanitize($data);
}
/**
* @param array $data
* @return \Zend\Validator\ResultInterface[]
*/
public function validate(array $data)
{
return $this->inputs->validate($data);
}
/**
* @param array
* @return \Zend\Validator\ResultInterface[]
*/
public function sanitizeAndValidate(array $data)
{
return $this->inputs->sanitizeAndValidate($data);
}
}
<?php
namespace Zend\Input;
interface ManagerInterface
{
public function sanitize(array $data);
public function validate(array $data);
public function sanitizeAndValidate(array $data);
}
<?php
namespace Zend\Validator;
interface ResultInterface extends \Serializable, \JsonSerializable
{
/**
* Was the validation successful?
*
* @return bool
*/
public function isValid();
/**
* List any messages
*
* @return string[]
*/
public function getMessages();
}
@bakura10
Copy link

bakura10 commented Aug 9, 2013

I've already renamed the InputFilter to InputCollection (you can check it here: zendframework/zendframework#4772 (comment)).

Regarding Input stateless, my question is: how can you retrieve the raw value and filtered value if you never store it?

In my refactor, it uses RecursiveIteratorIterator: https://github.com/bakura10/zf2/blob/902416598924a1749096d2026e6e8c02e01c3c4e/library/Zend/InputFilter/InputCollection.php#L317-L336

What we get is only Input, not input collection, therefore the logic of required/not required is handled by the input, not input collection.

Regarding ValidationResult, I'll try to integrate it.

@bakura10
Copy link

bakura10 commented Aug 9, 2013

The ValidationResult thing is really not easy at all, because of the recursiveiteratoriterator thing….

@bakura10
Copy link

bakura10 commented Aug 9, 2013

@texdc: what happen in case of InputCollection embedded another InputCollection, regarding the ValidationResult ? The problem is that with this new implementation using RecursiveIteratorIterator, what I get are only Input (only leaves). It makes the validation result stuff extremely complicated, and I would need to revert to the old way (ZF2) which is at least 10 times slower.

EDIT : what I don't understand too is that from your code, the "Input" class returns too a ValidationResult. So what a InputCollection / InputFilter would return ? An array of validation result? And what about an input collection containing input collection containing input ? Would it returns nested validation results too? I can't get it.

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