Lesson 48 バリデーションを理解して設定しましょう

バリデーションとは受け付けない値を弾いて、確かな(valid)値だけを扱うという処理です。 こう説明されてもわかりにくいと思うので、いつもの生活の中でどのようなチェックが行われているかを例に説明しましょう。 アンケートフォームなどで数字の項目に文字を入れるとエラーになったり、携帯電話で着信拒否の番号を登録したり、身近で値のチェックを行っていますね。 本書で作成したレシピアプリでも、POSTやGETで値を取得していますが、このような値はHTML5やJavaScriptなどで入力チェックを行っていても信用してはいけません。ここで重要になるのがバリデーションです。

※Mac環境の場合は、以下本文に登場するプログラム内の「¥」(半角エンマーク)を「\」(半角バックスラッシュ)に入力しなおす必要があります。日本語キーボードの場合、半角バックスラッシュは[option]+[¥]キーを押すことで入力できます。

チェックすべき項目

1. 文字種:(文字列か数字かなど)
2. 桁数、文字数(何桁までか、何文字までかなど)
3. フォーマット(いくつ以上、いくつ未満、電話番号やメールアドレスのように形が決まっているなど)

これらを調べて、必要の無いものを除外する必要があります。

バリデーションに便利なpreg_match

バリデーションに適した関数を紹介しましょう。 preg_matchという関数です。 preg_match()は正規表現により、値が期待するパターンにマッチするかを検査します。

preg_match(パターン,検査する文字列);
    パターンにマッチすると1
    パターンにマッチしないと0
    エラーの場合falseを返します。

正規表現というと難しいですが、ここでは下記のサンプルを見て雰囲気をつかんでください。

一桁の整数で1か2か3

preg_match('/¥A[123]¥z/',変数);

4桁の整数

preg_match('/¥A[0-9]{1,4}+¥z/',変数);

100文字以内の文字列

preg_match('/¥A[[:^cntrl:]]{1,100}+¥z/u',変数);

改行のある150文字以内の文字列

preg_match('/¥A[ r n[:^cntrl:]]{1,150}+¥z/u',変数);

なんとなく予想がつきませんか? パターンの部分で/¥A ... ¥z/に挟まれた中にそれぞれ違いがあります。 ここでは細かく説明しませんが、この書き方を覚えておいてください。

項目 内容
// //囲まれた部分に正規表現が入っている
¥A 文字列の先頭
¥z 文字列の最後
[] []の中が使って良い文字列になる
0-9 0〜9の数字
a-z アルファベット
[:^cntrl:] 制御文字以外
 r n 改行
+ 一文字以上の繰り返し
{} {}の直前のパターンの有効桁(文字)数
u UTF-8が対象

*上記の表は例に使われている正規表現の簡単な説明です。

さらに詳細な内容ついては「PHP 正規表現」などで検索し、どのようなものなのか勉強してください
(コピペでの利用は意図しない処理になる場合もあるので、よく内容を理解して利用しましょう)。 この先では上記サンプルのみを用います。

一歩進んだif文での判定

さてバリデーションをするためには、色々な条件で受け付けない条件を設定する必要があります。 そのようなときにif文を用いて複数の条件を1回で判定する方法をここで補足します。 まずif文の構文を見てみましょう。

if (条件) {
}

でしたね。この条件というところがポイントで、中が真(TRUE),偽(FALSE)かで条件の判定を行います。 elseも覚えましたが、ここでは条件の部分に注目して新しい使い方を覚えましょう。

否定

●●では無いというような条件を利用したい場合は多いと思います。そのような場合は論理演算子の!を利用することで条件の否定を行うことができます。

例:emptyでは無い場合

if (!empty($a)) {
    echo 'emptyではありません。';
}
 
if ( $a !== 'blue') {
    echo '$aはblueではありません。';
}

例を見てみると何か気づくと思いますが、 if文の条件で!(否定)を付けることはelseの条件のときと同じです。 真(true)の側の判定が不要な場合、!を用いるとif文が短くなり可読性がよくなるので覚えておきましょう。

例1をif〜elseで表すと

if (empty($a)) {
        echo 'emptyです。'; // <===今回は不要
} else {
        echo 'emptyではありません。'; // <==ここだけを使いたい
}

条件文でよく出てくるので覚えておくといいでしょう。

複合条件

いままではシンプルな判定を行ってきましたが複合的な判定も必要になります。 例えば●●かつ●●、や●●または●●といった判定です。

かつの場合は論理積と呼ばれるもので、2つの条件を&&でつなぎます。 またはの場合は論理和と呼ばれ、2つの条件を||でつなぎます。 なお、複数の条件を利用する場合、1つ1つの条件を()で括ることで可読性が増します。

例:

if (($color === "blue") && ($flower === 'バラ')) {
    echo '青いバラです。';
}
 
if (($prefecture === '東京都') || ($prefecture === '神奈川県')) {
    echo '東京都か神奈川県です。';
}

下記でも利用するので覚えておいてください。

emptyとisset

emptyとは、逆に存在するということを判定するissetという言語構造があります。 先ほど!(否定)を学びました。この2つは完璧に対になるものではないので注意が必要です。 大きな違いは0の判定を行ったときに0を空とするのか、数値が入力されているのかどちらで判定するかです。

$a = 0;
var_dump(empty($a))
var_dump(!isset($a))

このような短いサンプルをtest.phpなどとして実行してみましょう。 結果は int(0) bool(true) bool(false) このように返ってくるので注意が必要です。

※他にも$a="";なども違う判定が返ります。

レシピアプリではidはautoincrementで1から始まりました。通し番号では0から始まる場合もありますね。このような場合、上記のようにif (empty($id)) と行うと0も条件を満たしたと判断されてしまいます。これはempty()が0を真(TRUE)と判定するためです。このような場合は値がある場合はisset()という構文を用いましょう。isset()は0が入力されても入力があると判定します。このisset()に先ほど説明に出てきた!(否定)を行うと何も入力されていない場合の判定が行えます。なお、0や未入力の扱いには注意が必要です。マニュアルを参考にテストを行い意図したデータを判定しているか確認しましょう。

PHPマニュアルなどを参考にして、仕様上どちらが最適かを検討してください。
http://php.net/manual/ja/function.isset.php
http://php.net/manual/ja/function.empty.php

IDのバリデーションを行う

いよいよ実際にバリデーションを行っていきましょう。 まずはIDの場合を考えてみます。

idのみ(detail.php、edit.php、delete.php)

いまIDの値はどのように作成されるかというと、 データベースを作成したときのauto incrementが有効になっているので、 1から順にカウントアップしていきます。 PHPにもMySQLにも扱える型により扱える限界はあります。 ※執筆時点でのMAMPおよびXAMMPの環境では、MySQLのINT型の範囲-2147483648〜2147483647、PHPのinteger型の最大値9223372036854775807(脚注) いままで決めていませんでしたが、今回のプログラムでは1000件まで入力できることに決めましょう。 とすると、IDに入力されるべき整数は1-1000の整数ということになります。 では、それ以外の値についても下記表のように、入力できる値に制約を設けましょう。

表:許可する変数

項目 内容
ID 1から1000までの整数
料理名 100文字までの文字列
カテゴリ、難易度 1〜3の整数
予算 0-9999までの整数
作り方 150文字の文字列

ではそれぞれの項目についてソースコードを修正していきましょう。 レシピの数は1000個まで登録できるようにしましょう。

対象となるファイルはdetail.php、edit.php、delete.phpになります。 それぞれの箇所から該当する箇所を探して修正しましょう。

元の箇所

if(empty($_GET['id'])) throw new Exception('Error');
$id = (int)$_GET['id'];

変更後

if(!isset($_GET['id'])) throw new Exception('ID無し');
if(!preg_match('/¥A[0-9]{1,4}+¥z/',$_GET['id'])) throw new Exception('ID不正');
$id = (int)$_GET['id'];
if(($id < 1) || ($id) > 1000 ) throw new Exception('ID範囲外');
  1. emptyでの判定だと0を入力しているのに「ID無し」のエラーになっています。0はIDが無しではなく範囲外ということなので、はじめのemptyを!issetに変更しましょう。
  2. 次はpreg_matchのパターンを使って整数の4桁の判定を行います。
  3. 値の範囲を決めてif文で判定しましょう。ここでは、値は1より小さいか1000より大きいという2つの条件に当てはまらない場合にExceptioをthrowします。またここで追加する箇所は$id = (int) $_GET['ind']の後です。$idに数値型キャストを行ってから、$idを利用して数値の大小比較を行います。
  4. いままでExceptionの後にはErrorという文字を指定していましたが、ここはメッセージが出せるのでエラーメッセージを表示するようにしましょう(適時わかりやすいメッセージに変えてみましょう)。

さてURLにIDが付与されてくるのは、いま修正した3つのファイルでしたが、 もう一箇所IDを受け渡す処理がありましたね。 更新作業の送信をした後にデータベースに書き込むupdate.phpです。 update.phpは次に入力フォームの値のチェックで修正もしますが、 同様の処理を先に入れておきましょう。 該当する行を見てみると違いはGETとPOSTだけですね。同様に修正しておきましょう。

if(empty($_POST['id'])) throw new Exception('Error');
if(!preg_match('/¥A[0-9]{1,4}+¥z/',$_POST['budget'])) throw new Exception('IDが不正です。');

では修正のテストしてみましょう。

  1. 新規追加が正しくできているか
  2. 変更が正しくできているか
  3. detail.phpの引数にid=0,id=あ,id=10000,id= などと入力してErrorが表示されるか試してみましょう。

どうでしょう。追加、更新に問題は無かったですか?メッセージは正しく表示されましたか? 処理が上手くいかない場合はもう一度見直しましょう。 エラーメッセージの内容をよく読んで、どこか原因か探りましょう。

detail.php、edit.php、delete.php共に修正して、index.phpから処理が正しく動くことを確認してください。

次は新規追加と更新の修正を行います。 つまりadd.phpとupdate.phpを修正します。

この処理はadd.phpとupdate.phpで共通する部分が多いので、 先ほど覚えたinclude_onceを利用しましょう。 db_config.phpと同じ階層にerror_check.phpを作成しましょう。

<?php
if(!preg_match('/¥A[[:^cntrl:]]{1,100}+¥z/u',$_POST['recipe_name'])) {
    throw new Exception('料理名を正しく入力してください。');
}
if(!preg_match('/¥A[123]¥z/',$_POST['category'])) {
    throw new Exception('カテゴリを正しく入力してください。');
}
if(!preg_match('/¥A[123]¥z/',$_POST['difficulty'])) {
    throw new Exception('難易度を正しく入力してください。');
}
if(!preg_match('/¥A[0-9]{1,4}+¥z/',$_POST['budget'])) {
    throw new Exception('予算を正しく入力してください。');
}
if(!preg_match('/¥A[ r n[:^cntrl:]]{1,200}+¥z/u',$_POST['howto'])) {
    throw new Exception('作り方を正しく入力してください。');
}
?>

いままではif文の後{}で括らずにすぐにthrow new Exceptionを記述していましたが、 判定やメッセージなど1行で表示するには長くなってきたので、 if{}の書き方にしましょう(いままでは誌面の節約に一行の記述が多かったのですが、{}を使いましょう)。

それぞれのpreg_matchによる判定で利用している正規化表現はこのレッスンの冒頭で説明したものばかりです。内容を見比べてください。 POSTされたそれぞれの変数と比べてマッチしないもの(!を先頭に付ける)でExceptionを発生させます。 Exceptionの引数のエラーメッセージもエラーの内容がわかるものにしましょう。

後は実際にadd.phpに、このファイルを読み込むだけです。db_config.phpの読み込み箇所を参考に追加してみましょう。

追加する箇所はtryのすぐ後で項目ごとに変数に格納する前です。

include_once '/Applications/MAMP/error_check.php';(Macの場合)
 
include_once '¥xampp¥error_check.php';(Windowsの場合)

※includeできない場合はファイル名で検索して、保存場所を指定してください。

これでadd.phpは完了です。 includeさせると、そのファイルの中身がプログラムに取り込まれます。 これでadd.phpが実行されるたびに毎回、いま作成したバリデーション処理が実行されることになります。

では実際に予算に9999より大きな数字を入れてみてメッセージを確認してみましょう。 エラーメッセージが表示されましたか? これでバリデーションは完成です。 お疲れ様でした!

バリデーションは実際にWebアプリを公開する上で必要になってきます。 ぜひこの仕組を覚えておいて活用してください。