検索した値を保持しつつページネーションする方法 && リレーション先で条件を指定する方法
課題
- 検索している値を保持したままページネートしたい
- リレーション先で条件を指定する方法。
対処法
- 状況としては、userにリレーションしているuser_profileで検索を掛けたい。
- リクエストのgetメソッドで
age
と、residence
が送られてきたとする。 その場合コントローラ側では以下のように返す。
<?php $query = $this->user->query()->with('user_profile'); $query->where('sex', $target_sex); if(array_key_exists('age', $data) && !is_null($data['age'])) { $age = $data['age']; $query->whereHas('user_profile', function($q) use ($age){ $q->where('age', $age); }); } if(array_key_exists('residence', $data) && !is_null($data['residence'])) { $residence = $data['residence']; $query->whereHas('user_profile', function($q) use ($residence){ $q->where('residence', $residence); }); } $users = $query->paginate(12); return $users;
- 受け取ったview側では以下のように記載する。
<?php <div class="row justify-content"> {{ $result['users']->appends(['age' => Request::get('age'), 'residence' => Request::get('residence')])->links() }} </div>
こうすれば、ページネートしても検索条件は保たれる。
Formのテンプレート
<?php <form class="border border-light p-5" action="{{ route('mypage.confirm') }}" method="POST" enctype="multipart/form-data"> @csrf <p class="h4 text-center mb-4">ユーザー情報編集</p> <div class="form-group"> <label for="user_name">ユーザー名</label> <span class="badge badge-primary">必須</span> <input type="text" id="user_name" name="user_name" class="form-control @error('user_name') is-invalid @enderror" value="{{ old('user_name') ?? $result['user_profile']->user_name ?? '' }}"> @error('user_name') <span class="invalid-feedback"> <strong>{{ $message }}</strong> </span> @enderror </div> <div class="form-group"> <label for="age">年齢</label> <span class="badge badge-primary">必須</span> <div class="w-25"> <select id="age" name="age" class="form-control @error('age') is-invalid @enderror"> <option value="25" @if(old("age")) @if(old("age") == 25) selected @else @endif @else @if(!is_null($result['user_profile']) && $result['user_profile']->age == "25") selected @else @endif @endif>25歳</option> <option value="26" @if(old("age")) @if(old("age") == 26) selected @else @endif @else @if(!is_null($result['user_profile']) && $result['user_profile']->age == "26") selected @else @endif @endif>26歳</option> </select> </div> </div> <div class="form-group"> <label for="residence">居住地</label> <span class="badge badge-primary">必須</span> <div class="w-25"> <select id="residence" name="residence" class="form-control"> <option value="東京都" @if(old("residence")) @if(old("residence") == "東京都") selected @else @endif @else @if(!is_null($result['user_profile']) && $result['user_profile']->residence == "東京都") selected @else @endif @endif>東京都</option> <option value="静岡県" @if(old("residence")) @if(old("residence") == "静岡県") selected @else @endif @else @if(!is_null($result['user_profile']) && $result['user_profile']->residence == "静岡県") selected @else @endif @endif>静岡県</option> </select> </div> </div> <div class="form-group"> <label for="job">職業</label> <span class="badge badge-primary">必須</span> <input type="text" id="job" name="job" class="form-control @error('job') is-invalid @enderror" value="{{ old('job') ?? $result['user_profile']->job ?? '' }}"> @error('job') <span class="invalid-feedback"> <strong>{{ $message }}</strong> </span> @enderror </div> <div class="form-group"> <label for="img_url">写真</label> <vue-cropper-component></vue-cropper-component> </div> <div class="form-group"> <label for="text">自己紹介文</label> <span class="badge badge-primary">必須</span> <textarea id="text" name="text" class="form-control @error('text') is-invalid @enderror" placeholder="最低5文字以上、最高1000文字まで">{{ old('text') ?? $result['user_profile']->text ?? '' }}</textarea> @error('text') <span class="invalid-feedback"> <strong>{{ $message }}</strong> </span> @enderror </div> <button class="btn btn-default my-4 btn-block" type="submit">ユーザ情報変更確認画面へ</button> </form>
テストを行うために必要になる設定あれこれ
課題
- テストを行いたい時にスムーズにテストがかけるようにしたい
対処法
- テスト時には他のデータベースを使用するために、
phpunit.xml
を以下のように変更
<php> <server name="APP_ENV" value="testing"/> <server name="BCRYPT_ROUNDS" value="4"/> <server name="CACHE_DRIVER" value="array"/> <server name="DB_CONNECTION" value="mysql"/> //ここ <server name="DB_DATABASE" value="laravel-test"/> // ここ <server name="MAIL_DRIVER" value="array"/> <server name="QUEUE_CONNECTION" value="sync"/> <server name="SESSION_DRIVER" value="array"/> </php>
テストの作成
php artisan make:test XXXXTest
テスト時にモックを使用する方法ためには、プロバイダーで差し替えたい箇所を以下のように記述。ちなみにプロパイダーは
php artisan make:provider
で作成できる。下記ファイルをapp.php
に登録するのを忘れずに。
<?php public function register() { $this->app->bind( \App\Repository\Contract\ImageFileRepository::class, function($app) { if(env("APP_ENV") == "testing"){ return new \App\Repository\TestImageFileRepository(); //テスト時はこっち } return new \App\Repository\S3ImageFileRepository(); //普段はこっち } ); }
- データの投入方法
- seederを作成
php artisan make:seeder xxxx
中身はこんな感じ
<?php public function run() { \DB::table('users')->insert([ 0 => [ 'id' => 1, 'name' => 'kou', 'email' => 'test@gmail.com', 'email_verified_at' => null, 'password' => '$2y$10$ZKJEkDJuTMvcizuqghW2l.yyOW5NG4ythAhYnDnt1LUEDh/BS5gZK', 'sex' => 1, 'remember_token' => null, 'created_at' => '2020-04-24 15:31:45', 'updated_at' => '2020-04-24 15:31:45' ], 1 => [ 'id' => 2, 'name' => 'tisato', 'email' => 'test-w@gmail.com', 'email_verified_at' => null, 'password' => '$2y$10$ZKJEkDJuTMvcizuqghW2l.yyOW5NG4ythAhYnDnt1LUEDh/BS5gZK', 'sex' => 0, 'remember_token' => null, 'created_at' => '2020-04-24 15:31:45', 'updated_at' => '2020-04-24 15:31:45' ] ]); }
- テスト前に行うべき内容をまとめるとこんな感じ。
- DBの初期化
- テストしたいサービス(クラス)の生成
- データの追加
<?php use RefreshDatabase; public function setUp(): void { parent::setUp(); $this->mypage_service = app(MypageService::class); $this->seed('UserTableSeeder'); $this->seed('UserProfileTableSeeder'); }
入力フォームの初期値に正しい情報を入れる方法
課題
- text入力ボックスで、バリデーション時には入力していた値、初期にはDBに登録されていた値を表示する方法
- select入力ボックスで、バリデーション時には入力していた値、初期にはDBに登録されていた値を表示する方法
対処法
- こちらは比較的簡単
<?php <div class="form-group"> <label for="job">職業</label> <span class="badge badge-primary">必須</span> <input type="text" id="job" name="job" class="form-control @error('job') is-invalid @enderror" value="{{ old('job') ?? $user_profile->job ?? '' }}"> //ここを真似れば良い @error('job') <span class="invalid-feedback"> <strong>{{ $message }}</strong> </span> @enderror </div>
- こちらは結構複雑
<?php <div class="form-group"> <label for="age">年齢</label> <span class="badge badge-primary">必須</span> <div class="w-25"> <select id="age" name="age" class="form-control @error('age') is-invalid @enderror"> //ここから真似する <option value="25" @if(old("age")) @if(old("age") == 25) selected @else @endif @else @if(!is_null($user_profile) && $user_profile->age == "25") selected @else @endif @endif>25歳</option> <option value="26" @if(old("age")) @if(old("age") == 26) selected @else @endif @else @if(!is_null($user_profile) && $user_profile->age == "26") selected @else @endif @endif>26歳</option> </select> </div> </div>
bladeでエラー内容を表示する方法。
課題
- 入力フォームでエラーが起きた際に、エラーが起きた項目の下にエラー内容を表示したい。
対処法
- 状況としては
uesr_name
にバリデーションをかけたとする。 - こんな感じ。ポイントinputタグのclassに
is-invalid
を持たせることである。
<?php <div class="form-group"> <label for="user_name">ユーザー名</label> <span class="badge badge-primary">必須</span> <input type="text" id="user_name" name="user_name" class="form-control @error('user_name') is-invalid @enderror" value="{{ old('user_name') ?? $user_profile->user_name ?? '' }}"> @error('user_name') <span class="invalid-feedback"> <strong>{{ $message }}</strong> </span> @enderror </div>
Laravel+Vue vue-cropperjsを使用しトリミングした画像データ(base64エンコードされたデータ)をアップロードする方法
課題
- vue-cropperjsを使用しトリミングまではできたが、その画像をアップロードする方法がわからない。
対処法
- vueコンポーネントでの書き方。まんまコピーすれば、ファイル選択及び、トリミングまで可能。
<template> <div> <input type="file" v-on:change="onFileChange()" class="mb-3"> <input type="hidden" :value="imgData" name="img_data"> <div class="content"> <div class="vue-cropper-wrap" v-if="srcImg != ''"> <vue-cropper ref="cropper" :guides="true" :view-mode="2" :src="srcImg" :background='true' drag-mode="crop" :auto-crop-area="1" :aspect-ratio="1" :img-style="{'width': '320px', 'height':'320px'}" ></vue-cropper> </div> <div v-if="srcImg != ''"> <button type="button" class="btn btn-success" @click="cropImage">トリミングする。</button> </div> </div> </div> </template> <script> import VueCropper from "vue-cropperjs"; import "cropperjs/dist/cropper.css"; export default { components: { VueCropper }, data() { return { uploadFile:"", cropImg: "", srcImg:"", imgData: "", }; }, methods: { onFileChange() { var file = event.target.files[0]; if (!/\.(gif|jpg|jpeg|png|bmp|GIF|JPG|PNG)$/.test(event.target.value)) { alert(".gif,jpeg,jpg,png,bmpファイルしかアップロードできません。"); return false; } var reader = new FileReader(); reader.onload = e => { let data; if (typeof e.target.result === "object") { data = window.URL.createObjectURL(new Blob([e.target.result])); } else { data = e.target.result; } this.srcImg = data; this.uploadFile = data; this.$refs.cropper.replace(data); }; reader.readAsArrayBuffer(file); }, cropImage() { let data = this.$refs.cropper.getCroppedCanvas().toDataURL(); this.$refs.cropper.replace(data); this.imgData = data; } } }; </script> <<style> * { margin: 0; padding: 0; } .content { margin: auto; max-width: 1200px; } .show-info h2 { line-height: 50px; } .vue-cropper-wrap { height: 340px; } </style>
上記ポイントは、cropした画像データをhiddenで隠して上げてimg_data
としてpostして上げることである。
また、画像データはbase64エンコードされているので、コントローラ側では、アップロード時にデコードする必要がある。
関係ないが、base64エンコードされていると、画像がサーバに上がっていなくても表示が可能。これは初耳であった。
<?php public function store() { $img_data = str_replace('data:image/png;base64,', '', $request->img_data); //ここで不必要な文字列を削除 $data = base64_decode($img_data); //デコード file_put_contents("$file_name", $data); //$file_nameに$dataを書き出す。 $path = Storage::disk('s3')->putFile($file_directory, $file_name, "public"); //S3に保存 $user_profile->img_url = Storage::disk('s3')->url($path); }
Laravel FormのInput幅を調整する方法。
課題
- form画面を作る際、よくinput部分が画面一杯で幅を狭めたい時がある。その方法にいつも迷う。
対処方法
- 幅を狭めたい対象を
div
で囲い、classにw-25
、w-50
、w-75
のどれかを指定してあげる。
<?php <form class="border border-light p-5" action="{{ route('mypage.confirm') }}" method="POST"> @csrf <p class="h4 text-center mb-4">ユーザー情報編集</p> <div class="form-group"> <label for="user_name">ユーザー名</label> <input type="text" id="user_name" name="user_name" class="form-control"> </div> <div class="form-group"> <label for="age">年齢</label> <div class="w-25"> <select id="age" name="age" class="form-control"> <option value="25">25歳</option> <option value="26">26歳</option> </select> </div> </div> <div class="form-group"> <label for="residence">居住地</label> <div class="w-25"> <select id="residence" name="residence" class="form-control"> <option value="東京都">東京都</option> <option value="静岡県">静岡県</option> </select> </div> </div> </form>