LoginSignup
472
408

More than 3 years have passed since last update.

PHP+MySQLでポートフォリオ作成

Last updated at Posted at 2019-06-09

記事の概要

私が作成したポートフォリオの解説です。
作った背景から、作成手順、機能、工夫したところ、課題を
まとめました。

背景

私は愛知県名古屋市東山を拠点とする社会人バンドサークルに所属しています。
そこではサークルメンバー同士でバンドを組み、定期的にライブを開催します。

この夏のライブでは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を入力することで、開催するライブを登録することができます。

live_insert.gif

  • メンバー登録

名前とメンバーIDを登録することでメンバー登録ができます。
一度登録したら、そのIDで複数のバンドに登録することができ、
バンドやライブを消去しても、メンバーのデータは消えません。

member_insert.gif

  • バンド登録

バンド名、出演順、持ち時間、バンドIDを入力することでバンド登録をすることができます。
バンドはライブと関連し、ライブが親データとなっており、ライブが消去されればバンドも消去されます。

band_insert.gif

  • バンドメンバー結成

バンドに参加するメンバーのIDを入力することにより、
バンドメンバーを登録できます。
もちろんバンドが親データとなっており、
バンドが消えたりライブが消えたら、
「メンバー結成データ」のみが消去されます。
メンバー情報自体は消去されません。

formation.gif

  • 各項目の削除

ライブ定府、バンド情報、メンバー情報、バンドメンバー結成情報を
それぞれごとに削除することができます。
前述した通り、親データが消えた場合、それに所属する子データも消去されます。

開発手順

  1. 要件定義
  2. 環境選定
  3. データベース設計
  4. GUI設計
  5. コーディング
  6. 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

スクリーンショット 2019-06-08 22.31.51-min.png

4.GUI設計

データベースの仕様が決まったところで、
そのデータをどう入力して、どう出力するか、を
簡易的な画面遷移図にまとめました。

使用ソフト:PlantUML Salt

スクリーンショット 2019-05-26 16.12.07-min.png

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や文字数相違は網を張れるが、
意図しない入力形式が入力できてしまう。

スクリーンショット 2019-06-09 22.15.21.png

また、現状はPOST受信時のみにバリデーションを
行なっているが、データベース側でもチェックをした方が良い。

フロント側のチェックは、トラフィックを発生させずにチェックする。
バック側のチェックは、直POSTによる不正データ登録防止。

Cookieを使用していない

Webアプリケーションのポートフォリオとして、
基本的な技術であるCookieを使用していない。
あくまでポートフォリオとしての欠陥であるが、
著者に使い方が身についていないという事。

セッションを使用していない

上記と同様の理由。
このWebアプリにはログイン機能が無い為、
必要無かった。
しかしセッションハイジャック等のリスクもあるので
これからバックエンドエンジニアとして成長するために
必ず習得する必要がある。

CSRF対策が為されていない

セッションを用いたトークンを使用していないので
CSRF対策が出来ていない。

ボタンが大きい

バンド管理画面はデータ表示領域よりボタンの方が大きく
無駄な配置となっている。
フロントエンドを出来るだけ簡素に作る為
CSSフレームワークに頼ったが、
ユーザビリティを向上させるために
CSSで調整する方法を今後身に付けたい。

ボタンの位置がおかしい

テーブル内のボタンの位置が上に寄っている。
CSSでの細かい修正が出来ていないため。

スクリーンショット 2019-06-07 20.35.01.png

スマホだと見にくい

表示が横長画面を前提として作られている。
このアプリの目的はバンドサークルのライブ運営を円滑にするため、
現場でスマホを用いて操作することが多くなると予測できる。
このままではスマホ上で操作しずらく、かえって不便。

ユーザーがIDを意識して操作しなければいけない

このWebアプリのデータベースの
各テーブルの一意キーは文字列IDの形式を取っている。
それにより、新規登録で、
ユーザーが普通は意識しないIDを入力したり、
一意キーの為に、重複しないよう気を使う必要がある。

自然キーに拘った理由は、「達人に学ぶDB設計徹底指南書」内で、
まともな理論家ならばサロゲートキーを推奨しない、とあり
それを鵜呑みにした結果である。
ユーザビリティを後回しにし、思考を柔軟に出来なかったことが要因。

参考文献

2019/6/10追記

GitHub晒します。
https://github.com/RinyuDrvo/soul_live_app

472
408
25

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
472
408