見出し画像

Laravel + Vue + MySQLで簡易投票機能を作る

下の画像のような簡易投票機能を作ったので、そのメモを残します。

画像1

作成したものは、こちらから試してみてください。想定した要件は以下のようなものです。

要件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テーブルを作成しましょう。

画像2

これで下準備が完了です。実際に簡易投票機能を作成していきます。

投票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から試してみてください。


この記事が気に入ったらサポートをしてみませんか?