簡易メモ帳の作り方
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('<br>','<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('<br>','<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;
}
改良後のプログラムは、こちらよりダウンロード出来ます。