「Laravel」の版間の差分

提供:senooken JP Wiki
(Many To Many)
(モデルのリレーションの反復)
 
(同じ利用者による、間の33版が非表示)
67行目: 67行目:
  UNION ALL SELECT '0000010109','2','X','X','X','41','111 X','10','2024-01-01','2024-01-01','地上','1','2024-01','2024-01','1','1','メーカー','型式','2024-01'
  UNION ALL SELECT '0000010109','2','X','X','X','41','111 X','10','2024-01-01','2024-01-01','地上','1','2024-01','2024-01','1','1','メーカー','型式','2024-01'
  <nowiki>;</nowiki><nowiki>
  <nowiki>;</nowiki><nowiki>
                                      \"}}"</nowiki>
                                                                        \"}}"</nowiki>


====Directory Structure====
====Directory Structure====
214行目: 214行目:


Bladeで使う場合、request() ([https://laravel.com/docs/5.8/helpers#method-request Helpers - Laravel 5.8 - The PHP Framework For Web Artisans]) のグローバルヘルパー関数を経由して、Requestインスタンス経由でアクセスする。
Bladeで使う場合、request() ([https://laravel.com/docs/5.8/helpers#method-request Helpers - Laravel 5.8 - The PHP Framework For Web Artisans]) のグローバルヘルパー関数を経由して、Requestインスタンス経由でアクセスする。
なお、queryやinputはPHPの$_GET/$_POST経由でアクセスしている。その都合で、.は_に変換される。
.を維持したければ、 $request->getQueryString();から独自処理が必要。
=====Retrieving Input Via Dynamic Properties=====
=====Retrieving Input Via Dynamic Properties=====
  $name = $request->name;
  $name = $request->name;
273行目: 277行目:
主にPOST系のアクション実行用に、バリデーションというデータの検証の仕組みがある。
主にPOST系のアクション実行用に、バリデーションというデータの検証の仕組みがある。


==== Quickstart ====
ControllerのValidateRequestsトレイトに用意されており、Controllerのメソッドとして使える。
ControllerのValidateRequestsトレイトに用意されており、Controllerのメソッドとして使える。
  $this->validate($request, [検証設定の配列]);
  $this->validate($request, [検証設定の配列]);
上記の書式で使う。
 
$request->validate([検証設定の配列]);
上記の書式で使う。$request->validateでもいい。どちらかというとこちらだと第一引数を省略できるので望ましい。
 
validateに失敗したら自動的に元の画面をリダイレクト表示する。
 
==== Displaying The Validation Errors ====
失敗して元の画面をリダイレクト表示した後、エラー内容をユーザーに知らせたい。その場合、\Illuminate\Support\MessgeBagの$errors変数に必要な情報が格納される。これを使う。
 
name属性がキーの連想配列になっていて、valueにエラーメッセージが入る。
<!-- /resources/views/post/create.blade.php -->
<nowiki> </nowiki>
<nowiki><h1>Create Post</h1></nowiki>
<nowiki> </nowiki>
@if ($errors->any())
<nowiki> </nowiki>  <nowiki><div class="alert alert-danger">
                          <ul>
                              @foreach ($errors->all() as $error)
                                  <li>{{ $error }}</nowiki><nowiki></li></nowiki>
<nowiki> </nowiki>          @endforeach
<nowiki> </nowiki>      <nowiki></ul></nowiki>
<nowiki> </nowiki>  <nowiki></div></nowiki>
@endif
<nowiki> </nowiki>
<!-- Create Post Form -->
 
===== The @error Directive =====
@error指令も使える。
<!-- /resources/views/post/create.blade.php -->
<nowiki> </nowiki>
<nowiki><label for="title">Post Title</label></nowiki>
<nowiki> </nowiki>
<nowiki><input id="title" type="text" class="@error('title') is-invalid @enderror"></nowiki>
<nowiki> </nowiki>
@error('title')
<nowiki> </nowiki>  <nowiki><div class="alert alert-danger">{{ $message }}</nowiki><nowiki></div></nowiki>
@enderror
@error(name属性名)@enderrorの書式。@error指令内の$messageで$error[属性名]相当を参照できる。


ただ、この基本的なバリデーションだとコントローラーに都度記載が必要。できれば、リクエストなどで別でやりたい。
ただ、この基本的なバリデーションだとコントローラーに都度記載が必要。できれば、リクエストなどで別でやりたい。
285行目: 327行目:
エラーメッセージもFormRequest派生クラスで作れる。
エラーメッセージもFormRequest派生クラスで作れる。


=== Logging ===
[https://laravel.com/docs/5.8/logging Logging - Laravel 5.8 - The PHP Framework For Web Artisans]
=====Other=====
*[https://stackoverflow.com/questions/41978290/how-to-log-object laravel - How to Log object? - Stack Overflow]
*[https://qiita.com/ucan-lab/items/29614d0f3ded1d3a94fb 【超入門】Laravelのデバッグ手法22選 #PHP - Qiita]
Laravelで便利なログ出力方法がいくつかある。
*ヘルパー関数 ([https://laravel.com/docs/5.8/helpers Helpers - Laravel 5.8 - The PHP Framework For Web Artisans])
**dd: dump and die。引数の変数をその場で表示して終了。
**dump: 引数の変数をその場で表示。
*Log::debug(): ログファイルstorage/logsに出力 (<code>use Illuminate\Support\Facades\Log;</code>)。
**<code>Log::info(print_r($user, true));</code>
**<code>Log::info(json_encode($user));</code>
**オブジェクト類はjson_encodeがいい。
======Allowed memory size of 134217728 bytes exhausted (tried to allocate 90181632 bytes)======
なお、print_r($request, true) などをすると、以下のエラーが出る。
[2017-09-06 15:19:44] production.ERROR: Symfony\Component\Debug\Exception\FatalErrorException: Allowed memory size of 134217728 bytes exhausted (tried to allocate 90181632 bytes) in [path to file reducted] Stack trace: #0 {main}
[[https://laracasts.com/discuss/channels/requests/print-rrequest-causes-out-of-memory-error print_r($request) causes out of memory error.]] にあるように、print_rは継承元も再帰的に出力し、Laravelはたくさん継承しているからいっぱいになるらしい。


これはせずに、dumpなどを使う。
======Helpers======
[https://laravel.com/docs/5.8/helpers Helpers - Laravel 5.8 - The PHP Framework For Web Artisans]


デバッグに役立つグローバルなヘルパー関数がいくつかある。Bladeでもそのまま使用できる。
*info: Log::info相当。第二引数に配列データも渡せる。
*logger: Log::debug相当。引数を空にするとloggerインスタンスを取得できて、そこから個別のエラーレベルに出力もできる。
Log::info/Log::debugはuse宣言が必要で面倒なので、info/loggerを使ったほうがいい。
logger('Debug message');
logger('User has logged in.', ['id' => $user->id]);
logger()->error('You are not allowed here.');


=====debugbar=====
==== Available Validation Rules ====
*[https://github.com/barryvdh/laravel-debugbar barryvdh/laravel-debugbar: Debugbar for Laravel (Integrates PHP Debug Bar)]
利用可能なルール一覧。
*[https://qiita.com/goto_smv/items/b7be0985029ab3d03217 Laravel Debugbarについて #Laravel - Qiita]


*[https://qiita.com/rarrrrsQ/items/34f3867bbcb17604e136 Laravelでのデバッグ方法4パターンまとめ #初心者 - Qiita]
Accepted
DebugbarというLaravelの開発デバッグにかなり便利なツールがある。


導入方法
Active URL
composer require barryvdh/laravel-debugbar --dev
.envでAPP_DEBUG=trueの場合に機能する。


クエリーの発行数、メモリー使用量、実行時間。このあたりが特に重要と思われる。
After (Date)


== Frontend ==
After Or Equal (Date)


=== Blade Templates ===
Alpha
[https://laravel.com/docs/5.8/blade Blade Templates - Laravel 5.8 - The PHP Framework For Web Artisans]
=====@section/@yield: Template Inheritance=====
Bladeでは、継承とセクションの2種類のテンプレートの流用方法がある。


Layoutはページ全体のテンプレート。セクションは区画単位のテンプレート。
Alpha Dash


セクションは@section/@yieldを使って実現する。
Alpha Numeric


@sectionは@showか@endsectionで終わる。
Array


@show/@endsectionの違い ([https://qiita.com/sirogane/items/1829c2a12d284ae20eb5 Bladeテンプレートの@showと@endsectionを間違えないようにする #Laravel - Qiita])。親テンプレートで定義する場合は@show。親でも@endsectionを使うと、sectionの内容が消える。sectionは本来、元テンプレートから継承したものを埋め込むためのものなので、ベーステンプレートだと埋め込み元がないので消えるのだと思う。だから、@showを使う必要があると思われる。
Bail
=====@component/@slot: Components & Slots=====
テンプレートよりも細かい部品単位の流用方法がcomponent。ヘッダーやフッター、ボタンなどの部品単位で流用できる。


viewsディレクトリー以下に格納する。一般的にはviews/components/ok.blade.phpなどのように配置し、components.okなどで、コンポーネント名を指定して読み込む。
Before (Date)


component定義側は、通常のBladeと同じだが、コンポーネント内で変数のプレースホルダーを使用できる。これは、利用側の@slotのブロックで引き渡す。<!--components/message.blade.php-->
Before Or Equal (Date)


<nowiki><div class="message">
Between
                                                                                                                  <p class="msg_title">{{$msg_title}}</nowiki><nowiki></p></nowiki>
  <nowiki><p class="msg_content">{{$msg_content}}</nowiki><nowiki></p></nowiki>
<nowiki> </nowiki><nowiki>{{$slot}}</nowiki>
<nowiki></div></nowiki>
component利用側で組み込むために工夫する。
@component(名前)
  @slot('msg_title')
    title
  @endslot
    <nowiki><strong>Whoops!</strong></nowiki> Something went wrong!
@endcomponent
$slot変数には、@componentsのテキスト、@slotブロック以外が入る。


Laravelに複数の在不明のcomponentを順番に適用させたい場合componentFirstを使う。
Boolean
@componentFirst(['custom.alert', 'alert'])
    <nowiki><strong>Whoops!</strong></nowiki> Something went wrong!
@endcomponent
@slot以外に、変数を渡すこともできる。
@component('alert', ['foo' => 'bar'])
    ...
@endcomponent
@component('alert')
  @slot('foo', 'bar')
@endcomponent
slotの設定方法は複数ある。@slot/@endslotよりかは@slot()で設定するほうが短い。が、@component内は$slotのデフォルト値を入れるとしたほうがわかりやすいかもしれない。


ただし、@endcomponentは省略できない。
Confirmed


名前付きslotのデフォルト値設定はない。やりたければ、??や@if/@issetで自分でやっておく。
Date


デフォルトの$slotは、@componentの配列式 (@component(, ['slot']) では指定できないが、view関数で呼ぶ際は['slot']で指定できる。
Date Equals


基本は短くできるので、component内では$slotを使うほうがいい。
Date Format


なお、$slotは扱いに注意が必要。使う側で指定がなかったら、nullではないが見えない変な値が入っている。変数として使う際は"$slot"のように二重引用符で囲んで、値がない場合に確実に空にしておく。
Different
=====@include: Including Sub-Views=====
レイアウトやコンポーネントのように、変数の引き渡しなど複雑なことをしない場合、単純な定形固定文字列を読み込むような場合、Sub-Viewsというのを使うこともできる。


これは@includeでテンプレートファイルをそのまま読み込むのが基本。親の変数もそのまま使える。他に、引数で変数を渡すこともできる。
Digits
@include('view.name', ['some' => 'data'])
@includeIf('view.name', ['some' => 'data'])
@includeFirst(['custom.admin', 'admin'], ['some' => 'data'])
@includeで指定したテンプレートが不在の場合、Laravelはエラーを投げる。このエラーを回避したい場合、@includeIf指令を使う。@includeIfのバリエーションの一種で、配列のテンプレート名で存在する最初のものを使う場合、@includeFirstを使う。


条件がtrueなら読み込む場合、@includeWhenがある。
Digits Between
@include($boolean, 'view.name', ['some' => 'data'])
@ifよりもシンプル。


[https://qiita.com/ah__memo/items/1936419d908875477aa8 【Laravel】bladeの@includeと@componentの違い #PHP - Qiita]
Dimensions (Image Files)


includeはcomponentと違って@endincludeがいらない。
Distinct


componentはslotで渡すこともできる。$slotを使える点が大きな違い。
E-Mail


複雑で長いHTMLなどを、引き渡して使いたい場合、componentのほうがいい。
Ends With
=====@each: Rendering Views For Collections=====
意外と使用頻度が高いのが繰り返し表示。例えば、リストの項目、テーブルの行など。これようの指令が@each
  @each('components.item', $data, 'item');
$data配列の要素をコンポーネントのitem変数に渡す。
// components/item.blade.php
<nowiki><li>{{$item['name']}}</nowiki> <nowiki>[{{$item['mail']}}]</nowiki><nowiki></li></nowiki>


==== Displaying Data ====
Exists (Database)


===== Displaying escaped Data =====
File
Bladeでデータを表示する際は、二重波括弧を使う。
Route::get('greeting', function () {
    return view('welcome', ['name' => 'Samantha']);
});


Hello, <nowiki>{{ $name }}</nowiki>.
Filled
二重波括弧は自動的にhtmlspecialcharsでXSS対策してくれる。変数の他に、PHP関数の結果も表示できる。


===== Displaying Unescaped Data =====
Greater Than
なお、生のHTMLなどのデータをそのまま表示させたい場合、二重波括弧の代わりに<code>{!! !!}</code>で囲む。
Hello, {!! $name !!}.
Hello, {!! e($name) !!}.
もっというと、e()でエスケープしておくと安心 ([https://qiita.com/itty-star/items/22b4293cdbd847a1aa70 Bladeで変数に入れたhtml文字列を表示させる #Laravel - Qiita])。


[https://laravel.com/docs/5.8/helpers Helpers - Laravel 5.8 - The PHP Framework For Web Artisans]
Greater Than Or Equal


混乱するが、二重波括弧内はPHP扱い、外はHTML扱い。二重波括弧内で表示文字列扱いしたいなら、文字列にする必要がある。
Image (File)
 
In
 
In Array
 
Integer
 
IP Address


===== Rendering JSON =====
JSON


* [https://laravel.com/docs/5.8/blade#displaying-data Blade Templates - Laravel 5.8 - The PHP Framework For Web Artisans]
Less Than
* [https://stackoverflow.com/questions/33326699/passing-laravel-array-in-javascript php - Passing (laravel) Array in Javascript - Stack Overflow]
* [https://www.dbestech.com/tutorials/passing-laravel-array-in-javascript Passing (laravel) Array in Javascript]


JavaScript変数として、配列をJSONとして描画したいことがあるだろう。
Less Than Or Equal
<nowiki><script>
                                                              var app = <?php echo json_encode($array); ?>;
                                                              var app = {{json_encode($array)}}</nowiki>;
      var app = {!! json_encode($array) !!};
<nowiki> </nowiki>    var array = JSON.parse('<nowiki>{{ json_encode($theArray) }}</nowiki>');
<nowiki></script></nowiki>
手動でjson_encodeを呼ぶ代わりに、@json指令がある。
<script>
    var app = @json($array);
 
    var app = @json($array, JSON_PRETTY_PRINT);
</script>
<nowiki>なお、@jsonはjson_encodeと同じではない。以下のコードで{{json_encode部分を@jsonに変えるとうまくいかない。</nowiki>
<nowiki><button type="button" onclick="
        ajaxGetAndSetTable('/ajax/楽楽販売_器具交換履歴_最新取得?消費者コード=' + document.getElementById('消費者コード').value, '#facilityHistory tbody',
        {{json_encode($names)}}, '部屋番号');
    "
    >楽楽販売最新取得 (未実装)</button></nowiki>
<code><nowiki>{{json_encode($array)}}</nowiki></code>で良いと思われる。


{{}} だとエスケープされて二重引用符が<nowiki>&</nowiki>quot;になって、扱いにくいことがある、{!! !!}でエスケープ解除すると問題ない。
Max


=====Blade & JavaScript Frameworks=====
MIME Types
JavaScriptフレームワークの中に、二重波括弧をプレースホルダーとしてそのまま使うものがある。そういう場合、@を波括弧に前置するとそのまま表示する。
 
<nowiki><h1>Laravel</h1></nowiki>
MIME Type By File Extension
<nowiki> </nowiki>
 
Hello, @<nowiki>{{ name }}</nowiki>.
Min
======The @verbatim Directive======
 
JavaScript変数を表示する場合、波括弧の前に@をたくさん置かないで済むように、@verbatimで囲める。
Not In
@verbatim
 
<nowiki> </nowiki>   <nowiki><div class="container">
Not Regex
                                                                                                                            Hello, {{ name }}</nowiki>.
 
<nowiki> </nowiki>  <nowiki></div></nowiki>
特に頻出の重要なもの。
@endverbatim
 
=====Control Structure/Blade Directives=====
* required: 存在を要求。PHPのemptyでtrueになるようなものはアウト。ただし、0は許容。
======If Statements======
 
@if (count($records) === 1)
=== Logging ===
    I have one record!
[https://laravel.com/docs/5.8/logging Logging - Laravel 5.8 - The PHP Framework For Web Artisans]
  @elseif (count($records) > 1)
=====Other=====
    I have multiple records!
*[https://stackoverflow.com/questions/41978290/how-to-log-object laravel - How to Log object? - Stack Overflow]
@else
*[https://qiita.com/ucan-lab/items/29614d0f3ded1d3a94fb 【超入門】Laravelのデバッグ手法22選 #PHP - Qiita]
    I don't have any records!
Laravelで便利なログ出力方法がいくつかある。
@endif
*ヘルパー関数 ([https://laravel.com/docs/5.8/helpers Helpers - Laravel 5.8 - The PHP Framework For Web Artisans])
**dd: dump and die。引数の変数をその場で表示して終了。
@unless (Auth::check())
**dump: 引数の変数をその場で表示。
    You are not signed in.
*Log::debug(): ログファイルstorage/logsに出力 (<code>use Illuminate\Support\Facades\Log;</code>)。
@endunless
**<code>Log::info(print_r($user, true));</code>
**<code>Log::info(json_encode($user));</code>
@isset($records)
**オブジェクト類はjson_encodeがいい。
    // $records is defined and is not null...
======Allowed memory size of 134217728 bytes exhausted (tried to allocate 90181632 bytes)======
@endisset
なお、print_r($request, true) などをすると、以下のエラーが出る。
 
  [2017-09-06 15:19:44] production.ERROR: Symfony\Component\Debug\Exception\FatalErrorException: Allowed memory size of 134217728 bytes exhausted (tried to allocate 90181632 bytes) in [path to file reducted] Stack trace: #0 {main}
@empty($records)
[[https://laracasts.com/discuss/channels/requests/print-rrequest-causes-out-of-memory-error print_r($request) causes out of memory error.]] にあるように、print_rは継承元も再帰的に出力し、Laravelはたくさん継承しているからいっぱいになるらしい。
    // $records is "empty"...
 
@endempty
これはせずに、dumpなどを使う。
======Helpers======
@auth
[https://laravel.com/docs/5.8/helpers Helpers - Laravel 5.8 - The PHP Framework For Web Artisans]
    // The user is authenticated...
 
@endauth
デバッグに役立つグローバルなヘルパー関数がいくつかある。Bladeでもそのまま使用できる。
 
*info: Log::info相当。第二引数に配列データも渡せる。
@guest
*logger: Log::debug相当。引数を空にするとloggerインスタンスを取得できて、そこから個別のエラーレベルに出力もできる。
    // The user is not authenticated...
Log::info/Log::debugはuse宣言が必要で面倒なので、info/loggerを使ったほうがいい。
@endguest
  logger('Debug message');
  logger('User has logged in.', ['id' => $user->id]);
@auth('admin')
  logger()->error('You are not allowed here.');
    // The user is authenticated...
 
  @endauth
=====debugbar=====
 
*[https://github.com/barryvdh/laravel-debugbar barryvdh/laravel-debugbar: Debugbar for Laravel (Integrates PHP Debug Bar)]
@guest('admin')
*[https://qiita.com/goto_smv/items/b7be0985029ab3d03217 Laravel Debugbarについて #Laravel - Qiita]
    // The user is not authenticated...
 
@endguest
*[https://qiita.com/rarrrrsQ/items/34f3867bbcb17604e136 Laravelでのデバッグ方法4パターンまとめ #初心者 - Qiita]
DebugbarというLaravelの開発デバッグにかなり便利なツールがある。
  @hasSection('navigation')
 
    <nowiki><div class="pull-right"></nowiki>
導入方法
        @yield('navigation')
  composer require barryvdh/laravel-debugbar --dev
    <nowiki></div></nowiki>
.envでAPP_DEBUG=trueの場合に機能する。
 
 
    <nowiki><div class="clearfix"></div></nowiki>
クエリーの発行数、メモリー使用量、実行時間。このあたりが特に重要と思われる。
@endif
 
特に@isset/@emptyをよく使うかも。ただ、これらには@elseはないので注意が必要かもしれない。やるなら、
== Frontend ==
@if(isset())
 
@else
=== Blade Templates ===
@endif
[https://laravel.com/docs/5.8/blade Blade Templates - Laravel 5.8 - The PHP Framework For Web Artisans]
=====@section/@yield: Template Inheritance=====
  @isset()
Bladeでは、継承とセクションの2種類のテンプレートの流用方法がある。
@endisset
 
@empty()
Layoutはページ全体のテンプレート。セクションは区画単位のテンプレート。
@endempty
 
======Switch Statements======
セクションは@section/@yieldを使って実現する。
======Loops======
 
PHPの反復構造に近いものがある。重要。
@sectionは@showか@endsectionで終わる。
@for ($i = 0; $i < 10; $i++)
 
<nowiki> </nowiki>  The current value is <nowiki>{{ $i }}</nowiki>
@show/@endsectionの違い ([https://qiita.com/sirogane/items/1829c2a12d284ae20eb5 Bladeテンプレートの@showと@endsectionを間違えないようにする #Laravel - Qiita])。親テンプレートで定義する場合は@show。親でも@endsectionを使うと、sectionの内容が消える。sectionは本来、元テンプレートから継承したものを埋め込むためのものなので、ベーステンプレートだと埋め込み元がないので消えるのだと思う。だから、@showを使う必要があると思われる。
@endfor
=====@component/@slot: Components & Slots=====
<nowiki> </nowiki>
テンプレートよりも細かい部品単位の流用方法がcomponent。ヘッダーやフッター、ボタンなどの部品単位で流用できる。
@foreach ($users as $user)
 
<nowiki> </nowiki>  <nowiki><p>This is user {{ $user->id }}</nowiki><nowiki></p></nowiki>
viewsディレクトリー以下に格納する。一般的にはviews/components/ok.blade.phpなどのように配置し、components.okなどで、コンポーネント名を指定して読み込む。
@endforeach
 
<nowiki> </nowiki>
component定義側は、通常のBladeと同じだが、コンポーネント内で変数のプレースホルダーを使用できる。これは、利用側の@slotのブロックで引き渡す。<!--components/message.blade.php-->
@forelse ($users as $user)
<nowiki> </nowiki>  <nowiki><li>{{ $user->name }}</nowiki><nowiki></li></nowiki>
@empty
<nowiki> </nowiki>  <nowiki><p>No users</p></nowiki>
@endforelse
  <nowiki> </nowiki>
@while (true)
<nowiki> </nowiki>  <nowiki><p>I'm looping forever.</p></nowiki>
@endwhile
公式マニュアルに記載がないが、foreachはPHPのforeachと同じく foreach($array as $key => $value) 形式にも対応している。
======The Loop Variable======
[https://laravel.com/docs/5.8/blade#the-loop-variable Blade Templates - Laravel 5.8 - The PHP Framework For Web Artisans]


ループ内で使用可能な$loop変数があり、これに便利なプロパティーがある。
<nowiki><div class="message">
{| class="wikitable"
                                                                                                                                                    <p class="msg_title">{{$msg_title}}</nowiki><nowiki></p></nowiki>
!Property
  <nowiki><p class="msg_content">{{$msg_content}}</nowiki><nowiki></p></nowiki>
!Description
<nowiki> </nowiki><nowiki>{{$slot}}</nowiki>
|-
<nowiki></div></nowiki>
|<code>$loop->index</code>
component利用側で組み込むために工夫する。
|The index of the current loop iteration (starts at 0).
@component(名前)
|-
  @slot('msg_title')
|<code>$loop->iteration</code>
    title
|The current loop iteration (starts at 1).
  @endslot
|-
    <nowiki><strong>Whoops!</strong></nowiki> Something went wrong!
|<code>$loop->remaining</code>
@endcomponent
|The iterations remaining in the loop.
$slot変数には、@componentsのテキスト、@slotブロック以外が入る。
|-
 
|<code>$loop->count</code>
Laravelに複数の在不明のcomponentを順番に適用させたい場合componentFirstを使う。
|The total number of items in the array being iterated.
@componentFirst(['custom.alert', 'alert'])
|-
    <nowiki><strong>Whoops!</strong></nowiki> Something went wrong!
|<code>$loop->first</code>
@endcomponent
|Whether this is the first iteration through the loop.
@slot以外に、変数を渡すこともできる。
|-
@component('alert', ['foo' => 'bar'])
|<code>$loop->last</code>
    ...
|Whether this is the last iteration through the loop.
@endcomponent
|-
|<code>$loop->even</code>
@component('alert')
|Whether this is an even iteration through the loop.
  @slot('foo', 'bar')
|-
@endcomponent
|<code>$loop->odd</code>
slotの設定方法は複数ある。@slot/@endslotよりかは@slot()で設定するほうが短い。が、@component内は$slotのデフォルト値を入れるとしたほうがわかりやすいかもしれない。
|Whether this is an odd iteration through the loop.
|-
|<code>$loop->depth</code>
|The nesting level of the current loop.
|-
|<code>$loop->parent</code>
|When in a nested loop, the parent's loop variable.
|}first/last/even/oddあたりは特に便利だろう。
======Additional Attributes======
[https://laravel.com/docs/9.x/blade Blade Templates - Laravel 9.x - The PHP Framework For Web Artisans]


Laravel v9から使用可能。
ただし、@endcomponentは省略できない。


@disabled
名前付きslotのデフォルト値設定はない。やりたければ、??や@if/@issetで自分でやっておく。
======Comments======
[https://stackoverflow.com/questions/27830200/laravel-blade-comments-blade-rendering-causing-page-to-crash php - Laravel - Blade comments , blade rendering causing page to crash - Stack Overflow]


Bladeテンプレートファイル内でのコメントには注意が必要。
デフォルトの$slotは、@componentの配列式 (@component(, ['slot']) では指定できないが、view関数で呼ぶ際は['slot']で指定できる。
<nowiki>{{-- code --}}</nowiki> これが基本
 
PHPコード扱いでのコメントアウトも便利。
基本は短くできるので、component内では$slotを使うほうがいい。
@php
/* */
@endphp
<?php /* */ ?>
Bladeの<nowiki>{{-- code --}}</nowiki><nowiki>は内部の{{}}が変数展開として解釈される。内部に波括弧がある場合は、phpコード扱いでコメントアウトしたほうが安全。</nowiki>
======PHP======
Bladeテンプレート内でPHPのコードをそのまま記述する際には、専用の指令を使う。
@php
@endphp
これを使うと、「[https://funbrew.tech/2022/06/22/1699/ Laravelのbladeで変数を定義する – FUNBREW]」にあるように、変数を定義してBlade内で使うことができる。変数の定義や空チェックなどを最初にできるので、シンプルになる。
@php
$newValue = $somethingValue;
if (empty($newValue)) {
<nowiki> </nowiki>  $newValue = 'Not Defined';
}
@endphp
<nowiki><div>{{ $newValue }}</nowiki><nowiki></div></nowiki>
=====Forms=====
======CSRF Field======
アプリ内でHTMLフォームを使用する場合、CSRFトークンフィールドを記述する。


具体的には、form要素の冒頭に@csrfを指定する。
なお、$slotは扱いに注意が必要。使う側で指定がなかったら、nullではないが見えない変な値が入っている。変数として使う際は"$slot"のように二重引用符で囲んで、値がない場合に確実に空にしておく。
<form method="POST" action="/profile">
=====@include: Including Sub-Views=====
    @csrf
レイアウトやコンポーネントのように、変数の引き渡しなど複雑なことをしない場合、単純な定形固定文字列を読み込むような場合、Sub-Viewsというのを使うこともできる。
 
    ...
</form>
=====@stack/@push/@prepend: Stacks=====
*[https://qiita.com/ttn_tt/items/9a3256101f50894827a2 bladeのcomponent化による再利用 #PHP - Qiita]
*[https://dad-union.com/php/3136 最適化されたWebページデザイン: Laravel Bladeで個別ページのJavaScriptとCSSファイルを効果的に追記・管理する詳細ガイド|DAD UNION - エンジニア同盟]
名前付きのスタックに、他の場所で使用するビューやレイアウトを格納して、流用できる。


まず@pushする。使いたいか所で@stackすると取り出せる。
これは@includeでテンプレートファイルをそのまま読み込むのが基本。親の変数もそのまま使える。他に、引数で変数を渡すこともできる。
@include('view.name', ['some' => 'data'])
@includeIf('view.name', ['some' => 'data'])
@includeFirst(['custom.admin', 'admin'], ['some' => 'data'])
@includeで指定したテンプレートが不在の場合、Laravelはエラーを投げる。このエラーを回避したい場合、@includeIf指令を使う。@includeIfのバリエーションの一種で、配列のテンプレート名で存在する最初のものを使う場合、@includeFirstを使う。


使い方としては、componentで@pushでscript要素やstyle要素を記述しておいて、レイアウトで@stackで呼び出す感じ。
条件がtrueなら読み込む場合、@includeWhenがある。
  @push('scripts')
  @include($boolean, 'view.name', ['some' => 'data'])
    <script src="/example.js"></script>
@ifよりもシンプル。
@endpush


<nowiki><head>
[https://qiita.com/ah__memo/items/1936419d908875477aa8 【Laravel】bladeの@includeと@componentの違い #PHP - Qiita]
 
      @stack('scripts')
  </head></nowiki>
順番が大事な場合、@prependでpushより先に詰め込める。


扱いは、sectionに似ている。componentのためのsectionのような感じだと思う。
includeはcomponentと違って@endincludeがいらない。


pushしたものは描画のたびにstackで表示される。後のバージョンで@onceというのが登場したので、これを使えば1個だけになる。それまでは自作が必要。
componentはslotで渡すこともできる。$slotを使える点が大きな違い。


push/stackを使わない場合、そのページに必要なくても、使う可能性のあるcss/jsを親で全部読み込んでおかないといけない。それを回避できる。
複雑で長いHTMLなどを、引き渡して使いたい場合、componentのほうがいい。
 
=====@each: Rendering Views For Collections=====
コンポーネントやテンプレート固有で必要なものを、同じファイルに記載しておいて、反映だけstackでまとめてできる。これが利点だろう。
意外と使用頻度が高いのが繰り返し表示。例えば、リストの項目、テーブルの行など。これようの指令が@each
  @each('components.item', $data, 'item');
$data配列の要素をコンポーネントのitem変数に渡す。
// components/item.blade.php
<nowiki><li>{{$item['name']}}</nowiki> <nowiki>[{{$item['mail']}}]</nowiki><nowiki></li></nowiki>
 
==== Displaying Data ====
 
===== Displaying escaped Data =====
Bladeでデータを表示する際は、二重波括弧を使う。
Route::get('greeting', function () {
    return view('welcome', ['name' => 'Samantha']);
});


[https://laravel.com/docs/9.x/blade#the-once-directive Blade Templates - Laravel 9.x - The PHP Framework For Web Artisans]
Hello, <nowiki>{{ $name }}</nowiki>.
二重波括弧は自動的にhtmlspecialcharsでXSS対策してくれる。変数の他に、PHP関数の結果も表示できる。


[https://laravel.com/docs/7.x/blade#the-once-directive Blade Templates - Laravel 7.x - The PHP Framework For Web Artisans]
===== Displaying Unescaped Data =====
なお、生のHTMLなどのデータをそのまま表示させたい場合、二重波括弧の代わりに<code>{!! !!}</code>で囲む。
Hello, {!! $name !!}.
Hello, {!! e($name) !!}.
もっというと、e()でエスケープしておくと安心 ([https://qiita.com/itty-star/items/22b4293cdbd847a1aa70 Bladeで変数に入れたhtml文字列を表示させる #Laravel - Qiita])。


Laravel 9.xで@pushOnce。Laravel 7.xで@onceがある。
[https://laravel.com/docs/5.8/helpers Helpers - Laravel 5.8 - The PHP Framework For Web Artisans]


componentにcssやscriptを含めたい場合に@pushを使う。
混乱するが、二重波括弧内はPHP扱い、外はHTML扱い。二重波括弧内で表示文字列扱いしたいなら、文字列にする必要がある。
=====Component=====
コンポーネントのパターンがある。
======table======
[https://bootstrap-vue.org/docs/components/table Table | Components | BootstrapVue]


BootstrapVueを真似する。
===== Rendering JSON =====
<nowiki>{{--  </nowiki>
<nowiki> </nowiki>  fields = [field1, field2, field3]
<nowiki> </nowiki>  items = [[col1, col2, col3], {col1, col2, col3}]
<nowiki> </nowiki>  fields = [['label' => field1, 'class' => <nowiki>''</nowiki>, 'style' => <nowiki>''</nowiki>], ['label' => field1, 'class' => <nowiki>''</nowiki>, 'style' => <nowiki>''</nowiki>]]
<nowiki> </nowiki>  items = [['label' => <nowiki>''</nowiki>, 'class' => <nowiki>''</nowiki>, 'style' => <nowiki>''</nowiki>], ['label' => <nowiki>''</nowiki>, 'class' => <nowiki>''</nowiki>, 'style' => <nowiki>''</nowiki><nowiki>]]
                                                                                                  --}}</nowiki>
<nowiki><table class="table table-sm table-bordered">
                                                                                                      <thead class="bg-info text-center">
                                                                                                          @foreach ($fields as $field)
                                                                                                              @if (!is_array($field))
                                                                                                                  <th>{{ $field }}</nowiki><nowiki></th></nowiki>
<nowiki> </nowiki>          @else
<nowiki> </nowiki>              <nowiki><th class="{{empty($field['class']) ? '' : $field['class']}}"
                                                                                                                      style="{{empty($field['class']) ? '' : $field['style']}}">
                                                                                                                      {{empty($field['label']) ? '' : $field['label']}}</nowiki><nowiki></th></nowiki>
<nowiki> </nowiki>          @endif
<nowiki> </nowiki>      @endforeach
<nowiki> </nowiki>  <nowiki></thead></nowiki>
<nowiki> </nowiki>  <nowiki><tbody>
                                                                                                          @foreach ($items as $row)
                                                                                                              <tr></nowiki>
<nowiki> </nowiki>              @foreach ($row as $column)
<nowiki> </nowiki>                  @if (!is_array($column))
<nowiki> </nowiki>                      <nowiki><td></nowiki><nowiki>{{ $column }}</nowiki><nowiki></td></nowiki>
<nowiki> </nowiki>                  @else
<nowiki> </nowiki>                      <nowiki><td class="{{empty($column['class']) ? '' : $column['class']}}"
                                                                                                                              style="{{empty($column['style']) ? '' : $column['style']}}"></nowiki>
<nowiki> </nowiki>                          <nowiki>{{empty($column['label']) ? '' : $column['label']}}</nowiki><nowiki></td></nowiki>
<nowiki> </nowiki>                  @endif
<nowiki> </nowiki>              @endforeach
<nowiki> </nowiki>          <nowiki></tr></nowiki>
<nowiki> </nowiki>      @endforeach
<nowiki> </nowiki>  <nowiki></tbody></nowiki>
<nowiki></table></nowiki>
=====Other=====
======Bladeのレンダー結果の取得======
場合によっては、componentにBladeの描画結果を埋め込みたいことがある。
*[https://stackoverflow.com/questions/50938285/how-to-get-blade-template-view-as-a-raw-html-string php - How to get Blade template view as a raw HTML string? - Stack Overflow]
*[https://laravel.com/api/6.x/Illuminate/View/View.html Illuminate\View\View | Laravel API]
*[https://github.com/laravel/framework/blob/5.6/src/Illuminate/View/View.php#L87 framework/src/Illuminate/View/View.php at 5.6 · laravel/framework]
view関数でViewインスタンス作成後に、render/renderContents/getContentsを呼ぶと描画後のHTML文字列を取得できる。


これを使う。{!! $var !!} のような感じ。必要に応じてe()でエスケープする。renderを実行しなくても、Viewインスタンスをそのまま渡すとHTMLになっている。けど、エラーが出たときよくわからないのでrender()しておいたほうがいい。
* [https://laravel.com/docs/5.8/blade#displaying-data Blade Templates - Laravel 5.8 - The PHP Framework For Web Artisans]
======パスの取得======
* [https://stackoverflow.com/questions/33326699/passing-laravel-array-in-javascript php - Passing (laravel) Array in Javascript - Stack Overflow]
*[https://stackoverflow.com/questions/17591181/how-to-get-the-current-url-inside-if-statement-blade-in-laravel-4 How to Get the Current URL Inside @if Statement (Blade) in Laravel 4? - Stack Overflow]
* [https://www.dbestech.com/tutorials/passing-laravel-array-in-javascript Passing (laravel) Array in Javascript]
*[https://laracasts.com/discuss/channels/laravel/how-to-get-current-url-path How to get Current url path]
現在表示ビューのパスを取得したいことがある。いくつか方法がある。
*Request::path()
*Route::current()->uri()
Request::path()がシンプル。
======子ビューの変数の使用======
*[https://teratail.com/questions/159409 Laravel includeで呼び出したbladeテンプレート中の変数を呼び出し元で使う方法]
*[https://laravel.io/forum/how-can-i-make-blade-variables-global How can I make Blade variables "global"? | Laravel.io]
*[https://stackoverflow.com/questions/41495347/how-to-get-a-variable-from-parent-view-in-laravel-blade How to get a variable from parent view in laravel blade - Stack Overflow]
*[https://forum.laravel-livewire.com/t/passing-form-data-from-child-to-parent-component/4498/2 Passing form data from Child to Parent Component - Help - Livewire Forum]
基本は不能。全体で共有する方法ならある。
======$slotの型======
*[https://laracasts.com/discuss/channels/laravel/return-a-view-from-an-html-string Return a view from an HTML string]
*[https://laracasts.com/discuss/channels/laravel/how-to-check-component-default-slot-is-empty How to check component default $slot is empty ?]
*[https://laravel.com/api/10.x/Illuminate/Support/HtmlString.html Illuminate\Support\HtmlString | Laravel API]
Bladeのcomponentなどで使用するslotはHtmlString。だから、これをstring扱いで、old($name) などに使うとエラーになる。oldは内部で、array_key_existsなどで、inputのキーなどをチェックしている。


$slotを他のプロパティーなどに引き渡す際に、toHtmlで文字列に変換して渡すとよい。
JavaScript変数として、配列をJSONとして描画したいことがあるだろう。
 
<nowiki><script>
== Digging Deeper ==
                                                                                                var app = <?php echo json_encode($array); ?>;
 
                                                                                                var app = {{json_encode($array)}}</nowiki>;
=== Artisan Console ===
      var app = {!! json_encode($array) !!};
[https://laravel.com/docs/5.8/artisan Artisan Console - Laravel 5.8 - The PHP Framework For Web Artisans]
<nowiki> </nowiki>    var array = JSON.parse('<nowiki>{{ json_encode($theArray) }}</nowiki>');
 
<nowiki></script></nowiki>
Laravelでのartisanコマンドやコマンドラインアプリケーションの作成方法が記載されている。
手動でjson_encodeを呼ぶ代わりに、@json指令がある。
<script>
    var app = @json($array);
 
    var app = @json($array, JSON_PRETTY_PRINT);
</script>
<nowiki>なお、@jsonはjson_encodeと同じではない。以下のコードで{{json_encode部分を@jsonに変えるとうまくいかない。</nowiki>
<nowiki><button type="button" onclick="
        ajaxGetAndSetTable('/ajax/楽楽販売_器具交換履歴_最新取得?消費者コード=' + document.getElementById('消費者コード').value, '#facilityHistory tbody',
        {{json_encode($names)}}, '部屋番号');
    "
    >楽楽販売最新取得 (未実装)</button></nowiki>
<code><nowiki>{{json_encode($array)}}</nowiki></code>で良いと思われる。
 
{{}} だとエスケープされて二重引用符が<nowiki>&</nowiki>quot;になって、扱いにくいことがある、{!! !!}でエスケープ解除すると問題ない。
 
===== Context =====
BladeのコードのPHPとHTML/JavaScriptの解釈が場所によって異なって混乱するので整理する。
 
blade.phpはPHPファイルではあるが、テンプレートで基本はHTML。以下の部分的にPHPが使用可能。


==== Writing Commands ====
# @php-@endphp内。
app/Console/Commandsディレクトリーにコマンドは一般的に格納される。が、別の場所にも配置できる。
# @指令の引数内。
# <nowiki>{{-}}</nowiki>/{!!-!!}内。


===== Generating Commands =====
波括弧記法は@指令の引数部分では使用不能。
make:commandで新規コマンドを作成できる。
php artisan make:command SendEmails
app/Console/Commandsに指定したコマンド名でファイルが作られる。


なお、作成新するコマンドの名前は、「[https://github.com/laravel/framework/blob/b9cf7d3217732e9a0fa4f00e996b3f9cc5bf7abd/src/Illuminate/Foundation/Console/StorageLinkCommand.php#L8 framework/src/Illuminate/Foundation/Console/StorageLinkCommand.php at b9cf7d3217732e9a0fa4f00e996b3f9cc5bf7abd · laravel/framework]」を見る限lり名詞:動詞で、ファイル名は「名詞動詞Command.php」になっている。
@指令の引数でonclickなどのJS用のコードとPHPの処理を埋め込む場合は、PHPの文字列として扱う。なので、.などでPHPで文字列連結を駆使する。


===== Command Structure =====
=====Blade & JavaScript Frameworks=====
signatureとdescriptionプロパティーの記入が必要。これらのプロパティーはartisan listで表示される。handleメソッドにコマンド実行時の処理を配置する。
JavaScriptフレームワークの中に、二重波括弧をプレースホルダーとしてそのまま使うものがある。そういう場合、@を波括弧に前置するとそのまま表示する。
  <?php
<nowiki><h1>Laravel</h1></nowiki>
    
<nowiki> </nowiki>
  namespace App\Console\Commands;
Hello, @<nowiki>{{ name }}</nowiki>.
 
======The @verbatim Directive======
  use App\User;
JavaScript変数を表示する場合、波括弧の前に@をたくさん置かないで済むように、@verbatimで囲める。
  use App\DripEmailer;
@verbatim
  use Illuminate\Console\Command;
  <nowiki> </nowiki>  <nowiki><div class="container">
                                                                                                                                                              Hello, {{ name }}</nowiki>.
<nowiki> </nowiki>   <nowiki></div></nowiki>
  @endverbatim
=====Control Structure/Blade Directives=====
======If Statements======
@if (count($records) === 1)
    I have one record!
@elseif (count($records) > 1)
    I have multiple records!
@else
    I don't have any records!
@endif
@unless (Auth::check())
    You are not signed in.
@endunless
   
  @isset($records)
    // $records is defined and is not null...
  @endisset
    
    
  class SendEmails extends Command
  @empty($records)
{
     // $records is "empty"...
     /**
@endempty
      * The name and signature of the console command.
      *
@auth
      * @var string
    // The user is authenticated...
      */
@endauth
    protected $signature = 'email:send {user}';
    
    
     /**
@guest
      * The console command description.
     // The user is not authenticated...
      *
@endguest
      * @var string
      */
@auth('admin')
    protected $description = 'Send drip e-mails to a user';
    // The user is authenticated...
@endauth
    
    
     /**
@guest('admin')
      * Create a new command instance.
     // The user is not authenticated...
      *
@endguest
      * @return void
      */
@hasSection('navigation')
    public function __construct()
     <nowiki><div class="pull-right"></nowiki>
     {
         @yield('navigation')
         parent::__construct();
     <nowiki></div></nowiki>
     }
    
    
     /**
     <nowiki><div class="clearfix"></div></nowiki>
      * Execute the console command.
@endif
      *
特に@isset/@emptyをよく使うかも。ただ、これらには@elseはないので注意が必要かもしれない。やるなら、
      * @param \App\DripEmailer $drip
@if(isset())
      * @return mixed
@else
      */
@endif
    public function handle(DripEmailer $drip)
    {
@isset()
        $drip->send(User::find($this->argument('user')));
@endisset
    }
  @empty()
  }
@endempty
signatureは特に重要。後で詳述する。
======Switch Statements======
======Loops======
PHPの反復構造に近いものがある。重要。
  @for ($i = 0; $i < 10; $i++)
<nowiki> </nowiki>  The current value is <nowiki>{{ $i }}</nowiki>
@endfor
<nowiki> </nowiki>
@foreach ($users as $user)
<nowiki> </nowiki>  <nowiki><p>This is user {{ $user->id }}</nowiki><nowiki></p></nowiki>
@endforeach
<nowiki> </nowiki>
@forelse ($users as $user)
<nowiki> </nowiki>  <nowiki><li>{{ $user->name }}</nowiki><nowiki></li></nowiki>
@empty
<nowiki> </nowiki>  <nowiki><p>No users</p></nowiki>
@endforelse
<nowiki> </nowiki>
@while (true)
<nowiki> </nowiki>  <nowiki><p>I'm looping forever.</p></nowiki>
  @endwhile
公式マニュアルに記載がないが、foreachはPHPのforeachと同じく foreach($array as $key => $value) 形式にも対応している。
======The Loop Variable======
[https://laravel.com/docs/5.8/blade#the-loop-variable Blade Templates - Laravel 5.8 - The PHP Framework For Web Artisans]


==== Defining Input Expectations ====
ループ内で使用可能な$loop変数があり、これに便利なプロパティーがある。
signatureプロパティーでユーザーからの想定入力を定義する。コマンド名、引数、オプションなどを定義できる。
{| class="wikitable"
 
!Property
最低限コマンド名 (例だとemail:send) は記載する。
!Description
 
|-
コロンはなくてもいい。signature='some' だとphp artisan someで実行できる。
|<code>$loop->index</code>
 
|The index of the current loop iteration (starts at 0).
===== Arguments =====
|-
引数がある場合、波括弧で指定できる。
|<code>$loop->iteration</code>
  protected $signature = 'email:send {user}';
|The current loop iteration (starts at 1).
この波括弧にはいくつか気法がある。
|-
 
|<code>$loop->remaining</code>
* email:send {user?}: オプション引数。
|The iterations remaining in the loop.
* email:send {user=default}: デフォルト引数ありのオプション引数。
|-
 
|<code>$loop->count</code>
? =がないと、必須引数。
|The total number of items in the array being iterated.
 
|-
===== Options =====
|<code>$loop->first</code>
オプションはハイフン2個--を前置して指定する。オプション引数の有無で2系統ある。オプション引数がない場合、スイッチのような意味合い。
|Whether this is the first iteration through the loop.
protected $signature = 'email:send {user} {--queue}';
|-
上記の例では--queueがスイッチ系のオプション。--queueが渡されたらtrueになる。それ以外はfalse。
|<code>$loop->last</code>
|Whether this is the last iteration through the loop.
|-
|<code>$loop->even</code>
|Whether this is an even iteration through the loop.
|-
|<code>$loop->odd</code>
|Whether this is an odd iteration through the loop.
|-
|<code>$loop->depth</code>
|The nesting level of the current loop.
|-
|<code>$loop->parent</code>
|When in a nested loop, the parent's loop variable.
|}first/last/even/oddあたりは特に便利だろう。
======Additional Attributes======
[https://laravel.com/docs/9.x/blade Blade Templates - Laravel 9.x - The PHP Framework For Web Artisans]


引数がある場合、末尾を=にする。
Laravel v9から使用可能。
protected $signature = 'email:send {user} {--queue=}';
以下のように実行する。
php artisan email:send 1 --queue=default
=の後にデフォルト値の指定も可能。
email:send {user} {--queue=default}
短縮形。


オプションの短縮形も指定できる。
@disabled
email:send {user} {--Q|queue}
======Comments======
短縮形の場合、ハイフン1個で実行できる。また、短縮形は先頭に書かないと認識しない模様。
[https://stackoverflow.com/questions/27830200/laravel-blade-comments-blade-rendering-causing-page-to-crash php - Laravel - Blade comments , blade rendering causing page to crash - Stack Overflow]
email:send -Q


===== Input Descriptions =====
Bladeテンプレートファイル内でのコメントには注意が必要。
入力引数とオプションに、:を使って説明を指定できる。
<nowiki>{{-- code --}}</nowiki> これが基本
  protected $signature = 'email:send
PHPコード扱いでのコメントアウトも便利。
                        {user : The ID of the user}
@php
                        {--queue= : Whether the job should be queued}';
/* */
長い説明などで行をまたぎもOK。:の前後にはスペースが必要。
@endphp
<?php /* */ ?>
Bladeの<nowiki>{{-- code --}}</nowiki><nowiki>は内部の{{}}が変数展開として解釈される。内部に波括弧がある場合は、phpコード扱いでコメントアウトしたほうが安全。</nowiki>
======PHP======
Bladeテンプレート内でPHPのコードをそのまま記述する際には、専用の指令を使う。
@php
@endphp
これを使うと、「[https://funbrew.tech/2022/06/22/1699/ Laravelのbladeで変数を定義する – FUNBREW]」にあるように、変数を定義してBlade内で使うことができる。変数の定義や空チェックなどを最初にできるので、シンプルになる。
@php
  $newValue = $somethingValue;
if (empty($newValue)) {
<nowiki> </nowiki>  $newValue = 'Not Defined';
}
@endphp
<nowiki><div>{{ $newValue }}</nowiki><nowiki></div></nowiki>


以下のような感じになる。
==== Forms ====
protected $signature = 'rakuraku:import {table : .envのRAKURAKU_TABLESで指定する、楽楽販売のAPI連携に必要な情報を連想配列のキー名}';


docker exec -i docker-php-1 php ../artisan help rakuraku:import
===== CSRF Field =====
アプリ内でHTMLフォームを使用する場合、CSRFトークンフィールドを記述する。


Description:
具体的には、form要素の冒頭に@csrfを指定する。
  楽楽販売との連携コマンド。ガス基幹システムから楽楽販売にデータをインポートする。
  <form method="POST" action="/profile">
    @csrf
  Usage:
 
  rakuraku:import <nowiki><table></nowiki>
    ...
  </form>
Arguments:
=====@stack/@push/@prepend: Stacks=====
  table                .envのRAKURAKU_TABLESで指定する、楽楽販売のAPI連携に必要な情報を連想配列のキー名
*[https://qiita.com/ttn_tt/items/9a3256101f50894827a2 bladeのcomponent化による再利用 #PHP - Qiita]
   
*[https://dad-union.com/php/3136 最適化されたWebページデザイン: Laravel Bladeで個別ページのJavaScriptとCSSファイルを効果的に追記・管理する詳細ガイド|DAD UNION - エンジニア同盟]
Options:
名前付きのスタックに、他の場所で使用するビューやレイアウトを格納して、流用できる。
  -h, --help            Display this help message
  -q, --quiet          Do not output any message
  -V, --version        Display this application version
      --ansi            Force ANSI output
      --no-ansi        Disable ANSI output
  -n, --no-interaction  Do not ask any interactive question
      --env[=ENV]       The environment the command should run under
  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug


==== Command I/O ====
まず@pushする。使いたいか所で@stackすると取り出せる。


===== Retrieving Input =====
使い方としては、componentで@pushでscript要素やstyle要素を記述しておいて、レイアウトで@stackで呼び出す感じ。
引数やオプションはそれぞれargument/arguments|option/optionsで参照できる。
  @push('scripts')
  $userId = $this->argument('user');
    <script src="/example.js"></script>
$arguments = $this->arguments();
  @endpush
$queueName = $this->option('queue');
  $options = $this->options();
なお、これらのメソッドはインスタンスメソッドなので、__constructでは使用不能。


options()は以下のような利用可能なオプション一覧とその値を返す。
  <nowiki><head>
  array (
 
  'check' => false,
      @stack('scripts')
  'import' => NULL,
  </head></nowiki>
  'export' => '管理会社',
順番が大事な場合、@prependでpushより先に詰め込める。
  'help' => false,
  'quiet' => false,
  'verbose' => false,
  'version' => false,
  'ansi' => false,
  'no-ansi' => false,
  'no-interaction' => false,
  'env' => NULL,
)
現在指定中のオプションやその個数を見たければ、array_filterを使う。
count(array_filter($this->options()))


===== Writing Output =====
扱いは、sectionに似ている。componentのためのsectionのような感じだと思う。
コンソールへの出力には、line/info/comment/question/errorメソッドを使う。


* info: 緑色。
pushしたものは描画のたびにstackで表示される。後のバージョンで@onceというのが登場したので、これを使えば1個だけになる。それまでは自作が必要。
* error: 赤色。
* line: 色なし。


==== Other ====
push/stackを使わない場合、そのページに必要なくても、使う可能性のあるcss/jsを親で全部読み込んでおかないといけない。それを回避できる。


===== コマンド =====
コンポーネントやテンプレート固有で必要なものを、同じファイルに記載しておいて、反映だけstackでまとめてできる。これが利点だろう。


* [https://magecomp.com/blog/executing-shell-script-file-from-laravel-application/?srsltid=AfmBOopfaX3lSORTemqAiUqOwM3UfUSia1O8or1wX3kO3AnSmoKj308f Executing a Shell Script File from a Laravel Application - MageComp]
[https://laravel.com/docs/9.x/blade#the-once-directive Blade Templates - Laravel 9.x - The PHP Framework For Web Artisans]
* [https://laraveldaily.com/post/how-to-use-external-classes-and-php-files-in-laravel-controller How to use external classes and PHP files in Laravel Controller?]
* [https://stackoverflow.com/questions/44374495/run-sh-file-using-exec-laravel-php Run .sh file using exec Laravel PHP - Stack Overflow]
* [https://stackoverflow.com/questions/54266041/how-to-execute-external-shell-commands-from-laravel-controller php - How to execute external shell commands from laravel controller? - Stack Overflow]


外部プログラム類を実行したいことがある。
[https://laravel.com/docs/7.x/blade#the-once-directive Blade Templates - Laravel 7.x - The PHP Framework For Web Artisans]


storage以下に配置する。か、app_pathなどでパスを参照する。
Laravel 9.xで@pushOnce。Laravel 7.xで@onceがある。


* shell_exec
componentにcssやscriptを含めたい場合に@pushを使う。
* $schedule->exec
* Symfony\Component\Process\Process


同じ場所に配置して実行すればいいと思う。
==== Component ====
Laravel 7からクラスベースコンポーネントが誕生した。


===== グループ =====
コンポーネントのパターンがある。


* [https://stackoverflow.com/questions/52257533/laravel-artisan-command-with-sub-methods Laravel artisan command with sub methods - Stack Overflow]
===== table =====
* [https://stackoverflow.com/questions/51128285/how-can-my-command-pass-through-optional-arguments-to-another-artisan-command laravel - How can my command pass through optional arguments to another Artisan command? - Stack Overflow]
[https://bootstrap-vue.org/docs/components/table Table | Components | BootstrapVue]


Artisanコマンドはartisan some:function {argument} {--option}の書式で定義される。
BootstrapVueを真似する。
 
<nowiki>{{--  </nowiki>
が、別にコロンはなくてもいい。コロンをつけると、同じプレフィクスのコマンドをグループ化して、ヘルプなどで取り扱ってくれる。わかりやすい。
<nowiki> </nowiki>  fields = [field1, field2, field3]
 
<nowiki> </nowiki>  items = [[col1, col2, col3], {col1, col2, col3}]
処理を共通化したければ、クラスにして継承するか、Traitにする。
<nowiki> </nowiki>  fields = [['label' => field1, 'class' => <nowiki>''</nowiki>, 'style' => <nowiki>''</nowiki>], ['label' => field1, 'class' => <nowiki>''</nowiki>, 'style' => <nowiki>''</nowiki>]]
<nowiki> </nowiki>  items = [['label' => <nowiki>''</nowiki>, 'class' => <nowiki>''</nowiki>, 'style' => <nowiki>''</nowiki>], ['label' => <nowiki>''</nowiki>, 'class' => <nowiki>''</nowiki>, 'style' => <nowiki>''</nowiki><nowiki>]]
                                                                                                                                    --}}</nowiki>
<nowiki><table class="table table-sm table-bordered">
                                                                                                                                        <thead class="bg-info text-center">
                                                                                                                                            @foreach ($fields as $field)
                                                                                                                                                @if (!is_array($field))
                                                                                                                                                    <th>{{ $field }}</nowiki><nowiki></th></nowiki>
<nowiki> </nowiki>          @else
<nowiki> </nowiki>              <nowiki><th class="{{empty($field['class']) ? '' : $field['class']}}"
                                                                                                                                                        style="{{empty($field['class']) ? '' : $field['style']}}">
                                                                                                                                                        {{empty($field['label']) ? '' : $field['label']}}</nowiki><nowiki></th></nowiki>
<nowiki> </nowiki>          @endif
<nowiki> </nowiki>      @endforeach
<nowiki> </nowiki>  <nowiki></thead></nowiki>
<nowiki> </nowiki>  <nowiki><tbody>
                                                                                                                                            @foreach ($items as $row)
                                                                                                                                                <tr></nowiki>
<nowiki> </nowiki>              @foreach ($row as $column)
<nowiki> </nowiki>                  @if (!is_array($column))
<nowiki> </nowiki>                      <nowiki><td></nowiki><nowiki>{{ $column }}</nowiki><nowiki></td></nowiki>
<nowiki> </nowiki>                  @else
<nowiki> </nowiki>                      <nowiki><td class="{{empty($column['class']) ? '' : $column['class']}}"
                                                                                                                                                                style="{{empty($column['style']) ? '' : $column['style']}}"></nowiki>
<nowiki> </nowiki>                          <nowiki>{{empty($column['label']) ? '' : $column['label']}}</nowiki><nowiki></td></nowiki>
<nowiki> </nowiki>                  @endif
<nowiki> </nowiki>              @endforeach
<nowiki> </nowiki>          <nowiki></tr></nowiki>
<nowiki> </nowiki>      @endforeach
<nowiki> </nowiki>  <nowiki></tbody></nowiki>
<nowiki></table></nowiki>
https://chatgpt.com/c/673ec580-b24c-800b-ae7b-c47571a6948c


シンプルなものなら、オプションか引数で分けるというのもありだろう。
列定義と行本体を分離するというのが重要。


=== File Storage ===
テーブルでリレーションを表示する場合、ネストになっているのが都合悪いので、.区切りで平坦化するのがいい模様。まあ、こうするしかないか。ネストのループしないといけなくなるから。これは面倒くさいし。
[https://laravel.com/docs/5.8/filesystem File Storage - Laravel 5.8 - The PHP Framework For Web Artisans]


Laravelでファイル入出力の仕組み。S3など、ストレージシステムを抽象化して扱える。ただし、これは基本的に一般公開用ディレクトリーを念頭に置いている。
==== Other ====


==== File ====
===== Bladeのレンダー結果の取得 =====
場合によっては、componentにBladeの描画結果を埋め込みたいことがある。
*[https://stackoverflow.com/questions/50938285/how-to-get-blade-template-view-as-a-raw-html-string php - How to get Blade template view as a raw HTML string? - Stack Overflow]
*[https://laravel.com/api/6.x/Illuminate/View/View.html Illuminate\View\View | Laravel API]
*[https://github.com/laravel/framework/blob/5.6/src/Illuminate/View/View.php#L87 framework/src/Illuminate/View/View.php at 5.6 · laravel/framework]
view関数でViewインスタンス作成後に、render/renderContents/getContentsを呼ぶと描画後のHTML文字列を取得できる。
 
これを使う。{!! $var !!} のような感じ。必要に応じてe()でエスケープする。renderを実行しなくても、Viewインスタンスをそのまま渡すとHTMLになっている。けど、エラーが出たときよくわからないのでrender()しておいたほうがいい。
// index.blade.php
@component('components.tab', [
'tabs' => [
'物件' => view('c211000.form')->render(),
'代表オーナー' => view('c212100.代表オーナー')->render(),
'括りオーナー' => view('c212200.括りオーナー')->render(),
],
])
@endcomponent


* [https://www.sejuku.net/blog/63671 【PHP/Laravel】ファイル操作時には欠かせない!Fileクラスの使い方 | 侍エンジニアブログ]
// tab.blade.php
* [https://www.ozzu.com/questions/610927/whats-the-difference-between-the-laravel-file-and-storage-facades What's the difference between the Laravel File and Storage facades? - Ozzu]
<nowiki><section class="tab-wrap">
* [https://laravel.com/api/6.x/Illuminate/Support/Facades/File.html Illuminate\Support\Facades\File | Laravel API]
    @foreach($tabs as $label => $content)
        <label class="tab-label">{{$label}}</nowiki><nowiki><input type="radio" name="tab" class="tab-switch" {{$loop->first ? "checked=checked" : ""}} /></nowiki><nowiki></label></nowiki>
<nowiki> </nowiki>      <nowiki><div class="tab-content">{!! $content !!}</div></nowiki>
<nowiki> </nowiki>  @endforeach
<nowiki></section></nowiki>


公式マニュアルに記載がないが、Fileファサードが存在する。これは、PHPのファイル入出力処理のラッパー。Storageと異なり、ディスクシステムの考慮などはしていない。比較的シンプルなファイルIO用。
===== パスの取得 =====
*[https://stackoverflow.com/questions/17591181/how-to-get-the-current-url-inside-if-statement-blade-in-laravel-4 How to Get the Current URL Inside @if Statement (Blade) in Laravel 4? - Stack Overflow]
*[https://laracasts.com/discuss/channels/laravel/how-to-get-current-url-path How to get Current url path]
現在表示ビューのパスを取得したいことがある。いくつか方法がある。
*Request::path()
*Route::current()->uri()
Request::path()がシンプル。


侍エンジニアブログくらいしか解説がない。
===== 子ビューの変数の使用 =====
*[https://teratail.com/questions/159409 Laravel includeで呼び出したbladeテンプレート中の変数を呼び出し元で使う方法]
*[https://laravel.io/forum/how-can-i-make-blade-variables-global How can I make Blade variables "global"? | Laravel.io]
*[https://stackoverflow.com/questions/41495347/how-to-get-a-variable-from-parent-view-in-laravel-blade How to get a variable from parent view in laravel blade - Stack Overflow]
*[https://forum.laravel-livewire.com/t/passing-form-data-from-child-to-parent-component/4498/2 Passing form data from Child to Parent Component - Help - Livewire Forum]
基本は不能。全体で共有する方法ならある。


vendor/laravel/framework/src/illuminate/Filesystem/Filesystem.phpがソースコード。
===== $slotの型 =====
*[https://laracasts.com/discuss/channels/laravel/return-a-view-from-an-html-string Return a view from an HTML string]
*[https://laracasts.com/discuss/channels/laravel/how-to-check-component-default-slot-is-empty How to check component default $slot is empty ?]
*[https://laravel.com/api/10.x/Illuminate/Support/HtmlString.html Illuminate\Support\HtmlString | Laravel API]
Bladeのcomponentなどで使用するslotはHtmlString。だから、これをstring扱いで、old($name) などに使うとエラーになる。oldは内部で、array_key_existsなどで、inputのキーなどをチェックしている。


Illuminate\Support\Facades\Fileが名前空間。
$slotを他のプロパティーなどに引き渡す際に、toHtmlで文字列に変換して渡すとよい。


* File::extension
===== PHPDoc =====
* File::size
* [https://gummibeer.dev/blog/2020/phpdoc-in-blade-views/ PHP-Doc in Blade-Views | Blog | Tom Herrmann - Gummibeer]
* File::get
* [https://ohshige.hatenablog.com/entry/2019/08/05/190000 PhpStorm で Laravel の Blade 内でもエンティティ等の補完をしてほしい - 技術とかボドゲとかそんな話をしたい]
* File::put
* File::copy
* File::delete
* File::move
* File::isDirectory
* File::copyDirectory: 中も全部。
* File::deleteDirectory: 中も全部。


=== Helpers ===
Bladeのコンポーネントで何の変数が使用可能かわかりにくくなる。対策としてPHPDocの記述がある。


==== Paths ====
単に、<?phpや@phpのPHPブロック内でPHPDocを記述するだけでいい。
パス取得関係のヘルパー関数がいくつかある。
@extends('layouts.app')
@php
/** @var App\Entity\User[] $users */
@endphp
@section('content')
<nowiki> </nowiki> <nowiki><h1>ユーザ一覧</h1></nowiki>
<nowiki> </nowiki> <nowiki><ul>
                                @foreach($users as $user)
                                  <li>{{ $user->getName() }}</nowiki><nowiki></li></nowiki>
<nowiki> </nowiki> @endforeach
<nowiki> </nowiki> <nowiki></ul></nowiki>
@endsection


* app_path: appの絶対パス。
<?php /** @var \Illuminate\View\ComponentAttributeBag $attributes */ ?>
* base_path: アプリのルートパス。
<?php /** @var \Illuminate\Support\HtmlString $slot */ ?>
* config_path: config
どちらでも問題ない。
* database_path: database
* mix
* public_path
* resource_path
* storage_path


特にbase_pathは重要に感じる。
===== Blade内共通処理 =====
https://chatgpt.com/c/673ad9c5-5d48-800b-a937-b8ff4fade246


=== Task Scheduling ===
複数のBladeで共通で登場する処理の共通化。
[https://laravel.com/docs/5.8/scheduling Task Scheduling - Laravel 5.8 - The PHP Framework For Web Artisans]


==== Introduction ====
app/Helpersに追加したヘルパー関数用クラスにstaticで定義して使う。XXHelper.phpみたいな感じ。
cronでタスクスケジューリングできるが、cronでやる場合、タスクが増えるたびに設定が必要になる。


LaravelではLaravelのスケジュールコマンドを1個cronに追加するだけで、Laravelのタスクを全部管理できる。
== Digging Deeper ==


タスクスケジュールはapp/Console/Kernel.phpのscheduleメソッドで定義できる。
=== Artisan Console ===
[https://laravel.com/docs/5.8/artisan Artisan Console - Laravel 5.8 - The PHP Framework For Web Artisans]
 
Laravelでのartisanコマンドやコマンドラインアプリケーションの作成方法が記載されている。
 
==== Writing Commands ====
app/Console/Commandsディレクトリーにコマンドは一般的に格納される。が、別の場所にも配置できる。


===== Starting The Scheduler =====
===== Generating Commands =====
スケジューラー使用時に、以下の項目をcrontabに登録する。
make:commandで新規コマンドを作成できる。
  * * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
  php artisan make:command SendEmails
これで毎分実行される。
app/Console/Commandsに指定したコマンド名でファイルが作られる。


==== Defining Schedules ====
なお、作成新するコマンドの名前は、「[https://github.com/laravel/framework/blob/b9cf7d3217732e9a0fa4f00e996b3f9cc5bf7abd/src/Illuminate/Foundation/Console/StorageLinkCommand.php#L8 framework/src/Illuminate/Foundation/Console/StorageLinkCommand.php at b9cf7d3217732e9a0fa4f00e996b3f9cc5bf7abd · laravel/framework]」を見る限lり名詞:動詞で、ファイル名は「名詞動詞Command.php」になっている。


===== Defining Schedules =====
===== Command Structure =====
App\Console\Kernelクラスのscheduleメソッドに、全タスクを定義する。
signatureとdescriptionプロパティーの記入が必要。これらのプロパティーはartisan listで表示される。handleメソッドにコマンド実行時の処理を配置する。
  <?php
  <?php
    
    
  namespace App\Console;
  namespace App\Console\Commands;
    
    
  use Illuminate\Support\Facades\DB;
  use App\User;
  use Illuminate\Console\Scheduling\Schedule;
  use App\DripEmailer;
  use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
  use Illuminate\Console\Command;
    
    
  class Kernel extends ConsoleKernel
  class SendEmails extends Command
  {
  {
     /**
     /**
       * The Artisan commands provided by your application.
       * The name and signature of the console command.
       *
       *
       * @var array
       * @var string
      */
    protected $signature = 'email:send {user}';
 
    /**
      * The console command description.
      *
      * @var string
       */
       */
     protected $commands = [
     protected $description = 'Send drip e-mails to a user';
        //
    ];
    
    
     /**
     /**
       * Define the application's command schedule.
       * Create a new command instance.
       *
       *
      * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
       * @return void
       * @return void
       */
       */
     protected function schedule(Schedule $schedule)
     public function __construct()
     {
     {
         $schedule->call(function () {
         parent::__construct();
            DB::table('recent_users')->delete();
        })->daily();
     }
     }
  }
 
callメソッドで、PHPコードを指定できる。
    /**
 
      * Execute the console command.
この例では、毎日深夜にClosureを実行している。Closureでテーブルを削除している。__invokeを定義済みのinvokableオブジェクトなら以下でシンプルにかける。
      *
$schedule->call(new DeleteRecentUsers)->daily();
      * @param \App\DripEmailer  $drip
      * @return mixed
      */
    public function handle(DripEmailer $drip)
    {
        $drip->send(User::find($this->argument('user')));
    }
}
signatureは特に重要。後で詳述する。


===== Scheduling Artisan Commands =====
==== Defining Input Expectations ====
ArtisanコマンドやOSのコマンドも指定できる。
signatureプロパティーでユーザーからの想定入力を定義する。コマンド名、引数、オプションなどを定義できる。


その場合、commandメソッドを使う。
最低限コマンド名 (例だとemail:send) は記載する。
$schedule->command('emails:send Taylor --force')->daily();
 
$schedule->command(EmailsCommand::class, ['Taylor', '--force'])->daily();
Artisanコマンドの場合、コマンド名かクラスを指定する。


===== Scheduling Shell Commands =====
コロンはなくてもいい。signature='some' だとphp artisan someで実行できる。
execメソッドはシェルコマンドの発行に使える。
$schedule->exec('node /home/forge/script.js')->daily();


===== Schedule Frequency Options =====
===== Arguments =====
[https://stackoverflow.com/questions/47224505/laravel-schedule-hourly-daily-understanding-when-exactly-start php - Laravel schedule - hourly, daily - understanding when exactly start - Stack Overflow]
引数がある場合、波括弧で指定できる。
  protected $signature = 'email:send {user}';
この波括弧にはいくつか気法がある。


dailyは ["0 0 * * *" ] 相当。
* email:send {user?}: オプション引数。
{| class="wikitable"
* email:send {user=default}: デフォルト引数ありのオプション引数。
!Method
 
!Description
? =がないと、必須引数。
|-
 
|<code>->cron('* * * * *');</code>
===== Options =====
|Run the task on a custom Cron schedule
オプションはハイフン2個--を前置して指定する。オプション引数の有無で2系統ある。オプション引数がない場合、スイッチのような意味合い。
|-
protected $signature = 'email:send {user} {--queue}';
|<code>->everyMinute();</code>
上記の例では--queueがスイッチ系のオプション。--queueが渡されたらtrueになる。それ以外はfalse。
|Run the task every minute
 
|-
引数がある場合、末尾を=にする。
|<code>->everyFiveMinutes();</code>
protected $signature = 'email:send {user} {--queue=}';
|Run the task every five minutes
以下のように実行する。
|-
php artisan email:send 1 --queue=default
|<code>->everyTenMinutes();</code>
=の後にデフォルト値の指定も可能。
|Run the task every ten minutes
email:send {user} {--queue=default}
|-
短縮形。
|<code>->everyFifteenMinutes();</code>
 
|Run the task every fifteen minutes
オプションの短縮形も指定できる。
|-
email:send {user} {--Q|queue}
|<code>->everyThirtyMinutes();</code>
短縮形の場合、ハイフン1個で実行できる。また、短縮形は先頭に書かないと認識しない模様。
|Run the task every thirty minutes
email:send -Q
|-
 
|<code>->hourly();</code>
===== Input Descriptions =====
|Run the task every hour
入力引数とオプションに、:を使って説明を指定できる。
|-
protected $signature = 'email:send
|<code>->hourlyAt(17);</code>
                        {user : The ID of the user}
|Run the task every hour at 17 mins past the hour
                        {--queue= : Whether the job should be queued}';
|-
長い説明などで行をまたぎもOK。:の前後にはスペースが必要。
|<code>->daily();</code>
 
|Run the task every day at midnight
以下のような感じになる。
|-
protected $signature = 'rakuraku:import {table : .envのRAKURAKU_TABLESで指定する、楽楽販売のAPI連携に必要な情報を連想配列のキー名}';
|<code>->dailyAt('13:00');</code>
 
|Run the task every day at 13:00
docker exec -i docker-php-1 php ../artisan help rakuraku:import
|-
|<code>->twiceDaily(1, 13);</code>
|Run the task daily at 1:00 & 13:00
|-
|<code>->weekly();</code>
|Run the task every week
|-
|<code>->weeklyOn(1, '8:00');</code>
|Run the task every week on Monday at 8:00
|-
|<code>->monthly();</code>
|Run the task every month
|-
|<code>->monthlyOn(4, '15:00');</code>
|Run the task every month on the 4th at 15:00
|-
|<code>->quarterly();</code>
|Run the task every quarter
|-
|<code>->yearly();</code>
|Run the task every year
|-
|<code>->timezone('America/New_York');</code>
|Set the timezone
|}
daily()=dailyAt('00:00')。


===== Preventing Task Overlaps =====
Description:
デフォルトだと、直前のタスクが実行中でも無視して次のタスクが実行される。withoutOverlappingを使うと、排他処理で他のタスクをさせない。
  楽楽販売との連携コマンド。ガス基幹システムから楽楽販売にデータをインポートする。
 
実行時間が大幅に異なるタスクがあって、終了予定時間が予測できない場合に便利。
Usage:
  rakuraku:import <nowiki><table></nowiki>
Arguments:
  table                .envのRAKURAKU_TABLESで指定する、楽楽販売のAPI連携に必要な情報を連想配列のキー名
Options:
  -h, --help            Display this help message
  -q, --quiet          Do not output any message
  -V, --version        Display this application version
      --ansi            Force ANSI output
      --no-ansi        Disable ANSI output
  -n, --no-interaction  Do not ask any interactive question
      --env[=ENV]      The environment the command should run under
  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug


デフォルトだと24時間でロックは解除される。引数で分単位で解除時間を指定できる。
==== Command I/O ====


タスクが異常終了したらロックファイルが残る。
===== Retrieving Input =====
引数やオプションはそれぞれargument/arguments|option/optionsで参照できる。
$userId = $this->argument('user');
$arguments = $this->arguments();
$queueName = $this->option('queue');
$options = $this->options();
なお、これらのメソッドはインスタンスメソッドなので、__constructでは使用不能。


[https://blog.e2info.co.jp/2023/01/07/laravel_task_scheduke_lock/ Laravelタスクスケジュールの多重起動制御のロックを解除する - ハマログ]
options()は以下のような利用可能なオプション一覧とその値を返す。
  php artisan schedule:clear-cache
array (
  'check' => false,
  'import' => NULL,
  'export' => '管理会社',
  'help' => false,
  'quiet' => false,
  'verbose' => false,
  'version' => false,
  'ansi' => false,
  'no-ansi' => false,
  'no-interaction' => false,
  'env' => NULL,
  )
現在指定中のオプションやその個数を見たければ、array_filterを使う。
count(array_filter($this->options()))


上記のコマンドでロックファイルを削除できる。
===== Writing Output =====
コンソールへの出力には、line/info/comment/question/errorメソッドを使う。


===== Running Tasks On One Server =====
* info: 緑色。
memcachedかredisでキャッシュサーバーと同期していることが前提。
* error: 赤色。
* line: 色なし。


アプリが複数サーバーで稼働させてスケーリングしている場合、そのままだと全部のサーバーで重複実行される。
==== Other ====


処理の最後に->onOneServer()を指定すると、別の場所で実行済みだとスキップするらしい。
===== コマンド =====


== Database ==
* [https://magecomp.com/blog/executing-shell-script-file-from-laravel-application/?srsltid=AfmBOopfaX3lSORTemqAiUqOwM3UfUSia1O8or1wX3kO3AnSmoKj308f Executing a Shell Script File from a Laravel Application - MageComp]
LaravelのDBの取得結果は、以下のような行ごとの連想配列になっている。
* [https://laraveldaily.com/post/how-to-use-external-classes-and-php-files-in-laravel-controller How to use external classes and PHP files in Laravel Controller?]
[
* [https://stackoverflow.com/questions/44374495/run-sh-file-using-exec-laravel-php Run .sh file using exec Laravel PHP - Stack Overflow]
['column1' => 1, 'column2' => 2],
* [https://stackoverflow.com/questions/54266041/how-to-execute-external-shell-commands-from-laravel-controller php - How to execute external shell commands from laravel controller? - Stack Overflow]
['column1' => 3, 'column2' => 4],
 
]
外部プログラム類を実行したいことがある。


===Getting Started===
storage以下に配置する。か、app_pathなどでパスを参照する。
[https://laravel.com/docs/5.8/database Database: Getting Started - Laravel 5.8 - The PHP Framework For] 1[https://laravel.com/docs/5.8/database Web Artisans]


クエリービルダー
* shell_exec
use Illuminate\Support\Facades\DB;
* $schedule->exec
上記のクラスメソッドで生SQLを使えeる。
* Symfony\Component\Process\Process


====Running Raw SQL Queries====
同じ場所に配置して実行すればいいと思う。
[https://qiita.com/alpha_z/items/3d2e3c283b4cd8dbc955 LaravelのクエリビルダでSQL文を直接実行(select,insert,update,delete,その他) #Laravel - Qiita]


DB::select/insert/update/deleteなどよく使うCRUD操作はメソッドがある。そういうのとは関係なしに、SQLを直実行する場合DB::statementを使う。
===== グループ =====
DB::statement('drop table users');
なお、DB::statementは1文ずつしか実行できない。複数実行する場合、1文ごとにDB::statementを記載する。


=== Query Builder ===
* [https://stackoverflow.com/questions/52257533/laravel-artisan-command-with-sub-methods Laravel artisan command with sub methods - Stack Overflow]
[https://laravel.com/docs/5.8/queries Database: Query Builder - Laravel 5.8 - The PHP Framework For Web Artisans]
* [https://stackoverflow.com/questions/51128285/how-can-my-command-pass-through-optional-arguments-to-another-artisan-command laravel - How can my command pass through optional arguments to another Artisan command? - Stack Overflow]


==== Retrieving Results ====
Artisanコマンドはartisan some:function {argument} {--option}の書式で定義される。


===== Aggregates =====
が、別にコロンはなくてもいい。コロンをつけると、同じプレフィクスのコマンドをグループ化して、ヘルプなどで取り扱ってくれる。わかりやすい。
集約メソッドがある。


* count: レコード数を返す。テーブルの行数などの把握で頻出。データの可否確認には、exists/doesntExistもある。がcountでいい気もする。
処理を共通化したければ、クラスにして継承するか、Traitにする。
* max
 
* min
シンプルなものなら、オプションか引数で分けるというのもありだろう。
* avg
 
* sum
=== File Storage ===
[https://laravel.com/docs/5.8/filesystem File Storage - Laravel 5.8 - The PHP Framework For Web Artisans]


=====Joins=====
Laravelでファイル入出力の仕組み。S3など、ストレージシステムを抽象化して扱える。ただし、これは基本的に一般公開用ディレクトリーを念頭に置いている。
$users = DB::table('users')
            ->join('contacts', 'users.id', '=', 'contacts.user_id')
            ->join('orders', 'users.id', '=', 'orders.user_id')
            ->select('users.*', 'contacts.phone', 'orders.price')
            ->get();


==== Where Clauses ====
==== File ====
重要。いくつか書き方がある。
*->where('vote','=', 100);
*->where('vote', 100);: 演算子を省略すると=扱い。
*->where([['status', '=', '1'], ['subscribed', '<>', '1'],]);: 二次元配列にすれば一度に複数条件渡せる。キー・演算子・バリューのペア。
=====分割実行=====
[https://stackoverflow.com/questions/24010724/is-it-possible-to-split-query-builder-in-laravel php - Is it possible to split query builder in Laravel? - Stack Overflow]


問題ない。
* [https://www.sejuku.net/blog/63671 【PHP/Laravel】ファイル操作時には欠かせない!Fileクラスの使い方 | 侍エンジニアブログ]
public function getStatuses($dates)
* [https://www.ozzu.com/questions/610927/whats-the-difference-between-the-laravel-file-and-storage-facades What's the difference between the Laravel File and Storage facades? - Ozzu]
{
* [https://laravel.com/api/6.x/Illuminate/Support/Facades/File.html Illuminate\Support\Facades\File | Laravel API]
    $query = DB::table('tickets');
 
    if ($dates['from'])
公式マニュアルに記載がないが、Fileファサードが存在する。これは、PHPのファイル入出力処理のラッパー。Storageと異なり、ディスクシステムの考慮などはしていない。比較的シンプルなファイルIO用。
        $query->where('from', $dates['from']);
 
    if ($dates['to'])
侍エンジニアブログくらいしか解説がない。
        $query->where('to', $dates['to']);
    $query->select('Active');
    return $query->get()->toArray();
}
whereの戻り値を変数に入れて流用しても問題ない。


==== Insert ====
vendor/laravel/framework/src/illuminate/Filesystem/Filesystem.phpがソースコード。


===== Upsert =====
Illuminate\Support\Facades\Fileが名前空間。
Laravel 8からupsertメソッドが登場した。
Laravel 8未満の場合、自分でDB::statementなどで行うしかない。


「[https://github.com/yadakhov/insert-on-duplicate-key GitHub - yadakhov/insert-on-duplicate-key]」が例。
* File::extension
====Transaction====
* File::size
*[https://laravel.com/docs/5.8/database Database: Getting Started - Laravel 5.8 - The PHP Framework For Web Artisans]
* File::get
*[https://qiita.com/shimizuyuta/items/dc69fb30d9f8600592db 新卒がLaravelのトランザクション実装について学んだこと #初心者 - Qiita]
* File::put
*[https://www.happylifecreators.com/blog/20220513/ Laravelのトランザクションとエラーハンドリグについて | Happy Life Creators]
* File::copy
*[https://www.yui-web-beginner.net/laravel-transaction/ Laravelのトランザクション処理を2パターン徹底解説 | 現役プログラマーYuiの開発ブログ]
* File::delete
*[https://shinyasunamachi.com/blog/Laravel%E3%81%A7%E3%83%88%E3%83%A9%E3%83%B3%E3%82%B6%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E5%87%A6%E7%90%86%E3%81%AE%E3%82%A8%E3%83%A9%E3%83%BC%E3%83%8F%E3%83%B3%E3%83%89%E3%83%AA%E3%83%B3%E3%82%B0%E3%82%92%E8%A1%8C%E3%81%86%E6%96%B9%E6%B3%95 Laravelでトランザクション処理のエラーハンドリングを行う方法 | Shinya Sunamachi]
* File::move
DB::transactionに処理の無名関数を指定してやると、DB処理が失敗したら自動的にロールバックする。
* File::isDirectory
try {
* File::copyDirectory: 中も全部。
$result = DB::transaction(function () use ($a, $b) {
* File::deleteDirectory: 中も全部。
    DB::table('users')->update(['votes' => 1]);
 
    DB::table('posts')->delete();
    return true;
});
} catch (Exception $e) {
    Log::error($e->getMessage());
    return;
}
2引数でデッドロック用のリトライ回数を指定できる。基本はいらない?


無名関数内でreturnすると、返せる。DB::transactionで失敗時の処理を個別にしたい場合、内部でthrowしてtry-catchをする。
=== Helpers ===


=== Migrations ===
==== Paths ====
[https://laravel.com/docs/5.8/migrations Database: Migrations - Laravel 5.8 - The PHP Framework For Web Artisans]
パス取得関係のヘルパー関数がいくつかある。


マイグレーションはデータベースのバージョン管理のようなもの。データベーススキーマを簡単に修正・共有を可能にする。
* app_path: appの絶対パス。
* base_path: アプリのルートパス。
* config_path: config
* database_path: database
* mix
* public_path
* resource_path
* storage_path


==== Generating Migrations ====
特にbase_pathは重要に感じる。
make:migrationコマンドでマイグレーションファイルを作れる。
php artisan make:migration create_users_table
php artisan make:migration add_votes_to_users_table --table=users
CRUD_テーブル名_tableなどのような命名規則にしておくとわかりやすい。


実行すると、database/migrationsディレクトリーにマイグレーションファイルが生成される。
=== Task Scheduling ===
[https://laravel.com/docs/5.8/scheduling Task Scheduling - Laravel 5.8 - The PHP Framework For Web Artisans]


オプションで--create=テーブル名、--table=テーブル名がある。
==== Introduction ====
cronでタスクスケジューリングできるが、cronでやる場合、タスクが増えるたびに設定が必要になる。


現在のテーブルの状況などで、マイグレーションファイルのひな形が変わる。ある程度マイグレーション名から推測してくれるが、日本語などが入るときかなくなる。オプションを指定したほうが無難。
LaravelではLaravelのスケジュールコマンドを1個cronに追加するだけで、Laravelのタスクを全部管理できる。


なお、このマイグレーションの名前は非常に重要。この名前がクラス名になるので、既存と被ってはいけない。変更内容が分かるように一意にする必要がある。
タスクスケジュールはapp/Console/Kernel.phpのscheduleメソッドで定義できる。


Laravel 8からは匿名マイグレーションで名前を意識しなくてもよくなる。
===== Starting The Scheduler =====
return new class extends Migration
スケジューラー使用時に、以下の項目をcrontabに登録する。
  {
  * * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
    //
これで毎分実行される。
};


==== Migration Structure ====
==== Defining Schedules ====
コマンド実行で生成されたテンプレートには、upとdownメソッドが用意されている。ここにテーブルの更新と、巻戻の処理を記載する。


==== Running Migrations ====
===== Defining Schedules =====
以下のコマンドで用意済みのマイグレーションを実行する。
App\Console\Kernelクラスのscheduleメソッドに、全タスクを定義する。
php artisan migrate
  <?php
 
 
===== Forcing Migrations To Run In Production =====
  namespace App\Console;
 
===== Rolling Back Migrations =====
マイグレーションを打ち消すようにrollbackコマンドがある。
php artisan migrate:rollback
最後のバッチを自動判別してそのブロックで巻き戻す。--stepでステップ数を指定できる。
php artisan migrate:rollback --step=5
migrate:resetでアプリのマイグレーションを全部戻せる。
  php artisan migrate:reset
 
====== Rollback & Migrate In Single Command ======
マイグレーションファイルやシーダーの更新などで、ロールバックとマイグレーションをやり直したいことがある。そういうコマンドがmigrate:refresh。--seedも指定するとシーダー登録もやってくれる。これはよく使う。
php artisan migrate:refresh
  // php artisan migrate:reset && php artisan migrate // 相当と思われる
    
    
  // Refresh the database and run all database seeds...
  use Illuminate\Support\Facades\DB;
  php artisan migrate:refresh --seed
  use Illuminate\Console\Scheduling\Schedule;
  // こちらは最後にphp artisan db:seed相当
  use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
これも--stepで指定できる。
 
====== Drop All Tables & Migrate ======
migrate:freshコマンドは、全テーブルを削除してmigrateを実行する。これは、マイグレーションで管理していないテーブルも含むので、危険。基本は使わないほうがいい。
php artisan migrate:fresh
    
    
  php artisan migrate:fresh --seed
  class Kernel extends ConsoleKernel
 
{
==== Tables ====
    /**
      * The Artisan commands provided by your application.
      *
      * @var array
      */
    protected $commands = [
        //
    ];
 
    /**
      * Define the application's command schedule.
      *
      * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
      * @return void
      */
    protected function schedule(Schedule $schedule)
    {
        $schedule->call(function () {
            DB::table('recent_users')->delete();
        })->daily();
    }
}
callメソッドで、PHPコードを指定できる。


===== Renaming/Dropping Tables =====
この例では、毎日深夜にClosureを実行している。Closureでテーブルを削除している。__invokeを定義済みのinvokableオブジェクトなら以下でシンプルにかける。
  Schema::rename($from, $to);
  $schedule->call(new DeleteRecentUsers)->daily();


  Schema::drop('users');
===== Scheduling Artisan Commands =====
    
ArtisanコマンドやOSのコマンドも指定できる。
  Schema::dropIfExists('users');
 
その場合、commandメソッドを使う。
  $schedule->command('emails:send Taylor --force')->daily();
    
  $schedule->command(EmailsCommand::class, ['Taylor', '--force'])->daily();
Artisanコマンドの場合、コマンド名かクラスを指定する。
 
===== Scheduling Shell Commands =====
execメソッドはシェルコマンドの発行に使える。
$schedule->exec('node /home/forge/script.js')->daily();


==== Columns ====
===== Schedule Frequency Options =====
[https://stackoverflow.com/questions/47224505/laravel-schedule-hourly-daily-understanding-when-exactly-start php - Laravel schedule - hourly, daily - understanding when exactly start - Stack Overflow]


===== Creating Columns =====
dailyは ["0 0 * * *" ] 相当。
{| class="wikitable"
{| class="wikitable"
!Command
!Method
!Description
!Description
|-
|-
|<code>$table->bigIncrements('id');</code>
|<code>->cron('* * * * *');</code>
|Auto-incrementing UNSIGNED BIGINT (primary key) equivalent column.
|Run the task on a custom Cron schedule
|-
|-
|<code>$table->bigInteger('votes');</code>
|<code>->everyMinute();</code>
|BIGINT equivalent column.
|Run the task every minute
|-
|-
|<code>$table->binary('data');</code>
|<code>->everyFiveMinutes();</code>
|BLOB equivalent column.
|Run the task every five minutes
|-
|-
|<code>$table->boolean('confirmed');</code>
|<code>->everyTenMinutes();</code>
|BOOLEAN equivalent column.
|Run the task every ten minutes
|-
|-
|<code>$table->char('name', 100);</code>
|<code>->everyFifteenMinutes();</code>
|CHAR equivalent column with an optional length.
|Run the task every fifteen minutes
|-
|-
|<code>$table->date('created_at');</code>
|<code>->everyThirtyMinutes();</code>
|DATE equivalent column.
|Run the task every thirty minutes
|-
|-
|<code>$table->dateTime('created_at');</code>
|<code>->hourly();</code>
|DATETIME equivalent column.
|Run the task every hour
|-
|-
|<code>$table->dateTimeTz('created_at');</code>
|<code>->hourlyAt(17);</code>
|DATETIME (with timezone) equivalent column.
|Run the task every hour at 17 mins past the hour
|-
|-
|<code>$table->decimal('amount', 8, 2);</code>
|<code>->daily();</code>
|DECIMAL equivalent column with a precision (total digits) and scale (decimal digits).
|Run the task every day at midnight
|-
|-
|<code>$table->double('amount', 8, 2);</code>
|<code>->dailyAt('13:00');</code>
|DOUBLE equivalent column with a precision (total digits) and scale (decimal digits).
|Run the task every day at 13:00
|-
|-
|<code>$table->enum('level', ['easy', 'hard']);</code>
|<code>->twiceDaily(1, 13);</code>
|ENUM equivalent column.
|Run the task daily at 1:00 & 13:00
|-
|-
|<code>$table->float('amount', 8, 2);</code>
|<code>->weekly();</code>
|FLOAT equivalent column with a precision (total digits) and scale (decimal digits).
|Run the task every week
|-
|-
|<code>$table->geometry('positions');</code>
|<code>->weeklyOn(1, '8:00');</code>
|GEOMETRY equivalent column.
|Run the task every week on Monday at 8:00
|-
|-
|<code>$table->geometryCollection('positions');</code>
|<code>->monthly();</code>
|GEOMETRYCOLLECTION equivalent column.
|Run the task every month
|-
|-
|<code>$table->increments('id');</code>
|<code>->monthlyOn(4, '15:00');</code>
|Auto-incrementing UNSIGNED INTEGER (primary key) equivalent column.
|Run the task every month on the 4th at 15:00
|-
|-
|<code>$table->integer('votes');</code>
|<code>->quarterly();</code>
|INTEGER equivalent column.
|Run the task every quarter
|-
|-
|<code>$table->ipAddress('visitor');</code>
|<code>->yearly();</code>
|IP address equivalent column.
|Run the task every year
|-
|-
|<code>$table->json('options');</code>
|<code>->timezone('America/New_York');</code>
|JSON equivalent column.
|Set the timezone
|-
|}
|<code>$table->jsonb('options');</code>
daily()=dailyAt('00:00')
|JSONB equivalent column.
 
|-
===== Preventing Task Overlaps =====
|<code>$table->lineString('positions');</code>
デフォルトだと、直前のタスクが実行中でも無視して次のタスクが実行される。withoutOverlappingを使うと、排他処理で他のタスクをさせない。
|LINESTRING equivalent column.
 
|-
実行時間が大幅に異なるタスクがあって、終了予定時間が予測できない場合に便利。
|<code>$table->longText('description');</code>
 
|LONGTEXT equivalent column.
デフォルトだと24時間でロックは解除される。引数で分単位で解除時間を指定できる。
|-
 
|<code>$table->macAddress('device');</code>
タスクが異常終了したらロックファイルが残る。
|MAC address equivalent column.
 
|-
[https://blog.e2info.co.jp/2023/01/07/laravel_task_scheduke_lock/ Laravelタスクスケジュールの多重起動制御のロックを解除する - ハマログ]
|<code>$table->mediumIncrements('id');</code>
php artisan schedule:clear-cache
|Auto-incrementing UNSIGNED MEDIUMINT (primary key) equivalent column.
|-
 
|<code>$table->mediumInteger('votes');</code>
上記のコマンドでロックファイルを削除できる。
|MEDIUMINT equivalent column.
 
|-
===== Running Tasks On One Server =====
|<code>$table->mediumText('description');</code>
memcachedかredisでキャッシュサーバーと同期していることが前提。
|MEDIUMTEXT equivalent column.
 
|-
アプリが複数サーバーで稼働させてスケーリングしている場合、そのままだと全部のサーバーで重複実行される。
|<code>$table->morphs('taggable');</code>
 
|Adds <code>taggable_id</code> UNSIGNED BIGINT and <code>taggable_type</code> VARCHAR equivalent columns.
処理の最後に->onOneServer()を指定すると、別の場所で実行済みだとスキップするらしい。
|-
 
|<code>$table->uuidMorphs('taggable');</code>
== Database ==
|Adds <code>taggable_id</code> CHAR(36) and <code>taggable_type</code> VARCHAR(255) UUID equivalent columns.
LaravelのDBの取得結果は、以下のような行ごとの連想配列になっている。
|-
[
|<code>$table->multiLineString('positions');</code>
['column1' => 1, 'column2' => 2],
|MULTILINESTRING equivalent column.
['column1' => 3, 'column2' => 4],
|-
]
|<code>$table->multiPoint('positions');</code>
 
|MULTIPOINT equivalent column.
===Getting Started===
|-
[https://laravel.com/docs/5.8/database Database: Getting Started - Laravel 5.8 - The PHP Framework For] 1[https://laravel.com/docs/5.8/database Web Artisans]
|<code>$table->multiPolygon('positions');</code>
 
|MULTIPOLYGON equivalent column.
クエリービルダー
|-
use Illuminate\Support\Facades\DB;
|<code>$table->nullableMorphs('taggable');</code>
上記のクラスメソッドで生SQLを使えeる。
|Adds nullable versions of <code>morphs()</code> columns.
 
|-
====Running Raw SQL Queries====
|<code>$table->nullableUuidMorphs('taggable');</code>
[https://qiita.com/alpha_z/items/3d2e3c283b4cd8dbc955 LaravelのクエリビルダでSQL文を直接実行(select,insert,update,delete,その他) #Laravel - Qiita]
|Adds nullable versions of <code>uuidMorphs()</code> columns.
 
|-
DB::select/insert/update/deleteなどよく使うCRUD操作はメソッドがある。そういうのとは関係なしに、SQLを直実行する場合DB::statementを使う。
|<code>$table->nullableTimestamps();</code>
DB::statement('drop table users');
|Alias of <code>timestamps()</code> method.
なお、DB::statementは1文ずつしか実行できない。複数実行する場合、1文ごとにDB::statementを記載する。
|-
 
|<code>$table->point('position');</code>
=== Query Builder ===
|POINT equivalent column.
[https://laravel.com/docs/5.8/queries Database: Query Builder - Laravel 5.8 - The PHP Framework For Web Artisans]
|-
 
|<code>$table->polygon('positions');</code>
==== Retrieving Results ====
|POLYGON equivalent column.
 
|-
===== Aggregates =====
|<code>$table->rememberToken();</code>
集約メソッドがある。
|Adds a nullable <code>remember_token</code> VARCHAR(100) equivalent column.
 
|-
* count: レコード数を返す。テーブルの行数などの把握で頻出。データの可否確認には、exists/doesntExistもある。がcountでいい気もする。
|<code>$table->set('flavors', ['strawberry', 'vanilla']);</code>
* max
|SET equivalent column.
* min
|-
* avg
|<code>$table->smallIncrements('id');</code>
* sum
|Auto-incrementing UNSIGNED SMALLINT (primary key) equivalent column.
 
|-
=====Joins=====
|<code>$table->smallInteger('votes');</code>
$users = DB::table('users')
|SMALLINT equivalent column.
            ->join('contacts', 'users.id', '=', 'contacts.user_id')
|-
            ->join('orders', 'users.id', '=', 'orders.user_id')
|<code>$table->softDeletes();</code>
            ->select('users.*', 'contacts.phone', 'orders.price')
|Adds a nullable <code>deleted_at</code> TIMESTAMP equivalent column for soft deletes.
            ->get();
|-
[https://stackoverflow.com/questions/14318205/laravel-join-queries-as php - Laravel join queries AS - Stack Overflow]
|<code>$table->softDeletesTz();</code>
 
|Adds a nullable <code>deleted_at</code> TIMESTAMP (with timezone) equivalent column for soft deletes.
なお、joinの第1引数でテーブル指定の部分はASで別名指定が可能な模様。
|-
 
|<code>$table->string('name', 100);</code>
[https://laracasts.com/discuss/channels/laravel/query-builder-multiple-joins-based-on-array Query Builder: multiple joins based on array]
|VARCHAR equivalent column with a optional length.
 
|-
複数のjoinを行う場合、loopで行う。
|<code>$table->text('description');</code>
$builder = DB::table($table);
|TEXT equivalent column.
|-
foreach($leftJoins as $leftJoin) {
|<code>$table->time('sunrise');</code>
$builder->leftJoin(...$jeftJoin);
|TIME equivalent column.
}
|-
|<code>$table->timeTz('sunrise');</code>
$results = $builder->get();
|TIME (with timezone) equivalent column.
 
|-
* join: 内部結合 (集合積)。
|<code>$table->timestamp('added_on');</code>
* leftJoin/rightJoin: 外部結合 (集合和)
|TIMESTAMP equivalent column.
* crossJoin: クロス結合。
|-
 
|<code>$table->timestampTz('added_on');</code>
==== Where Clauses ====
|TIMESTAMP (with timezone) equivalent column.
重要。いくつか書き方がある。
|-
*->where('vote','=', 100);
|<code>$table->timestamps();</code>
*->where('vote', 100);: 演算子を省略すると=扱い。
|Adds nullable <code>created_at</code> and <code>updated_at</code> TIMESTAMP equivalent columns.
*->where([['status', '=', '1'], ['subscribed', '<>', '1'],]);: 二次元配列にすれば一度に複数条件渡せる。キー・演算子・バリューのペア。
|-
=====分割実行=====
|<code>$table->timestampsTz();</code>
[https://stackoverflow.com/questions/24010724/is-it-possible-to-split-query-builder-in-laravel php - Is it possible to split query builder in Laravel? - Stack Overflow]
|Adds nullable <code>created_at</code> and <code>updated_at</code> TIMESTAMP (with timezone) equivalent columns.
 
|-
問題ない。
|<code>$table->tinyIncrements('id');</code>
public function getStatuses($dates)
|Auto-incrementing UNSIGNED TINYINT (primary key) equivalent column.
{
|-
    $query = DB::table('tickets');
|<code>$table->tinyInteger('votes');</code>
    if ($dates['from'])
|TINYINT equivalent column.
        $query->where('from', $dates['from']);
|-
    if ($dates['to'])
|<code>$table->unsignedBigInteger('votes');</code>
        $query->where('to', $dates['to']);
|UNSIGNED BIGINT equivalent column.
    $query->select('Active');
|-
    return $query->get()->toArray();
|<code>$table->unsignedDecimal('amount', 8, 2);</code>
}
|UNSIGNED DECIMAL equivalent column with a precision (total digits) and scale (decimal digits).
whereの戻り値を変数に入れて流用しても問題ない。
|-
 
|<code>$table->unsignedInteger('votes');</code>
==== Insert ====
|UNSIGNED INTEGER equivalent column.
 
|-
===== Upsert =====
|<code>$table->unsignedMediumInteger('votes');</code>
Laravel 8からupsertメソッドが登場した。
|UNSIGNED MEDIUMINT equivalent column.
Laravel 8未満の場合、自分でDB::statementなどで行うしかない。
|-
|<code>$table->unsignedSmallInteger('votes');</code>
|UNSIGNED SMALLINT equivalent column.
|-
|<code>$table->unsignedTinyInteger('votes');</code>
|UNSIGNED TINYINT equivalent column.
|-
|<code>$table->uuid('id');</code>
|UUID equivalent column.
|-
|<code>$table->year('birth_year');</code>
|YEAR equivalent column.
|}


===== Column Modifiers =====
「[https://github.com/yadakhov/insert-on-duplicate-key GitHub - yadakhov/insert-on-duplicate-key]」が例。
カラム種類に加え、いくつかのカラム修飾がある。
====Transaction====
  Schema::table('users', function (Blueprint $table) {
*[https://laravel.com/docs/5.8/database Database: Getting Started - Laravel 5.8 - The PHP Framework For Web Artisans]
     $table->string('email')->nullable();
*[https://qiita.com/shimizuyuta/items/dc69fb30d9f8600592db 新卒がLaravelのトランザクション実装について学んだこと #初心者 - Qiita]
*[https://www.happylifecreators.com/blog/20220513/ Laravelのトランザクションとエラーハンドリグについて | Happy Life Creators]
*[https://www.yui-web-beginner.net/laravel-transaction/ Laravelのトランザクション処理を2パターン徹底解説 | 現役プログラマーYuiの開発ブログ]
*[https://shinyasunamachi.com/blog/Laravel%E3%81%A7%E3%83%88%E3%83%A9%E3%83%B3%E3%82%B6%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E5%87%A6%E7%90%86%E3%81%AE%E3%82%A8%E3%83%A9%E3%83%BC%E3%83%8F%E3%83%B3%E3%83%89%E3%83%AA%E3%83%B3%E3%82%B0%E3%82%92%E8%A1%8C%E3%81%86%E6%96%B9%E6%B3%95 Laravelでトランザクション処理のエラーハンドリングを行う方法 | Shinya Sunamachi]
DB::transactionに処理の無名関数を指定してやると、DB処理が失敗したら自動的にロールバックする。
try {
  $result = DB::transaction(function () use ($a, $b) {
     DB::table('users')->update(['votes' => 1]);
 
    DB::table('posts')->delete();
    return true;
  });
  });
以下が特に。
} catch (Exception $e) {
    Log::error($e->getMessage());
    return;
}
2引数でデッドロック用のリトライ回数を指定できる。基本はいらない?


* after
無名関数内でreturnすると、返せる。DB::transactionで失敗時の処理を個別にしたい場合、内部でthrowしてtry-catchをする。
* first
* default($value): default値を指定する。nullableを指定するとdefault=NULL扱い。
* nullable($value = true): nullを許容するかしないか。falseにするとnot nullにできるが、デフォルトnot nullなのでfalse指定は基本はしない。
* length: integerなどで幅が必要なケース ([https://qiita.com/msht0511/items/49ca034426ad7c036e23 【Laravel】マイグレーションを使用する際にintegerのサイズの指定方法に注意 #PHP - Qiita])。
デフォルトだとnullable(false)相当なので、nullを許容するならnullable()の指定が必要。
===== Modifying Columns =====
既存カラムの改名や、データ型変更も行えるが、癖がある。


なお、int(10) のような数値型の幅などは、MySQLの独自拡張で、DB独自拡張はマイグレーションのAPIで未対応。
=== Pagination ===
[https://laravel.com/docs/5.8/pagination Database: Pagination - Laravel 5.8 - The PHP Framework For Web Artisans]


「[https://stackoverflow.com/questions/35108133/define-property-zerofill-and-size-on-field-schema-migration-with-laravel php - Define property zerofill and size on field schema migration with laravel - Stack Overflow]」
テーブルの内容を全表示する場合などで、単純にallやgetでデータを取得・表示させようとすると、データ量が多すぎてメモリーアウトする可能性がある。


DB::statementで生SQLで対応するしかない。
それを回避しつつ全データを表示させる仕組みがページネーション。データを一定間隔で分割・表示することでメモリーアウトを防止しながら大量データを表示する。


====== Prerequisites ======
==== Introduction ====
事前に依存関係を追加する。
基本的な使い方。
composer require doctrine/dbal


====== Updating Column Attributes ======
# DBやEloquentでのデータ取得時に最後にpaginate(表示数)のメソッドを追加する。
既存のカラムを別の型に変更する場合、changeメソッドを使う。
# Bladeで<nowiki>{{ $users->links() }}</nowiki>のメソッドを配置。
Schema::table('users', function (Blueprint $table) {
    $table->string('name', 50)->change();
});


  Schema::table('users', function (Blueprint $table) {
基本は以上。
    $table->string('name', 50)->nullable()->change();
  <?php
  });
 
nullableの付与も可能。
namespace App\Http\Controllers;
 
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
 
class UserController extends Controller
{
    /**
      * Show all of the users for the application.
      *
      * @return Response
      */
    public function index()
    {
        $users = DB::table('users')->paginate(15);
 
        return view('user.index', ['users' => $users]);
    }
  }


後から変更可能な型は以下。
<nowiki><div class="container">
    @foreach ($users as $user)
        {{ $user->name }}</nowiki>
<nowiki> </nowiki>  @endforeach
<nowiki></div></nowiki>
<nowiki> </nowiki>
<nowiki>{{ $users->links() }}</nowiki>


bigInteger, binary, boolean, date, dateTime, dateTimeTz, decimal, integer, json, longText, mediumText, smallInteger, string, text, time, unsignedBigInteger, unsignedInteger and unsignedSmallInteger
==== Paginator Instance Methods ====
paginateなどが返すPaginatorインスタンスに役立つメソッドがある。
{| class="wikitable"
!Method
!Description
|-
|<code>$results->count()</code>
|Get the number of items for the current page.
|-
|<code>$results->currentPage()</code>
|Get the current page number.
|-
|<code>$results->firstItem()</code>
|Get the result number of the first item in the results.
|-
|<code>$results->getOptions()</code>
|Get the paginator options.
|-
|<code>$results->getUrlRange($start, $end)</code>
|Create a range of pagination URLs.
|-
|<code>$results->hasMorePages()</code>
|Determine if there are enough items to split into multiple pages.
|-
|<code>$results->items()</code>
|Get the items for the current page.
|-
|<code>$results->lastItem()</code>
|Get the result number of the last item in the results.
|-
|<code>$results->lastPage()</code>
|Get the page number of the last available page. (Not available when using <code>simplePaginate</code>).
|-
|<code>$results->nextPageUrl()</code>
|Get the URL for the next page.
|-
|<code>$results->onFirstPage()</code>
|Determine if the paginator is on the first page.
|-
|<code>$results->perPage()</code>
|The number of items to be shown per page.
|-
|<code>$results->previousPageUrl()</code>
|Get the URL for the previous page.
|-
|<code>$results->total()</code>
|Determine the total number of matching items in the data store. (Not available when using <code>simplePaginate</code>).
|-
|<code>$results->url($page)</code>
|Get the URL for a given page number.
|}
ヒット件数総数を表示するtotalが特に重要か。


====== Renaming Columns ======
=== Migrations ===
[https://laravel.com/docs/5.8/migrations Database: Migrations - Laravel 5.8 - The PHP Framework For Web Artisans]


===== Dropping Columns =====
マイグレーションはデータベースのバージョン管理のようなもの。データベーススキーマを簡単に修正・共有を可能にする。
downで戻す場合に使ったりする。
Schema::table('users', function (Blueprint $table) {
    $table->dropColumn('votes');
    $table->dropColumn(['votes', 'avatar', 'location']);
});
なお、非常に紛らわしいのだが、removeColumnメソッドもある ([https://stackoverflow.com/questions/30256463/what-is-the-difference-between-removecolumn-and-dropcolumn-methods-on-laravel-fr database - What is the difference between removeColumn and dropColumn methods on Laravel Framework? - Stack Overflow])。こちらはBlueprintから削除するだけで、実テーブルからは削除しない。使うことはほぼない。


==== Indexes ====
==== Generating Migrations ====
 
make:migrationコマンドでマイグレーションファイルを作れる。
===== Creating Indexes =====
  php artisan make:migration create_users_table
[https://stackoverflow.com/questions/20065697/schema-builder-laravel-migrations-unique-on-two-columns schema builder laravel migrations unique on two columns - Stack Overflow]
  $table->string('email')->unique();
   
   
  $table->unique('email');
  php artisan make:migration add_votes_to_users_table --table=users
$table->index(['account_id', 'created_at']);
CRUD_テーブル名_tableなどのような命名規則にしておくとわかりやすい。
$table->unique('email', 'unique_email');
$table->unique(['account_id', 'email']);
$table->dropUnique(['account_id', 'email']);
複合UNIQUE制約をつけたい場合、uniqueメソッドの第一引数を配列にする。


==== Other ====
実行すると、database/migrationsディレクトリーにマイグレーションファイルが生成される。


===== Command =====
オプションで--create=テーブル名、--table=テーブル名がある。
*migrate:rollback: マイグレーションをロールバックする。デフォルトだと最後の1回分。--step=Nで回数を指定できる。
*migrate:reset: 全部ロールバックする。
*migrate:refresh: rollback+migrate。
*migrate:fresh: ロールバックではなく、テーブルを削除してから戻す。


===== 既存DBの途中からの管理 =====
現在のテーブルの状況などで、マイグレーションファイルのひな形が変わる。ある程度マイグレーション名から推測してくれるが、日本語などが入るときかなくなる。オプションを指定したほうが無難。
*[https://gamushiros.hatenablog.com/entry/2019/04/23/000000 Laravelのmigrateを途中から実行する - mk-toolブログ]
*[https://teratail.com/questions/362984 Laravel 既存DBをマイグレーションする方法]
*[https://www.reddit.com/r/laravel/comments/aarkm7/laravel_migration_how_to_autoskip_tables_that/ Laravel Migration: how to auto-skip tables that already exist? : r/laravel]
今後の変更分だけ管理するということもできる。


なお、中途半端にLaravelのマイグレーションファイルがある場合、migrationsテーブルにデータを手動で登録しておくと、マイグレーション済みと認識できる。
なお、このマイグレーションの名前は非常に重要。この名前がクラス名になるので、既存と被ってはいけない。変更内容が分かるように一意にする必要がある。


作ってあげるデータは、id,migration, batchを持つデータで、idは1からインクリメント、migrationはマイグレーションのファイル名(.phpは除く)、batchを1(たぶんこれは何度マイグレーションを実行したかを保っておくもので状態を1つ戻すときに使われるのでは?)に設定すればよい。
Laravel 8からは匿名マイグレーションで名前を意識しなくてもよくなる。
{| class="wikitable"
return new class extends Migration
!id
{
!migration
    //
!batch
};
|-
 
|1
==== Migration Structure ====
|2017_09_01_134421_create_prefectures_table
コマンド実行で生成されたテンプレートには、upとdownメソッドが用意されている。ここにテーブルの更新と、巻戻の処理を記載する。
|1
|-
|2
|2017_09_01_134422_create_cities_table
|1
|}SQLだと以下相当。
insert into migrations(migration, batch) values('2015_12_08_134409_create_tables_script',1);
insert into migrations(migration, batch) values('2015_12_08_134410_create_foreign',1);
insert into migrations(migration, batch) values('2015_12_08_134411_create_index',1);


===== SQLでのマイグレーション =====
==== Running Migrations ====
[https://stackoverflow.com/questions/46507655/laravel-migration-is-it-possible-to-use-sql-instead-of-schema-commands-to-crea database - Laravel migration - is it possible to use SQL instead of schema commands to create tables and fields etc? - Stack Overflow]
以下のコマンドで用意済みのマイグレーションを実行する。
php artisan migrate


upメソッドにDB::updateなどで生のSQLをそのまま書けばOK。
===== Forcing Migrations To Run In Production =====
class AddColumnsToUsersTable extends Migration
{
    /**
      * Run the migrations.
      *
      * @return void
      */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('name');
            $table->string('age');
            $table->timestamps();
        });
        DB::update('update users set age = 30 where name = ?', ['John']);
    }
}
downメソッドにはDropIfExistsで削除すればOK。


===== Cannot declare class TableName, because the name is already in use =====
===== Rolling Back Migrations =====
マイグレーションを打ち消すようにrollbackコマンドがある。
php artisan migrate:rollback
最後のバッチを自動判別してそのブロックで巻き戻す。--stepでステップ数を指定できる。
php artisan migrate:rollback --step=5
migrate:resetでアプリのマイグレーションを全部戻せる。
php artisan migrate:reset


* [https://zoo200.net/laravel-migration-error-cannot-declare-class-tablename/ Laravelのマイグレーションで Cannot declare class TableNameのエラー | zoo200's MemoMemo]
====== Rollback & Migrate In Single Command ======
* [https://teratail.com/questions/112058 Laravelでmigrateができない]
マイグレーションファイルやシーダーの更新などで、ロールバックとマイグレーションをやり直したいことがある。そういうコマンドがmigrate:refresh。--seedも指定するとシーダー登録もやってくれる。これはよく使う。
* [https://qiita.com/takepan/items/86c59f15600c1f32a942 Laravelで「Cannot declare class UpdateUsersTable」ほか #laravel5.5 - Qiita]
php artisan migrate:refresh
// php artisan migrate:reset && php artisan migrate // 相当と思われる
 
// Refresh the database and run all database seeds...
php artisan migrate:refresh --seed
// こちらは最後にphp artisan db:seed相当
これも--stepで指定できる。


マイグレーションファイル作成時に生成されるクラスが、既存のマイグレーションファイルのクラス名と被っているのが原因の一つ。
====== Drop All Tables & Migrate ======
migrate:freshコマンドは、全テーブルを削除してmigrateを実行する。これは、マイグレーションで管理していないテーブルも含むので、危険。基本は使わないほうがいい。
php artisan migrate:fresh
 
php artisan migrate:fresh --seed


マイグレーションのファイル名/クラス名は固有にしないといけない。
==== Tables ====


Laravel 8からは匿名マイグレーションで名前を意識しなくても済むらしい ([https://stackoverflow.com/questions/54765710/error-migrations-cannot-declare-class-x-because-the-name-is-already-in-use php - Error migrations: Cannot declare class X, because the name is already in use - Stack Overflow])
===== Renaming/Dropping Tables =====
return new class extends Migration {
Schema::rename($from, $to);
  //
};


===== Doctrine\DBAL\Exception  : Unknown column type "mediuminteger" requested. =====
Schema::drop('users');
 
Schema::dropIfExists('users');


* [https://stackoverflow.com/questions/77359070/laravel-migration-to-change-an-unsignedmediuminteger-column eloquent - Laravel migration to change an unsignedMediumInteger column - Stack Overflow]
==== Columns ====
* [https://laracasts.com/discuss/channels/eloquent/unsupported-laravel-datatypes-in-doctrinedbal Unsupported laravel datatypes in doctrine/dbal]


Doctrine\DBAL\Exception  : Unknown column type "mediuminteger" requested. Any Doctrine type that you use has to be registered with \Doctrine\DBAL\Types\Type::addType(). You can get a list of all the known types with \Doctrine\DBAL\Types\Type::getTypesMap(). If this error occurs during database introspection then you might have forgotten to register all database types for a Doctrine Type. Use AbstractPlatform#registerDoctrineTypeMapping() or have your custom types implement Type#getMappedDatabaseTypes(). If the type name is empty you might have a problem with the cache or forgot some mapping information.
===== Creating Columns =====
Laravel 5.8のmigrationでmediumintergerが使えない。Doctrineが対応していないから。しかたないからintegerにする。
{| class="wikitable"
 
!Command
「[https://github.com/laravel/framework/issues/36509 "Unknown column type "mediumInteger" requested" error after downgrading doctrine/dbal to 2.* · Issue #36509 · laravel/framework · GitHub]」によると、Laravel 8.0、Doctrine DBAL 3.0以上で解決しているとのこと。
!Description
 
|-
=== Seeding ===
|<code>$table->bigIncrements('id');</code>
[https://laravel.com/docs/5.8/seeding Database: Seeding - Laravel 5.8 - The PHP Framework For Web Artisans]
|Auto-incrementing UNSIGNED BIGINT (primary key) equivalent column.
 
|-
マイグレーションで用意したDBのデフォルトデータの用意の話。
|<code>$table->bigInteger('votes');</code>
 
|BIGINT equivalent column.
デフォルトデータをシードと呼んでおり、シードを用意する機能をシーディングと呼んでいる。
|-
 
|<code>$table->binary('data');</code>
シードを作成するためのスクリプト (シーダー) ファイルを生成して、記述して実行する流れ。
|BLOB equivalent column.
 
|-
==== Writing Seeders ====
|<code>$table->boolean('confirmed');</code>
以下のコマンドでシーダーを作成する。
|BOOLEAN equivalent column.
php artisan make:seeder UsersTableSeeder
|-
database/seedsにファイルが作成される。
|<code>$table->char('name', 100);</code>
 
|CHAR equivalent column with an optional length.
中身はrunメソッドがあるだけ。run内でinsertするだけ。
|-
 
|<code>$table->date('created_at');</code>
==== Calling Additional Seeders ====
|DATE equivalent column.
追加したシーダーはそのままでは認識されない。
|-
 
|<code>$table->dateTime('created_at');</code>
DatabaseSeederに登録する。DatabaseSeeder.phpのrunのcallメソッド内に追加する。
|DATETIME equivalent column.
public function run()
|-
{
|<code>$table->dateTimeTz('created_at');</code>
    $this->call([
|DATETIME (with timezone) equivalent column.
        UsersTableSeeder::class,
|-
        PostsTableSeeder::class,
|<code>$table->decimal('amount', 8, 2);</code>
        CommentsTableSeeder::class,
|DECIMAL equivalent column with a precision (total digits) and scale (decimal digits).
    ]);
|-
}
|<code>$table->double('amount', 8, 2);</code>
 
|DOUBLE equivalent column with a precision (total digits) and scale (decimal digits).
==== Running Seeders ====
|-
追加したシーダーを認識させるために以下のコマンドでautoloadを更新する。
|<code>$table->enum('level', ['easy', 'hard']);</code>
composer dump-autoload
|ENUM equivalent column.
シーダーを用意したらコマンドを実行して作成する。
|-
php artisan db:seed
|<code>$table->float('amount', 8, 2);</code>
--classでシーダー名を指定もできる。あとからシーダーを追加する場合は--classで指定するイメージ。
|FLOAT equivalent column with a precision (total digits) and scale (decimal digits).
 
|-
=== Error ===
|<code>$table->geometry('positions');</code>
 
|GEOMETRY equivalent column.
==== Handling ====
|-
 
|<code>$table->geometryCollection('positions');</code>
* [https://laravel.com/docs/5.8/errors Error Handling - Laravel 5.8 - The PHP Framework For Web Artisans]
|GEOMETRYCOLLECTION equivalent column.
* [https://medium.com/@dayoolapeju/exception-error-handling-in-laravel-25843a8aabb3 Exception/Error Handling in Laravel | by Jeremiah Ekundayo | Medium]
|-
* [https://qiita.com/kenji123/items/6b72d412e1c60dd1f27e 例外処理の対応(Laravel) #Try-Catch - Qiita]
|<code>$table->increments('id');</code>
 
|Auto-incrementing UNSIGNED INTEGER (primary key) equivalent column.
DB関係の処理で、失敗した場合の扱いが、公式文書に記載がなくて混乱する。
|-
 
|<code>$table->integer('votes');</code>
基本はtry-catchでやればいい。
|INTEGER equivalent column.
use Exception;
|-
|<code>$table->ipAddress('visitor');</code>
public function result(Request $request)
|IP address equivalent column.
{
|-
    try {
|<code>$table->json('options');</code>
        $user = User::findOrFail($request->user_id);
|JSON equivalent column.
|-
        return view('result', compact('user'));
|<code>$table->jsonb('options');</code>
    } catch(Exception $exception) {
|JSONB equivalent column.
        $message = $exception->getMessage();
|-
|<code>$table->lineString('positions');</code>
        if($exception instanceof ModelNotFoundException) {
|LINESTRING equivalent column.
            $message = 'User with ID: '.$request->user_id.' not found!';
|-
        }
|<code>$table->longText('description');</code>
|LONGTEXT equivalent column.
        return back()->withError($message)->withInput();
|-
    }
|<code>$table->macAddress('device');</code>
}
|MAC address equivalent column.
 
|-
==== Using Docker I get the error: "SQLSTATE[HY000] [2002] No such file or directory" ====
|<code>$table->mediumIncrements('id');</code>
[https://stackoverflow.com/questions/40075065/using-docker-i-get-the-error-sqlstatehy000-2002-no-such-file-or-directory php - Using Docker I get the error: "SQLSTATE[HY000] [2002] No such file or directory" - Stack Overflow]
|Auto-incrementing UNSIGNED MEDIUMINT (primary key) equivalent column.
 
|-
[https://qiita.com/b-coffin/items/8103583efe3767b6748e PHPのPDOをDockerコンテナ内で使おうとしたところ、"No such file or directory" エラーが発生した話 #docker-compose - Qiita]
|<code>$table->mediumInteger('votes');</code>
 
|MEDIUMINT equivalent column.
dockerで.envのDB_HOSTの指定間違い。dockerのservice名をホスト名に指定する必要がある。
|-
 
|<code>$table->mediumText('description');</code>
==== local.ERROR: could not find driver ====
|MEDIUMTEXT equivalent column.
[2024-07-02 15:35:42] local.ERROR: could not find driver (SQL: select * from `sessions` where `id` = 0cDH7URcrsFzC7hPYlbAQcezNVjdDM16OJh1aCSZ limit 1) {"exception":"[object] (Illuminate\\Database\\QueryException(code: 0): could not find driver (SQL: select * from `sessions` where `id` = 0cDH7URcrsFzC7hPYlbAQcezNVjdDM16OJh1aCSZ limit 1) at /var/www/vendor/laravel/framework/src/Illuminate/Database/Connection.php:664, Doctrine\\DBAL\\Driver\\PDO\\Exception(code: 0): could not find driver at /var/www/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDO/Exception.php:18, PDOException(code: 0): could not find driver at /var/www/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php:40)
|-
このエラーはphpのモジュール不足。LaravelはPDOを使う。mysqliではなく。
|<code>$table->morphs('taggable');</code>
RUN docker-php-ext-install pdo_mysql
|Adds <code>taggable_id</code> UNSIGNED BIGINT and <code>taggable_type</code> VARCHAR equivalent columns.
これが必要。
|-
 
|<code>$table->uuidMorphs('taggable');</code>
== Eloquent ==
|Adds <code>taggable_id</code> CHAR(36) and <code>taggable_type</code> VARCHAR(255) UUID equivalent columns.
 
|-
====Getting Started====
|<code>$table->multiLineString('positions');</code>
[https://laravel.com/docs/5.8/eloquent Eloquent: Getting Started - Laravel 5.8 - The PHP Framework For Web Artisans]
|MULTILINESTRING equivalent column.
 
|-
Eloquent ORM。
|<code>$table->multiPoint('positions');</code>
=====Defining Models=====
|MULTIPOINT equivalent column.
make:modelコマンドでモデルインスタンスを作成できる。
|-
php artisan make:model Flight
|<code>$table->multiPolygon('positions');</code>
-mで新規作成のマイグレーションファイルも一緒に作れる。
|MULTIPOLYGON equivalent column.
======Eloquent Model Conventions======
|-
======Table Names======
|<code>$table->nullableMorphs('taggable');</code>
[https://qiita.com/igz0/items/d14fdff610dccadb169e Laravelで「Base table or view not found: 1146 Table」エラーが出るときの対処法 #PHP - Qiita]
|Adds nullable versions of <code>morphs()</code> columns.
 
|-
Eloquentのモデルでは、デフォルトでクラス名を複数形のスネークケースにしたものをテーブル名とみなす。
|<code>$table->nullableUuidMorphs('taggable');</code>
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'your_db.your_models' doesn't exist (SQL: select * from `your_models`)
|Adds nullable versions of <code>uuidMorphs()</code> columns.
不在の場合、上記のエラーが出る。
|-
 
|<code>$table->nullableTimestamps();</code>
デフォルト以外のテーブル名を使いたければ、$tableにテーブル名を指定する。
|Alias of <code>timestamps()</code> method.
<?php
|-
 
|<code>$table->point('position');</code>
namespace App;
|POINT equivalent column.
|-
use Illuminate\Database\Eloquent\Model;
|<code>$table->polygon('positions');</code>
 
|POLYGON equivalent column.
class Flight extends Model
|-
{
|<code>$table->rememberToken();</code>
    /**
|Adds a nullable <code>remember_token</code> VARCHAR(100) equivalent column.
      * The table associated with the model.
|-
      *
|<code>$table->set('flavors', ['strawberry', 'vanilla']);</code>
      * @var string
|SET equivalent column.
      */
|-
    protected $table = 'my_flights';
|<code>$table->smallIncrements('id');</code>
}
|Auto-incrementing UNSIGNED SMALLINT (primary key) equivalent column.
プロジェクトでテーブル名に日本語を使う場合など、クラスメイとテーブル名が同一なら、以下のような親クラスを使って、これを継承すると同じ処理を記載しなくていい。
|-
<?php
|<code>$table->smallInteger('votes');</code>
|SMALLINT equivalent column.
namespace App\Models;
|-
|<code>$table->softDeletes();</code>
use Illuminate\Database\Eloquent\Model;
|Adds a nullable <code>deleted_at</code> TIMESTAMP equivalent column for soft deletes.
|-
class BaseModel extends Model
|<code>$table->softDeletesTz();</code>
{
|Adds a nullable <code>deleted_at</code> TIMESTAMP (with timezone) equivalent column for soft deletes.
    function __construct()
|-
    {
|<code>$table->string('name', 100);</code>
        // デフォルトだと複数形のスネークケースのテーブルを探すため、クラス名=テーブル名で上書き。
|VARCHAR equivalent column with a optional length.
        $this->table = (new \ReflectionClass($this))->getShortName();
|-
    }
|<code>$table->text('description');</code>
}
|TEXT equivalent column.
======Timestamps======
|-
[https://qiita.com/ikadatic/items/2237a3c1b837894dfc30 LaravelのEloquentモデルでupdated_atがないテーブルを使う方法 #PHP - Qiita]
|<code>$table->time('sunrise');</code>
 
|TIME equivalent column.
[https://stackoverflow.com/questions/28277955/laravel-unknown-column-updated-at php - Laravel Unknown Column 'updated_at' - Stack Overflow]
|-
 
|<code>$table->timeTz('sunrise');</code>
デフォルトで、Eloquentはcreated_atとupdated_atカラムを期待する。使っていないならば、以下のエラーが出る。
|TIME (with timezone) equivalent column.
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'updated_at' in 'field list' at /var/www/vendor/laravel/framework/src/Illuminate/Database/Connection.php:664, Doctrine\\DBAL\\Driver\\PDO\\Exception(code: 42S22): SQLSTATE[42S22]: Column not found: 1054 Unknown column 'updated_at' in 'field list' at /var/www/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDO/Exception.php:18, PDOException(code: 42S22): SQLSTATE[42S22]: Column not found: 1054 Unknown column 'updated_at' in 'field list' at /var/www/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php:82)
|-
[stacktrace]
|<code>$table->timestamp('added_on');</code>
$timestamps=falseにしておく。
|TIMESTAMP equivalent column.
<?php
|-
 
|<code>$table->timestampTz('added_on');</code>
namespace App;
|TIMESTAMP (with timezone) equivalent column.
 
|-
use Illuminate\Database\Eloquent\Model;
|<code>$table->timestamps();</code>
 
|Adds nullable <code>created_at</code> and <code>updated_at</code> TIMESTAMP equivalent columns.
class Flight extends Model
|-
{
|<code>$table->timestampsTz();</code>
    /**
|Adds nullable <code>created_at</code> and <code>updated_at</code> TIMESTAMP (with timezone) equivalent columns.
      * Indicates if the model should be timestamped.
|-
      *
|<code>$table->tinyIncrements('id');</code>
      * @var bool
|Auto-incrementing UNSIGNED TINYINT (primary key) equivalent column.
      */
|-
    public $timestamps = false;
|<code>$table->tinyInteger('votes');</code>
}
|TINYINT equivalent column.
 
|-
==== Retrieving Models ====
|<code>$table->unsignedBigInteger('votes');</code>
Eloquentのモデルは、強力なクエリビルダーと考えてもいい。Eqloquentのallメソッドはモデルテーブルの全結果を返す。これはクエリービルダーとして、各モデルで提供されているので、条件を追加するなら、getメソッドを使う。
|UNSIGNED BIGINT equivalent column.
 
|-
つまり、whereなどを使うなら、最後の取得はgetメソッドを使う必要がある。
|<code>$table->unsignedDecimal('amount', 8, 2);</code>
 
|UNSIGNED DECIMAL equivalent column with a precision (total digits) and scale (decimal digits).
Eloquentのモデルはクエリービルダーなので、クエリービルダーの全メソッドが使用可能。
|-
 
|<code>$table->unsignedInteger('votes');</code>
モデル名::メソッドの他、select/where/findなども同じものを返すので、メソッドチェーンで呼び出せる。
|UNSIGNED INTEGER equivalent column.
=====Retrieving Single Models / Aggregates=====
|-
モデルからデータを取得するいくつか主要なメソッドがある。
|<code>$table->unsignedMediumInteger('votes');</code>
*find: プライマリーキーから値を探す。
|UNSIGNED MEDIUMINT equivalent column.
*all: 全項目を取得する。
|-
*where:
|<code>$table->unsignedSmallInteger('votes');</code>
*findOrFail
|UNSIGNED SMALLINT equivalent column.
*findMany
|-
メソッドチェーン
|<code>$table->unsignedTinyInteger('votes');</code>
*first: 先頭項目を取得する。
|UNSIGNED TINYINT equivalent column.
*firstOrFail
|-
*count
|<code>$table->uuid('id');</code>
*max
|UUID equivalent column.
[https://qiita.com/gone0021/items/951cd63a7e591e18cd2a 【laravel】データベースの操作:Eloquent ORM編 #Laravel6 - Qiita]
|-
 
|<code>$table->year('birth_year');</code>
Eloquentのfindは扱いが特殊。引数が配列なら配列で返してくれる。
|YEAR equivalent column.
|}
 
===== Column Modifiers =====
カラム種類に加え、いくつかのカラム修飾がある。
Schema::table('users', function (Blueprint $table) {
    $table->string('email')->nullable();
});
以下が特に。


配列で結果が欲しければ、findManyを使う。findManyで常に配列で返すようにしたほうが、型が揃って扱いやすいかもしれない。
* after
=====Inserting & Updating Models=====
* first
======Insert======
* default($value): default値を指定する。nullableを指定するとdefault=NULL扱い。
モデルインスタンスを作成し、属性をセットし、最後にsaveを呼ぶ。
* nullable($value = true): nullを許容するかしないか。falseにするとnot nullにできるが、デフォルトnot nullなのでfalse指定は基本はしない。
<?php
* length: integerなどで幅が必要なケース ([https://qiita.com/msht0511/items/49ca034426ad7c036e23 【Laravel】マイグレーションを使用する際にintegerのサイズの指定方法に注意 #PHP - Qiita])。
 
デフォルトだとnullable(false)相当なので、nullを許容するならnullable()の指定が必要。
namespace App\Http\Controllers;
===== Modifying Columns =====
 
既存カラムの改名や、データ型変更も行えるが、癖がある。
use App\Flight;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
 
class FlightController extends Controller
{
    /**
      * Create a new flight instance.
      *
      * @param  Request  $request
      * @return Response
      */
    public function store(Request $request)
    {
        // Validate the request...
 
        $flight = new Flight;
 
        $flight->name = $request->name;
 
        $flight->save();
    }
}


====== Updates ======
なお、int(10) のような数値型の幅などは、MySQLの独自拡張で、DB独自拡張はマイグレーションのAPIで未対応。
[https://laravel.com/docs/5.8/eloquent#updates Eloquent: Getting Started - Laravel 5.8 - The PHP Framework For Web Artisans]
$flight = App\Flight::find(1);
 
$flight->name = 'New Flight Name';
 
$flight->save();
saveメソッドはinsertだけでなく、updateにも使える。updated_atも自動で更新される。


クエリーのupdateで配列で複数レコードも一括更新できる。
「[https://stackoverflow.com/questions/35108133/define-property-zerofill-and-size-on-field-schema-migration-with-laravel php - Define property zerofill and size on field schema migration with laravel - Stack Overflow]」


注意点として、updateの一括更新時は、eloquentで本来発動するsaving/saved/updating/updatedのイベントは発動しない。
DB::statementで生SQLで対応するしかない。


======Mass Assignment======
====== Prerequisites ======
[https://qiita.com/yyy752/items/820260163d4883efb132 Add [] to fillable property to allow mass assignment onの解決方法 #初心者 - Qiita]
事前に依存関係を追加する。
  Add [_token] to fillable property to allow mass assignment on [App\].
composer require doctrine/dbal
こんなエラーが出る。
 
====== Updating Column Attributes ======
既存のカラムを別の型に変更する場合、changeメソッドを使う。
Schema::table('users', function (Blueprint $table) {
    $table->string('name', 50)->change();
});
 
Schema::table('users', function (Blueprint $table) {
    $table->string('name', 50)->nullable()->change();
  });
nullableの付与も可能。


プロパティーの代入で1個ずつ設定するほかに、まとめて1行での保存もできる。ただし、一括割り当ては危険なので、保護されており、fillable属性かguarded属性の指定が必要。
後から変更可能な型は以下。


fillメソッドは連想配列でデータをモデルにセットする。
bigInteger, binary, boolean, date, dateTime, dateTimeTz, decimal, integer, json, longText, mediumText, smallInteger, string, text, time, unsignedBigInteger, unsignedInteger and unsignedSmallInteger
$form = $request->all();
unset($form['_token']);
$flight->fill(['name' => 'Flight 22']);
フォームの項目と対応させておけばそのままセットできる。
======fillable======
まず、最初に一括割り当て可能な属性を定義する。


fillableプロパティーで、一括割り当て可能なカラムを定義できる。
====== Renaming Columns ======
<?php
 
namespace App;
 
use Illuminate\Database\Eloquent\Model;
 
class Flight extends Model
{
    /**
      * The attributes that are mass assignable.
      *
      * @var array
      */
    protected $fillable = ['name'];
}
======Guarding Attributes======
$fillableはホワイトリスト方式。$guardedも使用できる。一括割り当て可能にしたくない属性を指定する。ブラックリスト。 $fillableと$guardedのどちらかが最低限必要。


====== Other Creation Methods ======
===== Dropping Columns =====
insertもupdateもやることはほぼ同じなので、コードの冒頭で以下のような式でモデル生成部分だけ変更したらよいだろう。
downで戻す場合に使ったりする。
        $model = empty($request['オーナーコード'])
Schema::table('users', function (Blueprint $table) {
            ? new 楽楽販売_オーナーマスタ : 楽楽販売_オーナーマスタ::find($request['オーナーコード']);
    $table->dropColumn('votes');
と思っていたが、もっといいのがあった。
    $table->dropColumn(['votes', 'avatar', 'location']);
});
なお、非常に紛らわしいのだが、removeColumnメソッドもある ([https://stackoverflow.com/questions/30256463/what-is-the-difference-between-removecolumn-and-dropcolumn-methods-on-laravel-fr database - What is the difference between removeColumn and dropColumn methods on Laravel Framework? - Stack Overflow])。こちらはBlueprintから削除するだけで、実テーブルからは削除しない。使うことはほぼない。


firstOrCreate/firstOrNew
==== Indexes ====


firstOrNewは自分でsaveできるように、モデルインスタンスを返してくれる (new モデル相当)。
===== Creating Indexes =====
// Retrieve flight by name, or create it if it doesn't exist...
[https://stackoverflow.com/questions/20065697/schema-builder-laravel-migrations-unique-on-two-columns schema builder laravel migrations unique on two columns - Stack Overflow]
  $flight = App\Flight::firstOrCreate(['name' => 'Flight 10']);
  $table->string('email')->unique();
 
   
  // Retrieve flight by name, or create it with the name, delayed, and arrival_time attributes...
  $table->unique('email');
  $flight = App\Flight::firstOrCreate(
$table->index(['account_id', 'created_at']);
    ['name' => 'Flight 10'],
  $table->unique('email', 'unique_email');
    ['delayed' => 1, 'arrival_time' => '11:30']
  $table->unique(['account_id', 'email']);
);
$table->dropUnique(['account_id', 'email']);
 
複合UNIQUE制約をつけたい場合、uniqueメソッドの第一引数を配列にする。
// Retrieve by name, or instantiate...
 
  $flight = App\Flight::firstOrNew(['name' => 'Flight 10']);
==== Other ====
 
// Retrieve by name, or instantiate with the name, delayed, and arrival_time attributes...
  $flight = App\Flight::firstOrNew(
    ['name' => 'Flight 10'],
    ['delayed' => 1, 'arrival_time' => '11:30']
);
1番目の配列の情報でモデルを検索して、不在ならば、2番目の配列の情報で作成する。


updateOrCreate
===== Command =====
// If there's a flight from Oakland to San Diego, set the price to $99.
*migrate:rollback: マイグレーションをロールバックする。デフォルトだと最後の1回分。--step=Nで回数を指定できる。
// If no matching model exists, create one.
*migrate:reset: 全部ロールバックする。
$flight = App\Flight::updateOrCreate(
*migrate:refresh: rollback+migrate。
    ['departure' => 'Oakland', 'destination' => 'San Diego'],
*migrate:fresh: ロールバックではなく、テーブルを削除してから戻す。
    ['price' => 99, 'discounted' => 1]
);
1番目の配列の情報で検索して、2番目の配列で更新する。1番目の情報で不在なら、全部の情報で更新する。


ただし、これらのメソッド類は、一度に1レコードずつしか更新できない。大量に行う場合、何回も発動するので遅いかもしれない。そこだけ注意が必要。あまり遅いなら、DBクエリーでやったほうがいいかもしれない。
===== 既存DBの途中からの管理 =====
*[https://gamushiros.hatenablog.com/entry/2019/04/23/000000 Laravelのmigrateを途中から実行する - mk-toolブログ]
*[https://teratail.com/questions/362984 Laravel 既存DBをマイグレーションする方法]
*[https://www.reddit.com/r/laravel/comments/aarkm7/laravel_migration_how_to_autoskip_tables_that/ Laravel Migration: how to auto-skip tables that already exist? : r/laravel]
今後の変更分だけ管理するということもできる。


「[https://qiita.com/a-tsu/items/02f4c00b69556b7b4589 Laravel で updateOrCreate を使う際に Unknown column 'id' in 'where clause' でハマった #PHP - Qiita]」
なお、中途半端にLaravelのマイグレーションファイルがある場合、migrationsテーブルにデータを手動で登録しておくと、マイグレーション済みと認識できる。


なお、updateOrCreateを使う際は、使用するモデルの主キーの設定をきちんとしておくこと。
作ってあげるデータは、id,migration, batchを持つデータで、idは1からインクリメント、migrationはマイグレーションのファイル名(.phpは除く)、batchを1(たぶんこれは何度マイグレーションを実行したかを保っておくもので状態を1つ戻すときに使われるのでは?)に設定すればよい。
 
{| class="wikitable"
====== Upserts ======
!id
 
!migration
* [https://laravel.com/docs/8.x/eloquent#upserts Eloquent: Getting Started - Laravel 8.x - The PHP Framework For Web Artisans]
!batch
* [https://nextat.co.jp/staff/archives/333 Eloquentのupsert() 知ってると便利ですよ。1つのクエリで複数のレコードにアプローチしよう。|Laravel|PHP|開発ブログ|株式会社Nextat(ネクスタット)]
|-
 
|1
Laravel 8からupsertメソッドが登場して、一括更新が可能になった。以前から、updateOrCreateがあった。
|2017_09_01_134421_create_prefectures_table
====Relationships====
|1
[https://laravel.com/docs/5.8/eloquent-relationships Eloquent: Relationships - Laravel 5.8 - The PHP Framework For Web Artisans]
|-
=====Defining Relationships=====
|2
======One To One======
|2017_09_01_134422_create_cities_table
<?php
|1
 
|}SQLだと以下相当。
namespace App;
insert into migrations(migration, batch) values('2015_12_08_134409_create_tables_script',1);
 
insert into migrations(migration, batch) values('2015_12_08_134410_create_foreign',1);
use Illuminate\Database\Eloquent\Model;
insert into migrations(migration, batch) values('2015_12_08_134411_create_index',1);
 
 
  class User extends Model
===== SQLでのマイグレーション =====
[https://stackoverflow.com/questions/46507655/laravel-migration-is-it-possible-to-use-sql-instead-of-schema-commands-to-crea database - Laravel migration - is it possible to use SQL instead of schema commands to create tables and fields etc? - Stack Overflow]
 
upメソッドにDB::updateなどで生のSQLをそのまま書けばOK。
  class AddColumnsToUsersTable extends Migration
  {
  {
     /**
     /**
       * Get the phone record associated with the user.
       * Run the migrations.
      *
      * @return void
       */
       */
     public function phone()
     public function up()
     {
     {
         return $this->hasOne('App\Phone');
         Schema::table('users', function (Blueprint $table) {
            $table->string('name');
            $table->string('age');
            $table->timestamps();
        });
        DB::update('update users set age = 30 where name = ?', ['John']);
     }
     }
  }
  }
主テーブルに外部キーが埋め込まれていて、シンプルな場合、上記のように外部テーブルをメソッド名にしてhasOneを実行すると全部返せる。非常にシンプル。
downメソッドにはDropIfExistsで削除すればOK。
 
===== Cannot declare class TableName, because the name is already in use =====


プロパティーとしてアクセス可能になる。1対1だから、以下のようにプライマリーキーなどで参照できる。
* [https://zoo200.net/laravel-migration-error-cannot-declare-class-tablename/ Laravelのマイグレーションで Cannot declare class TableNameのエラー | zoo200's MemoMemo]
$phone = User::find(1)->phone;
* [https://teratail.com/questions/112058 Laravelでmigrateができない]
デフォルトだと、外部キーは、 [モデル名_id] であることを想定している。これじゃないなら、以下のように引数で指定しておく。
* [https://qiita.com/takepan/items/86c59f15600c1f32a942 Laravelで「Cannot declare class UpdateUsersTable」ほか #laravel5.5 - Qiita]
return $this->hasOne('App\Phone', 'foreign_key');
そして、外部キーは、元のモデルのidか$primaryKeyと一致する想定になっている。これ以外のキーを使いたければ、hasOneの第3引数で、ローカルキーを指定する。
return $this->hasOne('App\Phone', 'foreign_key', 'local_key');
======One To Many======
複数の他のモデルを持つ場合。例えば、ブログ投稿は、無限のコメントをもつ。こういう場合のコメント。がOne To Many。これはhasManyで関連付ける。
    public function comments()
    {
        return $this->hasMany('App\Comment');
    }


====== Many To Many ======
マイグレーションファイル作成時に生成されるクラスが、既存のマイグレーションファイルのクラス名と被っているのが原因の一つ。
多対多の関係は、belongsToManyで定義される。


userとroleテーブルがあったとして、テーブル名はアルファベット順でrole_userのようにテーブル名を_で結合する。
マイグレーションのファイル名/クラス名は固有にしないといけない。
<?php
 
namespace App;
 
use Illuminate\Database\Eloquent\Model;
 
class User extends Model
{
    /**
      * The roles that belong to the user.
      */
    public function roles()
    {
        return $this->belongsToMany('App\Role');
    }
}
関係定義でroles動的プロパティーを使用してアクセスできる。
$user = App\User::find(1);
 
foreach ($user->roles as $role) {
    //
}
命名規則を定義時にオーバーライドもできる。第2引数でテーブル名を指定する。
return $this->belongsToMany('App\Role', 'role_user');
またキーの列名もカスタマイズ可能。
return $this->belongsToMany('App\Role', 'role_user', 'user_id', 'role_id');


====== Retrieving Intermediate Table Columns ======
Laravel 8からは匿名マイグレーションで名前を意識しなくても済むらしい ([https://stackoverflow.com/questions/54765710/error-migrations-cannot-declare-class-x-because-the-name-is-already-in-use php - Error migrations: Cannot declare class X, because the name is already in use - Stack Overflow])。
このままで通常は問題ない。リレーションで対応する外部テーブルのモデル・レコードを取得できている。
return new class extends Migration {
  //
};


万が一、直接中間テーブルの参照が必要な場合、pivot属性を間に挟んでアクセスする。
===== Doctrine\DBAL\Exception  : Unknown column type "mediuminteger" requested. =====
$user = App\User::find(1);
 
 
* [https://stackoverflow.com/questions/77359070/laravel-migration-to-change-an-unsignedmediuminteger-column eloquent - Laravel migration to change an unsignedMediumInteger column - Stack Overflow]
foreach ($user->roles as $role) {
* [https://laracasts.com/discuss/channels/eloquent/unsupported-laravel-datatypes-in-doctrinedbal Unsupported laravel datatypes in doctrine/dbal]
    echo $role->pivot->created_at;
 
  }
  Doctrine\DBAL\Exception  : Unknown column type "mediuminteger" requested. Any Doctrine type that you use has to be registered with \Doctrine\DBAL\Types\Type::addType(). You can get a list of all the known types with \Doctrine\DBAL\Types\Type::getTypesMap(). If this error occurs during database introspection then you might have forgotten to register all database types for a Doctrine Type. Use AbstractPlatform#registerDoctrineTypeMapping() or have your custom types implement Type#getMappedDatabaseTypes(). If the type name is empty you might have a problem with the cache or forgot some mapping information.
デフォルトではモデルキーのみが存在する。
Laravel 5.8のmigrationでmediumintergerが使えない。Doctrineが対応していないから。しかたないからintegerにする。


=====Querying Relations=====
[https://github.com/laravel/framework/issues/36509 "Unknown column type "mediumInteger" requested" error after downgrading doctrine/dbal to 2.* · Issue #36509 · laravel/framework · GitHub]」によると、Laravel 8.0、Doctrine DBAL 3.0以上で解決しているとのこと。
======Querying Relationship Existence======
*[https://laravel.com/docs/5.8/eloquent-relationships#querying-relationship-existence Eloquent: Relationships - Laravel 5.8 - The PHP Framework For Web Artisans]
*[https://laracoding.com/how-to-add-a-where-clause-to-a-relationship-query-in-laravel/ How to Add a Where Clause to a Relationship Query in Laravel]
*[https://stackoverflow.com/questions/29989908/laravel-where-on-relationship-object php - Laravel where on relationship object - Stack Overflow]
リレーション先のテーブルで絞り込みたい場合、has/wherehasを使う。


ただ、このwherehasはコールバックなどを使わないといけなくて、少々面倒くさい。
=== Seeding ===
[https://laravel.com/docs/5.8/seeding Database: Seeding - Laravel 5.8 - The PHP Framework For Web Artisans]


joinしてフラットにして、カラム名にプレフィクスをつけたほうがわかりやすい。フラットにしなかったら、入れ子になって扱いが面倒になる。が、たいしてプレフィクスをつけるのと手間は変わらないから。
マイグレーションで用意したDBのデフォルトデータの用意の話。
=====Eager Loading=====
Eloquentのリレーションにプロパティーでのアクセス時、リレーションデータは "lazy loaded" になる。これはプロパティーへのアクセスまで、実際には読み込まないことを意味する。つまり、アクセスのたびに読み込みが発生する。Eager Loadが可能になる。


Eagerは、熱心な、せっかちなという形容詞。
デフォルトデータをシードと呼んでおり、シードを用意する機能をシーディングと呼んでいる。


例えば、N件のデータを全部表示させたい場合、以下のようなコードを記述する。
シードを作成するためのスクリプト (シーダー)  ファイルを生成して、記述して実行する流れ。
  <?php
 
 
==== Writing Seeders ====
namespace App;
以下のコマンドでシーダーを作成する。
 
  php artisan make:seeder UsersTableSeeder
use Illuminate\Database\Eloquent\Model;
database/seedsにファイルが作成される。
 
 
  class Book extends Model
中身はrunメソッドがあるだけ。run内でinsertするだけ。
 
==== Calling Additional Seeders ====
追加したシーダーはそのままでは認識されない。
 
DatabaseSeederに登録する。DatabaseSeeder.phpのrunのcallメソッド内に追加する。
  public function run()
  {
  {
     /**
     $this->call([
      * Get the author that wrote the book.
        UsersTableSeeder::class,
      */
        PostsTableSeeder::class,
    public function author()
        CommentsTableSeeder::class,
     {
     ]);
        return $this->belongsTo('App\Author');
    }
  }
  }


  $books = App\Book::all();
==== Running Seeders ====
 
追加したシーダーを認識させるために以下のコマンドでautoloadを更新する。
  foreach ($books as $book) {
  composer dump-autoload
    echo $book->author->name;
シーダーを用意したらコマンドを実行して作成する。
}
  php artisan db:seed
最初に全取得+1、N件のデータを取得+Nで、合計1+N回のクエリーが発行される。
--classでシーダー名を指定もできる。あとからシーダーを追加する場合は--classで指定するイメージ。
 
=== Other ===


Eager loadによりこれを2回のクエリー発行で収めることができる。withメソッドを使う。
==== Error handling ====
$books = App\Book::with('author')->get();
 
foreach ($books as $book) {
    echo $book->author->name;
}
これは具体的には以下のSQLの実行になる。
select * from books
 
select * from authors where id in (1, 2, 3, 4, 5, ...)
ただ、Eager loadすると、クエリーの数は減ってもモデルのアクセスが増えて、メモリーの使用量と実行速度が遅くなって、逆に悪化することがある ([https://www.reddit.com/r/laravel/comments/1adbjcc/laravel_eager_loading_can_be_bad/ Laravel - Eager loading can be bad! : r/laravel]、[https://blog.oussama-mater.tech/laravel-eager-loading-is-bad/ Laravel - Eager loading can be bad! | Personal Blog]、[https://dev.to/nicolus/how-the-new-one-of-many-laravel-relationship-made-my-project-600-times-faster-27ll How the new 'One Of Many' Laravel relationship made my project 600 times faster - DEV Community])。


特に、hasManyの関係で、1対1じゃなくて、複数のモデルが入っている場合。
* [https://laravel.com/docs/5.8/errors Error Handling - Laravel 5.8 - The PHP Framework For Web Artisans]
* [https://medium.com/@dayoolapeju/exception-error-handling-in-laravel-25843a8aabb3 Exception/Error Handling in Laravel | by Jeremiah Ekundayo | Medium]
* [https://qiita.com/kenji123/items/6b72d412e1c60dd1f27e 例外処理の対応(Laravel) #Try-Catch - Qiita]


Laravel 8.42からLatestOfManyなどのメソッドが追加されており、これを使えば回避できる。これを使わない場合、JOINとMAXなどを駆使する必要があった。
DB関係の処理で、失敗した場合の扱いが、公式文書に記載がなくて混乱する。


ひとまず、hasManyとか、性能を見て、問題あったらeager loadingしたり工夫することにする。
基本はtry-catchでやればいい。
*[https://zenn.dev/yuzuyuzu0830/articles/cf4aedd9e9c08b58c8f2 withを使用した様々なデータの取得方法]
use Exception;
*[https://qiita.com/HuntingRathalos/items/5a7cae35a49a2795e8f2 [Laravel]withメソッドを理解する #初心者 - Qiita]
複数のテーブルと結合したい場合、withに複数指定する。必要な列が決まっているなら、withのテーブル名の後に:で列名指定でOK。
public function result(Request $request)
======Constraining Eager Loads======
{
=====Other=====
    try {
======リレーションの列名エイリアス======
        $user = User::findOrFail($request->user_id);
*[https://teratail.com/questions/170126 laravel Model の with() のリレーション先カラム名を変更して取得したい]
*[https://stackoverflow.com/questions/17174837/laravel-4-eloquent-column-alias php - Laravel 4 Eloquent Column Alias - Stack Overflow]
        return view('result', compact('user'));
Eloquentでリレーションで結合時、カラム名が同じだと困る。
    } catch(Exception $exception) {
        $message = $exception->getMessage();
        if($exception instanceof ModelNotFoundException) {
            $message = 'User with ID: '.$request->user_id.' not found!';
        }
        return back()->withError($message)->withInput();
    }
}
 
==== Using Docker I get the error: "SQLSTATE[HY000] [2002] No such file or directory" ====
[https://stackoverflow.com/questions/40075065/using-docker-i-get-the-error-sqlstatehy000-2002-no-such-file-or-directory php - Using Docker I get the error: "SQLSTATE[HY000] [2002] No such file or directory" - Stack Overflow]


別名をつける方法がいくつかある。
[https://qiita.com/b-coffin/items/8103583efe3767b6748e PHPのPDOをDockerコンテナ内で使おうとしたところ、"No such file or directory" エラーが発生した話 #docker-compose - Qiita]
*.get/.select: ->get(['tags.name AS tag_name', 'products.*'])/ ->select('tags.name AS tag_name', 'products.*')
*アクセサー
get/selectでやるのがよさそう。
====Other====
=====列の存在確認=====
*[https://laracasts.com/discuss/channels/eloquent/test-attributescolumns-existence Test attributes/columns existence]
*[https://stackoverflow.com/questions/51703381/check-if-column-exist-in-laravel-models-table-and-then-apply-condition php - Check if column exist in Laravel model's table and then apply condition - Stack Overflow]
いくつか方法がある。
*use Illuminate\Support\Facades\Schema; Schema::hasColumn('テーブル名', '列名'): これが一番シンプル。
*$model['列名']: インスタンス生成後ならこれ。
=====Eloquent vs. Query Builder=====
*[https://stackoverflow.com/questions/38391710/laravel-eloquent-vs-db-facade-when-to-use-which performance - Laravel Eloquent vs DB facade: When to use which? - Stack Overflow]
*[https://usescribe.ai/youtube-summaries/eloquent-vs-query-builder-in-laravel-performance-and-usability-compared-1707094546 Scribe - Eloquent vs. Query Builder in Laravel: Performance and Usability Compared]
*[https://www.zuzana-k.com/blog/laravel-eloquent-vs-query-builder/ Zuzana K | Laravel Eloquent vs Laravel query builder]
*[https://medium.com/@andreelm/eloquent-orm-vs-query-builder-in-laravel-47f104452644 Eloquent ORM VS Query Builder in Laravel | by Andre Elmustanizar | Medium]
*[https://www.reddit.com/r/laravel/comments/us98hc/hi_im_wondring_when_to_use_eloquent_and_when_to/ Hi , I'm wondring when to use eloquent and when to use query builder. Or Can we use them interchangeably? I love eloquent because it's easy to use but query buider is much faster. What do you think about the subject? And which one do you recommende? : r/laravel]
*[https://qiita.com/ryocha12/items/8bf538b89739b903e437 Laravelのwithとjoinの違いについて #PHP - Qiita]
ORMとクエリービルダー。どこでどう使い分けるか?最初の2個の記事が参考になる。
*ユーザーフォームのような基本的で簡単なCRUD処理はEloquent
*結合を含んだり、大量データの処理が必要な複雑な場合にクエリービルダー。
こういう方針で使い分けるといい。結合とかになると、モデルの範囲外になる。


レコードセットなのか、モデルなのかを意識する。
dockerで.envのDB_HOSTの指定間違い。dockerのservice名をホスト名に指定する必要がある。
=====列名の取得=====
*[https://stackoverflow.com/questions/45818177/laravelhow-to-get-columns-name-of-eloquent-model php - laravel:how to get columns name of eloquent model? - Stack Overflow]
*[https://laracasts.com/discuss/channels/eloquent/get-column-names-from-table Get column names from table]
use Illuminate\Support\Facades\Schema;
$columns = Schema::getColumnListing('users'); // users table
dd($columns); // dump the result and die


        $headings = DB::connection()
==== local.ERROR: could not find driver ====
            ->getSchemaBuilder()
[2024-07-02 15:35:42] local.ERROR: could not find driver (SQL: select * from `sessions` where `id` = 0cDH7URcrsFzC7hPYlbAQcezNVjdDM16OJh1aCSZ limit 1) {"exception":"[object] (Illuminate\\Database\\QueryException(code: 0): could not find driver (SQL: select * from `sessions` where `id` = 0cDH7URcrsFzC7hPYlbAQcezNVjdDM16OJh1aCSZ limit 1) at /var/www/vendor/laravel/framework/src/Illuminate/Database/Connection.php:664, Doctrine\\DBAL\\Driver\\PDO\\Exception(code: 0): could not find driver at /var/www/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDO/Exception.php:18, PDOException(code: 0): could not find driver at /var/www/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php:40)
            ->getColumnListing('colaborators');
このエラーはphpのモジュール不足。LaravelはPDOを使う。mysqliではなく。
===== ::query=====
RUN docker-php-ext-install pdo_mysql
*[https://stackoverflow.com/questions/51517203/what-is-the-meaning-of-eloquents-modelquery laravel - What is the meaning of Eloquent's Model::query()? - Stack Overflow]
これが必要。
*[https://zenn.dev/nshiro/articles/98d3826151af81 Laravel ::where() ではなく、::query() で書き始めるプチメリット]
 
*[https://qiita.com/fujita-goq/items/2279bb947ec4e7b103b2 【Laravel】Modelの::query()メソッドで快適なEloquent操作を #PHP - Qiita]
==== Log ====
EloquentのORMのモデルを使用する際は、<Model>::findなどのメソッドを使う。
[https://qiita.com/ucan-lab/items/753cb9d3e4ceeb245341 Laravel SQLの実行クエリログを出力する #PHP - Qiita]


が、インスタンスを生成してから、条件に応じて処理を実行したい場合など、都合が悪いことがある。
方法が2種類ある。


インスタンス生成用に<Model>::query()がある。<Model>::query()->findなどのように、メソッドの前に挟める。
# toSqlメソッド。getBindingsでプレースホルダーの値。シンプルな場合に有効。
# DB::enableQueryLog/DB::getQueryLog()。複雑な場合と実行時間も。


これを使う形で書いておくと、コードの書き方がきれいになったり、コード補完が効きやすかったりする。
== Eloquent ==
=====SQL関数=====
[https://debug.to/842/how-to-passing-mysql-functions-to-eloquent-orm-query how to Passing MySQL functions to Eloquent ORM query? - deBUG.to]


Eloquentのwhere関数内でSQL関数は使えない。工夫が必要。
====Getting Started====
[https://laravel.com/docs/5.8/eloquent Eloquent: Getting Started - Laravel 5.8 - The PHP Framework For Web Artisans]


[https://laravel.com/docs/5.8/queries#raw-expressions Database: Query Builder - Laravel 5.8 - The PHP Framework For Web Artisans]
Eloquent ORM。
=====Defining Models=====
make:modelコマンドでモデルインスタンスを作成できる。
php artisan make:model Flight
-mで新規作成のマイグレーションファイルも一緒に作れる。
======Eloquent Model Conventions======
======Table Names======
[https://qiita.com/igz0/items/d14fdff610dccadb169e Laravelで「Base table or view not found: 1146 Table」エラーが出るときの対処法 #PHP - Qiita]


DB::rawで書く。
Eloquentのモデルでは、デフォルトでクラス名を複数形のスネークケースにしたものをテーブル名とみなす。
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'your_db.your_models' doesn't exist (SQL: select * from `your_models`)
不在の場合、上記のエラーが出る。


あるいは、selectRaw/whereRawなどを使う。
デフォルト以外のテーブル名を使いたければ、$tableにテーブル名を指定する。
=====結果のオブジェクト・配列変換=====
<?php
*[https://laracasts.com/discuss/channels/eloquent/eloquent-results-as-array-instead-of-object?page=1&replyId=668509 Eloquent results as array instead of object]
 
*[https://stackoverflow.com/questions/41447275/laravel-toarray-still-returning-an-object-in-dbraw php - Laravel toArray() still returning an object in DB::raw - Stack Overflow]
namespace App;
EloquentのDB取得の結果は、配列になっている。が、DBクエリーの場合、オブジェクトになっている。
 
use Illuminate\Database\Eloquent\Model;
型が違っていろいろ困る。DBクエリーのオブジェクトを配列にしたい。
 
 
class Flight extends Model
Eloquentを使わずに、配列にしたい場合、mapやtransformで変換必要。
{
  $response['data'] = DB::table('customers')
    /**
     // query conditions, etc
      * The table associated with the model.
    ->get()
      *
    ->map(function ($item, $key) {
      * @var string
        return (array) $item;
      */
    })
    protected $table = 'my_flights';
    ->all();
}
最後のallも必要。これでEloquentと共通化できる。
プロジェクトでテーブル名に日本語を使う場合など、クラスメイとテーブル名が同一なら、以下のような親クラスを使って、これを継承すると同じ処理を記載しなくていい。
 
<?php
===== 一括更新 =====
[https://stackoverflow.com/questions/15622710/how-to-set-every-row-to-the-same-value-with-laravels-eloquent-fluent How to set every row to the same value with Laravel's Eloquent/Fluent? - Stack Overflow]
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
  class BaseModel extends Model
{
    function __construct()
     {
        // デフォルトだと複数形のスネークケースのテーブルを探すため、クラス名=テーブル名で上書き。
        $this->table = (new \ReflectionClass($this))->getShortName();
    }
}
======Timestamps======
[https://qiita.com/ikadatic/items/2237a3c1b837894dfc30 LaravelのEloquentモデルでupdated_atがないテーブルを使う方法 #PHP - Qiita]


マイグレーションなどで、列を切り詰めたり一括更新したいことがある。
[https://stackoverflow.com/questions/28277955/laravel-unknown-column-updated-at php - Laravel Unknown Column 'updated_at' - Stack Overflow]
Model::query()->update(['confirmed' => 1]);


これがシンプル。
デフォルトで、Eloquentはcreated_atとupdated_atカラムを期待する。使っていないならば、以下のエラーが出る。
 
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'updated_at' in 'field list' at /var/www/vendor/laravel/framework/src/Illuminate/Database/Connection.php:664, Doctrine\\DBAL\\Driver\\PDO\\Exception(code: 42S22): SQLSTATE[42S22]: Column not found: 1054 Unknown column 'updated_at' in 'field list' at /var/www/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDO/Exception.php:18, PDOException(code: 42S22): SQLSTATE[42S22]: Column not found: 1054 Unknown column 'updated_at' in 'field list' at /var/www/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php:82)
[https://qiita.com/kenji__n/items/7cbb6f04594b30ed3b9b 【Laravel】バルクアップデートを行う方法 #MySQL - Qiita]」に記載のall()->update()はallが配列を返すのでダメ。
[stacktrace]
 
$timestamps=falseにしておく。
== Testing ==
<?php
 
namespace App;
 
use Illuminate\Database\Eloquent\Model;
 
class Flight extends Model
{
    /**
      * Indicates if the model should be timestamped.
      *
      * @var bool
      */
    public $timestamps = false;
}


=== Getting Started ===
==== Retrieving Models ====
[https://laravel.com/docs/5.8/testing Testing: Getting Started - Laravel 5.8 - The PHP Framework For Web Artisans]
Eloquentのモデルは、強力なクエリビルダーと考えてもいい。Eqloquentのallメソッドはモデルテーブルの全結果を返す。これはクエリービルダーとして、各モデルで提供されているので、条件を追加するなら、getメソッドを使う。


==== Introduction ====
つまり、whereなどを使うなら、最後の取得はgetメソッドを使う必要がある。
Laravelはテストを念頭に置いており、デフォルトでPHPHUnitに対応していて、phpunit.xmlもある。


デフォルトで、testsディレクトリーは、Feature Unitの2ディレクトリーを持つ。Unitは関数単位の単体テスト。FeatureはHTTPリクエストとJSONエンドポイントなど、複数のオブジェクトの対話を含む大き目のテスト。
Eloquentのモデルはクエリービルダーなので、クエリービルダーの全メソッドが使用可能。


両方のディレクトリーに、ExampleTest.phpがある。
モデル名::メソッドの他、select/where/findなども同じものを返すので、メソッドチェーンで呼び出せる。
 
=====Retrieving Single Models / Aggregates=====
==== Environment ====
モデルからデータを取得するいくつか主要なメソッドがある。
pupunitのテスト時、phpunit.xmlの<server name="APP_ENV" value="testing"/>の設定により、Laravelは設定環境をtestingにする。また、自動的にセッションとキャッシュをarrayドライバーに自動的に構成する。つまり、テスト中はセッションやキャッシュを保持しない。
*find: プライマリーキーから値を探す。
*all: 全項目を取得する。
*where:
*findOrFail
*findMany
メソッドチェーン
*first: 先頭項目を取得する。
*firstOrFail
*count
*max
[https://qiita.com/gone0021/items/951cd63a7e591e18cd2a 【laravel】データベースの操作:Eloquent ORM編 #Laravel6 - Qiita]
 
Eloquentのfindは扱いが特殊。引数が配列なら配列で返してくれる。
 
配列で結果が欲しければ、findManyを使う。findManyで常に配列で返すようにしたほうが、型が揃って扱いやすいかもしれない。
=====Inserting & Updating Models=====
======Insert======
モデルインスタンスを作成し、属性をセットし、最後にsaveを呼ぶ。
<?php
 
namespace App\Http\Controllers;
 
use App\Flight;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
 
class FlightController extends Controller
{
    /**
      * Create a new flight instance.
      *
      * @param  Request  $request
      * @return Response
      */
    public function store(Request $request)
    {
        // Validate the request...
 
        $flight = new Flight;
 
        $flight->name = $request->name;
 
        $flight->save();
    }
}
 
====== Updates ======
[https://laravel.com/docs/5.8/eloquent#updates Eloquent: Getting Started - Laravel 5.8 - The PHP Framework For Web Artisans]
$flight = App\Flight::find(1);
 
$flight->name = 'New Flight Name';
 
$flight->save();
saveメソッドはinsertだけでなく、updateにも使える。updated_atも自動で更新される。
 
クエリーのupdateで配列で複数レコードも一括更新できる。
 
注意点として、updateの一括更新時は、eloquentで本来発動するsaving/saved/updating/updatedのイベントは発動しない。
 
======Mass Assignment======
[https://qiita.com/yyy752/items/820260163d4883efb132 Add [] to fillable property to allow mass assignment onの解決方法 #初心者 - Qiita]
Add [_token] to fillable property to allow mass assignment on [App\].
こんなエラーが出る。
 
プロパティーの代入で1個ずつ設定するほかに、まとめて1行での保存もできる。ただし、一括割り当ては危険なので、保護されており、fillable属性かguarded属性の指定が必要。
 
fillメソッドは連想配列でデータをモデルにセットする。
$form = $request->all();
unset($form['_token']);
$flight->fill(['name' => 'Flight 22']);
フォームの項目と対応させておけばそのままセットできる。
======fillable======
まず、最初に一括割り当て可能な属性を定義する。
 
fillableプロパティーで、一括割り当て可能なカラムを定義できる。
<?php
 
namespace App;
 
use Illuminate\Database\Eloquent\Model;
 
class Flight extends Model
{
    /**
      * The attributes that are mass assignable.
      *
      * @var array
      */
    protected $fillable = ['name'];
}
======Guarding Attributes======
$fillableはホワイトリスト方式。$guardedも使用できる。一括割り当て可能にしたくない属性を指定する。ブラックリスト。 $fillableと$guardedのどちらかが最低限必要。
 
$fillableは何か増えるたびに修正必要なので手間。$guardedを基本にするとよいと思う。もっというなら、$guardedでpkを指定するといい。pkはAUTO_INCREMENTで自動増分にすればいいから。
 
====== Other Creation Methods ======
insertもupdateもやることはほぼ同じなので、コードの冒頭で以下のような式でモデル生成部分だけ変更したらよいだろう。
        $model = empty($request['オーナーコード'])
            ? new 楽楽販売_オーナーマスタ : 楽楽販売_オーナーマスタ::find($request['オーナーコード']);
と思っていたが、もっといいのがあった。
 
firstOrCreate/firstOrNew
 
firstOrNewは自分でsaveできるように、モデルインスタンスを返してくれる (new モデル相当)。
// Retrieve flight by name, or create it if it doesn't exist...
$flight = App\Flight::firstOrCreate(['name' => 'Flight 10']);
 
// Retrieve flight by name, or create it with the name, delayed, and arrival_time attributes...
$flight = App\Flight::firstOrCreate(
    ['name' => 'Flight 10'],
    ['delayed' => 1, 'arrival_time' => '11:30']
);
 
// Retrieve by name, or instantiate...
$flight = App\Flight::firstOrNew(['name' => 'Flight 10']);
 
// Retrieve by name, or instantiate with the name, delayed, and arrival_time attributes...
$flight = App\Flight::firstOrNew(
    ['name' => 'Flight 10'],
    ['delayed' => 1, 'arrival_time' => '11:30']
);
1番目の配列の情報でモデルを検索して、不在ならば、2番目の配列の情報で作成する。
 
updateOrCreate
// If there's a flight from Oakland to San Diego, set the price to $99.
// If no matching model exists, create one.
$flight = App\Flight::updateOrCreate(
    ['departure' => 'Oakland', 'destination' => 'San Diego'],
    ['price' => 99, 'discounted' => 1]
);
1番目の配列の情報で検索して、2番目の配列で更新する。1番目の情報で不在なら、全部の情報で更新する。
 
Mass assignmentの対象。基本はAIのpkをguardedにしておけば問題ないと思う。
 
ただし、これらのメソッド類は、一度に1レコードずつしか更新できない。大量に行う場合、何回も発動するので遅いかもしれない。そこだけ注意が必要。あまり遅いなら、DBクエリーでやったほうがいいかもしれない。
 
「[https://qiita.com/a-tsu/items/02f4c00b69556b7b4589 Laravel で updateOrCreate を使う際に Unknown column 'id' in 'where clause' でハマった #PHP - Qiita]」
 
なお、updateOrCreateを使う際は、使用するモデルの主キーの設定をきちんとしておくこと。
 
====== Upserts ======
 
* [https://laravel.com/docs/8.x/eloquent#upserts Eloquent: Getting Started - Laravel 8.x - The PHP Framework For Web Artisans]
* [https://nextat.co.jp/staff/archives/333 Eloquentのupsert() 知ってると便利ですよ。1つのクエリで複数のレコードにアプローチしよう。|Laravel|PHP|開発ブログ|株式会社Nextat(ネクスタット)]
 
Laravel 8からupsertメソッドが登場して、一括更新が可能になった。以前から、updateOrCreateがあった。
====Relationships====
[https://laravel.com/docs/5.8/eloquent-relationships Eloquent: Relationships - Laravel 5.8 - The PHP Framework For Web Artisans]
=====Defining Relationships=====
======One To One======
<?php
 
namespace App;
 
use Illuminate\Database\Eloquent\Model;
 
class User extends Model
{
    /**
      * Get the phone record associated with the user.
      */
    public function phone()
    {
        return $this->hasOne('App\Phone');
    }
}
主テーブルに外部キーが埋め込まれていて、シンプルな場合、上記のように外部テーブルをメソッド名にしてhasOneを実行すると全部返せる。非常にシンプル。
 
プロパティーとしてアクセス可能になる。1対1だから、以下のようにプライマリーキーなどで参照できる。
$phone = User::find(1)->phone;
デフォルトだと、外部キーは、 [モデル名_id] であることを想定している。これじゃないなら、以下のように引数で指定しておく。
return $this->hasOne('App\Phone', 'foreign_key');
そして、外部キーは、元のモデルのidか$primaryKeyと一致する想定になっている。これ以外のキーを使いたければ、hasOneの第3引数で、ローカルキーを指定する。
return $this->hasOne('App\Phone', 'foreign_key', 'local_key');
======One To Many======
複数の他のモデルを持つ場合。例えば、ブログ投稿は、無限のコメントをもつ。こういう場合のコメント。がOne To Many。これはhasManyで関連付ける。
    public function comments()
    {
        return $this->hasMany('App\Comment');
    }
 
====== Many To Many ======
多対多の関係は、belongsToManyで定義される。
 
userとroleテーブルがあったとして、テーブル名はアルファベット順でrole_userのようにテーブル名を_で結合する。
<?php
 
namespace App;
 
use Illuminate\Database\Eloquent\Model;
 
class User extends Model
{
    /**
      * The roles that belong to the user.
      */
    public function roles()
    {
        return $this->belongsToMany('App\Role');
    }
}
関係定義でroles動的プロパティーを使用してアクセスできる。
$user = App\User::find(1);
 
foreach ($user->roles as $role) {
    //
}
命名規則を定義時にオーバーライドもできる。第2引数でテーブル名を指定する。
return $this->belongsToMany('App\Role', 'role_user');
またキーの列名もカスタマイズ可能。
return $this->belongsToMany('App\Role', 'role_user', 'user_id', 'role_id');
 
====== Retrieving Intermediate Table Columns ======
このままで通常は問題ない。リレーションで対応する外部テーブルのモデル・レコードを取得できている。
 
万が一、直接中間テーブルの参照が必要な場合、pivot属性を間に挟んでアクセスする。
$user = App\User::find(1);
 
foreach ($user->roles as $role) {
    echo $role->pivot->created_at;
}
デフォルトではモデルキーのみが存在する。
 
=====Querying Relations=====
======Querying Relationship Existence======
*[https://laravel.com/docs/5.8/eloquent-relationships#querying-relationship-existence Eloquent: Relationships - Laravel 5.8 - The PHP Framework For Web Artisans]
*[https://laracoding.com/how-to-add-a-where-clause-to-a-relationship-query-in-laravel/ How to Add a Where Clause to a Relationship Query in Laravel]
*[https://stackoverflow.com/questions/29989908/laravel-where-on-relationship-object php - Laravel where on relationship object - Stack Overflow]
リレーション先のテーブルで絞り込みたい場合、has/wherehasを使う。
// Retrieve all posts that have at least one comment...
$posts = App\Post::has('comments')->get();
// Retrieve all posts that have three or more comments...
$posts = App\Post::has('comments', '>=', 3)->get();
// Retrieve posts that have at least one comment with votes...
$posts = App\Post::has('comments.votes')->get();
hasは非常に限定的。テーブルがあるか、あとは件数しかない。基本はwhereHasを使う。
use Illuminate\Database\Eloquent\Builder;
 
// Retrieve posts with at least one comment containing words like foo%...
$posts = App\Post::whereHas('comments', function (Builder $query) {
    $query->where('content', 'like', 'foo%');
})->get();
 
// Retrieve posts with at least ten comments containing words like foo%...
$posts = App\Post::whereHas('comments', function (Builder $query) {
    $query->where('content', 'like', 'foo%');
}, '>=', 10)->get();
ただ、このwhereHasはコールバックなどを使わないといけなくて、少々面倒くさい。
 
whereHasの第一引数にはドット記法が使えるので、ネストしたリレーションも参照できる。これを使わないなら、モデルのメソッドからアクセスすることになるだろうか?
$users = User::whereHas('posts', function ($query) {
    $query->whereHas('comments', function ($query) {
        $query->where('content', 'like', '%Laravel%');
    });
})->get();
whereHasのネストでの対応になる模様。
 
joinしてフラットにして、カラム名にプレフィクスをつけたほうがわかりやすい。フラットにしなかったら、入れ子になって扱いが面倒になる。が、たいしてプレフィクスをつけるのと手間は変わらないから、まあEloquentでいいと思う。
 
なお、このgetはデフォルトだと親テーブルしか返さないので必要ならwith()をgetの前にメソッドチェーン。
 
新しめのLaravelだとwithWhereHasというwithとwhereHasを同時にやってくれるメソッドがある。
 
https://chatgpt.com/c/673d8956-103c-800b-8a65-caa56a38c8d0
 
whereHas/withを併用する場合、順番が大事。最初にwhereHasしてからwith。最後にpaginate。この順番。
 
withは空配列も指定可能。
=====Eager Loading=====
Eloquentのリレーションにプロパティーでのアクセス時、リレーションデータは "lazy loaded" になる。これはプロパティーへのアクセスまで、実際には読み込まないことを意味する。つまり、アクセスのたびに読み込みが発生する。Eager Loadが可能になる。
 
Eagerは、熱心な、せっかちなという形容詞。
 
例えば、N件のデータを全部表示させたい場合、以下のようなコードを記述する。
<?php
 
namespace App;
 
use Illuminate\Database\Eloquent\Model;
 
class Book extends Model
{
    /**
      * Get the author that wrote the book.
      */
    public function author()
    {
        return $this->belongsTo('App\Author');
    }
}
 
$books = App\Book::all();
 
foreach ($books as $book) {
    echo $book->author->name;
}
最初に全取得+1、N件のデータを取得+Nで、合計1+N回のクエリーが発行される。
 
Eager loadによりこれを2回のクエリー発行で収めることができる。withメソッドを使う。
$books = App\Book::with('author')->get();
 
foreach ($books as $book) {
    echo $book->author->name;
}
これは具体的には以下のSQLの実行になる。
select * from books
 
select * from authors where id in (1, 2, 3, 4, 5, ...)
ただ、Eager loadすると、クエリーの数は減ってもモデルのアクセスが増えて、メモリーの使用量と実行速度が遅くなって、逆に悪化することがある ([https://www.reddit.com/r/laravel/comments/1adbjcc/laravel_eager_loading_can_be_bad/ Laravel - Eager loading can be bad! : r/laravel]、[https://blog.oussama-mater.tech/laravel-eager-loading-is-bad/ Laravel - Eager loading can be bad! | Personal Blog]、[https://dev.to/nicolus/how-the-new-one-of-many-laravel-relationship-made-my-project-600-times-faster-27ll How the new 'One Of Many' Laravel relationship made my project 600 times faster - DEV Community])。
 
特に、hasManyの関係で、1対1じゃなくて、複数のモデルが入っている場合。
 
Laravel 8.42からLatestOfManyなどのメソッドが追加されており、これを使えば回避できる。これを使わない場合、JOINとMAXなどを駆使する必要があった。
 
ひとまず、hasManyとか、性能を見て、問題あったらeager loadingしたり工夫することにする。
*[https://zenn.dev/yuzuyuzu0830/articles/cf4aedd9e9c08b58c8f2 withを使用した様々なデータの取得方法]
*[https://qiita.com/HuntingRathalos/items/5a7cae35a49a2795e8f2 [Laravel]withメソッドを理解する #初心者 - Qiita]
複数のテーブルと結合したい場合、withに複数指定する。必要な列が決まっているなら、withのテーブル名の後に:で列名指定でOK。
 
====== Eager Loading By Default ======
交差テーブルなど、正規化の都合でテーブルを分離しているだけで、使うときは基本的に常に結合を想定するテーブルもある。
そういうときに毎回withでテーブルを指定するのは面倒だし、テーブルアクセスごとにどのテーブルと連結しているかを指定したり考慮必要なのは面倒。これの回避策として、$withプロパティーがある。ここで関連テーブルを指定すると、常にwithで結合したものを返す。
<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Book extends Model
{
    /**
      * The relationships that should always be loaded.
      *
      * @var array
      */
    protected $with = ['author'];
 
    /**
      * Get the author that wrote the book.
      */
    public function author()
    {
        return $this->belongsTo(Author::class);
    }
 
    /**
      * Get the genre of the book.
      */
    public function genre()
    {
        return $this->belongsTo(Genre::class);
    }
}
$withプロパティーがある状態で、結合せずに特定テーブルとの連結が欲しければ、withoutかwithOnlyを指定する。
withoutは$withプロパティーで指定したテーブル名を指定して除外する。withOnlyは$withを上書き。
======Constraining Eager Loads======
 
===== Inserting & Updating Related Models =====
 
====== The Save Method ======
多対多の関係の保存用に、いくつかのメソッドがある。
$comment = new App\Comment(['message' => 'A new comment.']);
 
$post = App\Post::find(1);
 
$post->comments()->save($comment);
関係のsaveメソッドから関係先のモデルに挿入できる。
 
関係に属する
 
associateメソッドで関係を新しく設定することもできる。
$account = App\Account::find(10);
 
$user->account()->associate($account);
 
$user->save();
削除はdissociate()
$user->account()->dissociate();
 
$user->save();
 
====== 多対多の関係 ======
取付/取外
 
多対多の場合は扱いが異なる。
 
attachメソッドで中間テーブルを更新する。
$user = App\User::find(1);
 
$user->roles()->attach($roleId);
中間テーブルに挿入する追加データも設定できる。
$user->roles()->attach($roleId, ['expires' => $expires]);
中間テーブルのレコード削除はdetach。
// Detach a single role from the user...
$user->roles()->detach($roleId);
 
// Detach all roles from the user...
$user->roles()->detach();
配列で指定もできる。
$user = App\User::find(1);
 
$user->roles()->detach([1, 2, 3]);
 
$user->roles()->attach([
    1 => ['expires' => $expires],
    2 => ['expires' => $expires]
]);
同期
 
syncメソッドで、指定した配列で上書き・同期することもできる。
$user->roles()->sync([1, 2, 3]);
idの他の列も更新も可能。
$user->roles()->sync([1 => ['expires' => true], 2, 3]);
 
=====Other=====
======リレーションの列名エイリアス======
*[https://teratail.com/questions/170126 laravel Model の with() のリレーション先カラム名を変更して取得したい]
*[https://stackoverflow.com/questions/17174837/laravel-4-eloquent-column-alias php - Laravel 4 Eloquent Column Alias - Stack Overflow]
Eloquentでリレーションで結合時、カラム名が同じだと困る。
 
別名をつける方法がいくつかある。
*.get/.select: ->get(['tags.name AS tag_name', 'products.*'])/ ->select('tags.name AS tag_name', 'products.*')
*アクセサー
get/selectでやるのがよさそう。
 
====== リレーションの把握 ======
モデルインスタンス取得後 (createOrUpdate)。
 
getRelations()でリレーションのオブジェクトを取得できる。連想配列でキー・バリュー形式でキー名が取れる模様。
 
====Other====
=====列の存在確認=====
*[https://laracasts.com/discuss/channels/eloquent/test-attributescolumns-existence Test attributes/columns existence]
*[https://stackoverflow.com/questions/51703381/check-if-column-exist-in-laravel-models-table-and-then-apply-condition php - Check if column exist in Laravel model's table and then apply condition - Stack Overflow]
いくつか方法がある。
*use Illuminate\Support\Facades\Schema; Schema::hasColumn('テーブル名', '列名'): これが一番シンプル。
*$model['列名']: インスタンス生成後ならこれ。
=====Eloquent vs. Query Builder=====
*[https://stackoverflow.com/questions/38391710/laravel-eloquent-vs-db-facade-when-to-use-which performance - Laravel Eloquent vs DB facade: When to use which? - Stack Overflow]
*[https://usescribe.ai/youtube-summaries/eloquent-vs-query-builder-in-laravel-performance-and-usability-compared-1707094546 Scribe - Eloquent vs. Query Builder in Laravel: Performance and Usability Compared]
*[https://www.zuzana-k.com/blog/laravel-eloquent-vs-query-builder/ Zuzana K | Laravel Eloquent vs Laravel query builder]
*[https://medium.com/@andreelm/eloquent-orm-vs-query-builder-in-laravel-47f104452644 Eloquent ORM VS Query Builder in Laravel | by Andre Elmustanizar | Medium]
*[https://www.reddit.com/r/laravel/comments/us98hc/hi_im_wondring_when_to_use_eloquent_and_when_to/ Hi , I'm wondring when to use eloquent and when to use query builder. Or Can we use them interchangeably? I love eloquent because it's easy to use but query buider is much faster. What do you think about the subject? And which one do you recommende? : r/laravel]
*[https://qiita.com/ryocha12/items/8bf538b89739b903e437 Laravelのwithとjoinの違いについて #PHP - Qiita]
ORMとクエリービルダー。どこでどう使い分けるか?最初の2個の記事が参考になる。
*ユーザーフォームのような基本的で簡単なCRUD処理はEloquent
*結合を含んだり、大量データの処理が必要な複雑な場合にクエリービルダー。
こういう方針で使い分けるといい。結合とかになると、モデルの範囲外になる。$withプロパティーを使うと、結合もいけなくはない。
 
レコードセットなのか、モデルなのかを意識する。
 
また、どちらを使うかで、返却値が違うことを注意する。Eloquentだとオブジェクト、DBクエリーだと配列。そのままだとコード修正が必要。
 
Eloquentのオブジェクトのほうが意味が分かりやすい。可変プロパティーでアクセスすればいい。
=====列名の取得=====
*[https://stackoverflow.com/questions/45818177/laravelhow-to-get-columns-name-of-eloquent-model php - laravel:how to get columns name of eloquent model? - Stack Overflow]
*[https://laracasts.com/discuss/channels/eloquent/get-column-names-from-table Get column names from table]
Eloquentのモデル自体は列名を保有していないらしい。
use Illuminate\Support\Facades\Schema;
$columns = Schema::getColumnListing('users'); // users table
dd($columns); // dump the result and die
 
        $headings = DB::connection()
            ->getSchemaBuilder()
            ->getColumnListing('colaborators');
 
$item = <nowiki>News::find($request-</nowiki>>newsID);
$attributes = array_keys($item->getOriginal());
$attributes = array_keys($item->getAttributes());
モデルインスタンスを作るところは、createOrUpdateで一時的に作るとよい。なお、getOriginalもgetAttbitutesもリレーション先のデータはない。
 
===== ::query=====
*[https://stackoverflow.com/questions/51517203/what-is-the-meaning-of-eloquents-modelquery laravel - What is the meaning of Eloquent's Model::query()? - Stack Overflow]
*[https://zenn.dev/nshiro/articles/98d3826151af81 Laravel ::where() ではなく、::query() で書き始めるプチメリット]
*[https://qiita.com/fujita-goq/items/2279bb947ec4e7b103b2 【Laravel】Modelの::query()メソッドで快適なEloquent操作を #PHP - Qiita]
*https://chatgpt.com/c/673ec2e5-0a3c-800b-9e9b-eefcc2f654fb
EloquentのORMのモデルを使用する際は、<Model>::findなどのメソッドを使う。
 
が、インスタンスを生成してから、条件に応じて処理を実行したい場合など、都合が悪いことがある。
 
インスタンス生成用に<Model>::query()がある。クエリビルダーインスタンスを返す。(new User())->query()のように空インスタンスを作ってからやるのと同じ。<Model>::query()->findなどのように、メソッドの前に挟める。
 
これを使う形で書いておくと、コードの書き方がきれいになったり、コード補完が効きやすかったりする。
=====SQL関数=====
[https://debug.to/842/how-to-passing-mysql-functions-to-eloquent-orm-query how to Passing MySQL functions to Eloquent ORM query? - deBUG.to]
 
Eloquentのwhere関数内でSQL関数は使えない。工夫が必要。
 
[https://laravel.com/docs/5.8/queries#raw-expressions Database: Query Builder - Laravel 5.8 - The PHP Framework For Web Artisans]
 
DB::rawで書く。
 
あるいは、selectRaw/whereRawなどを使う。
=====結果のオブジェクト・配列変換=====
*[https://laracasts.com/discuss/channels/eloquent/eloquent-results-as-array-instead-of-object?page=1&replyId=668509 Eloquent results as array instead of object]
*[https://stackoverflow.com/questions/41447275/laravel-toarray-still-returning-an-object-in-dbraw php - Laravel toArray() still returning an object in DB::raw - Stack Overflow]
EloquentのDB取得の結果は、配列になっている。が、DBクエリーの場合、オブジェクトになっている。
 
型が違っていろいろ困る。DBクエリーのオブジェクトを配列にしたい。
 
Eloquentを使わずに、配列にしたい場合、mapやtransformで変換必要。
$response['data'] = DB::table('customers') 
    // query conditions, etc
    ->get()
    ->map(function ($item, $key) {
        return (array) $item;
    })
    ->all();
最後のallも必要。これでEloquentと共通化できる。
 
===== 一括更新 =====
[https://stackoverflow.com/questions/15622710/how-to-set-every-row-to-the-same-value-with-laravels-eloquent-fluent How to set every row to the same value with Laravel's Eloquent/Fluent? - Stack Overflow]
 
マイグレーションなどで、列を切り詰めたり一括更新したいことがある。
Model::query()->update(['confirmed' => 1]);
 
これがシンプル。
 
「[https://qiita.com/kenji__n/items/7cbb6f04594b30ed3b9b 【Laravel】バルクアップデートを行う方法 #MySQL - Qiita]」に記載のall()->update()はallが配列を返すのでダメ。
 
===== 主キーの取得 =====
[https://chatgpt.com/c/67344cfe-dc20-800b-9cd3-1dee66a4deab ChatGPT]
 
モデルの定義時に、protected $primaryKeyで主キーを設定している。インスタンスでこの情報を保有しておりアクセス可能。
 
*主キーのカラム名: <code>getKeyName()</code> または <code>$primaryKey</code> プロパティで取得。
* 特定レコードの主キーの値: <code>getKey()</code> で取得。
 
$primaryKeyはprotectedだからアクセスしにくい。(new Model)->getKeyName()などでアクセスするのがよい。
 
https://chatgpt.com/c/67354314-fbdc-800b-a4e4-65f96d1a8fb2
 
リレーション先の主キーはgetRelated()でインスタンスを取得すれば同じ方法でアクセスできる。
// Postモデル内
public function user()
{
    return $this->belongsTo(User::class);
}
// 主キー名の取得
$post = Post::find(1);
$primaryKeyName = $post->user()->getRelated()->getKeyName();
echo $primaryKeyName;
 
===== モデルのリレーションの反復 =====
Eloquentのモデルインスタンスは特殊なオブジェクトでそのままforeachでテーブルカラム、外部リレーションのキーにアクセスできない。
 
反復させたければ、toArray()で連想配列に変換する必要がある。
    public function getFields(): array
{
<nowiki> </nowiki>      $model = $this->modelClass::first() ?? new $this->modelClass();
<nowiki> </nowiki>      return $this->getColumns($model->toArray());
}
<nowiki> </nowiki>  public function getColumns(array $model_array, string $relation = <nowiki>''</nowiki>): array
<nowiki> </nowiki>  {
<nowiki> </nowiki>      $columns = array_map(function($e)use($relation){return $relation ? $relation.'.'.$e : $e;}, array_keys($model_array));
<nowiki> </nowiki>      foreach($model_array as $key => $value) {
<nowiki> </nowiki>          if (is_array($value)) {
<nowiki> </nowiki>              $relation = $relation ? $relation.'.'.$key : $key;
<nowiki> </nowiki>              $columns = array_merge($columns, $this->getColumns($value, $relation));
<nowiki> </nowiki>          }
<nowiki> </nowiki>      }
<nowiki> </nowiki>      return $columns;
<nowiki> </nowiki>  }
 
== Testing ==
 
=== Getting Started ===
[https://laravel.com/docs/5.8/testing Testing: Getting Started - Laravel 5.8 - The PHP Framework For Web Artisans]
 
==== Introduction ====
Laravelはテストを念頭に置いており、デフォルトでPHPHUnitに対応していて、phpunit.xmlもある。
 
デフォルトで、testsディレクトリーは、Feature Unitの2ディレクトリーを持つ。Unitは関数単位の単体テスト。FeatureはHTTPリクエストとJSONエンドポイントなど、複数のオブジェクトの対話を含む大き目のテスト。
 
両方のディレクトリーに、ExampleTest.phpがある。
 
https://chatgpt.com/c/673be4e7-b73c-800b-9d18-3167d235ce06
 
基本はUnitディレクトリーにファイルを配置する。
 
tests/Unit/配下をさらにディレクトリー分割もできる。namespaceだけ注意すれば、後は自動で認識してくれる。
 
気軽に作っていくとよい。
 
==== Environment ====
pupunitのテスト時、phpunit.xmlの<server name="APP_ENV" value="testing"/>の設定により、Laravelは設定環境をtestingにする。また、自動的にセッションとキャッシュをarrayドライバーに自動的に構成する。つまり、テスト中はセッションやキャッシュを保持しない。


テスト実行前に、config:clearで設定キャッシュを削除しておく。
テスト実行前に、config:clearで設定キャッシュを削除しておく。


また、.env.testingファイルをプロジェクトルートに作成できる。このファイルは、PUPUnitテスト実行中か、artisanコマンドで--env-testingを指定時に、.envを上書きする。
また、.env.testingファイルをプロジェクトルートに作成できる。このファイルは、PUPUnitテスト実行中か、artisanコマンドで--env-testingを指定時に、.envを上書きする。
 
 
==== Creating & Running Tests ====
==== Creating & Running Tests ====
テストケースの新規作成には、make:test Artisanコマンドを使う。
テストケースの新規作成には、make:test Artisanコマンドを使う。
  // Create a test in the Feature directory...
  // Create a test in the Feature directory...
  php artisan make:test UserTest
  php artisan make:test UserTest
    
    
  // Create a test in the Unit directory...
  // Create a test in the Unit directory...
  php artisan make:test UserTest --unit
  php artisan make:test UserTest --unit
--unitの有無で、UnitディレクトリーかFeatureディレクトリーにテストファイルのテンプレートが作られる。
--unitの有無で、UnitディレクトリーかFeatureディレクトリーにテストファイルのテンプレートが作られる。
 
 
テストが作成されたら、通常のPHPUnit同様に、テストメソッドを定義していく。phppunitコマンドでtestを実行する。
テストが作成されたら、通常のPHPUnit同様に、テストメソッドを定義していく。phppunitコマンドでtestを実行する。
  <?php
  <?php
    
    
  namespace Tests\Unit;
  namespace Tests\Unit;
    
    
  use Tests\TestCase;
  use Tests\TestCase;
  use Illuminate\Foundation\Testing\RefreshDatabase;
  use Illuminate\Foundation\Testing\RefreshDatabase;
    
    
  class ExampleTest extends TestCase
  class ExampleTest extends TestCase
  {
  {
     /**
     /**
       * A basic test example.
       * A basic test example.
       *
       *
       * @return void
       * @return void
       */
       */
     public function testBasicTest()
     public function testBasicTest()
     {
     {
         $this->assertTrue(true);
         $this->assertTrue(true);
     }
     }
  }
  }
テストクラス内で、setUp/tearDownメソッドを定義する場合、parent::setUp/parent::tearCownを忘れずに呼ぶこと。
テストクラス内で、setUp/tearDownメソッドを定義する場合、parent::setUp/parent::tearCownを忘れずに呼ぶこと。
 
==== Other ====
 
===== ディレクトリー構造 =====
https://chatgpt.com/c/673be4e7-b73c-800b-9d18-3167d235ce06
 
基本はUnitディレクトリーにファイルを配置する。
 
tests/Unit/配下をさらにディレクトリー分割もできる。namespaceだけ注意すれば、後は自動で認識してくれる。
 
気軽に作っていくとよい。
 
ディレクトリーも指定できる。
php artisan make:test Models/UserTest --unit
これでtests/Unit/Models/UserTest.phpができる。
 
以下の基準でディレクトリー分割するとわかりやすい。
 
* '''テスト対象ごとに分ける'''
** モデル、サービス、リポジトリ、ヘルパーなど、アプリケーションの層や責務に応じて分割。
* '''機能やモジュール単位で分ける'''
** たとえば「ユーザー管理」「注文管理」「在庫管理」などのモジュールごとにディレクトリを作成。
 
テストの実行はphpunitでもいいし、php artisan test (Laravel 8以上) でもOKの模様。
 
=== HTTP Tests ===
[https://laravel.com/docs/5.8/http-tests HTTP Tests - Laravel 5.8 - The PHP Framework For Web Artisans]
 
Laravel固有に近いテスト。
 
==== Introduction ====
HTTPリクエストとその出力の基本的なテスト。内容的に結合試験、Featureのテストになる。
<?php
 
namespace Tests\Feature;
 
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
 
class ExampleTest extends TestCase
{
    /**
      * A basic test example.
      *
      * @return void
      */
    public function testBasicTest()
    {
        $response = $this->get('/');
 
        $response->assertStatus(200);
        // 特定の文字列が含まれていることを確認
        $response->assertSee('Welcome to My Website');
        // 特定のHTML要素が含まれていることを確認
        $response->assertSee('<nowiki><h1>Home</h1></nowiki>', false); // 第二引数をfalseにするとHTMLタグを検証
    }
}
リクエストを呼び出して、その終了ステータスをチェックするのがまず一番基本的なテスト。
 
他に、assertSeeで特定文字列の存在チェック。これもけっこう基本。


=== Console Tests ===
=== Console Tests ===

2024年11月21日 (木) 16:29時点における最新版

About

人気の理由

Laravelの人気を大検証 何が凄いの? | テクフリ

CakePHPのほうが早い。

  • 簡単にマスター可能
  • 自由度が高い

情報源

Getting Started

Configuration

Configuration - Laravel 5.8 - The PHP Framework For Web Artisans

Environment Configuration

DotEnvライブラリーを使っており、ルートディレクトリーの.env.exampleを.envにリネームするとこの設定を利用する。

Accessing Configuration Values

configグローバルヘルパー関数で、アプリ内のどこからでも設定値を参照できる。

$value = config('app.timezone');

実行時に設定もできる。

config(['app.timezone' => 'America/Chicago']);
Configuration Caching

config:cacheで設定をキャッシュすることができるらしい。ただし、このコマンドを実行すると、.envファイルは読み込まれないので、env関数は設定ファイル内でしか使ってはいけない。

つまり、基本は設定値の参照はconfig関数で行う。

Additional Directory

php - Laravel 5 - Config sub-folder - Stack Overflow

configディレクトリーに、ディレクトリーを追加することもできる。DotEnvライブラリーの作法と思われる。

config('subfolder.myfile.var');

上記のような命名で、ディレクトリーも解釈される。config('directory')を実行すると、ディレクトリー内の全部が連想配列で取得できる。

Array in .env

.envは環境変数の設定ファイル。そのままだと配列は使えない。

  • コンマ区切り
  • json

やるとしたら上記で記述しておいて、読込後にデコードする。

.env format

.envでSQLのような複雑なテキストを記述する場合。引用符の扱いが重要になる。dotEnvの書式を整理する。

基本はシェルスクリプトと同じだが一部違う。

  • 改行を含める場合、二重引用符で囲む。

コマンド置換$()が使えるのは重要。catでヒアドキュメントで生テキストを書ける→無理。シンプルなコマンドしかパースされない。

しかたないので、JSON内の二重引用符をエスケープする。

RAKURAKU_TABLES="{\"101174\":{\"dbSchemaId\":101174, \"importId\":100577, \"sqlFile\":\"101174.sql\", \"sqlText\":\"
SELECT \"0000010108\" as 消費者コード, \"1\" as 連番, \"111-1111\" as 郵便番号, \"XXXX\" as 住所1, \"X\" as 住所2, \"41\" as 販売店コード, \"111 X\" as 消費者名, \"10\" as メーター数, \"2024-01-01\" as 契約日, \"2024-01-01\" as 建築日, \"地上\" as 供給設備_バルク設置区分, \"1\" as 供給設備_バルク基数, \"2024-01\" as 供給設備_バルク設置年月, \"2024-01\" as 供給設備_バルク製造年月, \"1\" as 供給設備_バルク貯槽番号, \"1\" as 供給設備_ガスメータ種別, \"メーカー\" as 供給設備_ガスメータメーカ, \"型式\" as 供給設備_ガスメータ型式, \"2024-01\" as 供給設備_ガスメータ設置年月
UNION ALL SELECT '0000010109','2','X','X','X','41','111 X','10','2024-01-01','2024-01-01','地上','1','2024-01','2024-01','1','1','メーカー','型式','2024-01'
;
                                                                        \"}}"

Directory Structure

Directory Structure - Laravel 5.8 - The PHP Framework For Web Artisans

  • app
  • bootstrap
  • config
  • database
  • public
  • resources
  • routes
  • storage: フレームワークで自動生成される、コンパイル済みのBladeテンプレート、ファイル系セッション、ファイルキャッシュ類を格納。app, framework, logsがある。
    • app: アプリ生成ファイルの格納。
    • framework: フレームワーク生成ファイルとキャッシュ。
    • logs: ログ。
  • tests
  • vendor

Laravelの名前空間と、ディレクトリーは同一ではない。

命名規則の都合だろうと思われる。少々気持ち悪い。

Architecture Concepts

Service Container

Service Container - Laravel 5.8 - The PHP Framework For Web Artisans

Service Class

Serviceクラスは特定のビジネスロジックの管理に重点を置いたクラス。ビジネスロジックに特化しているから、他のクラスと異なり、通常はプロパティーを継承しない。

app/Servicesに配置して、クラス名の末尾にはServiceの接尾辞をつける。

多くの場合、Eloquentモデルにリンクされたロジックの追加で役立つ。例えば、Userモデルに対するUserServiceのような。ただ、Eloquentモデルとは関係なしに、PaymentServiceのように特定の機能 (ビジネスロジック) に合わせて調整したサービスクラスもある。

ビジネスロジックに特化したユーティリティークラスとかに近い。

うまい作りとしては、Controllerではtry-catchを含んだサービスクラスに定義した関数を呼ぶだけ、呼び終わった結果を返すだけにするとか。

コードがモジュール化されて、保守しやすく整理される。

Other

独自ライブラリーの追加

Laravelで独自に作ったライブラリファイルへのパスの通し方 | A Day In The Boy's Life

自前のライブラリーを追加したくなった。追加方法がいくつかある。

composerを使う方法が簡単。

The Basics

Routing

Basic Routing

routes/web.phpでURL別のアクセス時 (ルーティング) の処理 を設定する。

web.phpはwebミドルウェアグループに割り当てられていて、CSRFガードなどが入っている。

Route::get('/user', 'UserController@index');

上記のような書式で、パスとアクションを対応付ける。

Named Routes

ルートに名前を付けて、後で流用・参照できる。

Other

動的アクション

ルートに応じて、DBにアクセスして、値の取得や処理をしたいことがある。

evalでアクションメソッドを呼び出すこともできる。が、パラメーターとして渡して、1個のメソッドで処理するのがスマート。

evalでやるのはいまいち。

Middleware

リクエスト受信後にコントローラー処理の前後に割り込んで行う処理の仕組み。プログラムの基本はコントローラーのアクション。

このアクションに共通処理を一括で仕込む場合、コントローラーのアクション単位で処理が必要になる。そのままだと何回も同じことを書く必要がある。

例えば、フォームの送信チェックやログイン認証など。これらを一括で行うための仕組みがミドルウェア。

Controllers

Controllers - Laravel 5.8 - The PHP Framework For Web Artisans

Resource Controllers
Verb URI Action Route Name
GET /photos index photos.index
GET /photos/create create photos.create
POST /photos store photos.store
GET /photos/{photo} show photos.show
GET /photos/{photo}/edit edit photos.edit
PUT/PATCH /photos/{photo} update photos.update
DELETE /photos/{photo} destroy photos.destroy

create/editは新規作成、編集用の画面表示。実際の処理はstore/updateで行う。

Requests

HTTP Requests - Laravel 5.8 - The PHP Framework For Web Artisans

Retrieving Input
Retrieving All Input Data

allメソッドで入力のすべてを取得できる。

Retrieving An Input Value

inputメソッドで指定した入力 (HTTP ボディー) をキーで参照できる。第二引数になかった場合のデフォルト値を設定できる。

$name = $request->input('name', 'Sally');

$names = $request->input('products.*.name');

$request->input();

引数空だと、連想配列で全部取得できる。

Retrieving Input From The Query String

input同様、queryでURLクエリーを取得できる。

$name = $request->query('name');
$name = $request->query('name', 'Helen');
$request->query();

LaravelでURLのクエリパラメーターを取得する二つの方法と注意点 #PHP - Qiita

Bladeで使う場合、request() (Helpers - Laravel 5.8 - The PHP Framework For Web Artisans) のグローバルヘルパー関数を経由して、Requestインスタンス経由でアクセスする。

なお、queryやinputはPHPの$_GET/$_POST経由でアクセスしている。その都合で、.は_に変換される。

.を維持したければ、 $request->getQueryString();から独自処理が必要。

Retrieving Input Via Dynamic Properties
$name = $request->name;

input/queryなどを使わなくても、動的にプロパティーに持っている。

Old Input

Laravelは次のリクエストの送信完了まで、前回のリクエストの入力を保存している。これにより、バリデーションエラー時などに入力内容を復元できる。

Retrieving Old Input

Request$ondにより、セッションから前回の入力を取得できる。

$username = $request->old('username');

Bladeでも使える。フォーム要素の前回の値表示として使える。基本は指定するとよいだろう。

<input type="text" name="username" value="{{ old('username') }}">

なお、このBladeのold関数は「Helpers - Laravel 5.8 - The PHP Framework For Web Artisans」のグローバルのヘルパー関数。

Responses

Creating Responses

全てのルートとコントローラーは、応答を返す必要がある。いくつかの方法がある。

Strings & Arrays

基本はテキストで返すこと。フレームワークが自動的にHTTP応答に変換する。

Route::get('/', function () {
    return 'Hello World';
});

文字列の他に、配列も返せる。配列の場合、JSONに変換する。

Route::get('/', function () {
    return [1, 2, 3];
});
Response Objects

文字列と配列以外で返したい場合、Illuminate\Http\Responseインスタンスか、viewsを返せる。 ResponseインスタンスはHTTPステータスコードやヘッダーを完全に返せる。ResponseはSymfony\Component\HttpFoudation\Responseを継承しており、HTTP応答構築のメソッドをいろいろ使える。

Route::get('home', function () {
    return response('Hello World', 200)
                  ->header('Content-Type', 'text/plain');
});
  • header: 1項目ずつ引数指定。
  • withHeaders: 配列でまとめて指定。

Views

ControllerからViewにデータを渡す際、user情報など毎回渡すデータがあったりする。そういうのをControllerとは別の場所で自動処理する仕組みがView Composer。Controllerの処理がすっきりする。

ViewはHTMLを保持している。view関数で、テンプレートを指定して、テンプレートで使用する変数を渡せば、Viewインスタンスを取得する。ViewインスタンスがBladeのHTMLを保有している。

いくつかViewインスタンスのメソッドがある。

first: 指定した配列の最初のテンプレートを表示に使う。基本的にアプリなどでユーザーが上書きするよう。あまり使わない。

return view()->first(['custom.admin', 'admin'], $data);
return view('greetings', ['name' => 'Victoria']);
return view('greeting')->with('name', 'Victoria'); 

引数で渡すほかに、with関数でも渡せる。

Validation

主にPOST系のアクション実行用に、バリデーションというデータの検証の仕組みがある。

Quickstart

ControllerのValidateRequestsトレイトに用意されており、Controllerのメソッドとして使える。

$this->validate($request, [検証設定の配列]);
$request->validate([検証設定の配列]);

上記の書式で使う。$request->validateでもいい。どちらかというとこちらだと第一引数を省略できるので望ましい。

validateに失敗したら自動的に元の画面をリダイレクト表示する。

Displaying The Validation Errors

失敗して元の画面をリダイレクト表示した後、エラー内容をユーザーに知らせたい。その場合、\Illuminate\Support\MessgeBagの$errors変数に必要な情報が格納される。これを使う。

name属性がキーの連想配列になっていて、valueにエラーメッセージが入る。

 
<h1>Create Post</h1>
 
@if ($errors->any())
    <div class="alert alert-danger">
                          <ul>
                              @foreach ($errors->all() as $error)
                                  <li>{{ $error }}</li>
            @endforeach
        </ul>
    </div>
@endif
 
The @error Directive

@error指令も使える。

 
<label for="title">Post Title</label>
 
<input id="title" type="text" class="@error('title') is-invalid @enderror">
 
@error('title')
    <div class="alert alert-danger">{{ $message }}</div>
@enderror

@error(name属性名)@enderrorの書式。@error指令内の$messageで$error[属性名]相当を参照できる。

ただ、この基本的なバリデーションだとコントローラーに都度記載が必要。できれば、リクエストなどで別でやりたい。

FormRequestという仕組みがある。フォームに関する機能をリクエストに組み込む。コントローラーではなく、リクエストでフォーム処理をやってくれる。

FormRequestを派生させたクラスを作成し、適用するURLパスとルールを定義。使用するコントローラーのアクションの引数に、Reqeustではなく作ったFormRequest派生クラスに変更するとOK。これだけ。

エラーメッセージもFormRequest派生クラスで作れる。



Available Validation Rules

利用可能なルール一覧。

Accepted

Active URL

After (Date)

After Or Equal (Date)

Alpha

Alpha Dash

Alpha Numeric

Array

Bail

Before (Date)

Before Or Equal (Date)

Between

Boolean

Confirmed

Date

Date Equals

Date Format

Different

Digits

Digits Between

Dimensions (Image Files)

Distinct

E-Mail

Ends With

Exists (Database)

File

Filled

Greater Than

Greater Than Or Equal

Image (File)

In

In Array

Integer

IP Address

JSON

Less Than

Less Than Or Equal

Max

MIME Types

MIME Type By File Extension

Min

Not In

Not Regex

特に頻出の重要なもの。

  • required: 存在を要求。PHPのemptyでtrueになるようなものはアウト。ただし、0は許容。

Logging

Logging - Laravel 5.8 - The PHP Framework For Web Artisans

Other

Laravelで便利なログ出力方法がいくつかある。

  • ヘルパー関数 (Helpers - Laravel 5.8 - The PHP Framework For Web Artisans)
    • dd: dump and die。引数の変数をその場で表示して終了。
    • dump: 引数の変数をその場で表示。
  • Log::debug(): ログファイルstorage/logsに出力 (use Illuminate\Support\Facades\Log;)。
    • Log::info(print_r($user, true));
    • Log::info(json_encode($user));
    • オブジェクト類はjson_encodeがいい。
Allowed memory size of 134217728 bytes exhausted (tried to allocate 90181632 bytes)

なお、print_r($request, true) などをすると、以下のエラーが出る。

[2017-09-06 15:19:44] production.ERROR: Symfony\Component\Debug\Exception\FatalErrorException: Allowed memory size of 134217728 bytes exhausted (tried to allocate 90181632 bytes) in [path to file reducted] Stack trace: #0 {main}

[print_r($request) causes out of memory error.] にあるように、print_rは継承元も再帰的に出力し、Laravelはたくさん継承しているからいっぱいになるらしい。

これはせずに、dumpなどを使う。

Helpers

Helpers - Laravel 5.8 - The PHP Framework For Web Artisans

デバッグに役立つグローバルなヘルパー関数がいくつかある。Bladeでもそのまま使用できる。

  • info: Log::info相当。第二引数に配列データも渡せる。
  • logger: Log::debug相当。引数を空にするとloggerインスタンスを取得できて、そこから個別のエラーレベルに出力もできる。

Log::info/Log::debugはuse宣言が必要で面倒なので、info/loggerを使ったほうがいい。

logger('Debug message');
logger('User has logged in.', ['id' => $user->id]);
logger()->error('You are not allowed here.');
debugbar

DebugbarというLaravelの開発デバッグにかなり便利なツールがある。

導入方法

composer require barryvdh/laravel-debugbar --dev

.envでAPP_DEBUG=trueの場合に機能する。

クエリーの発行数、メモリー使用量、実行時間。このあたりが特に重要と思われる。

Frontend

Blade Templates

Blade Templates - Laravel 5.8 - The PHP Framework For Web Artisans

@section/@yield: Template Inheritance

Bladeでは、継承とセクションの2種類のテンプレートの流用方法がある。

Layoutはページ全体のテンプレート。セクションは区画単位のテンプレート。

セクションは@section/@yieldを使って実現する。

@sectionは@showか@endsectionで終わる。

@show/@endsectionの違い (Bladeテンプレートの@showと@endsectionを間違えないようにする #Laravel - Qiita)。親テンプレートで定義する場合は@show。親でも@endsectionを使うと、sectionの内容が消える。sectionは本来、元テンプレートから継承したものを埋め込むためのものなので、ベーステンプレートだと埋め込み元がないので消えるのだと思う。だから、@showを使う必要があると思われる。

@component/@slot: Components & Slots

テンプレートよりも細かい部品単位の流用方法がcomponent。ヘッダーやフッター、ボタンなどの部品単位で流用できる。

viewsディレクトリー以下に格納する。一般的にはviews/components/ok.blade.phpなどのように配置し、components.okなどで、コンポーネント名を指定して読み込む。

component定義側は、通常のBladeと同じだが、コンポーネント内で変数のプレースホルダーを使用できる。これは、利用側の@slotのブロックで引き渡す。

<div class="message">
                                                                                                                                                    <p class="msg_title">{{$msg_title}}</p>
 <p class="msg_content">{{$msg_content}}</p>
 {{$slot}}
</div>

component利用側で組み込むために工夫する。

@component(名前)
  @slot('msg_title')
    title
  @endslot
    <strong>Whoops!</strong> Something went wrong!
@endcomponent

$slot変数には、@componentsのテキスト、@slotブロック以外が入る。

Laravelに複数の在不明のcomponentを順番に適用させたい場合componentFirstを使う。

@componentFirst(['custom.alert', 'alert'])
    <strong>Whoops!</strong> Something went wrong!
@endcomponent

@slot以外に、変数を渡すこともできる。

@component('alert', ['foo' => 'bar'])
    ...
@endcomponent

@component('alert')
  @slot('foo', 'bar')
@endcomponent

slotの設定方法は複数ある。@slot/@endslotよりかは@slot()で設定するほうが短い。が、@component内は$slotのデフォルト値を入れるとしたほうがわかりやすいかもしれない。

ただし、@endcomponentは省略できない。

名前付きslotのデフォルト値設定はない。やりたければ、??や@if/@issetで自分でやっておく。

デフォルトの$slotは、@componentの配列式 (@component(, ['slot']) では指定できないが、view関数で呼ぶ際は['slot']で指定できる。

基本は短くできるので、component内では$slotを使うほうがいい。

なお、$slotは扱いに注意が必要。使う側で指定がなかったら、nullではないが見えない変な値が入っている。変数として使う際は"$slot"のように二重引用符で囲んで、値がない場合に確実に空にしておく。

@include: Including Sub-Views

レイアウトやコンポーネントのように、変数の引き渡しなど複雑なことをしない場合、単純な定形固定文字列を読み込むような場合、Sub-Viewsというのを使うこともできる。

これは@includeでテンプレートファイルをそのまま読み込むのが基本。親の変数もそのまま使える。他に、引数で変数を渡すこともできる。

@include('view.name', ['some' => 'data'])
@includeIf('view.name', ['some' => 'data'])
@includeFirst(['custom.admin', 'admin'], ['some' => 'data'])

@includeで指定したテンプレートが不在の場合、Laravelはエラーを投げる。このエラーを回避したい場合、@includeIf指令を使う。@includeIfのバリエーションの一種で、配列のテンプレート名で存在する最初のものを使う場合、@includeFirstを使う。

条件がtrueなら読み込む場合、@includeWhenがある。

@include($boolean, 'view.name', ['some' => 'data'])

@ifよりもシンプル。

【Laravel】bladeの@includeと@componentの違い #PHP - Qiita

includeはcomponentと違って@endincludeがいらない。

componentはslotで渡すこともできる。$slotを使える点が大きな違い。

複雑で長いHTMLなどを、引き渡して使いたい場合、componentのほうがいい。

@each: Rendering Views For Collections

意外と使用頻度が高いのが繰り返し表示。例えば、リストの項目、テーブルの行など。これようの指令が@each

 @each('components.item', $data, 'item');

$data配列の要素をコンポーネントのitem変数に渡す。

// components/item.blade.php
<li>{{$item['name']}} [{{$item['mail']}}]</li>

Displaying Data

Displaying escaped Data

Bladeでデータを表示する際は、二重波括弧を使う。

Route::get('greeting', function () {
    return view('welcome', ['name' => 'Samantha']);
});
Hello, {{ $name }}.

二重波括弧は自動的にhtmlspecialcharsでXSS対策してくれる。変数の他に、PHP関数の結果も表示できる。

Displaying Unescaped Data

なお、生のHTMLなどのデータをそのまま表示させたい場合、二重波括弧の代わりに{!! !!}で囲む。

Hello, {!! $name !!}.
Hello, {!! e($name) !!}.

もっというと、e()でエスケープしておくと安心 (Bladeで変数に入れたhtml文字列を表示させる #Laravel - Qiita)。

Helpers - Laravel 5.8 - The PHP Framework For Web Artisans

混乱するが、二重波括弧内はPHP扱い、外はHTML扱い。二重波括弧内で表示文字列扱いしたいなら、文字列にする必要がある。

Rendering JSON

JavaScript変数として、配列をJSONとして描画したいことがあるだろう。

<script>
                                                                                                var app = <?php echo json_encode($array); ?>;
                                                                                                var app = {{json_encode($array)}};
      var app = {!! json_encode($array) !!};
     var array = JSON.parse('{{ json_encode($theArray) }}');
</script>

手動でjson_encodeを呼ぶ代わりに、@json指令がある。

<script>
    var app = @json($array);
 
    var app = @json($array, JSON_PRETTY_PRINT);
</script>

なお、@jsonはjson_encodeと同じではない。以下のコードで{{json_encode部分を@jsonに変えるとうまくいかない。

<button type="button" onclick="
         ajaxGetAndSetTable('/ajax/楽楽販売_器具交換履歴_最新取得?消費者コード=' + document.getElementById('消費者コード').value, '#facilityHistory tbody', 
         {{json_encode($names)}}, '部屋番号');
     "
     >楽楽販売最新取得 (未実装)</button>

{{json_encode($array)}}で良いと思われる。

{{}} だとエスケープされて二重引用符が"になって、扱いにくいことがある、{!! !!}でエスケープ解除すると問題ない。

Context

BladeのコードのPHPとHTML/JavaScriptの解釈が場所によって異なって混乱するので整理する。

blade.phpはPHPファイルではあるが、テンプレートで基本はHTML。以下の部分的にPHPが使用可能。

  1. @php-@endphp内。
  2. @指令の引数内。
  3. {{-}}/{!!-!!}内。

波括弧記法は@指令の引数部分では使用不能。

@指令の引数でonclickなどのJS用のコードとPHPの処理を埋め込む場合は、PHPの文字列として扱う。なので、.などでPHPで文字列連結を駆使する。

Blade & JavaScript Frameworks

JavaScriptフレームワークの中に、二重波括弧をプレースホルダーとしてそのまま使うものがある。そういう場合、@を波括弧に前置するとそのまま表示する。

<h1>Laravel</h1>
 
Hello, @{{ name }}.
The @verbatim Directive

JavaScript変数を表示する場合、波括弧の前に@をたくさん置かないで済むように、@verbatimで囲める。

@verbatim
    <div class="container">
                                                                                                                                                               Hello, {{ name }}.
    </div>
@endverbatim
Control Structure/Blade Directives
If Statements
@if (count($records) === 1)
    I have one record!
@elseif (count($records) > 1)
    I have multiple records!
@else
    I don't have any records!
@endif

@unless (Auth::check())
    You are not signed in.
@endunless

@isset($records)
    // $records is defined and is not null...
@endisset
 
@empty($records)
    // $records is "empty"...
@endempty

@auth
    // The user is authenticated...
@endauth
 
@guest
    // The user is not authenticated...
@endguest

@auth('admin')
    // The user is authenticated...
@endauth
 
@guest('admin')
    // The user is not authenticated...
@endguest

@hasSection('navigation')
    <div class="pull-right">
        @yield('navigation')
    </div>
 
    <div class="clearfix"></div>
@endif

特に@isset/@emptyをよく使うかも。ただ、これらには@elseはないので注意が必要かもしれない。やるなら、

@if(isset())
@else
@endif

@isset()
@endisset
@empty()
@endempty
Switch Statements
Loops

PHPの反復構造に近いものがある。重要。

@for ($i = 0; $i < 10; $i++)
    The current value is {{ $i }}
@endfor
 
@foreach ($users as $user)
    <p>This is user {{ $user->id }}</p>
@endforeach
 
@forelse ($users as $user)
    <li>{{ $user->name }}</li>
@empty
    <p>No users</p>
@endforelse
 
@while (true)
    <p>I'm looping forever.</p>
@endwhile

公式マニュアルに記載がないが、foreachはPHPのforeachと同じく foreach($array as $key => $value) 形式にも対応している。

The Loop Variable

Blade Templates - Laravel 5.8 - The PHP Framework For Web Artisans

ループ内で使用可能な$loop変数があり、これに便利なプロパティーがある。

Property Description
$loop->index The index of the current loop iteration (starts at 0).
$loop->iteration The current loop iteration (starts at 1).
$loop->remaining The iterations remaining in the loop.
$loop->count The total number of items in the array being iterated.
$loop->first Whether this is the first iteration through the loop.
$loop->last Whether this is the last iteration through the loop.
$loop->even Whether this is an even iteration through the loop.
$loop->odd Whether this is an odd iteration through the loop.
$loop->depth The nesting level of the current loop.
$loop->parent When in a nested loop, the parent's loop variable.

first/last/even/oddあたりは特に便利だろう。

Additional Attributes

Blade Templates - Laravel 9.x - The PHP Framework For Web Artisans

Laravel v9から使用可能。

@disabled

Comments

php - Laravel - Blade comments , blade rendering causing page to crash - Stack Overflow

Bladeテンプレートファイル内でのコメントには注意が必要。

{{-- code --}} これが基本
PHPコード扱いでのコメントアウトも便利。
@php
/* */
@endphp
<?php /* */ ?>

Bladeの{{-- code --}}は内部の{{}}が変数展開として解釈される。内部に波括弧がある場合は、phpコード扱いでコメントアウトしたほうが安全。

PHP

Bladeテンプレート内でPHPのコードをそのまま記述する際には、専用の指令を使う。

@php
@endphp

これを使うと、「Laravelのbladeで変数を定義する – FUNBREW」にあるように、変数を定義してBlade内で使うことができる。変数の定義や空チェックなどを最初にできるので、シンプルになる。

@php
$newValue = $somethingValue;
if (empty($newValue)) {
    $newValue = 'Not Defined';
}
@endphp

<div>{{ $newValue }}</div>

Forms

CSRF Field

アプリ内でHTMLフォームを使用する場合、CSRFトークンフィールドを記述する。

具体的には、form要素の冒頭に@csrfを指定する。

<form method="POST" action="/profile">
    @csrf
 
    ...
</form>
@stack/@push/@prepend: Stacks

名前付きのスタックに、他の場所で使用するビューやレイアウトを格納して、流用できる。

まず@pushする。使いたいか所で@stackすると取り出せる。

使い方としては、componentで@pushでscript要素やstyle要素を記述しておいて、レイアウトで@stackで呼び出す感じ。

@push('scripts')
    <script src="/example.js"></script>
@endpush
<head>
  
      @stack('scripts')
  </head>

順番が大事な場合、@prependでpushより先に詰め込める。

扱いは、sectionに似ている。componentのためのsectionのような感じだと思う。

pushしたものは描画のたびにstackで表示される。後のバージョンで@onceというのが登場したので、これを使えば1個だけになる。それまでは自作が必要。

push/stackを使わない場合、そのページに必要なくても、使う可能性のあるcss/jsを親で全部読み込んでおかないといけない。それを回避できる。

コンポーネントやテンプレート固有で必要なものを、同じファイルに記載しておいて、反映だけstackでまとめてできる。これが利点だろう。

Blade Templates - Laravel 9.x - The PHP Framework For Web Artisans

Blade Templates - Laravel 7.x - The PHP Framework For Web Artisans

Laravel 9.xで@pushOnce。Laravel 7.xで@onceがある。

componentにcssやscriptを含めたい場合に@pushを使う。

Component

Laravel 7からクラスベースコンポーネントが誕生した。

コンポーネントのパターンがある。

table

Table | Components | BootstrapVue

BootstrapVueを真似する。

{{--  
    fields = [field1, field2, field3]
    items = [[col1, col2, col3], {col1, col2, col3}]

    fields = [['label' => field1, 'class' => '', 'style' => ''], ['label' => field1, 'class' => '', 'style' => '']]
    items = [['label' => '', 'class' => '', 'style' => ''], ['label' => '', 'class' => '', 'style' => '']]
                                                                                                                                    --}}
<table class="table table-sm table-bordered">
                                                                                                                                         <thead class="bg-info text-center">
                                                                                                                                             @foreach ($fields as $field)
                                                                                                                                                 @if (!is_array($field))
                                                                                                                                                     <th>{{ $field }}</th>
            @else
                <th class="{{empty($field['class']) ? '' : $field['class']}}"
                                                                                                                                                         style="{{empty($field['class']) ? '' : $field['style']}}">
                                                                                                                                                         {{empty($field['label']) ? '' : $field['label']}}</th>
            @endif
        @endforeach
    </thead>
    <tbody>
                                                                                                                                             @foreach ($items as $row)
                                                                                                                                                 <tr>
                @foreach ($row as $column)
                    @if (!is_array($column))
                        <td>{{ $column }}</td>
                    @else
                        <td class="{{empty($column['class']) ? '' : $column['class']}}"
                                                                                                                                                                 style="{{empty($column['style']) ? '' : $column['style']}}">
                            {{empty($column['label']) ? '' : $column['label']}}</td>
                    @endif
                @endforeach
            </tr>
        @endforeach
    </tbody>
</table>

https://chatgpt.com/c/673ec580-b24c-800b-ae7b-c47571a6948c

列定義と行本体を分離するというのが重要。

テーブルでリレーションを表示する場合、ネストになっているのが都合悪いので、.区切りで平坦化するのがいい模様。まあ、こうするしかないか。ネストのループしないといけなくなるから。これは面倒くさいし。

Other

Bladeのレンダー結果の取得

場合によっては、componentにBladeの描画結果を埋め込みたいことがある。

view関数でViewインスタンス作成後に、render/renderContents/getContentsを呼ぶと描画後のHTML文字列を取得できる。

これを使う。{!! $var !!} のような感じ。必要に応じてe()でエスケープする。renderを実行しなくても、Viewインスタンスをそのまま渡すとHTMLになっている。けど、エラーが出たときよくわからないのでrender()しておいたほうがいい。

// index.blade.php

	@component('components.tab', [
		'tabs' => [
			'物件' => view('c211000.form')->render(),
			'代表オーナー' => view('c212100.代表オーナー')->render(),
			'括りオーナー' => view('c212200.括りオーナー')->render(),
		],
	])
	@endcomponent
// tab.blade.php
<section class="tab-wrap">
     @foreach($tabs as $label => $content)
         <label class="tab-label">{{$label}}<input type="radio" name="tab" class="tab-switch" {{$loop->first ? "checked=checked" : ""}} /></label>
        <div class="tab-content">{!! $content !!}</div>
    @endforeach
</section>
パスの取得

現在表示ビューのパスを取得したいことがある。いくつか方法がある。

  • Request::path()
  • Route::current()->uri()

Request::path()がシンプル。

子ビューの変数の使用

基本は不能。全体で共有する方法ならある。

$slotの型

Bladeのcomponentなどで使用するslotはHtmlString。だから、これをstring扱いで、old($name) などに使うとエラーになる。oldは内部で、array_key_existsなどで、inputのキーなどをチェックしている。

$slotを他のプロパティーなどに引き渡す際に、toHtmlで文字列に変換して渡すとよい。

PHPDoc

Bladeのコンポーネントで何の変数が使用可能かわかりにくくなる。対策としてPHPDocの記述がある。

単に、<?phpや@phpのPHPブロック内でPHPDocを記述するだけでいい。

@extends('layouts.app')

@php
/** @var App\Entity\User[] $users */
@endphp

@section('content')
  <h1>ユーザ一覧</h1>
  <ul>
                                 @foreach($users as $user)
                                   <li>{{ $user->getName() }}</li>
  @endforeach
  </ul>
@endsection
<?php /** @var \Illuminate\View\ComponentAttributeBag $attributes */ ?>
<?php /** @var \Illuminate\Support\HtmlString $slot */ ?>

どちらでも問題ない。

Blade内共通処理

https://chatgpt.com/c/673ad9c5-5d48-800b-a937-b8ff4fade246

複数のBladeで共通で登場する処理の共通化。

app/Helpersに追加したヘルパー関数用クラスにstaticで定義して使う。XXHelper.phpみたいな感じ。

Digging Deeper

Artisan Console

Artisan Console - Laravel 5.8 - The PHP Framework For Web Artisans

Laravelでのartisanコマンドやコマンドラインアプリケーションの作成方法が記載されている。

Writing Commands

app/Console/Commandsディレクトリーにコマンドは一般的に格納される。が、別の場所にも配置できる。

Generating Commands

make:commandで新規コマンドを作成できる。

php artisan make:command SendEmails

app/Console/Commandsに指定したコマンド名でファイルが作られる。

なお、作成新するコマンドの名前は、「framework/src/Illuminate/Foundation/Console/StorageLinkCommand.php at b9cf7d3217732e9a0fa4f00e996b3f9cc5bf7abd · laravel/framework」を見る限lり名詞:動詞で、ファイル名は「名詞動詞Command.php」になっている。

Command Structure

signatureとdescriptionプロパティーの記入が必要。これらのプロパティーはartisan listで表示される。handleメソッドにコマンド実行時の処理を配置する。

<?php
 
namespace App\Console\Commands;
 
use App\User;
use App\DripEmailer;
use Illuminate\Console\Command;
 
class SendEmails extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'email:send {user}';
 
    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Send drip e-mails to a user';
 
    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }
 
    /**
     * Execute the console command.
     *
     * @param  \App\DripEmailer  $drip
     * @return mixed
     */
    public function handle(DripEmailer $drip)
    {
        $drip->send(User::find($this->argument('user')));
    }
}

signatureは特に重要。後で詳述する。

Defining Input Expectations

signatureプロパティーでユーザーからの想定入力を定義する。コマンド名、引数、オプションなどを定義できる。

最低限コマンド名 (例だとemail:send) は記載する。

コロンはなくてもいい。signature='some' だとphp artisan someで実行できる。

Arguments

引数がある場合、波括弧で指定できる。

 protected $signature = 'email:send {user}';

この波括弧にはいくつか気法がある。

  • email:send {user?}: オプション引数。
  • email:send {user=default}: デフォルト引数ありのオプション引数。

? =がないと、必須引数。

Options

オプションはハイフン2個--を前置して指定する。オプション引数の有無で2系統ある。オプション引数がない場合、スイッチのような意味合い。

protected $signature = 'email:send {user} {--queue}';

上記の例では--queueがスイッチ系のオプション。--queueが渡されたらtrueになる。それ以外はfalse。

引数がある場合、末尾を=にする。

protected $signature = 'email:send {user} {--queue=}';

以下のように実行する。

php artisan email:send 1 --queue=default

=の後にデフォルト値の指定も可能。

email:send {user} {--queue=default}

短縮形。

オプションの短縮形も指定できる。

email:send {user} {--Q|queue}

短縮形の場合、ハイフン1個で実行できる。また、短縮形は先頭に書かないと認識しない模様。

email:send -Q
Input Descriptions

入力引数とオプションに、:を使って説明を指定できる。

protected $signature = 'email:send
                        {user : The ID of the user}
                        {--queue= : Whether the job should be queued}';

長い説明などで行をまたぎもOK。:の前後にはスペースが必要。

以下のような感じになる。

protected $signature = 'rakuraku:import {table : .envのRAKURAKU_TABLESで指定する、楽楽販売のAPI連携に必要な情報を連想配列のキー名}';
docker exec -i docker-php-1 php ../artisan help rakuraku:import 
Description:
  楽楽販売との連携コマンド。ガス基幹システムから楽楽販売にデータをインポートする。

Usage:
  rakuraku:import <table>

Arguments:
  table                 .envのRAKURAKU_TABLESで指定する、楽楽販売のAPI連携に必要な情報を連想配列のキー名

Options:
  -h, --help            Display this help message
  -q, --quiet           Do not output any message
  -V, --version         Display this application version
      --ansi            Force ANSI output
      --no-ansi         Disable ANSI output
  -n, --no-interaction  Do not ask any interactive question
      --env[=ENV]       The environment the command should run under
  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug

Command I/O

Retrieving Input

引数やオプションはそれぞれargument/arguments|option/optionsで参照できる。

$userId = $this->argument('user');
$arguments = $this->arguments();
$queueName = $this->option('queue');
$options = $this->options();

なお、これらのメソッドはインスタンスメソッドなので、__constructでは使用不能。

options()は以下のような利用可能なオプション一覧とその値を返す。

array (
  'check' => false,
  'import' => NULL,
  'export' => '管理会社',
  'help' => false,
  'quiet' => false,
  'verbose' => false,
  'version' => false,
  'ansi' => false,
  'no-ansi' => false,
  'no-interaction' => false,
  'env' => NULL,
)

現在指定中のオプションやその個数を見たければ、array_filterを使う。

count(array_filter($this->options()))
Writing Output

コンソールへの出力には、line/info/comment/question/errorメソッドを使う。

  • info: 緑色。
  • error: 赤色。
  • line: 色なし。

Other

コマンド

外部プログラム類を実行したいことがある。

storage以下に配置する。か、app_pathなどでパスを参照する。

  • shell_exec
  • $schedule->exec
  • Symfony\Component\Process\Process

同じ場所に配置して実行すればいいと思う。

グループ

Artisanコマンドはartisan some:function {argument} {--option}の書式で定義される。

が、別にコロンはなくてもいい。コロンをつけると、同じプレフィクスのコマンドをグループ化して、ヘルプなどで取り扱ってくれる。わかりやすい。

処理を共通化したければ、クラスにして継承するか、Traitにする。

シンプルなものなら、オプションか引数で分けるというのもありだろう。

File Storage

File Storage - Laravel 5.8 - The PHP Framework For Web Artisans

Laravelでファイル入出力の仕組み。S3など、ストレージシステムを抽象化して扱える。ただし、これは基本的に一般公開用ディレクトリーを念頭に置いている。

File

公式マニュアルに記載がないが、Fileファサードが存在する。これは、PHPのファイル入出力処理のラッパー。Storageと異なり、ディスクシステムの考慮などはしていない。比較的シンプルなファイルIO用。

侍エンジニアブログくらいしか解説がない。

vendor/laravel/framework/src/illuminate/Filesystem/Filesystem.phpがソースコード。

Illuminate\Support\Facades\Fileが名前空間。

  • File::extension
  • File::size
  • File::get
  • File::put
  • File::copy
  • File::delete
  • File::move
  • File::isDirectory
  • File::copyDirectory: 中も全部。
  • File::deleteDirectory: 中も全部。

Helpers

Paths

パス取得関係のヘルパー関数がいくつかある。

  • app_path: appの絶対パス。
  • base_path: アプリのルートパス。
  • config_path: config
  • database_path: database
  • mix
  • public_path
  • resource_path
  • storage_path

特にbase_pathは重要に感じる。

Task Scheduling

Task Scheduling - Laravel 5.8 - The PHP Framework For Web Artisans

Introduction

cronでタスクスケジューリングできるが、cronでやる場合、タスクが増えるたびに設定が必要になる。

LaravelではLaravelのスケジュールコマンドを1個cronに追加するだけで、Laravelのタスクを全部管理できる。

タスクスケジュールはapp/Console/Kernel.phpのscheduleメソッドで定義できる。

Starting The Scheduler

スケジューラー使用時に、以下の項目をcrontabに登録する。

* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1

これで毎分実行される。

Defining Schedules

Defining Schedules

App\Console\Kernelクラスのscheduleメソッドに、全タスクを定義する。

<?php
 
namespace App\Console;
 
use Illuminate\Support\Facades\DB;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
 
class Kernel extends ConsoleKernel
{
    /**
     * The Artisan commands provided by your application.
     *
     * @var array
     */
    protected $commands = [
        //
    ];
 
    /**
     * Define the application's command schedule.
     *
     * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
     * @return void
     */
    protected function schedule(Schedule $schedule)
    {
        $schedule->call(function () {
            DB::table('recent_users')->delete();
        })->daily();
    }
}

callメソッドで、PHPコードを指定できる。

この例では、毎日深夜にClosureを実行している。Closureでテーブルを削除している。__invokeを定義済みのinvokableオブジェクトなら以下でシンプルにかける。

$schedule->call(new DeleteRecentUsers)->daily();
Scheduling Artisan Commands

ArtisanコマンドやOSのコマンドも指定できる。

その場合、commandメソッドを使う。

$schedule->command('emails:send Taylor --force')->daily();
 
$schedule->command(EmailsCommand::class, ['Taylor', '--force'])->daily();

Artisanコマンドの場合、コマンド名かクラスを指定する。

Scheduling Shell Commands

execメソッドはシェルコマンドの発行に使える。

$schedule->exec('node /home/forge/script.js')->daily();
Schedule Frequency Options

php - Laravel schedule - hourly, daily - understanding when exactly start - Stack Overflow

dailyは ["0 0 * * *" ] 相当。

Method Description
->cron('* * * * *'); Run the task on a custom Cron schedule
->everyMinute(); Run the task every minute
->everyFiveMinutes(); Run the task every five minutes
->everyTenMinutes(); Run the task every ten minutes
->everyFifteenMinutes(); Run the task every fifteen minutes
->everyThirtyMinutes(); Run the task every thirty minutes
->hourly(); Run the task every hour
->hourlyAt(17); Run the task every hour at 17 mins past the hour
->daily(); Run the task every day at midnight
->dailyAt('13:00'); Run the task every day at 13:00
->twiceDaily(1, 13); Run the task daily at 1:00 & 13:00
->weekly(); Run the task every week
->weeklyOn(1, '8:00'); Run the task every week on Monday at 8:00
->monthly(); Run the task every month
->monthlyOn(4, '15:00'); Run the task every month on the 4th at 15:00
->quarterly(); Run the task every quarter
->yearly(); Run the task every year
->timezone('America/New_York'); Set the timezone

daily()=dailyAt('00:00')。

Preventing Task Overlaps

デフォルトだと、直前のタスクが実行中でも無視して次のタスクが実行される。withoutOverlappingを使うと、排他処理で他のタスクをさせない。

実行時間が大幅に異なるタスクがあって、終了予定時間が予測できない場合に便利。

デフォルトだと24時間でロックは解除される。引数で分単位で解除時間を指定できる。

タスクが異常終了したらロックファイルが残る。

Laravelタスクスケジュールの多重起動制御のロックを解除する - ハマログ

php artisan schedule:clear-cache

上記のコマンドでロックファイルを削除できる。

Running Tasks On One Server

memcachedかredisでキャッシュサーバーと同期していることが前提。

アプリが複数サーバーで稼働させてスケーリングしている場合、そのままだと全部のサーバーで重複実行される。

処理の最後に->onOneServer()を指定すると、別の場所で実行済みだとスキップするらしい。

Database

LaravelのDBの取得結果は、以下のような行ごとの連想配列になっている。

[
['column1' => 1, 'column2' => 2],
['column1' => 3, 'column2' => 4],
]

Getting Started

Database: Getting Started - Laravel 5.8 - The PHP Framework For 1Web Artisans

クエリービルダー

use Illuminate\Support\Facades\DB;

上記のクラスメソッドで生SQLを使えeる。

Running Raw SQL Queries

LaravelのクエリビルダでSQL文を直接実行(select,insert,update,delete,その他) #Laravel - Qiita

DB::select/insert/update/deleteなどよく使うCRUD操作はメソッドがある。そういうのとは関係なしに、SQLを直実行する場合DB::statementを使う。

DB::statement('drop table users');

なお、DB::statementは1文ずつしか実行できない。複数実行する場合、1文ごとにDB::statementを記載する。

Query Builder

Database: Query Builder - Laravel 5.8 - The PHP Framework For Web Artisans

Retrieving Results

Aggregates

集約メソッドがある。

  • count: レコード数を返す。テーブルの行数などの把握で頻出。データの可否確認には、exists/doesntExistもある。がcountでいい気もする。
  • max
  • min
  • avg
  • sum
Joins
$users = DB::table('users')
            ->join('contacts', 'users.id', '=', 'contacts.user_id')
            ->join('orders', 'users.id', '=', 'orders.user_id')
            ->select('users.*', 'contacts.phone', 'orders.price')
            ->get();

php - Laravel join queries AS - Stack Overflow

なお、joinの第1引数でテーブル指定の部分はASで別名指定が可能な模様。

Query Builder: multiple joins based on array

複数のjoinを行う場合、loopで行う。

$builder = DB::table($table);

foreach($leftJoins as $leftJoin) {
	$builder->leftJoin(...$jeftJoin);
}

$results = $builder->get();
  • join: 内部結合 (集合積)。
  • leftJoin/rightJoin: 外部結合 (集合和)
  • crossJoin: クロス結合。

Where Clauses

重要。いくつか書き方がある。

  • ->where('vote','=', 100);
  • ->where('vote', 100);: 演算子を省略すると=扱い。
  • ->where([['status', '=', '1'], ['subscribed', '<>', '1'],]);: 二次元配列にすれば一度に複数条件渡せる。キー・演算子・バリューのペア。
分割実行

php - Is it possible to split query builder in Laravel? - Stack Overflow

問題ない。

public function getStatuses($dates)
{
    $query = DB::table('tickets');
    if ($dates['from'])
        $query->where('from', $dates['from']);
    if ($dates['to'])
        $query->where('to', $dates['to']);
    $query->select('Active');
    return $query->get()->toArray();
}

whereの戻り値を変数に入れて流用しても問題ない。

Insert

Upsert

Laravel 8からupsertメソッドが登場した。 Laravel 8未満の場合、自分でDB::statementなどで行うしかない。

GitHub - yadakhov/insert-on-duplicate-key」が例。

Transaction

DB::transactionに処理の無名関数を指定してやると、DB処理が失敗したら自動的にロールバックする。

try {
$result = DB::transaction(function () use ($a, $b) {
    DB::table('users')->update(['votes' => 1]);
 
    DB::table('posts')->delete();
    return true;
});
} catch (Exception $e) {
    Log::error($e->getMessage());
    return;
}

2引数でデッドロック用のリトライ回数を指定できる。基本はいらない?

無名関数内でreturnすると、返せる。DB::transactionで失敗時の処理を個別にしたい場合、内部でthrowしてtry-catchをする。

Pagination

Database: Pagination - Laravel 5.8 - The PHP Framework For Web Artisans

テーブルの内容を全表示する場合などで、単純にallやgetでデータを取得・表示させようとすると、データ量が多すぎてメモリーアウトする可能性がある。

それを回避しつつ全データを表示させる仕組みがページネーション。データを一定間隔で分割・表示することでメモリーアウトを防止しながら大量データを表示する。

Introduction

基本的な使い方。

  1. DBやEloquentでのデータ取得時に最後にpaginate(表示数)のメソッドを追加する。
  2. Bladeで{{ $users->links() }}のメソッドを配置。

基本は以上。

<?php
 
namespace App\Http\Controllers;
 
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
 
class UserController extends Controller
{
    /**
     * Show all of the users for the application.
     *
     * @return Response
     */
    public function index()
    {
        $users = DB::table('users')->paginate(15);
 
        return view('user.index', ['users' => $users]);
    }
}
<div class="container">
     @foreach ($users as $user)
         {{ $user->name }}
    @endforeach
</div>
 
{{ $users->links() }}

Paginator Instance Methods

paginateなどが返すPaginatorインスタンスに役立つメソッドがある。

Method Description
$results->count() Get the number of items for the current page.
$results->currentPage() Get the current page number.
$results->firstItem() Get the result number of the first item in the results.
$results->getOptions() Get the paginator options.
$results->getUrlRange($start, $end) Create a range of pagination URLs.
$results->hasMorePages() Determine if there are enough items to split into multiple pages.
$results->items() Get the items for the current page.
$results->lastItem() Get the result number of the last item in the results.
$results->lastPage() Get the page number of the last available page. (Not available when using simplePaginate).
$results->nextPageUrl() Get the URL for the next page.
$results->onFirstPage() Determine if the paginator is on the first page.
$results->perPage() The number of items to be shown per page.
$results->previousPageUrl() Get the URL for the previous page.
$results->total() Determine the total number of matching items in the data store. (Not available when using simplePaginate).
$results->url($page) Get the URL for a given page number.

ヒット件数総数を表示するtotalが特に重要か。

Migrations

Database: Migrations - Laravel 5.8 - The PHP Framework For Web Artisans

マイグレーションはデータベースのバージョン管理のようなもの。データベーススキーマを簡単に修正・共有を可能にする。

Generating Migrations

make:migrationコマンドでマイグレーションファイルを作れる。

php artisan make:migration create_users_table

php artisan make:migration add_votes_to_users_table --table=users

CRUD_テーブル名_tableなどのような命名規則にしておくとわかりやすい。

実行すると、database/migrationsディレクトリーにマイグレーションファイルが生成される。

オプションで--create=テーブル名、--table=テーブル名がある。

現在のテーブルの状況などで、マイグレーションファイルのひな形が変わる。ある程度マイグレーション名から推測してくれるが、日本語などが入るときかなくなる。オプションを指定したほうが無難。

なお、このマイグレーションの名前は非常に重要。この名前がクラス名になるので、既存と被ってはいけない。変更内容が分かるように一意にする必要がある。

Laravel 8からは匿名マイグレーションで名前を意識しなくてもよくなる。

return new class extends Migration
{
    //
};

Migration Structure

コマンド実行で生成されたテンプレートには、upとdownメソッドが用意されている。ここにテーブルの更新と、巻戻の処理を記載する。

Running Migrations

以下のコマンドで用意済みのマイグレーションを実行する。

php artisan migrate
Forcing Migrations To Run In Production
Rolling Back Migrations

マイグレーションを打ち消すようにrollbackコマンドがある。

php artisan migrate:rollback

最後のバッチを自動判別してそのブロックで巻き戻す。--stepでステップ数を指定できる。

php artisan migrate:rollback --step=5

migrate:resetでアプリのマイグレーションを全部戻せる。

php artisan migrate:reset
Rollback & Migrate In Single Command

マイグレーションファイルやシーダーの更新などで、ロールバックとマイグレーションをやり直したいことがある。そういうコマンドがmigrate:refresh。--seedも指定するとシーダー登録もやってくれる。これはよく使う。

php artisan migrate:refresh
// php artisan migrate:reset && php artisan migrate // 相当と思われる
 
// Refresh the database and run all database seeds...
php artisan migrate:refresh --seed
// こちらは最後にphp artisan db:seed相当

これも--stepで指定できる。

Drop All Tables & Migrate

migrate:freshコマンドは、全テーブルを削除してmigrateを実行する。これは、マイグレーションで管理していないテーブルも含むので、危険。基本は使わないほうがいい。

php artisan migrate:fresh
 
php artisan migrate:fresh --seed

Tables

Renaming/Dropping Tables
Schema::rename($from, $to);
Schema::drop('users');
 
Schema::dropIfExists('users');

Columns

Creating Columns
Command Description
$table->bigIncrements('id'); Auto-incrementing UNSIGNED BIGINT (primary key) equivalent column.
$table->bigInteger('votes'); BIGINT equivalent column.
$table->binary('data'); BLOB equivalent column.
$table->boolean('confirmed'); BOOLEAN equivalent column.
$table->char('name', 100); CHAR equivalent column with an optional length.
$table->date('created_at'); DATE equivalent column.
$table->dateTime('created_at'); DATETIME equivalent column.
$table->dateTimeTz('created_at'); DATETIME (with timezone) equivalent column.
$table->decimal('amount', 8, 2); DECIMAL equivalent column with a precision (total digits) and scale (decimal digits).
$table->double('amount', 8, 2); DOUBLE equivalent column with a precision (total digits) and scale (decimal digits).
$table->enum('level', ['easy', 'hard']); ENUM equivalent column.
$table->float('amount', 8, 2); FLOAT equivalent column with a precision (total digits) and scale (decimal digits).
$table->geometry('positions'); GEOMETRY equivalent column.
$table->geometryCollection('positions'); GEOMETRYCOLLECTION equivalent column.
$table->increments('id'); Auto-incrementing UNSIGNED INTEGER (primary key) equivalent column.
$table->integer('votes'); INTEGER equivalent column.
$table->ipAddress('visitor'); IP address equivalent column.
$table->json('options'); JSON equivalent column.
$table->jsonb('options'); JSONB equivalent column.
$table->lineString('positions'); LINESTRING equivalent column.
$table->longText('description'); LONGTEXT equivalent column.
$table->macAddress('device'); MAC address equivalent column.
$table->mediumIncrements('id'); Auto-incrementing UNSIGNED MEDIUMINT (primary key) equivalent column.
$table->mediumInteger('votes'); MEDIUMINT equivalent column.
$table->mediumText('description'); MEDIUMTEXT equivalent column.
$table->morphs('taggable'); Adds taggable_id UNSIGNED BIGINT and taggable_type VARCHAR equivalent columns.
$table->uuidMorphs('taggable'); Adds taggable_id CHAR(36) and taggable_type VARCHAR(255) UUID equivalent columns.
$table->multiLineString('positions'); MULTILINESTRING equivalent column.
$table->multiPoint('positions'); MULTIPOINT equivalent column.
$table->multiPolygon('positions'); MULTIPOLYGON equivalent column.
$table->nullableMorphs('taggable'); Adds nullable versions of morphs() columns.
$table->nullableUuidMorphs('taggable'); Adds nullable versions of uuidMorphs() columns.
$table->nullableTimestamps(); Alias of timestamps() method.
$table->point('position'); POINT equivalent column.
$table->polygon('positions'); POLYGON equivalent column.
$table->rememberToken(); Adds a nullable remember_token VARCHAR(100) equivalent column.
$table->set('flavors', ['strawberry', 'vanilla']); SET equivalent column.
$table->smallIncrements('id'); Auto-incrementing UNSIGNED SMALLINT (primary key) equivalent column.
$table->smallInteger('votes'); SMALLINT equivalent column.
$table->softDeletes(); Adds a nullable deleted_at TIMESTAMP equivalent column for soft deletes.
$table->softDeletesTz(); Adds a nullable deleted_at TIMESTAMP (with timezone) equivalent column for soft deletes.
$table->string('name', 100); VARCHAR equivalent column with a optional length.
$table->text('description'); TEXT equivalent column.
$table->time('sunrise'); TIME equivalent column.
$table->timeTz('sunrise'); TIME (with timezone) equivalent column.
$table->timestamp('added_on'); TIMESTAMP equivalent column.
$table->timestampTz('added_on'); TIMESTAMP (with timezone) equivalent column.
$table->timestamps(); Adds nullable created_at and updated_at TIMESTAMP equivalent columns.
$table->timestampsTz(); Adds nullable created_at and updated_at TIMESTAMP (with timezone) equivalent columns.
$table->tinyIncrements('id'); Auto-incrementing UNSIGNED TINYINT (primary key) equivalent column.
$table->tinyInteger('votes'); TINYINT equivalent column.
$table->unsignedBigInteger('votes'); UNSIGNED BIGINT equivalent column.
$table->unsignedDecimal('amount', 8, 2); UNSIGNED DECIMAL equivalent column with a precision (total digits) and scale (decimal digits).
$table->unsignedInteger('votes'); UNSIGNED INTEGER equivalent column.
$table->unsignedMediumInteger('votes'); UNSIGNED MEDIUMINT equivalent column.
$table->unsignedSmallInteger('votes'); UNSIGNED SMALLINT equivalent column.
$table->unsignedTinyInteger('votes'); UNSIGNED TINYINT equivalent column.
$table->uuid('id'); UUID equivalent column.
$table->year('birth_year'); YEAR equivalent column.
Column Modifiers

カラム種類に加え、いくつかのカラム修飾がある。

Schema::table('users', function (Blueprint $table) {
    $table->string('email')->nullable();
});

以下が特に。

デフォルトだとnullable(false)相当なので、nullを許容するならnullable()の指定が必要。

Modifying Columns

既存カラムの改名や、データ型変更も行えるが、癖がある。

なお、int(10) のような数値型の幅などは、MySQLの独自拡張で、DB独自拡張はマイグレーションのAPIで未対応。

php - Define property zerofill and size on field schema migration with laravel - Stack Overflow

DB::statementで生SQLで対応するしかない。

Prerequisites

事前に依存関係を追加する。

composer require doctrine/dbal
Updating Column Attributes

既存のカラムを別の型に変更する場合、changeメソッドを使う。

Schema::table('users', function (Blueprint $table) {
    $table->string('name', 50)->change();
});
Schema::table('users', function (Blueprint $table) {
    $table->string('name', 50)->nullable()->change();
});

nullableの付与も可能。

後から変更可能な型は以下。

bigInteger, binary, boolean, date, dateTime, dateTimeTz, decimal, integer, json, longText, mediumText, smallInteger, string, text, time, unsignedBigInteger, unsignedInteger and unsignedSmallInteger

Renaming Columns
Dropping Columns

downで戻す場合に使ったりする。

Schema::table('users', function (Blueprint $table) {
    $table->dropColumn('votes');
    $table->dropColumn(['votes', 'avatar', 'location']);
});

なお、非常に紛らわしいのだが、removeColumnメソッドもある (database - What is the difference between removeColumn and dropColumn methods on Laravel Framework? - Stack Overflow)。こちらはBlueprintから削除するだけで、実テーブルからは削除しない。使うことはほぼない。

Indexes

Creating Indexes

schema builder laravel migrations unique on two columns - Stack Overflow

$table->string('email')->unique();

$table->unique('email');
$table->index(['account_id', 'created_at']);
$table->unique('email', 'unique_email');
$table->unique(['account_id', 'email']);
$table->dropUnique(['account_id', 'email']);

複合UNIQUE制約をつけたい場合、uniqueメソッドの第一引数を配列にする。

Other

Command
  • migrate:rollback: マイグレーションをロールバックする。デフォルトだと最後の1回分。--step=Nで回数を指定できる。
  • migrate:reset: 全部ロールバックする。
  • migrate:refresh: rollback+migrate。
  • migrate:fresh: ロールバックではなく、テーブルを削除してから戻す。
既存DBの途中からの管理

今後の変更分だけ管理するということもできる。

なお、中途半端にLaravelのマイグレーションファイルがある場合、migrationsテーブルにデータを手動で登録しておくと、マイグレーション済みと認識できる。

作ってあげるデータは、id,migration, batchを持つデータで、idは1からインクリメント、migrationはマイグレーションのファイル名(.phpは除く)、batchを1(たぶんこれは何度マイグレーションを実行したかを保っておくもので状態を1つ戻すときに使われるのでは?)に設定すればよい。

id migration batch
1 2017_09_01_134421_create_prefectures_table 1
2 2017_09_01_134422_create_cities_table 1

SQLだと以下相当。

insert into migrations(migration, batch) values('2015_12_08_134409_create_tables_script',1);
insert into migrations(migration, batch) values('2015_12_08_134410_create_foreign',1);
insert into migrations(migration, batch) values('2015_12_08_134411_create_index',1);
SQLでのマイグレーション

database - Laravel migration - is it possible to use SQL instead of schema commands to create tables and fields etc? - Stack Overflow

upメソッドにDB::updateなどで生のSQLをそのまま書けばOK。

class AddColumnsToUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('name');
            $table->string('age');
            $table->timestamps();
        });

        DB::update('update users set age = 30 where name = ?', ['John']);
    }
}

downメソッドにはDropIfExistsで削除すればOK。

Cannot declare class TableName, because the name is already in use

マイグレーションファイル作成時に生成されるクラスが、既存のマイグレーションファイルのクラス名と被っているのが原因の一つ。

マイグレーションのファイル名/クラス名は固有にしないといけない。

Laravel 8からは匿名マイグレーションで名前を意識しなくても済むらしい (php - Error migrations: Cannot declare class X, because the name is already in use - Stack Overflow)。

return new class extends Migration {
  //
};
Doctrine\DBAL\Exception  : Unknown column type "mediuminteger" requested.
Doctrine\DBAL\Exception  : Unknown column type "mediuminteger" requested. Any Doctrine type that you use has to be registered with \Doctrine\DBAL\Types\Type::addType(). You can get a list of all the known types with \Doctrine\DBAL\Types\Type::getTypesMap(). If this error occurs during database introspection then you might have forgotten to register all database types for a Doctrine Type. Use AbstractPlatform#registerDoctrineTypeMapping() or have your custom types implement Type#getMappedDatabaseTypes(). If the type name is empty you might have a problem with the cache or forgot some mapping information.

Laravel 5.8のmigrationでmediumintergerが使えない。Doctrineが対応していないから。しかたないからintegerにする。

"Unknown column type "mediumInteger" requested" error after downgrading doctrine/dbal to 2.* · Issue #36509 · laravel/framework · GitHub」によると、Laravel 8.0、Doctrine DBAL 3.0以上で解決しているとのこと。

Seeding

Database: Seeding - Laravel 5.8 - The PHP Framework For Web Artisans

マイグレーションで用意したDBのデフォルトデータの用意の話。

デフォルトデータをシードと呼んでおり、シードを用意する機能をシーディングと呼んでいる。

シードを作成するためのスクリプト (シーダー) ファイルを生成して、記述して実行する流れ。

Writing Seeders

以下のコマンドでシーダーを作成する。

php artisan make:seeder UsersTableSeeder

database/seedsにファイルが作成される。

中身はrunメソッドがあるだけ。run内でinsertするだけ。

Calling Additional Seeders

追加したシーダーはそのままでは認識されない。

DatabaseSeederに登録する。DatabaseSeeder.phpのrunのcallメソッド内に追加する。

public function run()
{
    $this->call([
        UsersTableSeeder::class,
        PostsTableSeeder::class,
        CommentsTableSeeder::class,
    ]);
}

Running Seeders

追加したシーダーを認識させるために以下のコマンドでautoloadを更新する。

composer dump-autoload

シーダーを用意したらコマンドを実行して作成する。

php artisan db:seed

--classでシーダー名を指定もできる。あとからシーダーを追加する場合は--classで指定するイメージ。

Other

Error handling

DB関係の処理で、失敗した場合の扱いが、公式文書に記載がなくて混乱する。

基本はtry-catchでやればいい。

use Exception;

public function result(Request $request)
{
    try {
        $user = User::findOrFail($request->user_id);

        return view('result', compact('user'));
    } catch(Exception $exception) {
        $message = $exception->getMessage();

        if($exception instanceof ModelNotFoundException) {
            $message = 'User with ID: '.$request->user_id.' not found!';
        }

        return back()->withError($message)->withInput();
    }
}

Using Docker I get the error: "SQLSTATE[HY000] [2002] No such file or directory"

php - Using Docker I get the error: "SQLSTATE[HY000 [2002] No such file or directory" - Stack Overflow]

PHPのPDOをDockerコンテナ内で使おうとしたところ、"No such file or directory" エラーが発生した話 #docker-compose - Qiita

dockerで.envのDB_HOSTの指定間違い。dockerのservice名をホスト名に指定する必要がある。

local.ERROR: could not find driver

[2024-07-02 15:35:42] local.ERROR: could not find driver (SQL: select * from `sessions` where `id` = 0cDH7URcrsFzC7hPYlbAQcezNVjdDM16OJh1aCSZ limit 1) {"exception":"[object] (Illuminate\\Database\\QueryException(code: 0): could not find driver (SQL: select * from `sessions` where `id` = 0cDH7URcrsFzC7hPYlbAQcezNVjdDM16OJh1aCSZ limit 1) at /var/www/vendor/laravel/framework/src/Illuminate/Database/Connection.php:664, Doctrine\\DBAL\\Driver\\PDO\\Exception(code: 0): could not find driver at /var/www/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDO/Exception.php:18, PDOException(code: 0): could not find driver at /var/www/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php:40)

このエラーはphpのモジュール不足。LaravelはPDOを使う。mysqliではなく。

RUN docker-php-ext-install pdo_mysql

これが必要。

Log

Laravel SQLの実行クエリログを出力する #PHP - Qiita

方法が2種類ある。

  1. toSqlメソッド。getBindingsでプレースホルダーの値。シンプルな場合に有効。
  2. DB::enableQueryLog/DB::getQueryLog()。複雑な場合と実行時間も。

Eloquent

Getting Started

Eloquent: Getting Started - Laravel 5.8 - The PHP Framework For Web Artisans

Eloquent ORM。

Defining Models

make:modelコマンドでモデルインスタンスを作成できる。

php artisan make:model Flight

-mで新規作成のマイグレーションファイルも一緒に作れる。

Eloquent Model Conventions
Table Names

Laravelで「Base table or view not found: 1146 Table」エラーが出るときの対処法 #PHP - Qiita

Eloquentのモデルでは、デフォルトでクラス名を複数形のスネークケースにしたものをテーブル名とみなす。

SQLSTATE[42S02]: Base table or view not found: 1146 Table 'your_db.your_models' doesn't exist (SQL: select * from `your_models`)

不在の場合、上記のエラーが出る。

デフォルト以外のテーブル名を使いたければ、$tableにテーブル名を指定する。

<?php
 
namespace App;

use Illuminate\Database\Eloquent\Model;
 
class Flight extends Model
{
    /**
     * The table associated with the model.
     *
     * @var string
     */
    protected $table = 'my_flights';
}

プロジェクトでテーブル名に日本語を使う場合など、クラスメイとテーブル名が同一なら、以下のような親クラスを使って、これを継承すると同じ処理を記載しなくていい。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class BaseModel extends Model
{
    function __construct()
    {
        // デフォルトだと複数形のスネークケースのテーブルを探すため、クラス名=テーブル名で上書き。
        $this->table = (new \ReflectionClass($this))->getShortName();
    }
}
Timestamps

LaravelのEloquentモデルでupdated_atがないテーブルを使う方法 #PHP - Qiita

php - Laravel Unknown Column 'updated_at' - Stack Overflow

デフォルトで、Eloquentはcreated_atとupdated_atカラムを期待する。使っていないならば、以下のエラーが出る。

SQLSTATE[42S22]: Column not found: 1054 Unknown column 'updated_at' in 'field list' at /var/www/vendor/laravel/framework/src/Illuminate/Database/Connection.php:664, Doctrine\\DBAL\\Driver\\PDO\\Exception(code: 42S22): SQLSTATE[42S22]: Column not found: 1054 Unknown column 'updated_at' in 'field list' at /var/www/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDO/Exception.php:18, PDOException(code: 42S22): SQLSTATE[42S22]: Column not found: 1054 Unknown column 'updated_at' in 'field list' at /var/www/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php:82)
[stacktrace]

$timestamps=falseにしておく。

<?php
 
namespace App;
 
use Illuminate\Database\Eloquent\Model;
 
class Flight extends Model
{
    /**
     * Indicates if the model should be timestamped.
     *
     * @var bool
     */
    public $timestamps = false;
}

Retrieving Models

Eloquentのモデルは、強力なクエリビルダーと考えてもいい。Eqloquentのallメソッドはモデルテーブルの全結果を返す。これはクエリービルダーとして、各モデルで提供されているので、条件を追加するなら、getメソッドを使う。

つまり、whereなどを使うなら、最後の取得はgetメソッドを使う必要がある。

Eloquentのモデルはクエリービルダーなので、クエリービルダーの全メソッドが使用可能。

モデル名::メソッドの他、select/where/findなども同じものを返すので、メソッドチェーンで呼び出せる。

Retrieving Single Models / Aggregates

モデルからデータを取得するいくつか主要なメソッドがある。

  • find: プライマリーキーから値を探す。
  • all: 全項目を取得する。
  • where:
  • findOrFail
  • findMany

メソッドチェーン

  • first: 先頭項目を取得する。
  • firstOrFail
  • count
  • max

【laravel】データベースの操作:Eloquent ORM編 #Laravel6 - Qiita

Eloquentのfindは扱いが特殊。引数が配列なら配列で返してくれる。

配列で結果が欲しければ、findManyを使う。findManyで常に配列で返すようにしたほうが、型が揃って扱いやすいかもしれない。

Inserting & Updating Models
Insert

モデルインスタンスを作成し、属性をセットし、最後にsaveを呼ぶ。

<?php
 
namespace App\Http\Controllers;
 
use App\Flight;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
 
class FlightController extends Controller
{
    /**
     * Create a new flight instance.
     *
     * @param  Request  $request
     * @return Response
     */
    public function store(Request $request)
    {
        // Validate the request...
 
        $flight = new Flight;
 
        $flight->name = $request->name;
 
        $flight->save();
    }
}
Updates

Eloquent: Getting Started - Laravel 5.8 - The PHP Framework For Web Artisans

$flight = App\Flight::find(1);
 
$flight->name = 'New Flight Name';
 
$flight->save();

saveメソッドはinsertだけでなく、updateにも使える。updated_atも自動で更新される。

クエリーのupdateで配列で複数レコードも一括更新できる。

注意点として、updateの一括更新時は、eloquentで本来発動するsaving/saved/updating/updatedのイベントは発動しない。

Mass Assignment

Add [ to fillable property to allow mass assignment onの解決方法 #初心者 - Qiita]

Add [_token] to fillable property to allow mass assignment on [App\]. 

こんなエラーが出る。

プロパティーの代入で1個ずつ設定するほかに、まとめて1行での保存もできる。ただし、一括割り当ては危険なので、保護されており、fillable属性かguarded属性の指定が必要。

fillメソッドは連想配列でデータをモデルにセットする。

$form = $request->all();
unset($form['_token']);
$flight->fill(['name' => 'Flight 22']);

フォームの項目と対応させておけばそのままセットできる。

fillable

まず、最初に一括割り当て可能な属性を定義する。

fillableプロパティーで、一括割り当て可能なカラムを定義できる。

<?php
 
namespace App;
 
use Illuminate\Database\Eloquent\Model;
 
class Flight extends Model
{
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = ['name'];
}
Guarding Attributes

$fillableはホワイトリスト方式。$guardedも使用できる。一括割り当て可能にしたくない属性を指定する。ブラックリスト。 $fillableと$guardedのどちらかが最低限必要。

$fillableは何か増えるたびに修正必要なので手間。$guardedを基本にするとよいと思う。もっというなら、$guardedでpkを指定するといい。pkはAUTO_INCREMENTで自動増分にすればいいから。

Other Creation Methods

insertもupdateもやることはほぼ同じなので、コードの冒頭で以下のような式でモデル生成部分だけ変更したらよいだろう。

        $model = empty($request['オーナーコード'])
            ? new 楽楽販売_オーナーマスタ : 楽楽販売_オーナーマスタ::find($request['オーナーコード']);

と思っていたが、もっといいのがあった。

firstOrCreate/firstOrNew

firstOrNewは自分でsaveできるように、モデルインスタンスを返してくれる (new モデル相当)。

// Retrieve flight by name, or create it if it doesn't exist...
$flight = App\Flight::firstOrCreate(['name' => 'Flight 10']);
 
// Retrieve flight by name, or create it with the name, delayed, and arrival_time attributes...
$flight = App\Flight::firstOrCreate(
    ['name' => 'Flight 10'],
    ['delayed' => 1, 'arrival_time' => '11:30']
);
 
// Retrieve by name, or instantiate...
$flight = App\Flight::firstOrNew(['name' => 'Flight 10']);
 
// Retrieve by name, or instantiate with the name, delayed, and arrival_time attributes...
$flight = App\Flight::firstOrNew(
    ['name' => 'Flight 10'],
    ['delayed' => 1, 'arrival_time' => '11:30']
);

1番目の配列の情報でモデルを検索して、不在ならば、2番目の配列の情報で作成する。

updateOrCreate

// If there's a flight from Oakland to San Diego, set the price to $99.
// If no matching model exists, create one.
$flight = App\Flight::updateOrCreate(
    ['departure' => 'Oakland', 'destination' => 'San Diego'],
    ['price' => 99, 'discounted' => 1]
);

1番目の配列の情報で検索して、2番目の配列で更新する。1番目の情報で不在なら、全部の情報で更新する。

Mass assignmentの対象。基本はAIのpkをguardedにしておけば問題ないと思う。

ただし、これらのメソッド類は、一度に1レコードずつしか更新できない。大量に行う場合、何回も発動するので遅いかもしれない。そこだけ注意が必要。あまり遅いなら、DBクエリーでやったほうがいいかもしれない。

Laravel で updateOrCreate を使う際に Unknown column 'id' in 'where clause' でハマった #PHP - Qiita

なお、updateOrCreateを使う際は、使用するモデルの主キーの設定をきちんとしておくこと。

Upserts

Laravel 8からupsertメソッドが登場して、一括更新が可能になった。以前から、updateOrCreateがあった。

Relationships

Eloquent: Relationships - Laravel 5.8 - The PHP Framework For Web Artisans

Defining Relationships
One To One
<?php
 
namespace App;
 
use Illuminate\Database\Eloquent\Model;
 
class User extends Model
{
    /**
     * Get the phone record associated with the user.
     */
    public function phone()
    {
        return $this->hasOne('App\Phone');
    }
}

主テーブルに外部キーが埋め込まれていて、シンプルな場合、上記のように外部テーブルをメソッド名にしてhasOneを実行すると全部返せる。非常にシンプル。

プロパティーとしてアクセス可能になる。1対1だから、以下のようにプライマリーキーなどで参照できる。

$phone = User::find(1)->phone;

デフォルトだと、外部キーは、 [モデル名_id] であることを想定している。これじゃないなら、以下のように引数で指定しておく。

return $this->hasOne('App\Phone', 'foreign_key');

そして、外部キーは、元のモデルのidか$primaryKeyと一致する想定になっている。これ以外のキーを使いたければ、hasOneの第3引数で、ローカルキーを指定する。

return $this->hasOne('App\Phone', 'foreign_key', 'local_key');
One To Many

複数の他のモデルを持つ場合。例えば、ブログ投稿は、無限のコメントをもつ。こういう場合のコメント。がOne To Many。これはhasManyで関連付ける。

    public function comments()
    {
        return $this->hasMany('App\Comment');
    }
Many To Many

多対多の関係は、belongsToManyで定義される。

userとroleテーブルがあったとして、テーブル名はアルファベット順でrole_userのようにテーブル名を_で結合する。

<?php
 
namespace App;
 
use Illuminate\Database\Eloquent\Model;
 
class User extends Model
{
    /**
     * The roles that belong to the user.
     */
    public function roles()
    {
        return $this->belongsToMany('App\Role');
    }
}

関係定義でroles動的プロパティーを使用してアクセスできる。

$user = App\User::find(1);
 
foreach ($user->roles as $role) {
    //
}

命名規則を定義時にオーバーライドもできる。第2引数でテーブル名を指定する。

return $this->belongsToMany('App\Role', 'role_user');

またキーの列名もカスタマイズ可能。

return $this->belongsToMany('App\Role', 'role_user', 'user_id', 'role_id');
Retrieving Intermediate Table Columns

このままで通常は問題ない。リレーションで対応する外部テーブルのモデル・レコードを取得できている。

万が一、直接中間テーブルの参照が必要な場合、pivot属性を間に挟んでアクセスする。

$user = App\User::find(1);
 
foreach ($user->roles as $role) {
    echo $role->pivot->created_at;
}

デフォルトではモデルキーのみが存在する。

Querying Relations
Querying Relationship Existence

リレーション先のテーブルで絞り込みたい場合、has/wherehasを使う。

// Retrieve all posts that have at least one comment...
$posts = App\Post::has('comments')->get();
// Retrieve all posts that have three or more comments...
$posts = App\Post::has('comments', '>=', 3)->get();
// Retrieve posts that have at least one comment with votes...
$posts = App\Post::has('comments.votes')->get();

hasは非常に限定的。テーブルがあるか、あとは件数しかない。基本はwhereHasを使う。

use Illuminate\Database\Eloquent\Builder;
 
// Retrieve posts with at least one comment containing words like foo%...
$posts = App\Post::whereHas('comments', function (Builder $query) {
    $query->where('content', 'like', 'foo%');
})->get();
 
// Retrieve posts with at least ten comments containing words like foo%...
$posts = App\Post::whereHas('comments', function (Builder $query) {
    $query->where('content', 'like', 'foo%');
}, '>=', 10)->get();

ただ、このwhereHasはコールバックなどを使わないといけなくて、少々面倒くさい。

whereHasの第一引数にはドット記法が使えるので、ネストしたリレーションも参照できる。これを使わないなら、モデルのメソッドからアクセスすることになるだろうか?

$users = User::whereHas('posts', function ($query) {
    $query->whereHas('comments', function ($query) {
        $query->where('content', 'like', '%Laravel%');
    });
})->get();

whereHasのネストでの対応になる模様。

joinしてフラットにして、カラム名にプレフィクスをつけたほうがわかりやすい。フラットにしなかったら、入れ子になって扱いが面倒になる。が、たいしてプレフィクスをつけるのと手間は変わらないから、まあEloquentでいいと思う。

なお、このgetはデフォルトだと親テーブルしか返さないので必要ならwith()をgetの前にメソッドチェーン。

新しめのLaravelだとwithWhereHasというwithとwhereHasを同時にやってくれるメソッドがある。

https://chatgpt.com/c/673d8956-103c-800b-8a65-caa56a38c8d0

whereHas/withを併用する場合、順番が大事。最初にwhereHasしてからwith。最後にpaginate。この順番。

withは空配列も指定可能。

Eager Loading

Eloquentのリレーションにプロパティーでのアクセス時、リレーションデータは "lazy loaded" になる。これはプロパティーへのアクセスまで、実際には読み込まないことを意味する。つまり、アクセスのたびに読み込みが発生する。Eager Loadが可能になる。

Eagerは、熱心な、せっかちなという形容詞。

例えば、N件のデータを全部表示させたい場合、以下のようなコードを記述する。

<?php
 
namespace App;
 
use Illuminate\Database\Eloquent\Model;
 
class Book extends Model
{
    /**
     * Get the author that wrote the book.
     */
    public function author()
    {
        return $this->belongsTo('App\Author');
    }
}
$books = App\Book::all();
 
foreach ($books as $book) {
    echo $book->author->name;
}

最初に全取得+1、N件のデータを取得+Nで、合計1+N回のクエリーが発行される。

Eager loadによりこれを2回のクエリー発行で収めることができる。withメソッドを使う。

$books = App\Book::with('author')->get();
 
foreach ($books as $book) {
    echo $book->author->name;
}

これは具体的には以下のSQLの実行になる。

select * from books
 
select * from authors where id in (1, 2, 3, 4, 5, ...)

ただ、Eager loadすると、クエリーの数は減ってもモデルのアクセスが増えて、メモリーの使用量と実行速度が遅くなって、逆に悪化することがある (Laravel - Eager loading can be bad! : r/laravelLaravel - Eager loading can be bad! | Personal BlogHow the new 'One Of Many' Laravel relationship made my project 600 times faster - DEV Community)。

特に、hasManyの関係で、1対1じゃなくて、複数のモデルが入っている場合。

Laravel 8.42からLatestOfManyなどのメソッドが追加されており、これを使えば回避できる。これを使わない場合、JOINとMAXなどを駆使する必要があった。

ひとまず、hasManyとか、性能を見て、問題あったらeager loadingしたり工夫することにする。

複数のテーブルと結合したい場合、withに複数指定する。必要な列が決まっているなら、withのテーブル名の後に:で列名指定でOK。

Eager Loading By Default

交差テーブルなど、正規化の都合でテーブルを分離しているだけで、使うときは基本的に常に結合を想定するテーブルもある。 そういうときに毎回withでテーブルを指定するのは面倒だし、テーブルアクセスごとにどのテーブルと連結しているかを指定したり考慮必要なのは面倒。これの回避策として、$withプロパティーがある。ここで関連テーブルを指定すると、常にwithで結合したものを返す。

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Book extends Model
{
    /**
     * The relationships that should always be loaded.
     *
     * @var array
     */
    protected $with = ['author'];
 
    /**
     * Get the author that wrote the book.
     */
    public function author()
    {
        return $this->belongsTo(Author::class);
    }
 
    /**
     * Get the genre of the book.
     */
    public function genre()
    {
        return $this->belongsTo(Genre::class);
    }
}

$withプロパティーがある状態で、結合せずに特定テーブルとの連結が欲しければ、withoutかwithOnlyを指定する。 withoutは$withプロパティーで指定したテーブル名を指定して除外する。withOnlyは$withを上書き。

Constraining Eager Loads
Inserting & Updating Related Models
The Save Method

多対多の関係の保存用に、いくつかのメソッドがある。

$comment = new App\Comment(['message' => 'A new comment.']);
 
$post = App\Post::find(1);
 
$post->comments()->save($comment);

関係のsaveメソッドから関係先のモデルに挿入できる。

関係に属する

associateメソッドで関係を新しく設定することもできる。

$account = App\Account::find(10);
 
$user->account()->associate($account);
 
$user->save();

削除はdissociate()

$user->account()->dissociate();
 
$user->save();
多対多の関係

取付/取外

多対多の場合は扱いが異なる。

attachメソッドで中間テーブルを更新する。

$user = App\User::find(1);
 
$user->roles()->attach($roleId);

中間テーブルに挿入する追加データも設定できる。

$user->roles()->attach($roleId, ['expires' => $expires]);

中間テーブルのレコード削除はdetach。

// Detach a single role from the user...
$user->roles()->detach($roleId);
 
// Detach all roles from the user...
$user->roles()->detach();

配列で指定もできる。

$user = App\User::find(1);
 
$user->roles()->detach([1, 2, 3]);
 
$user->roles()->attach([
    1 => ['expires' => $expires],
    2 => ['expires' => $expires]
]);

同期

syncメソッドで、指定した配列で上書き・同期することもできる。

$user->roles()->sync([1, 2, 3]);

idの他の列も更新も可能。

$user->roles()->sync([1 => ['expires' => true], 2, 3]);
Other
リレーションの列名エイリアス

Eloquentでリレーションで結合時、カラム名が同じだと困る。

別名をつける方法がいくつかある。

  • .get/.select: ->get(['tags.name AS tag_name', 'products.*'])/ ->select('tags.name AS tag_name', 'products.*')
  • アクセサー

get/selectでやるのがよさそう。

リレーションの把握

モデルインスタンス取得後 (createOrUpdate)。

getRelations()でリレーションのオブジェクトを取得できる。連想配列でキー・バリュー形式でキー名が取れる模様。

Other

列の存在確認

いくつか方法がある。

  • use Illuminate\Support\Facades\Schema; Schema::hasColumn('テーブル名', '列名'): これが一番シンプル。
  • $model['列名']: インスタンス生成後ならこれ。
Eloquent vs. Query Builder

ORMとクエリービルダー。どこでどう使い分けるか?最初の2個の記事が参考になる。

  • ユーザーフォームのような基本的で簡単なCRUD処理はEloquent
  • 結合を含んだり、大量データの処理が必要な複雑な場合にクエリービルダー。

こういう方針で使い分けるといい。結合とかになると、モデルの範囲外になる。$withプロパティーを使うと、結合もいけなくはない。

レコードセットなのか、モデルなのかを意識する。

また、どちらを使うかで、返却値が違うことを注意する。Eloquentだとオブジェクト、DBクエリーだと配列。そのままだとコード修正が必要。

Eloquentのオブジェクトのほうが意味が分かりやすい。可変プロパティーでアクセスすればいい。

列名の取得

Eloquentのモデル自体は列名を保有していないらしい。

use Illuminate\Support\Facades\Schema;
$columns = Schema::getColumnListing('users'); // users table
dd($columns); // dump the result and die
        $headings = DB::connection()
            ->getSchemaBuilder()
            ->getColumnListing('colaborators');
$item = News::find($request->newsID);
$attributes = array_keys($item->getOriginal());
$attributes = array_keys($item->getAttributes());

モデルインスタンスを作るところは、createOrUpdateで一時的に作るとよい。なお、getOriginalもgetAttbitutesもリレーション先のデータはない。

::query

EloquentのORMのモデルを使用する際は、<Model>::findなどのメソッドを使う。

が、インスタンスを生成してから、条件に応じて処理を実行したい場合など、都合が悪いことがある。

インスタンス生成用に<Model>::query()がある。クエリビルダーインスタンスを返す。(new User())->query()のように空インスタンスを作ってからやるのと同じ。<Model>::query()->findなどのように、メソッドの前に挟める。

これを使う形で書いておくと、コードの書き方がきれいになったり、コード補完が効きやすかったりする。

SQL関数

how to Passing MySQL functions to Eloquent ORM query? - deBUG.to

Eloquentのwhere関数内でSQL関数は使えない。工夫が必要。

Database: Query Builder - Laravel 5.8 - The PHP Framework For Web Artisans

DB::rawで書く。

あるいは、selectRaw/whereRawなどを使う。

結果のオブジェクト・配列変換

EloquentのDB取得の結果は、配列になっている。が、DBクエリーの場合、オブジェクトになっている。

型が違っていろいろ困る。DBクエリーのオブジェクトを配列にしたい。

Eloquentを使わずに、配列にしたい場合、mapやtransformで変換必要。

$response['data'] = DB::table('customers')  
    // query conditions, etc
    ->get()
    ->map(function ($item, $key) {
        return (array) $item;
    })
    ->all();

最後のallも必要。これでEloquentと共通化できる。

一括更新

How to set every row to the same value with Laravel's Eloquent/Fluent? - Stack Overflow

マイグレーションなどで、列を切り詰めたり一括更新したいことがある。

Model::query()->update(['confirmed' => 1]);

これがシンプル。

【Laravel】バルクアップデートを行う方法 #MySQL - Qiita」に記載のall()->update()はallが配列を返すのでダメ。

主キーの取得

ChatGPT

モデルの定義時に、protected $primaryKeyで主キーを設定している。インスタンスでこの情報を保有しておりアクセス可能。

  • 主キーのカラム名: getKeyName() または $primaryKey プロパティで取得。
  • 特定レコードの主キーの値: getKey() で取得。

$primaryKeyはprotectedだからアクセスしにくい。(new Model)->getKeyName()などでアクセスするのがよい。

https://chatgpt.com/c/67354314-fbdc-800b-a4e4-65f96d1a8fb2

リレーション先の主キーはgetRelated()でインスタンスを取得すれば同じ方法でアクセスできる。

// Postモデル内
public function user()
{
    return $this->belongsTo(User::class);
}

// 主キー名の取得
$post = Post::find(1);
$primaryKeyName = $post->user()->getRelated()->getKeyName();

echo $primaryKeyName;
モデルのリレーションの反復

Eloquentのモデルインスタンスは特殊なオブジェクトでそのままforeachでテーブルカラム、外部リレーションのキーにアクセスできない。

反復させたければ、toArray()で連想配列に変換する必要がある。

    public function getFields(): array
	{
        $model = $this->modelClass::first() ?? new $this->modelClass();
        return $this->getColumns($model->toArray());
	}

    public function getColumns(array $model_array, string $relation = ''): array
    {
        $columns = array_map(function($e)use($relation){return $relation ? $relation.'.'.$e : $e;}, array_keys($model_array));
        foreach($model_array as $key => $value) {
            if (is_array($value)) {
                $relation = $relation ? $relation.'.'.$key : $key;
                $columns = array_merge($columns, $this->getColumns($value, $relation));
            }

        }
        return $columns;
    }

Testing

Getting Started

Testing: Getting Started - Laravel 5.8 - The PHP Framework For Web Artisans

Introduction

Laravelはテストを念頭に置いており、デフォルトでPHPHUnitに対応していて、phpunit.xmlもある。

デフォルトで、testsディレクトリーは、Feature Unitの2ディレクトリーを持つ。Unitは関数単位の単体テスト。FeatureはHTTPリクエストとJSONエンドポイントなど、複数のオブジェクトの対話を含む大き目のテスト。

両方のディレクトリーに、ExampleTest.phpがある。

https://chatgpt.com/c/673be4e7-b73c-800b-9d18-3167d235ce06

基本はUnitディレクトリーにファイルを配置する。

tests/Unit/配下をさらにディレクトリー分割もできる。namespaceだけ注意すれば、後は自動で認識してくれる。

気軽に作っていくとよい。

Environment

pupunitのテスト時、phpunit.xmlの<server name="APP_ENV" value="testing"/>の設定により、Laravelは設定環境をtestingにする。また、自動的にセッションとキャッシュをarrayドライバーに自動的に構成する。つまり、テスト中はセッションやキャッシュを保持しない。

テスト実行前に、config:clearで設定キャッシュを削除しておく。

また、.env.testingファイルをプロジェクトルートに作成できる。このファイルは、PUPUnitテスト実行中か、artisanコマンドで--env-testingを指定時に、.envを上書きする。

Creating & Running Tests

テストケースの新規作成には、make:test Artisanコマンドを使う。

// Create a test in the Feature directory...
php artisan make:test UserTest
 
// Create a test in the Unit directory...
php artisan make:test UserTest --unit

--unitの有無で、UnitディレクトリーかFeatureディレクトリーにテストファイルのテンプレートが作られる。

テストが作成されたら、通常のPHPUnit同様に、テストメソッドを定義していく。phppunitコマンドでtestを実行する。

<?php
 
namespace Tests\Unit;
 
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
 
class ExampleTest extends TestCase
{
    /**
     * A basic test example.
     *
     * @return void
     */
    public function testBasicTest()
    {
        $this->assertTrue(true);
    }
}

テストクラス内で、setUp/tearDownメソッドを定義する場合、parent::setUp/parent::tearCownを忘れずに呼ぶこと。

Other

ディレクトリー構造

https://chatgpt.com/c/673be4e7-b73c-800b-9d18-3167d235ce06

基本はUnitディレクトリーにファイルを配置する。

tests/Unit/配下をさらにディレクトリー分割もできる。namespaceだけ注意すれば、後は自動で認識してくれる。

気軽に作っていくとよい。

ディレクトリーも指定できる。

php artisan make:test Models/UserTest --unit

これでtests/Unit/Models/UserTest.phpができる。

以下の基準でディレクトリー分割するとわかりやすい。

  • テスト対象ごとに分ける
    • モデル、サービス、リポジトリ、ヘルパーなど、アプリケーションの層や責務に応じて分割。
  • 機能やモジュール単位で分ける
    • たとえば「ユーザー管理」「注文管理」「在庫管理」などのモジュールごとにディレクトリを作成。

テストの実行はphpunitでもいいし、php artisan test (Laravel 8以上) でもOKの模様。

HTTP Tests

HTTP Tests - Laravel 5.8 - The PHP Framework For Web Artisans

Laravel固有に近いテスト。

Introduction

HTTPリクエストとその出力の基本的なテスト。内容的に結合試験、Featureのテストになる。

<?php
 
namespace Tests\Feature;
 
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
 
class ExampleTest extends TestCase
{
    /**
     * A basic test example.
     *
     * @return void
     */
    public function testBasicTest()
    {
        $response = $this->get('/');
 
        $response->assertStatus(200);

        // 特定の文字列が含まれていることを確認
        $response->assertSee('Welcome to My Website');

        // 特定のHTML要素が含まれていることを確認
        $response->assertSee('<h1>Home</h1>', false); // 第二引数をfalseにするとHTMLタグを検証

    }
}

リクエストを呼び出して、その終了ステータスをチェックするのがまず一番基本的なテスト。

他に、assertSeeで特定文字列の存在チェック。これもけっこう基本。

Console Tests

ユーザーの入力を要求するようなコンソールコマンドのテストもできる。

  • expectsQuestionメソッド:、ユーザー入力のモックを簡単に許可する。
  • assertExitCode/expectsOutputメソッドで、終了コードと出力テキストも指定できる。

例えば、以下のコンソールコマンドがあったとする。

Artisan::command('question', function () {
    $name = $this->ask('What is your name?');
 
    $language = $this->choice('Which language do you program in?', [
        'PHP',
        'Ruby',
        'Python',
    ]);
 
    $this->line('Your name is '.$name.' and you program in '.$language.'.');
});

expectsQuestion/assertExitCode/expectsOutputメソッドで、以下のようにテストできる。

/**
 * Test a console command.
 *
 * @return void
 */
public function test_console_command()
{
    $this->artisan('question')
         ->expectsQuestion('What is your name?', 'Taylor Otwell')
         ->expectsQuestion('Which language do you program in?', 'PHP')
         ->expectsOutput('Your name is Taylor Otwell and you program in PHP.')
         ->assertExitCode(0);
}

Database Testing

シーダーでデータの自動登録方法を整理した。ただ、たくさんデータを登録してチェックする場合、ダミーデータを自動生成したい。Factoryというのでダミーデータを作成できる。

Generating Factories
php artisan make:factory PostFactory

database/factoriesに生成される。

--modelオプションで使用するモデル (テーブル名) を指定しておくと、テンプレートも作成してくれる。

Other

Facade

【Laravel】ファサードとは?何が便利か?どういう仕組みか? #初心者 - Qiita

インスタンスメソッドをstaticメソッドのように使うための仕組み。ややこしいだけなのでいらないかなと思う。

Controllerの共通処理

例えば、/city/nycで都市都市の一覧表示。/city/nyc/streetで該当都市の通りの一覧表示。こういうタイプの処理を都市ごとに実装するというようなことがよくある。

ただし、表示処理は共通処理があったりする。

この共通処理をどうまとめて実装するか?いくつか方法がある。

  • 親Controller
  • ファサード/トレイト (関数クラス)/サービスクラス
  • middleware
  • モデルに共通処理を定義して呼び出し。
  • validation/FormRequest

Controllerの前後に挟み込む感じならmiddleware、Controllerの処理の中でやりたいなら親Controllerか、ファサード/トレイト/サービスクラス、DB周りならModel?

基本はmiddlewareの模様。ビジネスロジックになるなら、サービスクラスを使うのがいい。トレイトでやる場合、app/traitsに格納する感じ。

やっぱりサービスクラスに出すのいい。

Coding Style Guide

PSR-2/4に準拠。

ローカル変数名やプロパティーの記法の指定がない。

Laravel本体のソースコードを見るとcamelCaseになっているのでこれに合わせる。

Bladeファイル名 [Laravel naming convention for blade files - Stack Overflow]

チェインケースかcamelCaseを推奨。特に決まってはいない。

framework/src/Illuminate/Foundation/resources/exceptions/renderer/components/theme-switcher.blade.php at 1a1a61068bc3c5594376a559e424ae09ec3fe64a · laravel/framework」にあるように、Laravel本体はチェインケースなので、チェインケースがよいだろう。

component類のslot名はcamelCase [framework/tests/View/Blade/BladeComponentTagCompilerTest.php at 7d26b7ee454a0ccc339db92a641487f668b44331 · laravel/framework]。

コマンド

framework/src/Illuminate/Foundation/Console/StorageLinkCommand.php at b9cf7d3217732e9a0fa4f00e996b3f9cc5bf7abd · laravel/framework

  • 名詞:動詞
  • 名詞動詞Command.php

ViewCacheCommand/ViewClearCommandなど1コマンド1ファイル。

ただし、make:command/CommandMakeCommandなど一部命名規則に外れるものがある。