laravel whereで複数の一致条件 ~ a =A, b = B もしくは a = B, b = A

Controllerでa =A, b = B もしくは a = B, b = A を探したい時。

複数のwhereの場合は、whereを繋げれば良いのですが、複数のwhereのどちらかの場合は、where whereをorWhereで繋げれば良い。

$messages = Hotels::where(function($q) use ($id, $user_id){
            $q->where('user_id', $id)->where('second_id', $user_id);
        })->orWhere(function($q) use ($id, $user_id){
            $q->where('user_id', $user_id)->where('second_id', $id);
        })->get();

割と簡単に書けます。

Collectiveでのcheckboxの書き方

– view側でnameをブランケットで記述する。
– controllerのwhereInでチェックした値を呼び出せる

### blade

<div class="item">
	{!! Form::checkbox('category[]', 1, 1, ['class'=>'form-check-input']) !!}
	{!! Form::label('category[]', 'シティホテル', ['class'=>'form-check-label']) !!} 
</div>
<div class="item">
	{!! Form::checkbox('category[]', 2, 1, ['class'=>'form-check-input']) !!}
	{!! Form::label('category[]', 'ビジネスホテル', ['class'=>'form-check-label']) !!} 
</div>

### controller

if($request->has('category')){
            $Hotels = Message::where('id', $id)->whereIn('category_id', $request->input('category'))->orderBy('created_at','ASC')->paginate(2)->onEachSide(1);
        } else

input::all()の場合は、配列で渡ってくるので、そのままDBに流し込めば良い。
controller側で配列を作ろうとしたら、checkboxの最後の値しか渡ってこずに失敗しました。

onEachSide()が効かない時

bladeのpaginationでonEachSideを実装するも、Laravel6.xで全て表示されてしまう。

{{$users->onEachSide(1)->appends(request()->input())->render()}}
{{$users->appends(request()->input())->onEachSide(1)->render()}}

Controller側で、onEachSide()を付ける。

$users = User::orderBy('updated_at','DESC')->paginate(10)->onEachSide(1);

公式では、デフォルトで3つと書かれているが、機能しない。何故だか釈然としないが、controllerで対応する。
https://readouble.com/laravel/6.x/ja/pagination.html

blade内でのswitch文の書き方

idなどの判定で分岐が3つ以上になると、if文が冗長に感じてくるのでswitch文を使用したくなる。

@switch($hoge->id)
	@case(1)
		<li></li>
		@break
	@case(2)
	@case(3)
		<li></li>
		@break
	@default
		<li></li>
@endswitch

@case(1, 2) と書くとエラーになるので注意が必要
一般的にインデントは半角スペース4つだが、テーブル内などで分岐処理を書くと、分岐処理をしているところだけインデントが4つ右にずれるが、これはDOMにした時に揃うように左にずらすべきなのだろうか?
綺麗なコードを書いていると見られたいので、DOMのインデントを揃えたい、という欲求が頭を巡るが、レジストリの処理は殆どの変わらないし、コードの保守性は規約に沿ったほうが高まるように思えるので、いつも葛藤になる。

date型レコードの範囲内のYYYYMMの連続した配列を作る

mysql内に、 ‘2019-10-05′,’2020-01-05’,’2020-02-13’などのレコードがあった場合に、その範囲内の連続したYYYYMMの配列(2019-10, 2019-11, 2019-12, 2020-01)を作りたい時
-> 最初と最後のUnixTime範囲内で、1ヵ月づつ増えるFor文を作る。

        $last = Hoge::where('user_id', $id)->orderBy('time','ASC')->first();
        $last_month = substr($last['time'], 0, 7);
        $last_unix = strtotime('first day of ' . $last_month);
        $latest = Hoge::where('user_id', $id)->orderBy('time','DESC')->first();
        $latest_month = substr($latest['time'], 0, 7);
        $latest_unix = strtotime('last day of ' . $latest_month);

        for($time = $last_unix; $time <= $latest_unix; $time = strtotime('+1 month', $time)){
            $Ym[] = date('Ym', $time);
        }

取得したデータをそのままUnixTimeにして 1ヵ月づつ増やすと、 $last_month + 1 month > $latest_month となる場合があるので、$last_monthを月初の日付、$latest_monthを月末の日付に変換してからFor文を回す必要がある。

う〜ん、最初にDBのデータ型考える際に、ロジックの計算処理を考えてデータ型を決めれるようになるな。

laravelでcsv出力

### view
csvダウンロードボタンを作り、postメソッドのhiddenで引数を渡します。

{!! Form::open(['method'=>'POST', 'action'=>['HogeController@download'] ]) !!}
 <input type="hidden" name="period" value="{{ $inputs&#91;'data'&#93; }}">
 {!! Form::submit('CSVダウンロード',['class'=>'btn', 'name'=>'csv']) !!}
{!! Form::close() !!}

### route
csvファイルをreturnするだけなので、設計のコンベンションが無ければディレクトリは然程きにする必要なし。

Route::post('/dir/csv', 'HogeController@download');

### controller

use Symfony\Component\HttpFoundation\StreamedResponse;
public function download(Request $request){
// 省略

// CSV生成
        $response = new StreamedResponse(function() use($data1, $data2){
                $stream = fopen('php://output', 'w');
                stream_filter_prepend($stream, 'convert.iconv.utf-8/cp932//TRANSLIT');
                fputcsv($stream, ['id','raw1','raw2','raw3','raw4','raw5']);
                foreach($data as $value){
                     fputcsv($stream, ['id',$value->raw1,$value->raw2,$value->raw3,$value->raw4,$value->raw5]);
                }
                fclose($stream);
        });
        $response->headers->set('Content-Type', 'application/octet-stream');
        $response->headers->set('Content-Disposition', 'attachment; filename="user.csv"');
        return $response;

特段ストレージやS3などに一時保存しなくて良いので、シンプルだ。
上記はユーザがダウンロードボタンを押した時の処理だが、月次のバッチ処理によるcsv出力なども比較的簡単に実装できそうだ。

連想配列のデータをbuttonのonclick=”location.href=””からactionに渡したい時

やりたい事: controllerからbladeに渡ってきた複数の変数を、csv生成の為に、そのままの値で再度controllerの別のメソッドに渡したい

aタグでidをコントローラーに渡したい時は、controllerと引数を渡すだけ。

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

上記を踏まえて、buttonのonclickでコントローラーに連想配列のデータを渡したい時

<button type="button" value="送信" class="btn btn-success mx-auto d-block" onclick="location.href='{{ action('HogeController@download'), $data1 }}'">ダウンロード</button>

このように書くと、onclick=”location.href=””の中にシングルクオテーション(”)が入るためエラーになる

バックスラッシュでエスケープしてもダメ。

<button type="button" value="送信" class="btn btn-success mx-auto d-block" onclick="location.href='{{ action(\'HogeController@download\'), $data1 }}'">ダウンロード</button>

フォームで連想配列を渡そうと思ったが上手くいかない。

{!! Form::open(['method'=>'POST', 'action'=>['HogeController@download', $data1] ]) !!}
    {!! Form::submit('ダウンロード',['class'=>'btn btn-success', 'name'=>'download']) !!}
{!! Form::close() !!}

Facade\Ignition\Exceptions\ViewException
syntax error, unexpected

結局hiddenで引数をcontrollerに渡し、controller側で再度データを成形するしかないのか。

Eloquent query内での条件式(When)の書き方

Eloquent query内で条件判定をする場合は、whenで判定する引数を渡す
– inputsのperiodの値がotherの時は、whereBetweenでdateがinputsの指定した範囲で値を取得する
– inputsのperiodの値がother以外の時は、whereMontheでdateが$dateの値を取得する

$data =  Hoge::where('user_id', $id)->when($inputs, function($query, $inputs) use($date){
                if($inputs['period'] == 'other'){
                    return $query->whereBetween('date', [$inputs['date_start'], $inputs['date_end']]);
                } else {
                    return $query->whereMonth('date', '=', $date);
                }
            })->count('date');

最初、public functionで条件判定する関数を別に書いて呼び出すのかと思ったが、whenを使えば、インラインでいける。
うん、綺麗^^
上記だとコード量は減るんだが、ただこれをforeachで回す場合、foreachの前で条件分岐をした方が、foreachで毎回判定しなくて良いので処理速度は上がる。
コード量が少ない方を優先してしまいがちだが、あくまでユーザの処理スピードを優先して書かないとダメか。
このwhenの書き方は凄く好きなんだけどなー

条件に一致するcount

countの条件処理は、count関数の中ではなく、前で行う

$count =  Hoge::where('user_id', $id)->whereMonth('time', '=', date('m'))->count('apply_id' == 2);

$count =  Hoge::where('user_id', $id)->whereMonth('time', '=', date('m'))->where('apply_id', 2)->count();

公式: https://readouble.com/laravel/5.8/ja/queries.html#aggregates
公式ドキュメントや検索に引っかからない時はデバッグしながらやるしかありません。

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🤩