Laravel 6.x カラムを結合してCollectiveでselect文を作る書き方

– last_name, first_nameを結合して、bladeのselect文で選択できるようにしたい。
— 以下のようなpluckだと、引数は2つだけしか取得できないので、上手く行かない。

$names = User::pluck('last_name', 'first_name', 'id')->all();

## solution
concatでカラムの値を繋げてあげる。
controller

use DB;
// 省略
$users = User::select(DB::Raw('concat(first_name, last_name) as name'), 'id')->pluck('name','id');
dd($users);

blade

{!! Form::select('name', $users, null, ['class' => 'form-control','placeholder'=>'選択してください']) !!}

OK🤩

laravel 6.x paginationで2ページ目以降も検索条件を引き継ぐ書き方

検索フォームから検索結果に遷移した後、2ページ目以降も検索条件を引継ぎたい

### bladeのページネーション

{{$data->render()}}

{{$data->appends(request()->input())->render()}}

こう書いたんだが、2ページ目以降、一向反映されない。
何故だああああああ????
(※今日はbiz confortってコワーキングに行こうと思って、横浜元町まで来たら祝日でやってないじゃないか。。。何故だあああああ)

フラ〜とアクエリアス飲んで考えてたら、気が付いた。appendsは、パラメータを配列で渡すメソッドなので、検索フォームはGETメソッドでないと上手く動作しない

### 検索フォーム

{!! Form::open(['method'=>'POST', 'action'=>'HogeController@index']) !!}
// 省略
{!! Form::close() !!}

{!! Form::open(['method'=>'GET', 'action'=>'HogeController@index']) !!}
 <input type="hidden" name="search" value="{{ rand() }}">
// 省略
{!! Form::close() !!}

Postメソッド時のControllerでの検索時の処理は、isMethod(‘post’)で判定していたが、Getメソッドの場合は、hiddenでname=”search”をcontrollerに送って、それで判定する事にした。

### Controller

if($request->isMethod('post')){
$inputs = $request->all();
$query = Data::query();
// 検索処理 省略 
}

if($request->has('search')){
  $inputs = $request->all();
  $query = Data::query();
// 検索処理 省略 
}

FORMはセキュリティ上、POSTメソッドしか使わないと考えてたが、ユーザ検索のページネーションの処理では、POSTメソッドではなくGETメソッドの方が都合が良い、ということを理解しました。

belongsToのテーブルを検索するときのwhereHas

– 複数の入力フォームから、ユーザの入力値に応じて、belongsToのテーブルも検索したい

### blade

{!! Form::open(['method'=>'POST', 'action'=>'SearchController@index']) !!}
 // 省略
{!! Form::close() !!}

### model

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

### route

Route::match(['get','post'],'/search', 'SearchController@index');

### controller
– Data::query()->whereHas(‘user’, function($q){})で検索する
– 検索時にユーザ入力の変数で検索したい場合は、whereHas(‘user’, function($q) use($inputs){} として、渡す

public function index(Request $request){
 if($request->isMethod('post')){
 $inputs = $request->all();
 $query = Data::query();

 if(!empty($inputs['start'])){
    $query->where('start_at','>=', date('Y-m-d', strtotime($inputs['start'])) );
                }
// 省略

// 名前検索
            if(!empty($inputs['name'])){
                $works = $query->whereHas('user', function($q) use($inputs){
                    $q->where('last_name','like', '%'.$inputs['name'].'%')->orWhere('first_name','like', '%'.$inputs['name'].'%');
                })->orderBy('start','ASC')->paginate(10);
            } else {
                $data = $query->orderBy('start','ASC')->paginate(10);
            }

} else {
            $data = Data::whereMonth('start', '=', date('m'))->orderBy('start','ASC')->paginate(10);
        }
// 省略

whereHas(‘user’, function($q, $inputs){} で上手く行きそうな気がしたのですがエラーに成ります。
入力フォームの設置順に検索するのではなく、DBのリレーション順に検索をかけるので、注意が必要です。

DBから今月のデータを取得したい時

– DBから今月のデータを取得して、一覧で表示したい時
— 月単位で探す場合はwhereMonthを使う

### whereMonth

$data = Hoge::whereMonth('start_at', '=', date('m'))->get();
dd($data);

whereMonth以外にも、whereDay、whereYearなどがある。
whereDate(‘start_at’, ‘=’, date(‘Y-m’)) だとうまくいかない。

改めてカラムのデータ型の重要性を認識。

Laravel 6.x bladeのaタグでS3からファイルダウンロード

### index.blade.php
aタグでは、actionでコントローラへリンクします。その際に、S3のファイルを指定するための引数を渡します。

<a href="{{ action('HogesController@download', $data->id) }}">{{ substr($data->attachment_file }}</a>

### route
ルーティングはgetメソッドで大丈夫です。

Route::get('/download/{id}', 'HogesController@download');

### controller
1. 渡ってきた引数($id)を元に、DB保存してあったS3のファイル名(ファイルパス)を取得します。
2. ファイルのmimeTypeを取得し、ContentTypeに指定します。
3. headerを指定してreturnで返します。

public function download($id){
        $data = Data::where('id', $id)->first();
        $file_path = '/hogehoge/prd/'.$data->attachment_file; 

        $mimeType = Storage::disk('s3')->mimeType($file_path);
        $file_name = $data->attachment_file;

        $headers = [
            'Content-Type' => $mimeType,
            'Content-Disposition' => 'attachment; filename="'. $file_name. '"'
        ];
        return \Response::make(Storage::disk('s3')->get($file_path), 200, $headers);
    }

てっきりDownloadの処理はview側で書くのかと思いましたが、コントローラーに引数を渡してコントローラーで処理する方が一般的のようです。
しかし、S3のディレクトリ構造をどのようにして、ディレクトリ名・ファイル名のどこまでをDBに保存するか悩みます🤔🤔🤔

migration:rollbackで二つ以上前のmigrationに戻したい時

-カラムの修正などで、2つ以上前のmigrationにrollbackしたい時

### –step=n
migrationsのレコードが以下の状態で、aaaaaaa_tableに戻りたい。
mysql> select * from migrations;
+—-+————————————————+——-+
| id | migration | batch |
+—-+————————————————+——-+
| 11 | 2014_10_12_000000_create_users_table | 1 |
| 12 | 2014_10_12_100000_create_password_resets_table | 1 |
| 13 | 2019_08_19_000000_create_failed_jobs_table | 1 |
| 14 | 2019_12_29_222614_create_*****_table | 1 |
| 24 | 2020_01_02_003618_create_*****_table | 2 |
| 25 | 2020_01_02_113019_create_********_table | 2 |
| 26 | 2020_01_02_113040_create_******_table | 2 |
| 27 | 2020_01_02_113114_create_********_table | 2 |
| 28 | 2020_01_03_205110_create_*****_table | 2 |
| 29 | 2020_01_03_205139_create_*******_table | 2 |
| 30 | 2020_01_07_164639_create_aaaaaa_table | 3 |
| 34 | 2020_01_07_170922_create_bbbbb_bbbbbbbb_table | 4 |
| 35 | 2020_01_07_170950_create_ccccccc_table | 4 |
| 36 | 2020_01_10_005229_create_ddddddd_table | 4 |
+—-+————————————————+——-+

–step=nで、バッチを指定するのかと思い、–step=3とすると、3つだけ戻る
$ php artisan migrate:rollback –step=3
Rolling back: 2020_01_10_005229_create_ddddddd_table
Rolled back: 2020_01_10_005229_create_ddddddd_table (0.03 seconds)
Rolling back: 2020_01_07_170950_create_ccccccc_table
Rolled back: 2020_01_07_170950_create_ccccccc_table (0.02 seconds)
Rolling back: 2020_01_07_170922_create_bbbbb_bbbbbbbb
Rolled back: 2020_01_07_170922_create_bbbbb_bbbbbbbb_table (0.02 seconds)

$ php artisan migrate:rollback –step=4
Rolling back: 2020_01_10_005229_create_ddddddd_table
Rolled back: 2020_01_10_005229_create_ddddddd_table (0.02 seconds)
Rolling back: 2020_01_07_170950_create_ccccccc_table
Rolled back: 2020_01_07_170950_create_ccccccc_table (0.02 seconds)
Rolling back: 2020_01_07_170922_create_bbbbb_bbbbbbbb
Rolled back: 2020_01_07_170922_create_bbbbb_bbbbbbbb_table (0.02 seconds)
Rolling back: 2020_01_07_164639_create_aaaaaa_table
Rolled back: 2020_01_07_164639_create_aaaaaa_table (0.02 seconds)

–step=n は、テーブル数の指定なので、4つ戻したい時は4と指定する。

間違ってもクリティカルなエラーにはならないが、手順書などを書く場合は間違うと恥ずかしい😂

request model, requestsテーブルを作成すると

controller

use Illuminate\Http\Request;
// 省略
use App\Request;

view
-> Symfony\Component\Debug\Exception\FatalErrorException
Cannot use App\Request as Request because the name is already in use

laravelではpost methodなどでrequestクラスを使っているので、requestモデル、requestsテーブルを作って使おうとすると、重複エラーになる。

modelやmigrationファイルの作り直しは、関連のコードだけでなく、設計書の修正まで色々手間がかかるので、make:modelの時にエラーを吐いてほしい。
データベース周りの出戻りは面倒なので、やはりDB設計は優秀な人がやらないと大変になる。

Laravel 6.xでS3への保存・呼び出し方法

– hasManyの1M以下のファイルを、storage保存から、S3保存に切り替えたい
### 現状:storage保存

if($file = $request->file('file')){
            $path = $file->getClientOriginalName();
            $file->storeAs('./public/files/tmp/', $path);
            $inputs['path'] = $path;
        } else {
            $inputs['path'] = '';
        }

## 手順
### 1. AWSマネージメントコンソールログイン
https://ap-northeast-1.console.aws.amazon.com/
ユーザ名メニュー 「My Security Credentials」 -> IAMページ表示

### 2. S3用のIAM作成
Users -> Add user
1ページ目
– User name: [ ${project name} ]
– Select AWS access type: check [Programmatic access] ※Enables an access key ID and secret access key for the AWS API, CLI, SDK, and other development tools.

2ページ目
– Set permissions: [Attach existing policies directly]
— [AmazonS3FullAccess]

3ページ目
– Add tags (optional): none

4ページ目
– Review: nothing to do

5ページ目
– User name: ${project name}
– Access key ID : 新規発行
– Secret access key : 新規発行

### 3. S3 bucket作成
https://s3.console.aws.amazon.com/s3/home?region=ap-northeast-1
1ページ目
– Bucket name: [ ${project name} ]
– Region: [Asia Pacific (Tokyo)]

2ページ目
– Properties : 必要に応じて設定

3ページ目
– Block public access : off

4ページ目
– review -> create bucket

### 4. composerインストール
https://readouble.com/laravel/6.x/ja/filesystem.html#driver-prerequisites

// out of memoryとなるのでswapメモリを追加
$ sudo /bin/dd if=/dev/zero of=/var/swap.1 bs=1M count=1024
$ sudo /sbin/mkswap /var/swap.1
$ sudo /sbin/swapon /var/swap.1
$ free

// s3パッケージインストール
$ php composer.phar require league/flysystem-aws-s3-v3 ~1.0
// キャッシュアダプタインストール(公式ドキュメントに絶対に必要と書いてある。。)
$ php composer.phar require league/flysystem-cached-adapter ~1.0

### 5. envファイルにS3アクセス情報追記

AWS_ACCESS_KEY_ID=${access_key}
AWS_SECRET_ACCESS_KEY=${secrete_access_key}
AWS_DEFAULT_REGION=ap-northeast-1
AWS_BUCKET=${bucket name}

### 6. controllerから保存
putFileAs(‘S3ディレクトリ’, $file, ‘filename’, ‘public’) で保存。
ファイル名を気にしない場合はputFileでもOK

if($file = $request->file('file')){
            $path = $file->getClientOriginalName();
            Storage::disk('s3')->putFileAs('/', $file, $path,'public');
            $inputs['path'] = $path;
        } else {
            $inputs['path'] = '';
        }

### 7. S3ファイルの表示

Route::get('/read', function(){
	$path = Storage::disk('s3')->url('image.jpeg');
	return "<img src=".$path.">";
});

思ったより簡単でワロタ。ベタがきだと、もう少しコードが必要だった記憶があります。

DBに現在時刻を5分区切りの単位で入れたい時

### フォーム入力
1. フォームから時間を入力する場合はselect文で5分単位で選べるように、5分単位のテーブルを作る
insert into minutes (name) values (’00’);
insert into minutes (name) values (’05’);
insert into minutes (name) values (’10’);
insert into minutes (name) values (’15’);
insert into minutes (name) values (’20’);
insert into minutes (name) values (’25’);
insert into minutes (name) values (’30’);
insert into minutes (name) values (’35’);
insert into minutes (name) values (’40’);
insert into minutes (name) values (’45’);
insert into minutes (name) values (’50’);
insert into minutes (name) values (’55’);

2. select文では、minutesテーブルから値を呼び込む

{!! Form::select('start', $minutes, 1, ['class' => 'form-control']) !!}

### 現在時刻を5分単位でDBに入力

$minute = date('i');

        $minute_margin = 5;
        if($minute % $minute_margin){
            $minute += $minute_margin - ($minute % $minute_margin);
        }

例: 43分の時:43 + (5-3) = 45

上記は端数があった場合繰り上がりですが、$minute -= $minute % $minute_margin とすれば、繰り下がりになる筈です。

あ。。。55〜00分の処理忘れてた。。
### 追加

$hour = date('H');
        $minute = date('i');


        $minute_margin = 5;
        if($minute % $minute_margin){
            if($minute > 55){
                $hour = date('H',strtotime("+1 hour"));
                $minute = 00;
            } else {
                $minute += $minute_margin - ($minute % $minute_margin);
            }          

        }

Laravel 入力フォームが時間・分に別れている時の未来判定バリデーション

– 入力フィールドが時間・分に別れている時に、「終了時間が開始時間より未来」のバリデーションルール作成方法
— どこで時間と分を結合してバリデーションをかけるか? 

*.blade.php

<tr>
                    <th>開始時刻 <span class="badge badge-danger">必須</span></th>
                    <td>
                        <div class="form-inline">
                                {!! Form::select('start_H', $hours, 10, ['class' => 'form-control']) !!}
                                <span class="unit">時</span>
                                {!! Form::select('start_i', $minutes, 1, ['class' => 'form-control']) !!}
                                <span class="unit">分</span>
                        </div>
                    </td>
                </tr>
                <tr>
                    <th>終了時刻 <span class="badge badge-danger">必須</span></th>
                    <td>
                        <div class="form-inline">
                                {!! Form::select('end_H', $hours, 19, ['class' => 'form-control']) !!}
                                <span class="unit">時</span>
                                {!! Form::select('end_i', $minutes, 1, ['class' => 'form-control']) !!}
                                <span class="unit">分</span>
                        </div>
                        @error('end')
                                <span class="error">{{$message}}</span>
                        @enderror
                    </td>
                </tr>

### controller
コントローラーでunix timeを計算し、「終了時間」-「開始時間」 <= 0 の時、withErrorsでエラーメッセージと一緒にredirect backする [php] public function confirm(CustomeRequest $request){ $inputs = $request->all();
$start = $inputs[‘start_H’] . ‘:’ . $inputs[‘remote_start_i’] . ‘:00’;
$end = $inputs[‘end_H’] . ‘:’ . $inputs[‘remote_end_i’] . ‘:00’;
if((strtotime($end) – strtotime($start)) <= 0) { return redirect()->back()->withInput()->withErrors(array(‘end’ => ‘終了時刻は、開始時刻より未来を設定してください’));
}
[/php]

フォームリクエストやカスタムリクエストは、attribute、バリデーション処理、エラーメッセージが対になっているので、コントローラー側で処理をした。