Laravelのみでクイズアプリを作る方法③同じユーザーの結果を違うデータとしてテーブルに保存

Laravelのみでクイズアプリを作る方法③同じユーザーの結果を違うデータとしてテーブルに保存

229 回閲覧されました

みなさんこんにちは、jonioです。

2回目までの解説でクイズアプリとしての機能はできているので今回は少し機能を追加します。

細かく見ると機能的に必要な内容は色々あると思いますがそれをやるとキリがないのである程度の所で止めます。

今の状態はクイズ結果のランキング表がありません。

クイズの結果をテーブルに保存してそれを表示することでランキング表を作るのですが2回目までの解説では同じユーザーがクイズを2回以上やると結果のデータが上書きされます。

同じユーザーが何度もクイズに答えて結果を別の物としてテーブルに保存する場合は当然あります。

下記は同じユーザーの情報を何度もテーブルに保存しています。(user_idカラムの値に1と2がありますがそれがユーザーに該当すると思って下さい)

今回は同じユーザーが何度もクイズをやった時の結果を別の物としてテーブルに保存する方法を解説します。

前提条件

クイズの正解数を保存する為のマイグレーションファイルとモデルを作成しています。

モデル名は「Score」です。

マイグレーションファイルの内容は下記になります。

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateScoresTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('scores', function (Blueprint $table) {
            $table->id();
            
            
            //ここから追加
            $table->unsignedBigInteger('user_id');
            $table->integer('score');
            //ここまで追加
            
            
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('scores');
    }
}

リレーションを使ってusersテーブルとscoresテーブルを繋ぐ為に21行目の「user_id」カラムを追加します。

モデル(Score.php)の内容は下記になります。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Score extends Model
{
    use HasFactory;

    protected $fillable = [
        'score',
        'user_id'
    ];

    public function user()
    {
        return $this->belongsTo('App\Models\User');
    }
}

User.phpの内容は下記になります。

<?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array<int, string>
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array<int, string>
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * The attributes that should be cast.
     *
     * @var array<string, string>
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    public function roles()
    {
        return $this->belongsToMany('App\Models\Role');
    }


    //ここから追加
    public function scores()
    {
        return $this->hasOne('App\Models\Score');
    }
    //ここまで追加
    
    
}

クイズアプリを作った時のコントローラーは「QuizController」だったので今回もそれに記述します。

考え方

こう考えます。

  1. 今が何問目かを表す変数を設定
  2. 変数がクイズの総数と同じになったらscoresテーブルのuser_idカラム(これがユーザーidに当たる、下記画像の赤枠)とscoreカラム(下記画像の青枠)に値を保存

クイズの総数はテーブルのレコードの総数と同じになるのでこれを使います。

ではここからはコードで説明します。

コード

Quizcontroller.phpのコードを下記にします、クイズアプリの作り方を解説した時はindexメソッドとshowメソッドとcreateメソッドとstoreメソッドもありましたが今回は変更がないので省略します。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Question;
use App\Models\Score;                     //この行を追加
use App\Models\User;                      //この行を追加
use Illuminate\Support\Facades\Gate;

class QuizController extends Controller
{
    public function answer(Request $request)
    {
        $userAnswer = $request->input('choice');
        $questionId = $request->input('question_id');
        $question = Question::findOrFail($questionId);
        $correctChoiceIndex = $question->correct_choice - 1;
        $questions = Question::count();                //この行を追加
        Session::increment('flagNumber');              //この行を追加

        if ($userAnswer == $correctChoiceIndex) {
            $isCorrect = true;

            Session::increment('key');         //この行を追加
        } else {
            $isCorrect = false;
        }

        $nextQuestionId = Question::where('id', '>', $questionId)->first();

        $flag = Session::get('flagNumber', 0);        //この行を追加
        
        $correctNumber = Session::get('key',0);       //この行を追加

        $userId = auth()->id();                       //この行を追加


        //ここから追加
        if($flag == $questions){
            $scoreRecord = new Score([
                'user_id' => $userId,
                'score' => $correctNumber
            ]);
            
            $scoreRecord->save();
            
            Session::forget('key');
            Session::forget('flagNumber');
        }
        //ここまで追加
        
        
        return view('quiz.answer', compact('isCorrect', 'question', 'userAnswer', 'nextQuestionId'));
    }
}

今が何問目かを表す変数を20行目で設定しています。

ページをまたいで変数の値を保存する方法

変数の設定を下記で行います。

Session::increment('変数名'); 

変数名に「flagNumber」を使っています。

この変数の設定方法をすれば変数を自動的に1ずつ増やすことができます。

変数を1つずつ増やすなら下記のやり方でもできると思うかもしれません。

$flagNumber = 0;

//次のページに進む時
$flagNumber ++;

このやり方だと次のページに移動した時に$flagNumberの値は保持されません。

保持ができるように20行目では「Session」を使っています。

話を戻してクイズの総数はレコードの総数であり19行目です。

レコードの総数は下記で設定できます。

モデル名::count();

そして32行目でレコードの総数を取得して使うことができます。

取得して使う場合の書き方は下記になります。

Session::get('使いたい変数名', 0); 

「使いたい変数名」は20行目のflagNumberなのでこれを使います。

「0」はflagNumberの最初の値です。

これでflagNumberの最初の値を0として1ずつ増やして使うことができます。

25行目・34行目も同じ流れがありますがこれはクイズの正解数です。

40行目〜50行目はクイズに回答するのが終わる判定です。

40行目のifの条件は「今が何問目かを表す変数とレコードの総数が一致した」つまり「 クイズが全て終わった」という意味です。

42行目で「use_idカラム」にログインしたユーザーのIDを登録して43行目で「scoreカラム」にクイズの正解数を登録しています。

そして46行目で登録した値をテーブルに保存します。

クイズに答え終わったらSESSIONの保持を切る

Sessionは値を保持した状態になるのですがここで終わると正解数と今が何問目かを表す変数は保持した状態になります。

クイズが終わった時点でリセットしないと次に回答した人に値が引き継がれます。

だからリセットする為に48行目・49行目があります。

Sessionの値を削除する時は下記の書き方になります。

Session::forget('変数名');

これでクイズの問題を全て解き終わったら同じユーザーの正回数をテーブルに何度も保存できるようになります。

ビューに表示

ユーザーとクイズの正解数をトップページで表示します。

QuizController.phpのインデックスアクションを修正します。

public function index()
{
   $firstQuestionId = Question::where('id', '>=' ,1)->first();

   $score = Score::count();

   $tableScores = Score::orderBy('score', 'DESC')->take(5)->get();     //この行を追加

   return view('quiz.index',compact('firstQuestionId', 'score', 'tableScores'));     //この行を修正
}

クイズの正解数を表示する時に全員の正回数を表示すると長ったらしくなって見た目がよくないので正解数の上位5人を表示します。

それを実現する為に7行目があります。

そしてビューに正解数を表示しますがテーブルを使います。

quizフォルダの下の下層にあるindex.blade.phpを修正します。

<div class="ranking">
  <h2 class="has-text-centered has-text-weight-bold mt-6 is-size-4">ランキング</h2>
  <img class="menu-img mt-4 is-block ml-auto mr-auto" src="{{ asset('images/ranking.png') }}" alt="ランキング">
  <table border="1" class="mr-auto ml-auto mt-3">
    <tr>
      <th class="pl-6 pr-6 pt-2 pb-2">ユーザー名</th>
      <th class="pl-6 pr-6 pt-2 pb-2">正解数</th>
    </tr>
    @foreach($tableScores as $tableScore)
      <tr>
        <td class="pl-6 pr-6 pt-2 pb-2">{{ $tableScore->user->name }}</td>
        <td class="pl-6 pr-6 pt-2 pb-2">{{ $tableScore->score }}</td>
      </tr>
    @endforeach
  </table>
</div>

これで下記の見た目になります。(CSSは記載しないので自分で調節してください)

これで完成です。