学習備忘録

よく忘れてしまうのをここにメモしておく

検索した値を保持しつつページネーションする方法 && リレーション先で条件を指定する方法

課題

  • 検索している値を保持したままページネートしたい
  • リレーション先で条件を指定する方法。

対処法

  • 状況としては、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');
    }

入力フォームの初期値に正しい情報を入れる方法

課題

  1. text入力ボックスで、バリデーション時には入力していた値、初期にはDBに登録されていた値を表示する方法
  2. select入力ボックスで、バリデーション時には入力していた値、初期にはDBに登録されていた値を表示する方法

対処法

  1. こちらは比較的簡単
<?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>
  1. こちらは結構複雑
<?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-25w-50w-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>