掲示板の作り方#7 データのエラーチェックをする
こんにちは。Suipediaです(∩´∀`)∩
前回はスレッドの登録に向けてテーブルの作成とクライアント側からサーバ側へデータを渡す準備をしました。
今回は登録処理の手前でやっているエラーチェックを実装します。
エラーチェックはユーザの入力内容に不備がないかを確認することで、正しいデータの構造を保持し、システムが恒久的に正常動作するために非常に大切なことです。
では参りましょう!
今回のゴール
今回はこういう状態ができるようにします。
エラーチェックの結果が表示されていますね。
サーバで処理をするファイルを用意する
前回お渡ししたscript.phpの中にこんな1行がありました。
url: "/src/server/threadInsert.php",
クライアントサイドからデータを渡すのは上記のファイルです。
なので、このファイルを用意します。
まずはsrcフォルダの中にserverフォルダをつくりましょう。
Visual Studio Code上でsrcフォルダを選択した状態で、新しいフォルダボタンをクリックします。
フォルダ名はserverです。入力したらEnterキーを押下します。
今回つくるファイルは全部で100行あるのですが、あえてファイルは渡さず、あなたに実装していただく形で進めようと思います。
ですので、まずはファイルをご用意いただきましょう。
今作ったserverフォルダを選択した状態で、新しいファイルボタンをクリックしてください。
ファイル名の入力枠が出るので「threadInsert.php」と入力してEnterキーを押しましょう。
はい、ファイルの用意はOKです!次から中身を作りこんでいきます。
実装内容を整理する
早速プログラムを書いていきたいのですが、その前にこのファイルでは何をやるのか?をざっくりとでいいので頭にインプットしてください。
このファイルでは・・・
・クライアント側からデータを受け取る
・受け取ったデータに不備がないかチェックする(今回はここまで)
・データを登録する
・処理結果をクライアント側に返す
の4つをやります。
処理を始める前の前提処理を実装する
ではですね、
このまっさらなファイルにPHPプログラムをゴリゴリと書いていきましょう。
先ほどの案内通りクライアント側からのデータを受け取りたいのですが、その前の前提処理をコーディングします。
プログラムを書くことをコーディングと言います。
以下のソースを書きましょう。手打ちかコピペかはあなたのやりやすい方にしてください(∩´∀`)∩
<?php
$webroot = $_SERVER['DOCUMENT_ROOT'];
/* --------------------- DBとの接続オブジェクトを取得 --------------------- */
include_once($webroot."/src/common/function.php");
$dbh = getDbh();
/* 文字化け防止 */
header("Content-type: text/plain; charset=UTF-8");
date_default_timezone_set('Asia/Tokyo');
下図のようになっていればOKです。
1行ずつ解説していきます。
$webroot = $_SERVER['DOCUMENT_ROOT'];
これはこれまでにも出てきましたね。絶対パスを使うためにルートフォルダまでのパスを取得しています。ルートフォルダというのは最上位フォルダのことです。
↑ここがルートフォルダです。
次にこの3行ですね。
/* --------------------- DBとの接続オブジェクトを取得 --------------------- */
include_once($webroot."/src/common/function.php");
$dbh = getDbh();
まず無駄に長いコメント行ですが、これは処理のセクションが分かりやすいようにこうしています。こんな長々とハイフンを連ねる必要はぶっちゃけないです(笑)。この後も何度か出てきます。
で、function.phpファイルを読み込んでいますね。このおかげで、これ以降function.phpファイルに書いた関数を呼び出すことができます。
その次の行が早速getDbh()という関数を呼んでいます。これはデータベースハンドラーを取得するという関数でして、function.phpに関数を用意してやる必要があります。
function.phpファイルを開いて、以下のソースコードを追加してください。
/**
* データベースハンドラー(接続オブジェクト)の取得
*
* return 接続オブジェクト
*/
function getDbh(){
$dsn='mysql:dbname=test;host=127.0.0.1';
$user='root';
$pass='';
try{
$dbh = new PDO($dsn,$user,$pass);
$dbh->query('SET NAMES utf8');
}catch(PDOException $e){
p('Error:'.$e->getMessage());
p('データベースへの接続に失敗しました。時間をおいて再度お越し下さい。');
die();
}
return $dbh;
}
またまた長いですね。これの説明は割愛します。データベースハンドラーとはプログラムからデータベースに繋ぐアイテム、くらいに捉えてください。
threadInsert.phpに戻りまして、前提処理の残りは以下のソースです。
/* 文字化け防止 */
header("Content-type: text/plain; charset=UTF-8");
date_default_timezone_set('Asia/Tokyo');
まずひとつは文字化けの防止です。明示的に指定することで、データベースに登録した文字列が化けることがなくなります。
文字化けというのは初耳の方もいらっしゃるかもしれませんが、文字には文字コードというのがありまして「SHIFT-JIS」とか「UTF-8」のことを言います。
この文字コードが環境間やシステム間で差異があると訳の分からない変換がされて判読不能な文字になることがあるんです。
そしてもうひとつの処理は、タイムゾーンを指定しています。この連載を始める前に動作確認してるとデータベースに登録される登録日・更新日が何時間かずれちゃってたんですよね・・・(´Д`)
なので、東京時刻に合わせるように明示的に指定しています。
前提処理はここまでです。次、いきましょう。
クライアントからデータを受け取る
以下のソースをthreadInsert.phpに追記してください。
/* ------------------------------ 変数に格納 ------------------------------ */
/* タイトル */
$title = $_POST['title'];
$title = htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
/* 名前 */
$name = $_POST['name'];
$name = htmlspecialchars($name, ENT_QUOTES, 'UTF-8');
/* 顔アイコン */
$icon = $_POST['icon'];
/* メッセージ */
$message = $_POST['message'];
$message = htmlspecialchars($message, ENT_QUOTES, 'UTF-8');
/* パス */
$passDefault = $_POST['pass'];
$pass = htmlspecialchars($passDefault, ENT_QUOTES, 'UTF-8');
$pass = hash("sha256",$pass);
下図のようになればOKです。
ここで前回の#6でクライアントサイドから投げたデータを受け取ります。
同じ処理を5つのデータ分やっているので、ひとつを例に挙げて解説しましょう。
/* タイトル */
$title = $_POST['title'];
$title = htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
script.phpのAjax通信の中でPOSTという通信方式を指定しました。ですので、「$_POST['']」という風にデータを取得しています。この書き方は決まり文句です。
角括弧の中に指定している文字列はもうおわかり、script.phpのここ
var bbsData = {
title : $('#title').val(),
name : $('#name').val(),
icon : $('#selectIconValue').val(),
message : $('#message').val(),
pass : $('#pass').val()
};
で指定したキー(コロンの左側)たちです。
$title = $_POST['title'];
で、取得したデータはこの後の処理で使うので、一旦変数に代入している、というわけです。この次は
$title = htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
こんな処理をしています。
これはですね、セキュリティ的によくない文字がユーザの入力に含まれていた場合にそれを無害化する効果があります。
ただ、今は実際に検証していただけるほど開発が進んでいないので、折を見てまた解説しますね。(→#13で解説の準備)
顔アイコンの場合はユーザは入力ではなく選択するだけなので、この処理はつけていません。
/* 顔アイコン */
$icon = $_POST['icon'];
ただ受け取っているだけです。
パスワードを見てみましょう。
/* パス */
$passDefault = $_POST['pass'];
$pass = htmlspecialchars($passDefault, ENT_QUOTES, 'UTF-8');
$pass = hash("sha256",$pass);
パスワードは他の項目と同様の処理を行った後、最後に
$pass = hash("sha256",$pass);
という処理をしています。これはhash()(はっしゅ)関数と言いPHPが標準で提供しているものです。第二引数に渡した値を第一引数の方法でハッシュ化してくれます。
ここまでの説明で、関数は引数を受け取るということは伝えてきましたが、いずれもその個数は1つでした。(あ、strstr()は2つでしたね汗)
実は関数側の書き方次第で引数を複数受け取ることができます。このハッシュ関数は2つ受け取っていますね。
ハッシュ化とは与えられた文字列を全く別の文字列に変換することです。これはセキュリティ対策でやっています。入力されたパスワードをそのまま保管しておくことは大変危険なんです。
パスワード自体の用途は後続記事のどこかでお伝えします。(→# )
これでデータの受け取りもクリアしました。次行きましょう。
受け取ったデータに不備がないかチェックする
前回のscript.phpの中には入力に不備があった場合、それをユーザに伝えてあげるという処理がありました。
今回は実際に入力に不備がないかをチェックします。
以下のソースをthreadInsert.phpに追記してください。
/* ------------------------------ チェック処理 ------------------------------ */
/* スペースだけの投稿防止 */
$blank = array(' ',' ');
$checkTitle = str_replace($blank, "", $title);
$checkName = str_replace($blank, "", $name);
$checkMessage = str_replace($blank, "", $message);
$checkPass = str_replace($blank, "", $pass);
if($checkTitle == ""){
p('noTitle');
return;
}
if($checkName == ""){
p('noName');
return;
}
if($icon == ""){
p('noIcon');
return;
}
if($checkMessage == ""){
p('noMsg');
return;
}
if($checkPass == ""){
p('noPass');
return;
}
下図のようになっていればOKです。
では見ていきましょう。
まず、先ほどから言っている入力の不備ですが、どのような不備を検出するかというと未入力になっているデータです。
ですので、なにかしらの入力があればOK、なにもなければNGとして扱います。また、スペースだけの投稿も意味を成さないのでNGとします。
以下のソースを見てください。
$blank = array(' ',' ');
$checkTitle = str_replace($blank, "", $title);
ここでは各項目が空白でないかを確認していくのですが、まず、ユーザが入力したテキスト内のスペースをすべて排除します。
そうすることでスペースだけの投稿もNGとして検出できるからです。
スペースの排除にはstr_replace()を使います。replaceは置き換えるという意味です。この関数は3つの引数を受け取っていますね。
str_replace('A', 'B', $c);
とした場合、$cというテキストの中にある'A'という文字列を'B'に置き換えることができます。
そして置き換え元に指定するテキストは配列を使うと一度に複数指定することができるんです。その配列を作っている箇所が
$blank = array(' ',' ');
ここです。ここでは全角スペースと半角スペースとを納めた配列を$blankという変数に代入しています。(blankは空白という意味)
「配列(はいれつ)」という言葉が唐突に出てきたと思います(>_<)ここでは割愛しますが、複数の要素を一つの変数にもつための入れもの、と解釈してください。
そして
$checkTitle = str_replace($blank, "", $title);
ここまでの説明を総合すると、これは$titleに入っている文字列から半角スペースと全角スペースを""に置き換える、という処理をしています。
この処理の結果、スペースを除去したテキストが$checkTitleに入るわけです。
この処理を
$checkName = str_replace($blank, "", $name);
$checkMessage = str_replace($blank, "", $message);
$checkPass = str_replace($blank, "", $pass);
このように残りの項目に対しても行っています。これでチェックの前準備は整いました。
そして・・・
if($checkTitle == ""){
p('noTitle');
return;
}
この処理です。if文はこれまでに見てきたので難なく理解できると思います。空文字でないかどうかをチェックしていますね。この評価式の結果がtrueだった場合、returnします。
returnはこれまで関数の最後に値を返す形で出てきましたが、上記ソースの様に何も値を指定せずに使うこともできるんです。
この場合、これ以降の処理を行わず呼び出し元に返ることになります。
さらに'noTitle'というテキストを出力していますね。これが呼び出し元に返ります。ここで、前回の処理内容とリンクするでしょう。
script.phpには
if(data =='noTitle'){
alert('タイトルが入力されていません。');
このような記載がありました。これでサーバ側で行ったチェック内容をクライアント側に返し、ユーザに伝えるという一連の流れが完成します。
タイトルを例に伝えましたが、他の項目でも同様のことを行っています。アイコンだけはユーザに選択させる項目なのでテキストの置き換え処理を行っていません。
これでチェック処理は終わりです。
では、お楽しみの動作確認をやりましょう!
エラーチェックの動作確認
エラーチェックの動作確認を行うのですが、その前に2つやっていただきたいことがあります。
まずひとつめ。
現在threadInsert.phpを絶賛編集中ですが、処理を動かすには一旦完成した状態にしないといけません。
末尾の行に「?>」を追加してください。
ありがとうございます。
もうひとつ。
前回(#6)、クライアント側からのデータの送信にはjQueryを使うと伝えたのですが、jQueryを使うにはjQueryを使うためのライブラリを読み込まないといけないんです。
partsフォルダ内のhead.phpを開いていただけますでしょうか。
今こんな状態かと思います。ここにですね、以下のソースを追加してください。追加する場所はheadの閉じタグの前です。
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
下図のようになればOK。
これで Ctrl + S で保存していただきまして、ブラウザに戻りましょう。
ブラウザは今head.phpに追加した情報を読み込む必要があるので、リロードしてください。
そうしましたら、何も入力せずに、
入力ボタンをクリックしましょう。
このようになればOKです!
今度はタイトルを入力した状態で、送信ボタンを押してみましょう。
タイトルに対するエラーが消えるはずです。
今回は~~~ここまでっ!!ヾ(≧▽≦)ノ
うまく動作しない方へ(2020/10/06 追記)
ここのエラーチェックが上手く動作しない方はこの項をご確認ください。
サーバ側で無事にifの条件にはまり、「noTitle」や「noName」の値をdataに入れてクライアント側(script.php)に帰ってくるのに、なぜかエラーメッセージのポップアップがでない・・・という問い合わせを数件いただいております。
このケースでは「no~」の文字列の前後に不要な文字がなぜか付与されているケースが考えられます。(原因はわかりません)
その場合・・・
/**
* Ajax通信が成功した場合に呼び出されるメソッド
*/
success: function(data,dataType){
data = data.trim();
if(data =='noTitle'){
alert('タイトルが入力されていません。');
}else if(data =='noName'){
alert('名前が入力されていません。');
このようにdata = data.trim();というソースを追加してください。
これはいわゆるトリム処理というものでして、dataに入っている文字列の言前後についているスペースやタブ文字、改行文字を削除する処理をしています。
削除したらそれを再度dataに代入し、以降の評価で使用する、という流れです。
この方法で上手くいけばdata = data.trim();のソースはそのままにしておいてください。
よろしくお願いします。
バグフィックス --2021/2/6追記--
「バグフィックス」というのはコンピュータープログラムのバグ(不具合や誤り)を修正することです。
当マガジンをご購入いただいたユーザ様からバグのお知らせをいただきまして、調査・確認しましたところ、バグの存在が判明しました。
(#16にてDragon様、ありがとうございました。)
本記事でのお届け分が該当しますので、お手数おかけしますが、これから説明する手順を実施ください。
まず、パスワードだけを空欄にして送信してもらえますか。おそらくエラーチェックに引っかからないと思います。その原因はですね、
/* パス */
$passDefault = $_POST['pass'];
$pass = htmlspecialchars($passDefault, ENT_QUOTES, 'UTF-8');
$pass = hash("sha256",$pass);
クライアント側からパスを取得して、その直後にハッシュ化処理をしています。このハッシュ化処理は対象の文字列(ここでは$pass)が空っぽでも、実行して適当な文字列を生成してしまうんですね。
その結果、入力が空でも$passに入る値は空ではなくなるんです。
そして、その後、39行目で
$checkPass = str_replace($blank, "", $pass);
チェック対象の文字列に$passを使ってます。
これを次のように変更してください。
$checkPass = str_replace($blank, "", $passDefault);
$passを$passDefaultに変更しました。これをすることで、ハッシュ化前の純粋な入力値をチェックにかけることができます。
これでパスだけを未入力で実行するとちゃんとエラーとして弾きます。
今後の説明では当該箇所が$passDefaultではなく当初の$passのままかもしれませんが、ご容赦ください・・。
よろしくお願いします。(2021/2/6)
おわりに
今回はエラーチェックを実装しました。
ユーザによる入力が不十分だとシステムが想定通りに動かないことがあります。そういう事態を避けるためにエラーチェックはとても重要です。
残るはデータベースへの登録のみです。次は登録をやりましょう。
甘いもの食べてゆっくり休んでください。
今回もありがとうございました(∩´∀`)∩。