Laravel + Vue + MySQLで簡易投票機能を作る
下の画像のような簡易投票機能を作ったので、そのメモを残します。
作成したものは、こちらから試してみてください。想定した要件は以下のようなものです。
要件s
・投票は1IPアドレスにつき1回
・<form>ではなくaxiosの非同期処理で実装
前提として、LaravelでVue・axios・Bootstrap・FontAwesomeが使えるものとします。(BootstrapとFontAwesomeはなくても動きますが...)
実行環境
Laravel 5.8.23
MySQLの下準備
今回は投票されたデータをMySQLに格納します。よってLaravelにMySQLの接続情報を設定してあげる必要があります。.envファイルを開き、
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=<DB_DATABASE>
DB_USERNAME=<DB_USERNAME>
DB_PASSWORD=<DB_PASSWORD>
ここを適宜変更してください。また、MySQLを起動させておく必要があります。ターミナルorコマンドプロンプトで
$ mysql.server start
を叩き、MySQLを起動しましょう。
次に結果を格納するための投票テーブルを作成します。下のようなpollsテーブルを作成しましょう。
これで下準備が完了です。実際に簡易投票機能を作成していきます。
投票bladeコンポーネント作成
はじめにフロント部分であるbladeコンポーネントを作ります。(resources/views/components/vote.blade.php)
<!-- resources/views/components/vote.blade.php ->
<div class="p-3 bg-white shadow fixed-bottom text-right" id="vote">
<transition name="fade" mode="out-in">
<div v-if="status !== 'voted'" :key="'notvoted'">
<h4 class="h6 text-center">当サイトのデザインは?</h4>
<div v-if="status === 'notVoted'" class="row mt-2">
<div class="col text-center" v-if="answer < 1">
<i class="text-primary far fa-star fa-lg d-block" @click="vote(1)" ></i>
<small class="text-muted">1</small>
</div>
<div class="col text-center">
<i class="far fa-star text-primary fa-lg d-block" @click="vote(2)"></i>
<small class="text-muted">2</small>
</div>
<div class="col text-center">
<i class="far fa-star text-primary fa-lg d-block" @click="vote(3)"></i>
<small class="text-muted">3</small>
</div>
<div class="col text-center">
<i class="far fa-star text-primary fa-lg d-block" @click="vote(4)"></i>
<small class="text-muted">4</small>
</div>
<div class="col text-center">
<i class="far fa-star text-primary fa-lg d-block" @click="vote(5)"></i>
<small class="text-muted">5</small>
</div>
</div>
<div v-if="status === 'voting'" class="text-center">
<div class="spinner-grow spinner-grow-sm text-primary" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
</div>
<div v-if="status === 'voted'" :key="'voted'">
<h4 class="h6 text-center">ご協力ありがとうございました</h4>
<p class="text-muted small text-center mb-0">サイトデザイン向上に努めます</p>
</div>
</transition>
</div>
<script type="text/javascript">
const vote = new Vue({
el: '#vote',
data: {
answer: 0,
status: 'notVoted',
},
methods: {
vote(answer) {
this.status = 'voting';
axios.post('/api/vote/' + answer)
.then((res) => {
this.status = 'voted';
setTimeout(function() {
$('#vote').fadeOut();
}, 3000);
});
},
},
})
</script>
<style type="text/css">
#vote {
left: 10px;
bottom: 10px;
width: 275px;
}
#vote i:hover {
cursor: pointer;
}
.fade-enter-active, .fade-leave-active {
transition: opacity .25s ease;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
</style>
これを使いたいviewでincludeすることで読み込むことが可能です。今回は非同期処理の部分をVueとaxiosを使っています。5点の時のリクエストは「/api/vote/5」のような具合でPOST送信します。作成したbladeコンポーネントをresources/views/home.blade.phpで読み込む場合は、
@include('compoments.vote')
でincludeすることができます。bladeまわりはこちらのドキュメントを参考にしてください。
api.phpでルーター作成
リクエストをどこのコントローラーに渡すのかを定義する必要がありますので、routes/api.phpを編集します。resources/views/components/vote.blade.phpから「/api/vote/{answer}」が送信されてきますので、下のような記載をroutes/api.phpへ追加します。
// routes/api.php
Route::post('/vote/{answer}', 'VoteController@vote');
これで届いたリクエストをVoteControllerに渡すことができます。しかしまだVoteControllerを作っていませんので、次に実装していきましょう。
Voteコントローラー作成
投票リクエストを受け取りデータベースと連携するコントローラーを作成します。ターミナルorコマンドプロントから下のコマンドを打ちましょう。
$ php artisan make:controller VoteController
これでapp/Http/Controllers/にVoteController.phpが作成されます。VoteController.phpを下のコードに置き換えます。
<?php // app/Http/Controllers/VoteController.php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\VoteController;
use Illuminate\Http\Request;
class VoteController extends Controller
{
public function vote(Request $request, $answer)
{
$ip_address = $_SERVER['REMOTE_ADDR'];
// リクエスト元のIPアドレスの存在をチェック
$is_voted = DB::table('polls')
->where('ip_address', $ip_address)
->exists();
if ( $is_voted ) { // すでに投票済みの場合
return [
'result' => 'FAILURE',
'message' => 'Sorry. You have already voted.',
];
} else { // 未投票の場合
$arrow_answers = array(1, 2, 3, 4, 5);
// 答えが1~5の間にない場合
if ( !in_array( $answer, $arrow_answers ) ) {
return [
'result' => 'FAILURE',
'message' => 'Your answer is unexpected.',
'answer' => $answer,
];
} else {
DB::table('polls')->insert([
'ip_address' => $ip_address,
'answer' => $answer
]);
return [
'result' => 'SUCCESS',
'message' => 'Vote request completed.',
'answer' => $answer,
];
}
}
}
}
少し解説します。リクエストが届くとはじめにIPアドレスがテーブル内にすでに存在するかをチェックします。すでにIPアドレスが存在していた場合はそこでリターンをします(レスポンスは結構適当です)。余力があればレスポンスをJSON形式にし、Vue側で表示を分けるなどの実装をしても良いかもしれません。
まだIPアドレスがない場合は、答えが1~5内にあればデータベースへInsertし、レスポンスします。
未投票/投票済判別ミドルウェア作成
ミドルウェアはログイン状態などを判別するレイヤーのような働きをします。ミドルウェアで未投票/投票済を判別します。これにより、一度投票した人(テーブル内にIPアドレスが存在する人)とそうでない人を判別できるようになります。
$ php artisan make:middleware CheckIsVoted
作成されたapp/Http/Middleware/CheckIsVoted.phpの内容を下記に置き換えます。
<?php // app/Http/Middleware/CheckIsVoted.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\DB;
class CheckIsVoted
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$request->merge([
'is_voted' => DB::table('polls')->where('ip_address', $_SERVER['REMOTE_ADDR'])->exists()
]);
return $next($request);
}
}
こちらは単にpollsテーブル内にIPアドレスが存在するかを判別し、true/falseをリクエスト内に追加します。このミドルウェアを使うためには、routes/web.phpに
use App\Http\Middleware\CheckIsVoted;
Route::get('/', 'VoteController@index')->middleware(CheckIsVoted::class);
を追記し、VoteControllerにIPアドレスの存在情報を渡します。app/Http/Controllers/VoteController.phpへ下記の関数を追加します。
class VoteController extends Controller
{
public function index(Request $request)
{
return view('home', $request);
}
:
:
:
これでviewへテーブル内のIPアドレスの存在情報が渡りますので、home.blade.phpを
@include('components.vote') <--こいつを削除
@if ( !$is_voted )
@include('components.vote')
@endif
のように変更して完成です。これで1IPアドレスにつき1度きりしか投票できない簡易投票機能が完成しました。ヤッタネ。
「ここを改良できるんじゃない?」や「この書き方はよろしくない」などがございましたら、コメントをいただけると幸いです。
完成した簡易投票機能はは下のURLから試してみてください。