記事の概要
私が作成したポートフォリオの解説です。
作った背景から、作成手順、機能、工夫したところ、課題を
まとめました。
背景
私は愛知県名古屋市東山を拠点とする社会人バンドサークルに所属しています。
そこではサークルメンバー同士でバンドを組み、定期的にライブを開催します。
この夏のライブでは1日12組の3days、計36組ものバンドが熱演を繰り広げる予定です。
このライブのおよそ3ヶ月前のミーティングで、出演バンドを集計するのですが、
今まで紙ノートにバンド名と出演メンバー、出演日程を記録しており、
ホームページでの情報公開もただ文章で並べただけのものでした。
それではメンテナンス性が悪く、簡単に編集が出来る状態になっていないことから、
いつでもどこでもライブ出演バンドを登録し表示出来るWebアプリを作ろうと考えました。
また、現職を退職し、Web系開発企業のバックエンドエンジニアになろうと考えていたので、
このアプリを転職活動のポートフォリオとすることに決めました。
目的
- PHPを用いたWebアプリケーション開発経験を積む
- フルスクラッチ開発によってWebアプリの基本的な構成、動作を知る
- Web系企業さんの即戦力になれる技術を身につける(そこまでは厳しかった)
- 作成アプリによってバンドサークルのライブ運営を円滑化する
スペック
- 言語
- PHP 7.1.23
- DBMS
- MySQL 8.0.15
- CSSフレームワーク
- milligram
- 開発環境
- MacOS Mojave 10.14.5
- Apache 2.4.34
- バージョン管理
- Git 2.21.0
- 本番環境
- Heroku
主な機能
サークル内でのライブに出演するバンドを管理することができます。
- ライブ登録
ライブ名とライブIDを入力することで、開催するライブを登録することができます。
- メンバー登録
名前とメンバーIDを登録することでメンバー登録ができます。
一度登録したら、そのIDで複数のバンドに登録することができ、
バンドやライブを消去しても、メンバーのデータは消えません。
- バンド登録
バンド名、出演順、持ち時間、バンドIDを入力することでバンド登録をすることができます。
バンドはライブと関連し、ライブが親データとなっており、ライブが消去されればバンドも消去されます。
- バンドメンバー結成
バンドに参加するメンバーのIDを入力することにより、
バンドメンバーを登録できます。
もちろんバンドが親データとなっており、
バンドが消えたりライブが消えたら、
「メンバー結成データ」のみが消去されます。
メンバー情報自体は消去されません。
- 各項目の削除
ライブ定府、バンド情報、メンバー情報、バンドメンバー結成情報を
それぞれごとに削除することができます。
前述した通り、親データが消えた場合、それに所属する子データも消去されます。
開発手順
- 要件定義
- 環境選定
- データベース設計
- GUI設計
- コーディング
- Herokuデプロイ
1.要件定義
今回作成するアプリに必要な機能は
- ライブ登録
- バンド登録
- メンバー登録
の為、それらの情報が保存できるデータベースと、
データベースからユーザーが分かりやすいようにまとめて表示する
動的なビューの機能が必要となる。
また、ユーザーが登録〜削除まで行えるように、
各項目に対する登録画面、削除画面を設ける。
2.環境選定
学習当初はRuby、Ruby on Railsを学習していたが、
- 言語としての需要
- 初心者にとっての難易度
- Webアプリケーションの仕組みを1から作って理解する
という観点から見て、使用言語をPHPとしました。
DBMSは、PHPを調べた時に関連して取り上げられていることが多い、
情報量の多さを考慮し、MySQLの使用を決めました。
そもそもLAMP環境としての情報が多いですね。
なのでwebサーバーはApacheを使用。
ただ、上記環境はMacOSローカル上で構築しました。
Windowsでドットインストールやっていた時はVagrantを用いて
CentOS上で練習していたので、その時と違った環境でやりたかったのと、
単純にMacBookを買った嬉しさにより、そうなりました。
3.データベース設計
必要機能よりどんな情報を保存するか、
それらをどう関連付けて管理するか考えながら
データベース設計を行いました。
データベース設計のノウハウは、
書籍「達人に学ぶDB設計徹底指南書」にて学習。
正規化やバッドノウハウ、グレーノウハウを理解した上で
設計を進めれた事が、後工程のアプリ内でのSQL文発行にて
役立ちました。
ただ、下記のGUI設計で不便さに気づき、そこから立ち返って
修正することもありました。
そうなるとGUI設計も1からやり直しとなりましたが...
使用ソフト:WWW SQL Designer
4.GUI設計
データベースの仕様が決まったところで、
そのデータをどう入力して、どう出力するか、を
簡易的な画面遷移図にまとめました。
使用ソフト:PlantUML Salt
5.コーディング
設計が固まったところで、コーディングをスタート。
5.1データベース作成
ローカル環境のMySQLサーバに
専用ユーザ作成し、データベースを作成。
その後、テーブルを作成。
テーブル作成SQL
CREATE TABLE IF NOT EXISTS live(
live_id CHAR(7) PRIMARY KEY,
live_name VARCHAR(30)
);
CREATE TABLE IF NOT EXISTS band(
band_id CHAR(4) PRIMARY KEY,
live_id CHAR(7),
band_name VARCHAR(30),
performance_time VARCHAR(20),
performance_num INT
);
CREATE TABLE IF NOT EXISTS formation(
member_id CHAR(7),
band_id CHAR(4),
PRIMARY KEY(member_id,band_id)
);
CREATE TABLE IF NOT EXISTS member(
member_id CHAR(7) PRIMARY KEY,
member_name VARCHAR(20)
);
5.2 全体の形、フォームをHTMLで記述
GUI設計に従い画面を構築。
5.3 データベース接続確認
PDOを用い、データベースに接続(PDOオブジェクトの生成)。
try~catchを用い、成功しているかどうか判断。
安全性を考慮し、データベース情報は別ファイルに記述し、
require_onceで各画面に適用する。
GitHubへのpush時は、
.gitignoreにデータベース情報が書かれたファイルを記述し
公開されないようにした。
(一例)データベース接続部分
//DB設定読み込み
require_once __DIR__ . '/conf/database_conf.php';
//例外処理
try{
//DB接続
$db = new PDO("mysql:host=$dbServer;dbname=$dbName;charset=utf8","$dbUser","$dbPass");
$db ->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$db ->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
}catch (PDOException $e) {
echo 'データベースエラー発生:' . h($e->getMessage());
}catch (Exception $e){
echo 'エラー発生:' . h($e->getMessage());
}
5.4 PDOにてSQL文発行
入力、出力、削除、更新に必要なSQL文を
カラム名にプレースホルダーを代入する形で準備。
その後SQL文のプレースホルダーに値をバインドし、
クエリを実行。
(一例)バンドごとメンバー抽出部分
//SQL準備
$sql = "SELECT
member.member_name
FROM member
INNER JOIN formation
ON member.member_id=formation.member_id
INNER JOIN band
ON formation.band_id=band.band_id
WHERE band.live_id=:live_id AND band.band_id=:band_id";
$prepare = $db->prepare($sql);
//ライブIDでバインド
$prepare -> bindValue(':live_id',$live_id,PDO::PARAM_STR);
//バンドIDでバインド
$prepare -> bindValue(':band_id',$band_id,PDO::PARAM_STR);
//クエリ実行
$prepare->execute();
5.5 データベース内容表示
クエリを実行した結果を格納したオブジェクトを
foreachで1レコードずつHTMLのテーブル上に表示させていく。
(一例)メンバー一覧表示部分
<table>
<thead><th>メンバーID</th><th>名前</th><th></th></thead>
<tbody>
<?php
//SQL準備
$sql = 'SELECT * FROM member ORDER BY member_id';
$prepare = $db->prepare($sql);
//クエリ実行
$prepare->execute();
//メンバー名をひとつずつrowに設定
foreach ($prepare as $row) {
?>
<tr>
<td>
<!--メンバーID表示-->
<?= h($row['member_id']) ?>
</td>
<td>
<!--名前表示-->
<?= h($row['member_name']) ?>
</td>
<td>
<!--削除ボタン表示 POSTメソッドでmember_idを削除部分に渡す-->
<form method="POST">
<input type="submit" name="member_delete" value="delete">
<input type="hidden" name="member_id" value="<?= $row['member_id'] ?>">
</form>
</td>
</tr>
<?php
}
?>
</tbody>
</table>
5.6 HTTP通信
データベースへの入力や削除、更新部分はPOST、
データベースからの出力はGETを用いた。
(一例)ライブ追加部分と入力フォーム
try {
//DB接続
$db = new PDO("mysql:host=$dbServer;dbname=$dbName;charset=utf8","$dbUser","$dbPass");
$db ->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$db ->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
//追加ボタンが押されたら
if (isset($_POST['live_insert'])) {
//live_nameバリデーション
validation($_POST['live_name'],'ライブ名','');
//追加するライブ名を取得
$live_name = $_POST['live_name'];
//live_idバリデーション
validation($_POST['live_id'],'ライブID',7);
//追加するライブIDを取得
$live_id = $_POST['live_id'];
//SQL準備(新規ライブIDとライブ名をliveテーブルに追加)
$sql = "INSERT INTO live (live_id,live_name) VALUES (:live_id,:live_name)";
$prepare = $db -> prepare($sql);
//live_idに挿入する変数と型を指定
$prepare -> bindValue(':live_id',$live_id,PDO::PARAM_STR);
//live_nameに挿入する変数と型を指定
$prepare -> bindValue(':live_name',$live_name,PDO::PARAM_STR);
//クエリ実行
$prepare -> execute();
echo '<p>追加完了</p>';
}
} catch (PDOException $e) {
echo 'データベースエラー発生:' . h($e->getMessage());
}
catch (Exception $e){
echo 'その他エラー発生:' . h($e->getMessage());
}
<form method="POST">
<fieldset>
<div class="colomn">
<label for="liveName">ライブ名</label>
<input type="text" name="live_name" maxlength="30" id="liveName" placeholder="ライブ名">
<label for="liveID">ライブID</label>
<input type="text" name="live_id" maxlength="7" id="liveId" placeholder="(例)201901A"><br>
<p>半角英数字7文字で入力してください</p>
<p>入力例:201901A→(2019年1回目のライブのA日程)</p>
<p>ライブIDが被ると登録できません</p>
<p>前のページで他ライブのIDを確認してから入力してください</p>
<input type="submit" name="live_insert" value="add">
</div>
</fieldset>
</form>
5.7 XSS対策サニタイジング
htmlspecialchars関数を用いたサニタイジングを実施。
かさばるので関数化し別ファイルへ。
サニタイジング関数
function h($var){//htmlでエスケープ処理をする関数
//引数が配列だった場合はすべての要素にh()関数を実行する
if(is_array($var)){
return array_map('h',$var);
}
//引数に対しサニタイジング
else{
return htmlspecialchars($var,ENT_QUOTES,'UTF-8');
}
}
5.8 バリデーション
汎用的なバリデーションをまとめ、関数化し別ファイルへ。
意図しない入力値であれば、どの入力値がダメであったか表示できるよう、
入力値の分類を引数に指定できるようにした。
バリデーション関数
//汎用バリデーション関数
//第一引数:バリデーション対象のデータ(char) 第二引数:データの名前(char) 第三引数:文字列の長さ(int)
function validation($data,$data_name,$data_len){
//汎用バリデーション
if(!isset($data) || !is_string($data) || $data === ''){
//エラーをExceptionクラスに投げる
throw new Exception($data_name . 'が不正な値です');
}
//データの文字数が指定されていれば
if($data_len !== ''){
//文字数バリデーション
if(strlen($data) !== $data_len){
//エラーをErrorExceptionクラスに投げる
throw new Exception($data_name . 'の長さが不正です');
}
}
}
//live_idバリデーション
validation($_POST['live_id'],'ライブID',7);
//追加するライブIDを取得
$live_id = $_POST['live_id'];
5.9 CSSフレームワーク実装
超軽量CSSフレームワーク「milligram」を使用し、
フロントエンドは最小の労力での完成を目指した。
<link rel="stylesheet" href="//cdn.rawgit.com/milligram/milligram/master/dist/milligram.min.css">
6.Herokuデプロイ
本番環境としてのプラットホームは
Ruby on Railsチュートリアルで使い方を習得したHerokuを使用。
GitHubとの連携機能があった為、
GitHubにpushしたリポジトリをそこからHerokuにデプロイ。
#課題点
一区切りついたところで完成としましたが、
課題点は多数あるのでリストアップします。
今後の学習の指標とします。
###バリデーションが不十分
現状の汎用的なバリデーションのみだと
nullや文字数相違は網を張れるが、
意図しない入力形式が入力できてしまう。
また、現状はPOST受信時のみにバリデーションを
行なっているが、データベース側でもチェックをした方が良い。
フロント側のチェックは、トラフィックを発生させずにチェックする。
バック側のチェックは、直POSTによる不正データ登録防止。
###Cookieを使用していない
Webアプリケーションのポートフォリオとして、
基本的な技術であるCookieを使用していない。
あくまでポートフォリオとしての欠陥であるが、
著者に使い方が身についていないという事。
###セッションを使用していない
上記と同様の理由。
このWebアプリにはログイン機能が無い為、
必要無かった。
しかしセッションハイジャック等のリスクもあるので
これからバックエンドエンジニアとして成長するために
必ず習得する必要がある。
###CSRF対策が為されていない
セッションを用いたトークンを使用していないので
CSRF対策が出来ていない。
###ボタンが大きい
バンド管理画面はデータ表示領域よりボタンの方が大きく
無駄な配置となっている。
フロントエンドを出来るだけ簡素に作る為
CSSフレームワークに頼ったが、
ユーザビリティを向上させるために
CSSで調整する方法を今後身に付けたい。
###ボタンの位置がおかしい
テーブル内のボタンの位置が上に寄っている。
CSSでの細かい修正が出来ていないため。
###スマホだと見にくい
表示が横長画面を前提として作られている。
このアプリの目的はバンドサークルのライブ運営を円滑にするため、
現場でスマホを用いて操作することが多くなると予測できる。
このままではスマホ上で操作しずらく、かえって不便。
###ユーザーがIDを意識して操作しなければいけない
このWebアプリのデータベースの
各テーブルの一意キーは文字列IDの形式を取っている。
それにより、新規登録で、
ユーザーが普通は意識しないIDを入力したり、
一意キーの為に、重複しないよう気を使う必要がある。
自然キーに拘った理由は、「達人に学ぶDB設計徹底指南書」内で、
まともな理論家ならばサロゲートキーを推奨しない、とあり
それを鵜呑みにした結果である。
ユーザビリティを後回しにし、思考を柔軟に出来なかったことが要因。
参考文献
##2019/6/10追記
GitHub晒します。
https://github.com/RinyuDrvo/soul_live_app