【2021/10/15 追記】
この記事は更新が停止されています。現在では筆者の思想が変化している面もありますので,過去の記事として参考程度にご覧ください。
これは何?
filter_input_array 関数の惜しいところを使いやすく、安全にした強化版です。
フィルタリングされる変数名を多次元配列で定義できます。
cURLにおけるファイルアップロードに関する問題の解決策としてPHP公式が採用した葉要素にオプション専用のクラスを用意するという手段を真似て、任意のフィルタによる再帰的フィルタリングを可能にしました。(詳しくは 「PHPでcURLのクソ仕様 "@" を回避する」 で紹介しています)
filter_input_simple 関数との比較
filter_input_simple | filter_input_array_recursive | |
---|---|---|
パフォーマンス(初回コール時) | △ | ○ |
パフォーマンス(2回目以降) | ◎ | ○ |
ネストした配列への対応 | ◎ | ◎ |
ネストした任意の配列への対応 | × | ◎ |
クラスと関数の定義
FilterObject クラス
コンストラクタのシグネチャ
public __construct(int $filter = FILTER_DEFAULT, mixed $options = FILTER_REQUIRE_SCALAR)
コンストラクタの引数
$filter
filter_input 関数の引数と同様です。
$options
filter_input 関数の引数と同様です。
ソースコード
/**
* 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;
}
}
filter_input_array_recursive 関数
シグネチャ
array filter_input_array_recursive(int $type, array $filters)
引数
$type
INPUT_GET
INPUT_POST
INPUT_COOKIE
INPUT_REQUEST
のいずれか。
$filters
以下の形式の要素で構成される配列。
"name属性値" => FilterObjectのインスタンス
- 配列がネストしても構いません。
- FilterObjectのインスタンス以外を渡した場合、
new FilterObject
で生成されるデフォルトオプションが適用されます。
返り値
フィルタリングした配列を返します。
ソースコード
/**
* Apply filter_input() recursively.
*
* @param int $type INPUT_GET, INPUT_POST, INPUT_COOKIE or INPUT_REQUEST.
* @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) {
if (is_array($value)) {
// apply child filters
$ret[$key] = filter_input_array_recursive(
isset($var[$key]) ? $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($var[$key]) ?
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
// or multi-demensional array
if ($flag_match($options, FILTER_FORCE_ARRAY)) {
// eliminate arrays
$ret[$key] = array_filter((array)$ret[$key], $is_not_array);
} else {
// change arrays into false
$ret[$key] = array_map($filter_array, (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' => null,
),
)));
実行結果
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)
}
}