PHP - WEB - 15. filter_input 関数

あらためてさきほどの3つの入力パターンを整理しておきましょう。

  1. $_GET 変数に "name" キーが存在しない場合
  2. $_GET 変数に "name" キーは存在するが値が空の場合
  3. $_GET 変数に "name" キーが配列形式の場合

ここではこれらの3つのパターンを想定して入力チェックを実装してみましょう。

入力チェックの実装

検索処理( search.php )を以下のように修正します。

<?php
$name = null;
if (!isset($_GET["name"])) {
  $name = null;
} else if(!is_string($_GET["name"])) {
  $name = false;
} else {
  $name = $_GET["name"];
}

var_dump($name);
die("debug");
// ...省略

ここでは if 文を追加して $_GET["name"] の内容を検証しています。 if 文の条件式を順に見ていきましょう。

if (!isset($_GET["name"])) {

まず1つ目の条件式では isset 関数を使って $_GET 変数に "name" キーが存在しない場合を処理しています。これは1つ目の入力パターンで示した以下のアクセスを想定してのものです。

https://〜.vfs.cloud9.ap-northeast-1.amazonaws.com/search.php

今回の修正によって、もし上記のようにアクセスされた場合は $name 変数に null を代入するようにしています。

次に2つ目の条件式です。

} else if(!is_string($_GET["name"])) {

is_string 関数は引数が文字列データの場合に true 、そうでない場合に false を返します。ここでは ! 否定を使って is_string 関数を呼び出しているので、引数の $_GET["name"] が文字列型でない場合を条件にしています。これは3つ目のパターンで紹介したようなクエリパラメータに配列データを指定されたようなケースです。

https://〜.vfs.cloud9.ap-northeast-1.amazonaws.com/search.php?name[]=A&name[]=B

今回の修正によって、もし上記のようにアクセスされた場合は $name 変数に false を代入するようにしています。

このような2つの条件をパスした場合は最後の else ブロックによって $name = $_GET["name"]); が実行されます。

今回の修正によって、さきほどの3つの入力パターンがどのように処理されるか改めて確認しておきましょう。

1. $_GET 変数に "name" キーが存在しない場合 - 入力チェック実装後

アドレスバーから以下のURLにアクセスします。

https://〜.vfs.cloud9.ap-northeast-1.amazonaws.com/search.php

そうすると画面には次のような結果が表示されるでしょう。

以前はNoticeメッセージが表示されていましたが、今回の修正で NULL と表示されているのがわかります。

2. $_GET 変数に "name" キーは存在するが値が空の場合 - 入力チェック実装後

アドレスバーから以下のURLにアクセスします。

https://〜.vfs.cloud9.ap-northeast-1.amazonaws.com/search.php?name=

そうすると画面には次のような結果が表示されるでしょう。

前回と変わらず var_dump 関数によって空文字 "" が出力されているのがわかります。

3. $_GET 変数に "name" キーが配列形式の場合 - 入力チェック実装後

アドレスバーから以下のURLにアクセスします。

https://〜.vfs.cloud9.ap-northeast-1.amazonaws.com/search.php?name[]=A&name[]=B

そうすると画面には次のような結果が表示されるでしょう。

以前は配列データが出力されていましたが、今回の修正で false と表示されているのがわかります。

ここまでの修正で3つのパターンを以下のように制御することができるようになりました。

  1. $_GET 変数に "name" キーが存在しない場合 => null を返す
  2. $_GET 変数に "name" キーは存在するが値が空の場合 => "" 空文字を返す
  3. $_GET 変数に "name" キーが配列形式の場合 => false を返す

これまでに検索処理( search.php )において if 文を使って複雑な変換処理を実装してきました。

<?php
$name = null;
if (!isset($_GET["name"])) {
  $name = null;
} else if(!is_string($_GET["name"])) {
  $name = false;
} else {
  $name = $_GET["name"];
}
// ...省略

Webブラウザの入力フォームから送信される項目の一つひとつにこのような入力チェックを実装するのは手間がかかるので、このような変換処理は関数として定義しておくと便利です。実はPHPの組み込み関数の中には上記のような変換処理を行う filter_input 関数が用意されています。

filter_input 関数

さきほどの検索処理( search.php )を filter_input 関数を使うように修正してみましょう。

<?php
$name = filter_input(INPUT_GET, "name");
var_dump($name);
die("debug");
// ...省略

filter_input 関数は、第1引数にGETリクエストを処理する場合は INPUT_GET 定数、POSTリクエストを処理する場合は INPUT_POST 定数を指定します。また第2引数にはリクエストパラメータのキーを指定します。このように filter_input 関数を呼び出すだけで、さきほどの if 文と同じような変換処理が可能となります。

それでは実際に3つのパターンを検証してみましょう。

1. $_GET 変数に "name" キーが存在しない場合 - filter_input 関数

アドレスバーから以下のURLにアクセスします。

https://〜.vfs.cloud9.ap-northeast-1.amazonaws.com/search.php

そうすると画面には次のような結果が表示されるでしょう。

filter_input 関数による変換結果として NULL が表示されているのがわかります。

2. $_GET 変数に "name" キーは存在するが値が空の場合 - filter_input 関数

アドレスバーから以下のURLにアクセスします。

https://〜.vfs.cloud9.ap-northeast-1.amazonaws.com/search.php?name=

そうすると画面には次のような結果が表示されるでしょう。

filter_input 関数による変換結果として空文字 "" が表示されているのがわかります。

3. $_GET 変数に "name" キーが配列形式の場合 - filter_input 関数

アドレスバーから以下のURLにアクセスします。

https://〜.vfs.cloud9.ap-northeast-1.amazonaws.com/search.php?name[]=A&name[]=B

そうすると画面には次のような結果が表示されるでしょう。

filter_input 関数による変換結果として false が表示されているのがわかります。

また filter_input 関数の呼び出しに、期待するデータ型へのキャストを組み合わせることでさらに実用的になります。

<?php
$name = (string)filter_input(INPUT_GET, "name");
var_dump($name);
die("debug");
// ...省略

ここでは filter_input 関数の呼び出し結果を文字列型( string 型)にキャストしています。PHPでは NULLfalse といった値を文字列型にキャストするとすべて "" に置き換わるようになっています。つまりこのように実装することで、3つのパターンの入力を次のように変換できます。

  1. $_GET 変数に "name" キーが存在しない場合 => "" を返す
  2. $_GET 変数に "name" キーは存在するが値が空の場合 => "" 空文字を返す
  3. $_GET 変数に "name" キーが配列形式の場合 => "" を返す

filter_input 関数の戻り値を string 型にキャストすることで、想定外のケースをすべて空文字として判断できるようになりました。

さいごに検索処理( search.php )から不要な var_dump 関数、 die 関数を削除しておきましょう。

<?php
$name = (string)filter_input(INPUT_GET, "name");
$names = file("names.txt", FILE_IGNORE_NEW_LINES);
$searched_names = [];
if ($name !== "") {
  for ($i = 0; $i < count($names); $i++) {
    if (strpos($names[$i], $name) !== false) {
      $searched_names[] = $names[$i];
    }
  }
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>PHP Sample</title>
</head>
<body>
  <h3>Search</h3>
  <hr>
  <ul>
  <?php
    for ($i = 0; $i < count($searched_names); $i++) {
  ?>
    <li><?php echo $searched_names[$i]; ?></li>
  <?php
    }
  ?>
  </ul>
</body>
</html>

以上でプログラムの修正は完了です。以降はWebブラウザから3つの入力パターンを試すといずれも次のような結果を返すようになります。

さらに高度な用法として filter_input 関数の第3引数にはフィルターを指定できます。たとえば FILTER_SANITIZE_STRING と指定するとタグの入力などを除去できます。詳細についてはPHPマニュアルを確認してみましょう。https://www.php.net/manual/ja/function.filter-input.php

まとめ

  • Webアプリケーションのユーザの中には悪意のあるユーザも存在する
  • リクエストパラメータは簡単にカスタマイズできるため、期待するデータが存在するか検証する必要がある
  • リクエストパラメータの検証には filter_input 関数を使う