--- title: Laravelで弐寺のクリアランプマネージャーをつくる tags: Laravel PHP MySQL Python3 author: HO_Pollyanna slide: false --- [Laravel #2 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/laravel-2)5日目のの記事です。 #概要・動機 beatmania IIDXというゲームが好きです(唐突) 既存のクリアランプマネージャもあるにはあるが、機能が多くて僕には使いこなせません。 (低難度とか普段やらんしフォルダ分けもいらん) 身の丈にあったツールを使いたいので、クリアランプマネージャー兼地力推定サービスを自分用に~~車輪の再~~開発します。 前提として、自分はMVCが何ぞやということはゆるふわ理解している程度で、フルスタックのwebフレームワークは初心者です。 できたアプリはこんなかんじ↓ スクリーンショット 2019-12-03 15.30.42.png (開発期間:2日) クリアできそうな楽曲も教えてくれます!!(目玉機能) ###github https://github.com/dhaiibfiukkiu/estimator #MVCそれぞれの概要 ##モデル 正直ココが全てを決める気がする。 アプリの動作としては、 1.indexページに全楽曲の表とクリアした難易度のラジオボタン,submitボタン 2.ログイン後、submitでそのuserとクリアした楽曲を紐付けたclearsデータを挿入。 3.clearsデータに基づいてラジオボタンをchecked。また、地力の推定も行う。 といったことを想定しているので、userテーブル、musicsテーブル、clearsテーブルがいるかなと思った。 本来userとmusicsは多対多の関係であるので、clearsがうまく中間テーブルとして作用してくれる。 つまり、clearsの複合主キーが**u_id**と**m_id**であるから、新たに主キー**id**を作る必要は無いと感じた。 **このせいで後に苦労する羽目になるのだが**(後述) ![空白の ERD.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261602/6ab18ef1-11a7-d385-9d97-5cb2ea3d264f.png) musicsテーブルの**e**から**fc**まではそれぞれのクリア難易度(地力値)です。 値は[こちら](https://sp12.iidx.app/recommends)を参考にさせていただきました。 単純にページをコピペすると、それぞれの楽曲は改行(\n)で、それぞれの要素はタブ文字(\t)で区切られていたので、こちらをlaravelのシーディングにうまく合う形に加工します。 ```musicTableSeeder.php '#MAGiCVLGiRL_TRVP_B3VTZ', 'e' => '-2.914433', 'n' => '-2.038145', 'h' => '-1.965019', 'exh' => '3.882106', 'fc' => '8.346122', ]; $musics = new Music; $musics->fill($param)->save(); //(以下略) ``` 目標はこの形です。そのために以下のPythonスクリプトを書きました。 ```makelist.py input='''(ここに楽曲のリスト) ''' input=input.split('\n') for line in input: tmp=line.split('\t') if tmp[1]=='Infinity': continue name=tmp[0].replace("'",r"\'") e=tmp[1] n=tmp[2] h=tmp[3] exh=tmp[4] fc=tmp[5] print(''' $param=[ 'name' => '{}', 'e' => '{}', 'n' => '{}', 'h' => '{}', 'exh' => '{}', 'fc' => '{}', ]; $musics = new Music; $musics->fill($param)->save(); '''.format(name,e,n,h,exh,fc)) ``` あとはこのスクリプトの出力をmusicのseederスクリプトにぶち込んで`php artisan db:seed`してやるだけです。(勿論テーブルのマイグレーションをした後にです) あと、appディレクトリ以下のMusic,Clearモデルには以下のように記述しておきます。 ```Music.php 'integer', 'm_id' => 'integer', 'u_id' => 'integer', 'info' => 'integer' ]; } protected $fillable=[ 'id', 'm_id', 'u_id', 'info' ]; } ``` ここに各要素のバリデーションのルールやらレコードの更新設定とかをするわけですね。 ###トラブル Clear.phpの中に`//仕方ないのでidをサロゲートキーに`というコメントがあります。 実は、はじめにClearモデルを作成した際はidカラムは存在せず、m_idとu_idの複合主キーだけでした。 しかし、いざレコードをsaveメソッドで保存しようとすると、エラー。 どうやら複合主キーを使うとsaveメソッドが使えなくなるらしい。なんやそれ。 まあ、メソッドをオーバーライドすればできないこともないらしい。~~メンドクセー~~ ###参考 >https://qiita.com/wrbss/items/7245103a5fef88cbdde9 >https://github.com/laravel/framework/issues/5517 仕方がないので代理キーとして**id**を追加しました。 ##ビュー **laravel**での開発と銘打っているし見た目にはそこまで拘りません。 resources/viewsフォルダにlayoutsフォルダ、estimatorフォルダを作成し、 layoutsにはおおもとのレイアウト、estimatorではそれを継承した具体的なビューを管理します。 ```/layouts/estimator.blade.php @yield('title')

@yield('title')

@yield('loginfo')


@yield('content')
``` ここにはめ込むビューが以下 ```estimator/index.blade.php @extends('layouts.estimator') @section('title','地力Estimator') @section('loginfo') @php//@parent @endphp @if(Auth::check())

DJ NAME:{{$user->name}}

ログアウト

@else

ログインしていません(ログイン|登録)

@endif @endsection @section('content')

@if($jiriki==-100&&!Auth::check()) 推定値を取得するためにはログインしてください @elseif($jiriki==-100&&Auth::check()) 推定値を取得するためには、チェックボックスにチェックを入れたあとに、右下のボタンを押下してください @else 推定地力:{{$jiriki}} @endif

{{csrf_field()}} @foreach($musics as $item) @if(!isset($clears)) @php $check=-1 @endphp @else @php $check=$clears->where('m_id',$item->m_id)->first()->info @endphp @endif @endforeach
曲名NO PLAYEASYNORMALHARDEX-HARDFULLCOMBO
@endsection @section('footer') copyright 2019 Okada Hibiki @endsection ``` 今回は、主要なページが一つだけだったこともあり、ロジックをビュー側に少し任せすぎてしまったかなというのが反省点。 ##コントローラ ```estimatorController.php id)->get();///getをかかないとダメ!!! if(Clear::where('m_id',1)->where('u_id',$user->id)->count()==0){ foreach($musics as $music){ $clear = new Clear; $clear->m_id = $music->m_id; $clear->u_id = $user->id; $clear->info = 0; $clear->save(); } } } else{ $clears=null; } if($request->method()=='POST'){ if(Auth::check()){ foreach($musics as $music){ $clear = Clear::where('m_id',$music->m_id)->where('u_id',$user->id)->first(); $m_id = $music->m_id; $clear->info = (int)$request->$m_id; //$clear->timestamps = false; $clear->save(); } } else{ // } } if(Auth::check()){ $clears = Clear::where('u_id',$user->id)->get(); $jiriki = array(); foreach($clears as $clear){ switch($clear->info){ case 0: break; case 1: array_push($jiriki,$musics->where('m_id',$clear->m_id)->first()->e); break; case 2: array_push($jiriki,$musics->where('m_id',$clear->m_id)->first()->n); break; case 3: array_push($jiriki,$musics->where('m_id',$clear->m_id)->first()->h); break; case 4: array_push($jiriki,$musics->where('m_id',$clear->m_id)->first()->exh); break; case 5: array_push($jiriki,$musics->where('m_id',$clear->m_id)->first()->fc); break; } } } //ここに推定値の処理 if(!isset($jiriki)){$jiriki=-100;} elseif(count($jiriki)==0){$jiriki=-100;} else{ rsort($jiriki); $num=ceil(count($jiriki)*0.3); $sum=0; for($i=0;$i<$num;$i++){ $sum+=$jiriki[$i]; } $sum=$sum/$num; $jiriki=$sum; } $param = ['musics' => $musics,'user' => $user,'clears' => $clears,'jiriki' => $jiriki]; return view('estimator.index',$param); } public function logout(){ Auth::logout(); return redirect()->action('estimatorController@index'); } } ``` 注意点としては、モデルの利用の際にuseしなきゃいけないことくらいか。 処理の概要としては、ログインがあるかをチェックして、しているのであればデータベースをチェック。そのユーザのクリア情報があれば取得し、なければNO PLAYとしたレコードをclearsテーブルに挿入。 POSTメソッドでのアクセスであれば、それを元にクリア情報を更新。 さらに、最新のクリア情報から自力を計算します。 この地力の計算が困ったもので、それらしい確率分布を適用しても、地力低めなEASYクリアとかが増えると地力が下がる、という現象がどうしても起こってしまう。 ので、クリアした楽曲の地力上位3割の平均値を推定地力としています。こっちのほうがシンプルでいいし、さっきみたいな不具合も幾らか少なくなるので良い。 ##Auth Laravelには標準で認証機能があります。 これがものすごく便利で、`php artisan ui vue --auth`するだけでログイン機能が実装できます。 Laravelのバージョン5では`php artisan make:auth`で(読んだ本はこっちだった) 6系ではそれが使えない点に注意してください。 今回のアプリケーションでは、トップページの閲覧にログインは必須ではないが、データの保存や推定値の表示にはログインが必須であるような実装にしました。 そして、標準のAuthを利用すると、**/register**,**/login**,**/home**といったpathが用意されます。 初期状態だと、/registerで登録、/loginでログイン後、/homeにリダイレクトされる仕様だったのですが、これを/にリダイレクトするよう変更します。 この処理はAuth内のコントローラに記述されています。 **/app/Http/Controllers/Auth/**以下の**RegisterController.php**,**LoginController.php**内の `protected $redirectTo = '/home';` という記述を `protected $redirectTo = '/';` に変更すればOK。 ##ルーティング ```routes/web.php name('home'); ``` ルーティングとはいえどメインは/で動作するestimatorControllerだけなのでシンプルですね。 #おわりに フルスタックのフレームワークを使ったのは初めてですが、簡単に自分にとって実用的なアプリが作れました。 正直今回のアプリならSPAと相性が良さそうなので、気が向いたらそれもつくってみようかな(**たぶんやらん**) ###参考文献 >David Skler(2017)初めてのPHP >掌田津耶乃(2017)PHPフレームワーク Laravel入門