【PHP7】口コミサイト(掲示板)を構築する方法【愛のある解説です】
PHPで口コミサイトを作る方法です。
タイトルでは、口コミサイト(掲示板)と書きましたが、その理由は口コミサイトも掲示板も仕組み自体は同じである為です。なので、今回まとめるコードをマスターしたら、掲示板サイトや口コミサイトが作れるようになります。
世の中には分かりやすくプログラミングを解説しているサイトが少ない気がしているので、この記事がこれからプログラミングを勉強する方の参考になれば幸いです。
愛をこめて丁寧に解説したので、「いっちょやってみるか〜」って方はぜひご覧ください。わからない部分とかはメッセージいただけたら答えます\(^o^)/
» 完成版のコードをDLする(github)
もくじ
- データベース設計をする
- データベースを作る
- データベースの外部キー制約(FOREIGN KEY)を設定する
- データベースのリレーションを確認する
- フロントエンドの設計をする
- フロントエンドを作る
- バックエンドとフォルダ設計をする
- PHPでHTMLをテンプレート化する
- DBにテスト用データを入れる
- トップページを静的から動的にする
- 口コミ詳細ページを動的にする
- 会員登録機能を作る
- ログイン機能を作る
- 口コミの投稿機能を作る
上記もくじは僕が実際につくっていった手順でまとめています。
データベース設計をする
こんな感じです。reviewが口コミ、productsが商品、usersがユーザーになります。
» マインドマップを見る | MyMindNode
※余談:使っているマインドマップツール
MindNodeという最高に便利なアプリ使ってます。ちょっと高い(3,600円)けど持っておいて損しません。
» MindNode 2 – Delightful Mind Mapping
データベースを作る
DB名:kuchikomi(utf8_general_ci)
会員登録するユーザー用のDB
CREATE TABLE `users` ( `user_id` int(8) NOT NULL, `user_name` varchar(30) NOT NULL, `user_email` varchar(255) NOT NULL, `user_password` varchar(255) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
商品名のDB
CREATE TABLE `products` ( `product_id` int(8) NOT NULL, `product_name` varchar(255) NOT NULL, `product_description` text NOT NULL, `product_cat` int(8) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
product_catはなくてもOKですが、商品数が増えてきたときにカテゴリ分けできたほうが便利なので入れています。
口コミ投稿用のDB
CREATE TABLE `reviews` ( `review_id` int(8) NOT NULL, `review_comment` text NOT NULL, `review_date` datetime NOT NULL, `review_product_id` int(8) NOT NULL, `review_user_id` int(8) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
データベースの外部キー制約(FOREIGN KEY)を設定する
外部キー制約(FOREIGN KEY)の意味は下記のとおり。
外部キー制約とは、テーブルの指定したカラムに格納できる値を他のテーブルに格納されている値だけに限定するものです。
今回のケースだと、外部キー制約(FOREIGN KEY)は下記2つとなります。
- review_product_id
- review_user_id
マインドマップで言うところの、矢印が他のテーブルに向いているから、外部キー制約が必要って考えるとわかりやすい。
Relation viewをクリックする
Relation viewをクリックする前に。reviewのテーブルを選択しておいてください。
review_product_idの外部キー制約
ON DELETE CASCADEとON UPDATE CASCADEがあります。
CASCADEとは引き継ぐって感じの意味なので、1つ目は、『product_idがアップデートor削除されたときにreview_product_idもアップデートor削除される』という意味です。
2つ目は、『user_idがアップデートされたときに、review_user_idはアップデートする』また、『user_idが削除されたときに、review_user_idは削除しない』という意味です。ユーザー登録を削除されたときに、投稿済みのコメントは残しておきたいので、このようにしています。
※Columnの選択で、review_product_idとreview_user_idが表示されない場合
review_product_idとreview_user_idをチェックしたままの状態で『index』部分をクリックしてください。
データベースのリレーションを確認する
phpmyadminを使います。メニューバーからDesignerを選択してください。
上記のようになっていればOKです。マインドマップと組み合わせるとわかりやすいかなと。これでDB設計が完了。
フロントエンドの設計をする
まずは静的サイトにてコーディングしていきます。作成するページは合計で5ページです。
- トップページ(商品一覧):index.php
- 口コミ詳細ページ:detail.php
- 会員登録ページ:signup.php
- ログインページ:login.php
- 商品カテゴリ別の一覧ページ:detail.php?id=1
商品カテゴリ別の一覧ページは、パラメータ付与で作ります。また、ちょうざっくりですが、モックアップも作りました。
トップページ
口コミ詳細ページ
» 口コミサイトの口コミ詳細ページ/Moqups
このモックアップを元にコーディングしていきます。
※余談:使っているモックアップツール
Moqupsというツール(月額1,000円)を使っています。直感的にモックアップ作れるので便利です。デザイナーさんとかにイメージ共有するときによく使います。
» Online Mockup, Wireframe & UI Prototyping Tool · Moqups
フロントエンドを作る
ここは簡単なのでスクショのみ。コードも配布します。
» コードのDL(github)
トップページ
口コミの詳細ページ
会員登録ページ
ログインページ
バックエンドとフォルダ設計をする
まずバックエンドの機能としては下記2つのみ。めっちゃシンプルです。
- 会員登録機能
- 口コミの投稿機能
フォルダ設計は下記のとおり
/public/ :ユーザーが接続するフォルダ /public/views/ :フロントエンドのテンプレート /app/functions :バックエンド機能 /app/config/database.php :DB接続
/publicフォルダ以外はアクセス遮断します。
セキュリティ対策のためです。この点は話がちょっとずれるので別記事にしました。
» MAMPのバーチャルホスト設定:開発用のドメイン登録とhosts設定
PHPでHTMLをテンプレート化する
とてもかんたん。ヘッダーから順に処理していきましょう。
ヘッダーを外部ファイル化する
新規作成ファイル:/piblic/view/header.php
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>PHPの口コミサイト</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.0/css/bootstrap.min.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.0/css/bootstrap-theme.min.css">
</head>
<body>
<div class="container">
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">PHPの口コミサイト</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
<li><a href="#">カテゴリ①</a></li>
<li><a href="#">カテゴリ②</a></li>
<li><a href="#">カテゴリ③</a></li>
<li><a href="login.php">ログイン</a></li>
<li><a href="signup.php">会員登録</a></li>
</ul>
</div><!--/.nav-collapse -->
</div><!--/.container-fluid -->
</nav>
</div> <!-- /container -->
<div class="container">
<div class="row">
フッターを外部ファイル化する
新規作成ファイル:/piblic/view/footer.php
</div>
</div>
<br><br><br>
<p class="col-xs-12 text-center">Copyright - <a href="#">PHPの口コミサイト</a>, 2016 All Rights Reserved.</p>
</body>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.0/js/bootstrap.min.js"></script>
</html>
index.php
を修正しつつ移動する
修正ファイル:index.php
修正ファイルの設置先:/public/index.php
<?php
include 'app/config/database.php';
include 'public/view/header.php';
?>
<div class="col-xs-12">
<h2>モテるようになる薬</h2>
<p>この薬を飲むと、美女からモテます。すごい薬です。モテるだけじゃなく健康にもなります。すごいです。すごいです。すごいです。すごいです。すごいです。すごいです。すごいです。</p>
<a href="detail.php">» 口コミを見る</a>
<br><br>
</div>
<div class="col-xs-12">
<h2>頭が良くなる薬</h2>
<p>この薬を飲むと、頭が良くなります。すごい薬です。勉強なんて不要です。すごいです。すごいです。すごいです。すごいです。すごいです。すごいです。すごいです。</p>
<a href="detail.php">» 口コミを見る</a>
<br><br>
</div>
<div class="col-xs-12">
<h2>痩せるかもしれない薬</h2>
<p>この薬を飲むと、たぶん痩せます。すごい薬です。たぶん痩せるので幸せですよね。多分すごいです。多分すごいです。多分すごいです。多分すごいです。</p>
<a href="detail.php">» 口コミを見る</a>
<br><br>
</div>
<?php
include 'public/view/footer.php';
その他のファイルも同様に修正する。ここは当たり前の部分なので省略します。
DBにテスト用データを入れる
現在はすべて静的ですが、DBからデータ出力できるようにしていきます。
商品のダミーデータ
尚、product_catは1にしていますが、ここはなんでもOKです。商品が増えた場合にカテゴリ別に出力できるようにしたいので、ただ入れてるだけです。
会員情報のダミーデータ
こんな感じ。取り急ぎのデータなのでセキュリティ面も考えてあとで削除してください。
口コミのダミーデータ
これは1つ目の商品の口コミデータです。
review_product_id
とproduct_id
が紐付いていることを忘れないでください。review_user_id
はとりあえず1(先程作成した、佐々木 太郎さん)としています。
トップページを静的から動的にする
編集ファイル:/public/index.php
<?php
include '../app/config/database.php';
include '../public/view/header.php';
include '../app/functions/product.php'; //これから作ります
?>
<?php
// 商品一覧データを取得
fetch_products($mysqli);
?>
fetch_products()
を作る
新規作成ファイル:/app/functions/product.php
下記の感じです。コメントあるので多分かんたんかと。
<?php
function fetch_products($mysqli) {
// productsのDBを選択する
$query = "SELECT
product_id,
product_name,
product_description
FROM
products";
$result = $mysqli->query($query);
if( !$result ) {
// エラーが発生した場合
exit;
} else {
// カテゴリーが存在しない場合
if( mysqli_num_rows($result) == 0 ){
exit;
}else {
// エラーがない場合
// 連想配列にデータを格納する
$product_data = array();
while ($row = $result->fetch_assoc()) {
$product_data[] = $row;
}
return $product_data;
}
}
}
トップページで商品一覧を動的に動かす
編集ファイル:/public/index.php
<?php
include '../app/config/database.php';
include '../public/view/header.php';
include '../app/functions/product.php';
?>
<?php
// 商品一覧データを取得
$products_data = fetch_products($mysqli);
foreach ($products_data as $product_data ) {
echo $product_data['product_name'];
echo $product_data['product_description'];
}
?>
これでOKですが、見映えが悪いので下記のようにします。
<?php
include '../app/config/database.php';
include '../public/view/header.php';
include '../app/functions/product.php';
?>
<?php
// 商品一覧データを取得
$products_data = fetch_products($mysqli);
foreach ($products_data as $product_data ) {
?>
<div class="col-xs-12">
<h2><?php echo $product_data['product_name']; ?></h2>
<p><?php echo $product_data['product_description']; ?></p>
<a href="detail.php?id=<?php echo $product_data['product_id'] ?>">» 口コミを見る</a>
<br><br>
</div>
<?php } // End of foreach ?>
<?php
include '../public/view/footer.php';
口コミを見るのリンクにはパラメータ(?id=1)を与えています。detail.php?id=1
なら、product_id=1
の商品を表示し、detail.php?id=2
なら、product_id=2
の商品を表示する仕様にします。
※補足:detail.php?id=1
のようなURL設計ってSEOに弱くない?
URLがdetail.php?id=1
とかdetail.php?id=2
だったりすると、SEO上よくないのでこれはあとから変更しましょう。.htaccess
で修正できますがコード完成後でOKです。詳細は下記。
» [.htaccess] 動的URLを静的URLに変換しよう。
» url rewriting – Implement friendly URLs with product name – Stack Overflow
口コミ詳細ページを動的にする
編集ファイル:/public/detail.php
下記のかんじ。
<?php
include '../app/config/database.php';
include '../public/view/header.php';
include '../app//functions/review.php'; //これから作ります
?>
<?php
// 口コミデータとそのデータに紐づくユーザー情報を取得する
$product_id = $_GET['id'];
fetch_reviews($product_id, $mysqli);
?>
口コミデータとそのデータに紐づくユーザー情報を取得する
新規作成ファイル:/app/functions/review.php
下記のとおり。これで口コミデータとそのデータに紐づくユーザー情報を取得できます。
<?php
function fetch_reviews($product_id, $mysqli) {
// reviewsとusersのDBを選択する
$query ="SELECT
reviews.review_comment,
reviews.review_date,
reviews.review_product_id,
reviews.review_user_id,
users.user_id,
users.user_name
FROM
reviews
LEFT JOIN
users
ON
reviews.review_user_id = users.user_id
WHERE
reviews.review_product_id = $product_id";
$result = $mysqli->query($query);
if( !$result ) {
// エラーが発生した場合
exit;
} else {
if( mysqli_num_rows($result) == 0 ){
// 口コミが存在しない場合
return false;
}else {
// エラーがない場合
// 連想配列にデータを格納する
$reviews_data = array();
while ($row = $result->fetch_assoc()) {
$reviews_data[] = $row;
}
return $reviews_data;
}
}
}
※LEFT JOIN/ONってなに?
なんかややこしいのが出てますが、要はテーブルを結合するときに使うクエリです。このあたりは下記参考にどうぞ。
» MySQLのLEFT JOIN, RIGHT JOIN, INNER JOINの自分用まとめ – (゚∀゚)o彡
» ON 句は結合条件、WHERE 句は抽出条件
detail.php
を整えていきます
編集ファイル:/public/detail.php
<?php
include '../app/config/database.php';
include '../public/view/header.php';
include '../app//functions/review.php';
?>
<div class="col-xs-12">
<h2>モテるようになる薬</h2>
<p>この薬を飲むと、美女からモテます。すごい薬です。モテるだけじゃなく健康にもなります。すごいです。すごいです。すごいです。すごいです。すごいです。すごいです。すごいです。</p>
<hr>
</div>
<?php
// 口コミデータをそのデータに紐づくユーザー情報を取得する
$product_id = $_GET['id'];
$reviews_data = fetch_reviews($product_id, $mysqli);
foreach ($reviews_data as $review_data ) {
?>
<div class="col-xs-12">
<h4>
名前:<?php echo $review_data['user_name']; ?>さん
(<?php echo $review_data['review_date']; ?>)
</h4>
<p><?php echo $review_data['review_comment']; ?></p>
</div>
<?php } // End of foreach ?>
<div class="container">
<div class="row">
<div class="col-xs-12">
<h3>口コミを投稿する</h3>
<form>
<textarea name="add_review" class="form-control" placeholder="口コミを記入してください。"></textarea>
<button type="submit" class="btn btn-default">投稿する</button>
</form>
</div>
</div>
</div>
<?php
include '../public/view/footer.php';
このままだと口コミが0件のときにエラーが出ちゃうので、下記のようにします。
<?php
// 口コミデータをそのデータに紐づくユーザー情報を取得する
$product_id = $_GET['id'];
$reviews_data = fetch_reviews($product_id, $mysqli);
// 口コミがある場合はループ処理を実行する
if ( $reviews_data !== false ) {
foreach ($reviews_data as $review_data ) {
?>
<div class="col-xs-12">
<h4>
名前:<?php echo $review_data['user_name']; ?>さん
(<?php echo $review_data['review_date']; ?>)
</h4>
<p><?php echo $review_data['review_comment']; ?></p>
</div>
<?php } // End of foreach ?>
<?php } // End of if ?>
エラーメッセージとして「口コミはまだありません」とかって表示したほうがいいかもですが、とりあえずコードはシンプルにしておきたいので省いています。
※追記:商品名を動的に表示するクエリ文(2017年1月9日)
1点どうしても解決できないことがあり、質問させていただきました。
口コミの詳細ページを動的にするところで、口コミデータをそのデータに紐づくユーザー情報を取得するだけでなく、product情報も紐づいて動的に表示させたいのですが、その場合はどのようにすればいいのでしょうか?
上記のお問い合わせをいただいたので追記しました。
つまり、下記部分の商品名を動的に表示したいとの要望。たしかに、動的じゃないとダメですよね。ぼくのミスでした。
<div class="col-xs-12">
<h2>モテるようになる薬</h2>
<p>この薬を飲むと、美女からモテます。すごい薬です。モテるだけじゃなく健康にもなります。すごいです。すごいです。すごいです。すごいです。すごいです。すごいです。すごいです。</p>
<hr>
</div>
解決方法をまとめていきます。
編集ファイル:/public/detail.php
上記ファイルでは、product情報のidを下記のように取得しています。
$product_id = $_GET['id'];
なので、下記のようなSQLクエリを使えばproduct情報を引き抜けるかと思います。
$sql = "SELECT product_name FROM products WHERE product_id = $product_id";
echo $sql;
※追記ここまで。
これでほぼ出来上がってきましたね。残りは下記3点。
- 会員登録機能を作る
- ログイン機能を作る
- 口コミの投稿機能を作る
もうちょい頑張りましょう\(^o^)/
会員登録機能を作る
編集ファイル:/public/signup.php
下記の感じ。フォームのバリデーションです。
<?php
include '../app/config/database.php';
include '../public/view/header.php';
include '../app/functions/user.php';
?>
<?php
// 送信ボタンが押された時に下記を実行
if ( $_POST ) {
// 必須項目に情報が入っているかを確認する
if (
!empty( $_POST['user_name']) &&
!empty( $_POST['user_email']) &&
!empty( $_POST['user_password']) &&
!empty( $_POST['user_pass_check'])
) {
// 2回入力したEmailがマッチしているかを確認する
if ( $_POST['user_password'] === $_POST['user_pass_check']) {
// エラーがない場合
$user_name = $_POST['user_name'];
$user_email = $_POST['user_email'];
$user_password = $_POST['user_password'];
// 会員登録する
save_user($user_name, $user_email, $user_password, $mysqli);
} else {
echo "パスワードが一致しません";
}
} else {
echo "エラーがあります";
}
}
?>
<div class="col-xs-6 col-xs-offset-3">
<h2>会員登録</h2>
<form action="" method="post">
<div class="form-group">
<label for="user_name">名前</label>
<input type="text" class="form-control" id="user_name" name="user_name">
</div>
<div class="form-group">
<label for="user_email">Email</label>
<input type="email" class="form-control" id="user_email" name="user_email">
</div>
<div class="form-group">
<label for="user_password">パスワード</label>
<input type="password" class="form-control" id="user_password" name="user_password">
</div>
<div class="form-group">
<label for="user_pass_check">パスワードの確認</label>
<input type="password" class="form-control" id="user_pass_check" name="user_pass_check">
</div>
<button type="submit" class="btn btn-default">登録する</button>
</form>
</div>
<?php
include '../public/view/footer.php';
save_user()
のファンクションを作る
新規作成ファイル:/app/functions/user.php
下記のとおり。
<?php
function save_user($user_name, $user_email, $user_password, $mysqli) {
$user_name = $mysqli->real_escape_string($user_name);
$user_email = $mysqli->real_escape_string($user_email);
$user_password = password_hash($user_password, PASSWORD_DEFAULT);
$query = "INSERT INTO
users(
user_name,
user_email,
user_password
)
VALUES(
'$user_name',
'$user_email',
'$user_password'
)";
$result = $mysqli->query($query);
echo "<div class='alert alert-success'>
<a href='#' class='close' data-dismiss='alert' aria-label='close'>×</a>
会員登録が完了しました</div>";
}
これで会員登録機能が無事完了です。登録完了すると下記の画面みたくなります。
ログイン機能を作る
編集ファイル:/public/login.php
下記のとおり。会員登録とほぼ同じ。
<?php
include '../app/config/database.php';
include '../public/view/header.php';
include '../app/functions/user.php';
?>
<?php
// ログインボタンが押された時に下記を実行
if ( $_POST ) {
// 必須項目に情報が入っているかを確認する
if (
!empty( $_POST['user_email']) &&
!empty( $_POST['user_password'])
) {
// エラーがない場合
$user_email = $_POST['user_email'];
$user_password = $_POST['user_password'];
// ログインする
login_user($user_email, $user_password, $mysqli);
} else {
echo "エラーがあります";
}
}
?>
<div class="col-xs-6 col-xs-offset-3">
<h2>ログイン</h2>
<form action="" method="post">
<div class="form-group">
<label for="user_email">Email</label>
<input type="email" class="form-control" id="user_email" name="user_email">
</div>
<div class="form-group">
<label for="user_password">パスワード</label>
<input type="password" class="form-control" id="user_password" name="user_password">
</div>
<button type="submit" class="btn btn-default">ログイン</button>
</form>
</div>
<?php
include '../public/view/footer.php';
login_user()
のファンクションを作る
編集ファイル:/app/functions/user.php
下記の感じです。password_verify
あたりがややこしいですが、2016年現在ではこの方法がセキュリティ上で良いとされているみたいです。毎回ほぼこの書き方なのでコピペでOKです。
function login_user($user_email, $user_password, $mysqli) {
$user_email = $mysqli->real_escape_string($user_email);
$user_password = $mysqli->real_escape_string($user_password);
$query = "SELECT
user_id,
user_email,
user_password
FROM
users
WHERE
user_email = '$user_email'";
$result = $mysqli->query($query);
// パスワード(暗号化済み)とユーザーIDの取り出し
while ($row = $result->fetch_assoc()) {
$db_hashed_pwd = $row['user_password'];
$user_id = $row['user_id'];
}
// ハッシュ化されたパスワードがマッチするかどうかを確認
if (password_verify($user_password, $db_hashed_pwd)) {
$_SESSION['user'] = $user_id;
echo "<div class='alert alert-success'>
<a href='#' class='close' data-dismiss='alert' aria-label='close'>×</a>
ログインが完了しました</div>";
} else {
echo "エラーが発生しました";
}
}
その後、headerの1番上に下記を追加してください。
編集ファイル:/public/view/header.php
<?php
ob_start();
session_start();
?><!DOCTYPE html>
口コミの投稿機能を作る
編集ファイル:/public/detail.php
下記を追記してください。
<?php
// 口コミの投稿
if ($_POST['add_review']) {
$add_review = $_POST['add_review'];
add_review($product_id, $add_review, $mysqli);
}
?>
<div class="container">
<div class="row">
<div class="col-xs-12">
<h3>口コミを投稿する</h3>
<form>
<textarea name="add_review" class="form-control" placeholder="口コミを記入してください。"></textarea>
<button type="submit" class="btn btn-default">投稿する</button>
</form>
</div>
</div>
</div>
add_review
のファンクションを作っていきます
編集ファイル:/app/functions/product.php
下記のとおり。
// 口コミを投稿する
function add_review($product_id, $add_review, $mysqli) {
$product_id = $mysqli->real_escape_string($product_id);
$add_review = $mysqli->real_escape_string($add_review);
$user_id = $_SESSION['user'];
$query = "INSERT INTO
reviews(
review_comment,
review_date,
review_product_id,
review_user_id
)
VALUES (
'$add_review',
NOW(),
$product_id,
$user_id
)";
$result = $mysqli->query($query);
if(!$result) {
echo 'エラーが発生しました。';
} else {
echo "口コミを投稿しました。";
}
}
以上で完成!
思ったより長くなったので書いてて疲れました。
質問とかはお問い合せフォームからお願いします。
間違いの指摘とかもいありがたいので、お問い合せフォームからお願いします。
» お問い合わせ
※P.S:無料メルマガで発信中:過去の僕は「ブログ発信で5億円」を稼ぎました。次は「30億円」を目指します。挑戦しつつ、裏側の思考を「メルマガ」から発信します。不満足なら1秒で解約できます。無料登録は「こちら」です。