filter_input_array_recursive

**【2021/10/15 追記】**
**この記事は更新が停止されています。現在では筆者の思想が変化している面もありますので，過去の記事として参考程度にご覧ください。**

# **これは何？**

**[filter_input_array](http://www.php.net/manual/ja/function.filter-input-array.php)** 関数の惜しいところを使いやすく、安全にした強化版です。

**フィルタリングされる変数名を多次元配列で定義できます。**

cURLにおけるファイルアップロードに関する問題の解決策としてPHP公式が採用した<ins>葉要素にオプション専用のクラスを用意する</ins>という手段を真似て、任意のフィルタによる再帰的フィルタリングを可能にしました。（詳しくは **[「PHPでcURLのクソ仕様 "@" を回避する」](http://qiita.com/mpyw/items/c2d2f9cf67072e926721)** で紹介しています）

# **[filter_input_simple](http://qiita.com/mpyw/items/5a7242269f51dfabc973)** 関数との比較

|| **[filter_input_simple](http://qiita.com/mpyw/items/5a7242269f51dfabc973)** | **[filter_input_array_recursive](http://qiita.com/mpyw/items/c39b9ee695a5c2e74627)** |
|:--:|:---:|:---:|  
| パフォーマンス(初回コール時) | △ | ○ | 
| パフォーマンス(2回目以降) | ◎ | ○ |
| ネストした配列への対応 | ◎ | ◎ |
| ネストした任意の配列への対応 | × | ◎ |

# **クラスと関数の定義**

## **FilterObject クラス**

### **コンストラクタのシグネチャ**

```php
public __construct(int $filter = FILTER_DEFAULT, mixed $options = FILTER_REQUIRE_SCALAR)
```

### **コンストラクタの引数**

#### *$filter*

**[filter_input](http://www.php.net/manual/ja/function.filter-input.php)** 関数の引数と同様です。

#### *$options*

**[filter_input](http://www.php.net/manual/ja/function.filter-input.php)** 関数の引数と同様です。

### **ソースコード**

```php
/**
 * 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 関数**

### **シグネチャ**

```php
array filter_input_array_recursive(int $type, array $filters)
```

### **引数**

#### *$type*

`INPUT_GET` `INPUT_POST` `INPUT_COOKIE` `INPUT_REQUEST` のいずれか。

#### *$filters*

以下の形式の要素で構成される配列。

```
"name属性値" => FilterObjectのインスタンス
```

- 配列がネストしても構いません。
- FilterObjectのインスタンス以外を渡した場合、 `new FilterObject` で生成されるデフォルトオプションが適用されます。

### **返り値**

フィルタリングした配列を返します。

### **ソースコード**

```php
/**
 * 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;
}
```

# **基本的な使い方**

## **テストコード**

```php
$_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,
    ),
)));
```

## **実行結果**

```php
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)
  }
}
```