Using PHP filter functions like filter_var_array()

2019-07-16 11:19发布

I have been toying with PHP filter library. I liked it but I am unable to perform a simple filter function. I essentially want to invalidate those values in my input array that are strings and which are longer than certain value. Is there a way to do this like,

$data = array('input_string_array' => array('aaa', 'abaa', 'abaca'));
$args = array(
    'component'    => array('filter'    => FILTER_DEFAULT,
                            'flags'     => FILTER_REQUIRE_ARRAY, 
                            'options'   => array('min_length' => 1, 'max_length' => 10)
                           )
);

var_dump(filter_var_array($data, $args));

I tried this and its giving me error. because presumably there is no min_length/max_length option available. But then how to implement this? Also is there a place where it is mentioned about all such options like max_range, min_range, regexp.

Also I had another doubt, in filters, in FILTER_CALLBACK filter. I wanted to know if there is a way to pass a parameter other than the data to the called function? something like this,

echo filter_var($string, FILTER_CALLBACK, array("options"=> array("lengthChecker", "5")));

Thanks a lot for the help.

3条回答
干净又极端
2楼-- · 2019-07-16 11:38

Unless there's a better, more direct filter+option you can use FILTER_VALIDATE_REGEXP

$data = array('input_string_array' => array('', 'aaa', 'abaa', 'abaca'));
$args = array(
  'input_string_array' => array(
    'filter' => FILTER_VALIDATE_REGEXP,
    'flags'     => FILTER_REQUIRE_ARRAY|FILTER_NULL_ON_FAILURE,
    'options'   => array('regexp'=>'/^.{1,3}$/')
  )
);
var_dump(filter_var_array($data, $args));

prints

array(1) {
  ["input_string_array"]=>
  array(4) {
    [0]=>
    NULL
    [1]=>
    string(3) "aaa"
    [2]=>
    NULL
    [3]=>
    NULL
  }
}

To get rid of the NULL elements you can use e.g. array_filter().

查看更多
我欲成王,谁敢阻挡
3楼-- · 2019-07-16 11:50

I believe the original problem was that the field in the data array was not named the same as the one in the filter array.

查看更多
等我变得足够好
4楼-- · 2019-07-16 12:02

Introduction

You will need at least PHP 5.4+ for this answer to work (due, at minimum, to the array syntax).

If the goal is to do something like this:

$filteredArray = filter_var_array($arrayToFilter, $filterInstuctionsArray);

... and use the return results of basic PHP functions (along with decision logic) to check the length (mb_strlen(), strlen()) of a string, the key is to have an excellent filter instructions Array`. To pass arguments to your callback function (for reuse, encapsulation, and generalization purposes, etc...), I believe there are at least two scenarios.

Scenarios

A) The class/object forms.

'options' => [$this, 'callbackMethod']

'options' => [$this->object, 'callbackMethod']

'options' => [$object, 'callbackMethod']

B)The procedural form.

'options' => 'callbackFunction'

Solutions

1) Replace $this or $object in scenario A with a new instance of an object right then and there, passing in any arguments its constructor. This seems unlikely as it smacks of tightly coupled code.

Alternatively, one could inject a pre-populated object into a Validator class of some sort and run filter_input_array() from within that Validator class. Thus, passing arguments to the callbackMethod would not be necessary.

Or

2) Replace 'callbackFunction' in scenario B with a PHP anonymous function and implement the use syntax to pass arguments / constraints from the Validator class to it.

function ($value) use ($min, $max) {
    $length = mb_strlen($value, 'UTF-8');
    return ($length >= $min) && ($length <= $max);
}

Attempt #1: Filter Instructions Array with Anonymous Functions

Here is a sequential example for working with scalar values.

$filterInstructionsArray[
                            'fName' = ['filter'  => FILTER_CALLBACK,
                                       'flags'   => FILTER_REQUIRE_SCALAR,
                                       'options' => function ($value) use ($min, $max) {
                                                          $length = mb_strlen($value, 'UTF-8');
                                                          return ($length >= $min) && ($length <= $max);}],
                            'lName' = ['filter'  => FILTER_CALLBACK,
                                       'flags'   => FILTER_REQUIRE_SCALAR,
                                       'options' => function ($value) use ($min, $max) {
                                                          $length = mb_strlen($value, 'UTF-8');
                                                          return ($length >= $min) && ($length <= $max);}]
                         ];

Of course, this violates DRY principles. So, you might define the anonymous function as a property of a Validator class (or just assign it to a variable), which makes it a named instance of a Closure object.

private $checkNameLength = function ($value) use ($this->nameMin, $this->nameMax) {
        $length = mb_strlen($value, 'UTF-8');
        return ($length >= $this->nameMin) && ($length <= $this->nameMax);
    };

Or

$checkNameLength = function ($value) use ($min, $max) {
        $length = mb_strlen($value, 'UTF-8');
        return ($length >= $min) && ($length <= $max);
    };

So, my hope is that one of two things will work.

Attempt #2: Filter Instructions Array with Named Anonymous Functions

$filterInstructionsArray[
                            'fName' = ['filter'  => FILTER_CALLBACK,
                                       'flags'   => FILTER_REQUIRE_SCALAR,
                                       'options' => [$this, 'checkNameLength']]
                         ];

Or

$filterInstructionsArray[
                            'fName' = ['filter'  => FILTER_CALLBACK,
                                       'flags'   => FILTER_REQUIRE_SCALAR,
                                       'options' => 'checkNameLength']
                         ];

Potential Complications

The Closure instances $this->checkNameLength and $checkNameLength are objects, not normal "methods / functions". But, if PHP does not complain, I will not split hairs. I suppose one could try defining a function inside of an anonymous function.

$checkName = function ($value) use ($min, $max) {

        function lengthTest($string, $min, $max){
            $length = mb_strlen($string, 'UTF-8');
            return ($length >= $min) && ($length <= $max);
        }
    };

Then, your filter instruction array would look like this.

$filterInstructionsArray[
                            'fName' = ['filter'  => FILTER_CALLBACK,
                                       'flags'   => FILTER_REQUIRE_SCALAR,
                                       'options' => [$checkName, 'lengthTest']]
                         ];

Or, probably this ...

$filterInstructionsArray[
                            'fName' = ['filter'  => FILTER_CALLBACK,
                                       'flags'   => FILTER_REQUIRE_SCALAR,
                                       'options' => 'lengthTest']
                         ];

Conclusion

There is a better way to create a generic string length checker than using PHP's filter_var_array() and anonymous functions. Using 'filter' => FILTER_VALIDATE_REGEXP can make it easy to check the length of a single string, but it is not a substitute for adhering to DRY principles. You will end up having many statements in your filter instructions array like this, just to deal with string lengths in an array of input.

    //If you just want to test only the lengths first, this is
    //very inefficient. Assume each $regex is only checking string length.

    $filterLengthInstructions = [
                'fName'    => ['filter'  => FILTER_VALIDATE_REGEXP,
                               'flags'   => FILTER_REQUIRE_SCALAR,
                               'options' => ['regexp' => $fNameRegex]],
                'lName'    => ['filter'  => FILTER_VALIDATE_REGEXP,
                               'flags'   => FILTER_REQUIRE_SCALAR,
                               'options' => ['regexp' => $lNameRegex]],
                'company'  => ['filter'  => FILTER_VALIDATE_REGEXP,
                               'flags'   => FILTER_REQUIRE_SCALAR,
                               'options' => ['regexp' => $comanyRegex]],
                'address1' => ['filter'  => FILTER_VALIDATE_REGEXP,
                               'flags'   => FILTER_REQUIRE_SCALAR,
                               'options' => ['regexp' => $address1Regex]],
                'address2' => ['filter'  => FILTER_VALIDATE_REGEXP,
                               'flags'   => FILTER_REQUIRE_SCALAR,
                               'options' => ['regexp' => $address2Regex]],
                'zip'      => ['filter'  => FILTER_VALIDATE_REGEXP,
                               'flags'   => FILTER_REQUIRE_SCALAR,
                               'options' => ['regexp' => $zipRegex]],
                'website'  => ['filter'  => FILTER_VALIDATE_REGEXP,
                               'flags'   => FILTER_REQUIRE_SCALAR,
                               'options' => ['regexp' => $urlRegex]],
                'email'  => ['filter'  => FILTER_VALIDATE_REGEXP,
                               'flags'   => FILTER_REQUIRE_SCALAR,
                               'options' => ['regexp' => $emailRegex]]
     ];

You can do this if you are trying to make a pure filter_input_array() Validator class, or routine. But, you will have to make multiple passes of filter_var_array(), with multiple filter instruction arrays (because the length of a string is not the only thing that makes it valid, or invalid).

For website and email, you will want to take advantage of 'filter' => FILTER_VALIDATE_URL and 'filter' => FILTER_VALIDATE_EMAIL. At other times, you are going to want to take advantage of 'filter' => FILTER_VALIDATE_IP. Also, especially in the case of email, you might want to restrict valid email addresses to a subset of the official RFC with a regular expression. To keep things pure, you would not put that regular expression in the $filterLengthInstructions.

The fundamental reasons for wanting to change the min_length and max_length being able to write the business logic once and use it everywhere.

At minimum, my advice is to create an abstract Validator super class and define one (1) method that test string lengths.

Class Validator

Now, you can divide this method up, or delegate this task to an injected StringTester object's method.

Class Validator with StringTester

Whatever, but by defining concrete child classes of Validator all you have to do is define your test parameters in an array, create a loop, and call:

$this->testString($string, $min, $max, $pattern, $errorMessage);

or

$this->stringTester->testString($string, $min, $max, $pattern, $errorMessage);

... within the loop. Be sure to account for the $errorMessage.

abstract Class Tester
{

}

class StringTester extends Tester
{
    private function testString($string, $min, $max, $pattern, &$errorMessage)
    {
        $length = mb_strlen($string, 'UTF-8');

        if($length < $min)      //Test string against minimum length.
        {
            $errorMessage = 'Too small! ('.$min.' min, ' .$length. ' given.)';
        }
        elseif($length > $max)  //Test string against maximum length.
        {
            $errorMessage = 'Too large! ('.$max.' max, ' .$length. ' given.)';
        }
        elseif(preg_match($pattern, $string) === 0)  //Test string's pattern.
        {
            $errorMessage = 'Invalid string format!';
        }
        else
        {
            $errorMessage = '';  //The error message is the empty string.
        }

        return;
    }
}

abstract Class Validator
{
    //Arrays
    protected $inputArray;
    protected $errorMessagesArray = [];
    protected $stringTestRulesArray;  //I know. I know. :-)

    //Objects
    protected $stringTester;

    //Abstract functions
    abstract public function validate();

    public function __construct(Tester $stringTester, array $inputArray, array $stringTestRutlesArray)
    {
        $this->stringTester         = $stringTester;
        $this->inputArray           = $inputArray;
        $this->stringTestRulesArray = $stringTestRulesArray
    }

    public function getInput()
    {
        return $this->inputArray;
    }

    public function getErrorMessages()
    {
        return $this->errorMessagesArray();
    }

    protected function validateStrings()
    {
        //Notice how input values correspond to error message elements via $key.
        foreach($this->stringTestRulesArray as $key = $valuesArr)
        {
            $this->stringTester->testString($this->inputArray[$key], $valuesArr['min'], $valuesArr['max'], $valuesArr['pattern'], $this->errorMessagesArray[$key]);
        }

        return;
    }

}

class ContactValidator extends Validator
{
    public function __construct(Tester $stringTester, Sanitizer $sanitizer)
    {
        $stringTestRulesArray = [
                                  'fName' => ['min' => 1, 'max' => 25, 'pattern' => '/[A-Za-z\' -]/'],
                                  'lName' => ['min' => 1, 'max' => 40, 'pattern' => '/[A-Za-z\' -]/']
                                ];

        parent::__construct($stringTester, $sanitizer->getInput(), $stringTestRulesArray);
    }

    public function validate()
    {
        $this->validateStrings();
        //Other, contact form specific validation stuff.
    }
}

class RegisterValidator extends Validator
{
    public function __construct(Tester $stringTester, Sanitizer $sanitizer)
    {
        $stringTestRulesArray = [
                                  'fName' => ['min' => 1, 'max' => 30, 'pattern' => '/[A-Za-z\' -]/'],
                                  'lName' => ['min' => 1, 'max' => 45, 'pattern' => '/[A-Za-z\' -]/']
                                ];

        parent::__construct($stringTester, $sanitizer->getInput(), $stringTestRulesArray);
    }

    public function validate()
    {
        $this->validateStrings();
        //Other, register form specific validation stuff.
    }
}
查看更多
登录 后发表回答