Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
50
Help us understand the problem. What are the problem?

posted at

updated at

filter_input_array_recursive

【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)
  }
}
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
50
Help us understand the problem. What are the problem?