优雅的方式来验证多维$ _POST

时间:2014-06-09 12:15:44

标签: php validation multidimensional-array

我有这样的用户输入:$_POST['multi']['dim']['form'] = 1.213

我想验证这一点,目前我使用此功能:

public function checkFloat($number, $min = null, $max = null)
{
    $num = filter_var($number, FILTER_VALIDATE_FLOAT, FILTER_FLAG_ALLOW_THOUSAND);

    if($num === false || ($min !== null && $num < $min) || ($max !== null && $num > $max))
        return false;

    return $num;
}

但是,在调用此函数时,我必须先检查$_POST['multi']['dim']['form']是否已设置。

所以每次通话都是

if(isset($_POST['multi']['dim']['form']) && ($num = checkFloat($_POST['multi']['dim']['form']) !== false))
{
    // do something here
}

如果我不先检查,如果设置了变量,PHP将发出通知。 我注意到PHP函数filter_input,但似乎这不适用于多维$_POST

我想过像

这样的包装器
function checkFloat($names_of_the_fields,$min,$max)
{
    // check if $_POST[$name][$of][$the][...] is set

    // make the validation
}

但我不确定如何通过$name_of_the_fields

这里我想到了一个数组$arr['key1']['key2'][...]。但由于我不知道这有多深,我必须经常进行is_array次检查。或者我传递像$arr = ['key1','key2',...]这样的数组。

有一个干净利落的方法吗?我应该忽略通知吗? 或者我应该继续使用if(isset.. && checkFloat...)

更改表单并使用eval()不是一种选择。

提前致谢

编辑1: 如果设置is_array($var),则$var并不慢。如果我使用检查结构的函数,那就没关系。但问题仍然是,如果这是一个好方法,或者是否有更好(也许更快)的方法来做到这一点。

4 个答案:

答案 0 :(得分:0)

这个怎么样?你会喜欢它:D

function filter_input_simple($type, $name, $filter = FILTER_DEFAULT, $options = FILTER_REQUIRE_SCALAR) {
    static $vars;
    if (!$vars) {
        $vars = array(
            INPUT_GET    => filter_input(INPUT_SERVER, 'QUERY_STRING'),
            INPUT_POST   => file_get_contents('php://input'),
            INPUT_COOKIE => filter_input(INPUT_SERVER, 'HTTP_COOKIE'),
        );
        $s = array('&', '&', ';[; ]++');
        foreach ($vars as $t => $var) {
            $tmp = array();
            foreach (preg_split("@{$s[$t]}@", $var, -1, PREG_SPLIT_NO_EMPTY) as $i) {
                list($k, $v) = explode('=', $i, 2) + array(1 => '');
                $tmp[urldecode($k)] = urldecode($v);
            }
            unset($tmp['']);
            $vars[$t] = $tmp;
        }
        $vars += array(INPUT_REQUEST =>
            $vars[INPUT_COOKIE] + $vars[INPUT_POST] + $vars[INPUT_GET]
        );
    }
    $type = (int)$type;
    $name = filter_var($name);
    if (!isset($vars[$type][$name])) {
        return null;
    }
    return filter_var($vars[$type][$name], $filter, $options);
}

用法:

// This function does not use extracted values as $_GET,
// so put QueryString on your browser for testing.
// 
// ?foo[bar][validated_float]=1,234.213

$options = array(
    'options' => array(
        'min_range' => 1234,
        'max_range' => 1235,
    ),
    'flags' => FILTER_FLAG_ALLOW_THOUSAND,
);
var_dump(filter_input_simple(INPUT_GET, 'foo[bar][validated_float]', FILTER_VALIDATE_FLOAT, $options));

答案 1 :(得分:0)

经过一番思考和测试后,我想出了这个:

我将每个验证功能分成两个函数,并添加了一个检查数组键是否存在的函数。

  • validate[Type]Var($val, $options):检查$val是否包含有效值。 (此处没有变化)
  • validate[Type]Input(&$inputArr,$keys,$options):调用getArrayValue(&array,$keys),如果密钥存在,则调用validate [Type] Val()来验证该值。

详细说明:

public function getArrayValue(&array,$keys)
{
    $key = array_shift($keys);
    if(!isset($array[$key]))
        return null;

    if(empty($keys))
        return $array[$key];

    return $this->getArrayValue($array[$key],$keys);
}

public function validateTypeInput(&$inputArr, $keys, $options = [])
{
    $value = $this->getArrayValue($inputArr, $keys);

    if(isset($value))
        return $this->validateTypeVal($value,$options);
    else
        return null;     // or return anything else to show that the value was invalid 
}

该功能可以通过
调用 ValidationClass->validateTypeInput($_POST,['key1','key2','key3'],$options);
验证$_POST['key1']['key2']['key3']

注意:我为每种类型写了validateTypeInput,因为其中有些是不同的。例如。如果您验证复选框输入,则不希望将未设置的输入视为无效。

答案 2 :(得分:-1)

它不会验证传递给它的数据的结构,但它会确保每个传递checkFloat函数:

function validate_info($info) {
    if (is_array($info)) {
        // $info is an array, validate each of its children
        foreach ($info as $item) {
            // If item is invalid, stop processing
            if (validate_info($item) === false) { return false; }
        }

        // If each $item was valid, then this $info is valid.
        return true;

    } else {
        // $info is a single item
        return checkFloat($info, [YOUR_MIN_VAL], [YOUR_MAX_VAL]);
    }
}

如果$info中的每个项都为checkFloat返回true,则该函数将返回true。如果$info中的任何值返回false,则validate_info将返回false。无论结构如何,此函数都使用递归来检查整个数组。


编辑:此功能确实使用is_array功能,但您似乎错误地认为is_array功能很慢。 is_array函数以恒定时间运行,可以是verified by checking the PHP source code

答案 3 :(得分:-2)

类定义:

/**
 * Object for filter_input_array_recursive().
 */
class FilterObject {

    private $filter;
    private $options;

    /**
     * Constructor.
     * 
     * @param int   $filter  same as ones for filter_input().
     * @param mixed $options same as ones for filter_input().
     */
    public function __construct($filter = FILTER_DEFAULT, $options = FILTER_REQUIRE_SCALAR) {
        $this->filter  = $filter;
        $this->options = $options;
    }

    public function getFilter() {
        return $this->filter;
    }

    public function getOptions() {
        return $this->options;
    }

}

功能定义:

/**
 * Apply filter_input() recursively.
 * 
 * @param int   $type    INPUT_GET, INPUT_POST, INPUT_COOKIE, INPUT_REQUEST are supported.
 * @param array $filters Multi-demensional array, which contains FilterObject for each leaf.
 * @return array
 */
function filter_input_array_recursive($type, array $filters) {
    static $recursive_static;
    static $flag_match;
    static $is_not_array;
    static $filter_array;
    static $types;
    if (!$flag_match) {
        /* initialize static variables */
        $types = array(
            INPUT_GET     => $_GET,
            INPUT_POST    => $_POST,
            INPUT_COOKIE  => $_COOKIE,
            INPUT_REQUEST => $_REQUEST,
        );
        $flag_match = function ($v, $f) {
            return (int)(isset($v['flags']) ? $v['flags'] : $v) & $f;
        };
        $is_not_array = function ($v) {
            return !is_array($v);
        };
        $filter_array = function ($v) {
            return !is_array($v) ? $v : false;
        };
    }
    $recursive = $recursive_static;
    if (!$recursive) {
        /* only for first loop */
        $type = (int)$type;
        if (!isset($types[$type])) {
            throw new \InvalidArgumentException('unknown super global var type');
        }
        $var = $types[$type];
        $recursive_static = true;
    } else {
        /* after first loop */
        $var = $type;
    }
    $ret = array();
    foreach ($filters as $key => $value) {
        $isset = isset($var[$key]);
        if (is_array($value)) {
            // apply child filters
            $ret[$key] = filter_input_array_recursive($isset ? $var[$key] : array(), $value);
        } else {
            if (!($value instanceof FilterObject)) {
                // create default FilterObject for invalid leaf
                $value = new FilterObject;
            }
            $filter = $value->getFilter();
            $options = $value->getOptions();
            // check if key exists...
            // true  -> apply filter_var() with supplied filter and options
            // false -> regard as null
            try {
                $ret[$key] = $isset ? filter_var($var[$key], $filter, $options) : null;
            } catch (Exception $e) {
                $recursive_static = false;
                throw $e;
            }
            if ($flag_match($options, FILTER_FORCE_ARRAY | FILTER_REQUIRE_ARRAY)) {
                // differently from filter_input(),
                // this function prevent unexpected non-array value
                if (!is_array($ret[$key])) {
                    $ret[$key] = array();
                }
                // differently from filter_input(),
                // this function prevent unexpected multi-demensional array
                if ($flag_match($options, FILTER_FORCE_ARRAY)) {
                    // eliminate arrays
                    $ret[$key] = array_filter($ret[$key], $is_not_array);
                } else {
                    // change arrays into false
                    $ret[$key] = array_map($filter_array, $ret[$key]);
                }
            }
        }
    }
    if (!$recursive) {
        /* only for first loop */
        $recursive_static = false;
    }
    return $ret;
}

用法:

$_POST['foo']['bar']['validated_float'] = '1,234.213';
$_POST['foo']['forced_1d_array']        = array('a', array('b'), 'c');
$_POST['foo']['required_1d_array']      = array('a', array('b'), 'c');
$_POST['foo']['required_scalar']        = array('a', array('b'), 'c');

当输入如下时,

var_dump(filter_input_array_recursive(INPUT_POST, array(
    'foo' => array(
        'bar' => array(
            'validated_float' => new FilterObject(
                FILTER_VALIDATE_FLOAT,
                array(
                    'options' => array(
                        'min_range' => 1234,
                        'max_range' => 1235,
                    ),
                    'flags' => FILTER_FLAG_ALLOW_THOUSAND,
                )
            ),
        ),
        'forced_1d_array'   => new FilterObject(FILTER_DEFAULT, FILTER_FORCE_ARRAY),
        'required_1d_array' => new FilterObject(FILTER_DEFAULT, FILTER_REQUIRE_ARRAY),
        'required_scalar'   => new FilterObject,
    ),
)));

此代码段将输出...

array(1) {
  ["foo"]=>
  array(4) {
    ["bar"]=>
    array(1) {
      ["validated_float"]=>
      float(1234.213)
    }
    ["forced_1d_array"]=>
    array(2) {
      [0]=>
      string(1) "a"
      [2]=>
      string(1) "c"
    }
    ["required_1d_array"]=>
    array(3) {
      [0]=>
      string(1) "a"
      [1]=>
      bool(false)
      [2]=>
      string(1) "c"
    }
    ["required_scalar"]=>
    bool(false)
  }
}