ソースコードで学ぶWebプログラミング

簡易メモ帳の作り方

Webプログラミングの基本となる処理を学ぶために、実際にプログラムを作成してみます。コーディングを行なう言語はPHPとします。

学べること

・入力データの受け取り方 ・データの変換、加工の仕方 ・データの出力の仕方

簡易メモ帳の要件定義

プログラムを作成するために、まず要件定義を行います。要件定義とは、システム開発を行なう上で必要となる工程で、制作するものに必須の機能を定義しておくことです。

今回は「簡易メモ帳」に必須の機能をピックアップします。

1.データ「テキスト入力、日時」を受け取る 2.データ「テキスト入力、日時」を保存する 3.保存したデータ「テキスト入力、日時」を表示する 4.指定したデータを削除する

機能面では、すべて網羅できているようです。さらに詳細を決めていきます。

・データ「テキスト入力」は、複数行テキスト ・データ「日時」は、「2018/01/01 00:00」の書式(YYYY/mm/dd HH:ii) ・保存は、テキストファイルとして保存 ・表示は、日時の新しい順に表示 ・削除は、チェックボックスでチェックしたものを削除

これで制作するプログラムの仕様は決定できたように見えます。しかし実は見えない仕様が発生していて、これだけでは削除が行えません。削除を行なうには、対象データを特定できる番号が必要になります。そのため、データ毎に「ID」を割り当てる必要があります。

・データ「ID」は、メモ毎に異なる英数字

まとめると下記になります。

1.データ「ID、テキスト入力、日時」を受け取る ・データ「テキスト入力」は、複数行テキスト ・データ「日付」は、「2018/01/01 00:00」の書式(YYYY/mm/dd HH:ii) ・データ「ID」は、メモ毎に異なる英数字 2.データ「ID、テキスト入力、日時」を保存する ・保存は、テキストファイルとして保存 3.保存したデータ「ID、テキスト入力、日時」を表示する ・表示は、日時の新しい順に表示する 4.指定したデータを削除する ・削除は、チェックボックスでチェックしたものを削除

簡易メモ帳の要件をコード化する

それではコーディングに移ります。

1.データ「ID、テキスト入力、日時」を受け取る

扱うデータの中で、ユーザから受け取るのはテキスト入力だけです。
IDは、関数uniqid() で取得することができます。
日時は、関数date() で取得することができます。

<?php /* ユニークなID */ $id = uniqid(); /* 日時 */ $date = date('Y/m/d H:i'); /* テキスト */ $text = ''; /* $_POST の中にPOST入力が入る */ if (!empty($_POST['text'])) { $text = $_POST['text']; } ?> <form method="post"> <textarea name="text"></textarea> <input type="submit" value="記録"> </form>

やっている事

・uniqid() でユニークな英数字を作成 ・date() で現在日時を指定フォーマットで作成 ・テキスト入力用変数 $text を用意 ・POST送信にキー text があり、入力がある時(空欄で無い時)、$text に代入 ・POST送信を行うHTMLタグを記述 ・$_POST にキー text で入力されるように name要素を text に

押さえるべき要点としては、POST送信されたデータは連想配列 $_POST に自動的に入力される点、 キー名はHTMLタグで制御する点が挙げられます。

続いて保存処理に移ります。

2.データ「ID、テキスト入力、日時」を保存する

今回のシステムでは、テキストファイルとして保存します。 保存するファイル名を決めて、定数にしておきます。後でファイル名を 変更したくなった場合に、一箇所の変更のみで済みます。

テキストファイルは、一行毎に分けて処理するため、一行に一つのデータとする必要があります。 つまり複数行テキストの入力に対して、改行をデータ末尾の一回のみにする変換が必要です。

改行は「\r\n」「\r」「\n」の3パターンがあるため統一し、改行以外のかたちに変換します。 今回はHTMLの改行タグ<br>にします。

またデータを保存する際、各データを分けるため、区切り文字を決めておきます。 区切り文字は、保存対象のデータ内に出現しない文字にします。 今回はタブ(\t)にします。また念のため、入力データからタブを除去しておきます。

<?php /* 保存ファイル名 */ define('SAVE_NAME','memo.txt'); /* ユニークなID */ $id = uniqid(); /* 日時 */ $date = date('Y/m/d H:i'); /* テキスト */ $text = ''; /* $_POST の中にPOST入力が入る */ if (!empty($_POST['text'])) { $text = $_POST['text']; /* 改行コードの統一 */ $text = str_replace("\r\n","\n",$text); $text = str_replace("\r","\n",$text); /* 改行コードを改行タグに */ $text = str_replace("\n","<br>",$text); /* 区切り文字を除去 */ $text = str_replace("\t","",$text); /* 新規に登録するデータ */ $line = $id."\t".$date."\t".$text."\n"; /* 保存データが無い時、作成 */ if (!file_exists(SAVE_NAME)) touch(SAVE_NAME); /* 保存済データを読込 */ $lines = file_get_contents(SAVE_NAME); /* 新規データの後ろに保存済データを保存 */ file_put_contents(SAVE_NAME, $line.$lines); } ?> <form method="post"> <textarea name="text"></textarea> <input type="submit" value="記録"> </form>

やっている事

・define() で保存ファイル名を定数として定義 ・str_replace() で文字列内の特定の文字を別の文字に置き換え ・受け取った各データを「.」と「区切り文字」で結合して一行の文字列に ・file_get_contents() で 保存済データ を取得 ・file_put_contents() で 新規データ+保存済データを結合したもの を保存

押さえるべき要点は、新規データの後ろに保存済データを結合することで、 新着データを先頭に持ってくることが出来る点です。 表示時に並び替えを行う必要がなくなり、コーディングを少なく出来ます。

次は表示処理です。

3.保存したデータ「ID、テキスト入力、日時」を表示する

表示は、保存していたデータをHTMLタグに埋め込むかたちで出力することになります。

保存データは出力用の前処理時に一旦、連想配列に入力します。

前処理時に直接表示させることも出来ますが、その場合は表示処理内に前処理が含まれることになります。 表示ファイル(フレームワークのビュー)と処理ファイル(フレームワークのモデルやコントローラ)の 分離が難しくなるため、分ける方法で覚えていく方が後で役に立ちます。

<?php /* エスケープ関数 */ function h($v){ return htmlspecialchars($v, ENT_QUOTES, 'UTF-8'); } function eh($v){ echo h($v); } /* brタグの有効化 */ function br2tag($v){ /* エスケープされた<br>を戻す */ return str_replace('&lt;br&gt;','<br>',$v); } /* 保存ファイル名 */ define('SAVE_NAME','memo.txt'); /* ユニークなID */ $id = uniqid(); /* 日時 */ $date = date('Y/m/d H:i'); /* テキスト */ $text = ''; /* 保存データが無い時、作成 */ if (!file_exists(SAVE_NAME)) touch(SAVE_NAME); /* 保存済データを読込 */ $lines = file_get_contents(SAVE_NAME); /* $_POST の中にPOST入力が入る */ if (!empty($_POST['text'])) { $text = $_POST['text']; /* 改行コードの統一 */ $text = str_replace("\r\n","\n",$text); $text = str_replace("\r","\n",$text); /* 改行コードを改行タグに */ $text = str_replace("\n","<br>",$text); /* 区切り文字を除去 */ $text = str_replace("\t","",$text); /* 新規に登録するデータ */ $line = $id."\t".$date."\t".$text."\n"; /* 新規データの後ろに保存済データを追加、更新 */ $lines = $line.$lines; file_put_contents(SAVE_NAME, $lines); } /* 出力用の変数 */ $DATA = array(); /* 出力用に代入 */ foreach(explode("\n",$lines) as $line){ /* データ仕様に合わない場合、次へ */ if( strpos($line,"\t")===false ) continue; /* 区切り文字でデータを分離 */ list($id,$date,$text) = explode("\t",$line); $DATA[] = array( 'id'=>$id, 'date'=>$date, 'text'=>$text ); } ?> <table> <?php foreach($DATA as $d): ?> <tr> <td><?php eh($d['date']); ?></td> <td><?php echo br2tag( h($d['text']) ); ?></td> </tr> <?php endforeach; ?> </table> ?> <form method="post"> <textarea name="text"></textarea> <input type="submit" value="記録"> </form>

やっている事

・h() でブラウザ表示用のエスケープ処理 ・eh() でエスケープした文字列を表示 ・br2tag() でエスケープした改行タグを有効化 ・file_get_contents() の保存済データ読込の処理位置を変更 ・foreach() で保存済データを改行コード「\n」で分けて、繰り返し処理 ・foreach() 内で区切り文字「\t」が無い場合、次データ処理へ移行 ・foreach() 内で区切り文字「\t」で各データを取り出し ・foreach() 内で表示用変数 $DATA に連想配列で代入 ・表示箇所の foreach() で表示用変数から目的の値を取り出し

押さえるべき要点として、保存済データ読込の処理位置を変えていますが、 表示するための読込、保存するための読込の2種類の処理が必要となるためで、 処理位置を変える事で兼用できるようにしています。

また保存済データの処理時に、区切り文字「\t」が無い場合に処理を飛ばしていますが、 これはデータが壊れた場合など、データが仕様に合わない場合の安全対策となります。

最後は削除処理です。

4.指定したデータを削除する

これまでにプログラムに「データを保存する」処理と「データを表示する」処理が実装されています。 ここに「データを削除する」処理を追加する訳ですが、今回は処理を分ける方法を考えます。

・「表示する」は、常に行う ・「保存する」は、データが投稿された時 ・「削除する」は、データIDが指定された時

処理分け(フレームワークのルーティング)は、複数ファイルでの処理を前提として、URLで行うことが多いですが、 今回は一つのファイルのため、データが入力されたか否かで判定します。

<?php /* エスケープ関数 */ function h($v){ return htmlspecialchars($v, ENT_QUOTES, 'UTF-8'); } function eh($v){ echo h($v); } /* brタグの有効化 */ function br2tag($v){ /* エスケープされた<br>を戻す */ return str_replace('&lt;br&gt;','<br>',$v); } /* 保存ファイル名 */ define('SAVE_NAME','memo.txt'); /* ユニークなID */ $id = uniqid(); /* 日時 */ $date = date('Y/m/d H:i'); /* テキスト */ $text = ''; /* 保存データが無い時、作成 */ if (!file_exists(SAVE_NAME)) touch(SAVE_NAME); /* 保存済データを読込 */ $lines = file_get_contents(SAVE_NAME); /* $_POST の中にPOST入力が入る */ if (!empty($_POST['text'])) { $text = $_POST['text']; /* 改行コードの統一 */ $text = str_replace("\r\n","\n",$text); $text = str_replace("\r","\n",$text); /* 改行コードを改行タグに */ $text = str_replace("\n","<br>",$text); /* 区切り文字を除去 */ $text = str_replace("\t","",$text); /* 新規に登録するデータ */ $line = $id."\t".$date."\t".$text."\n"; /* 新規データの後ろに保存済データを追加、更新 */ $lines = $line.$lines; file_put_contents(SAVE_NAME, $lines); /* $_POST['id'] に削除対象のIDが入る */ } elseif(isset($_POST['id']) && is_array($_POST['id'])) { $new = ''; foreach(explode("\n",$lines) as $line){ /* データ仕様に合わない場合、次へ */ if( strpos($line,"\t")===false ) continue; /* 区切り文字でデータを分離 */ list($id,$date,$text) = explode("\t",$line); /* IDが指定されていた時、除外 */ if(in_array($id,$_POST['id'])) continue; /* 新規保存データを作成 */ $new .= $line."\n"; } file_put_contents(SAVE_NAME, $new); /* 新規保存データで読み込んだ変数を更新 */ $lines = $new; } /* 出力用の変数 */ $DATA = array(); /* 出力用に代入 */ foreach(explode("\n",$lines) as $line){ /* データ仕様に合わない場合、次へ */ if( strpos($line,"\t")===false ) continue; /* 区切り文字でデータを分離 */ list($id,$date,$text) = explode("\t",$line); $DATA[] = array( 'id'=>$id, 'date'=>$date, 'text'=>$text ); } ?> <?php if(count($DATA)): ?> <form method="post"> <table> <?php foreach($DATA as $d): ?> <tr> <td><?php eh($d['date']); ?></td> <td><?php echo br2tag( h($d['text']) ); ?></td> <td><label><input type="checkbox" name="id[]" value="<?php eh($d['id']); ?>">削除</label></td> </tr> <?php endforeach; ?> </table> <input type="submit" value="メモを削除"> </form> <?php endif; ?> <form method="post"> <textarea name="text"></textarea> <input type="submit" value="記録"> </form>

やっている事

・isset() && is_array() で削除対象のIDが指定されているか判定 ・if(条件) continue; で条件時は保存データに追加しない事で削除 ・chexkboxタグのnameを id[] で複数の選択受渡しを可能に

押さえておくべき要点は、PHPプログラムへ複数の値を渡したい場合に、name要素を キー[] のように [] を追加する事です。 PHP側では、キー の値が配列となっています。

プログラム完成後のカスタマイズ

以上でプログラムの機能は完成しました。 ただ実際に動かしてみると、気になるところがあります。

・見栄えが良くない ・ブラウザの再読み込みを行うと確認ウィンドウが表示される

見栄えの変更は css で行います。 今回はプログラムの学習のための記事なので、変更タグだけを載せておきます。

<style> table { width: 100%; } form { margin: 50px auto; width: 80%; } input[type='submit'] { display: block; margin: 20px auto; padding: 5px; width: 50%; } textarea { height: 100px; width: 100%; } td { border-bottom: 1px solid #333; } tr:first-child td { border-top: 1px solid #333; } .c2 { width: 160px; } .c3 { width: 70px; } </style>

再読み込みについては、POST送信後の表示のため、更新を行うと再度投稿が行われます。 これを防ぐには、$_GETアクセスの状態にすれば良いので、POST処理後に同URLへ遷移させます。

if ($_SERVER['REQUEST_METHOD']=='POST') { /* POST時の処理を記述 */ /* 最後に同じページへリダイレクト */ header('Location: '.$_SERVER['SCRIPT_NAME']); exit; } 改良後のプログラムは、こちらよりダウンロード出来ます。