「Laravel」の版間の差分

提供:senooken JP Wiki
(Task Scheduling Introduction)
(Inserting & Updating Related Models The Save Method)
 
(同じ利用者による、間の53版が非表示)
12行目: 12行目:
* Laravelのソースコード (外部モジュール類も含む): [https://github.com/laravel/laravel laravel/laravel: Laravel is a web application framework with expressive, elegant syntax. We’ve already laid the foundation for your next big idea — freeing you to create without sweating the small things.]
* Laravelのソースコード (外部モジュール類も含む): [https://github.com/laravel/laravel laravel/laravel: Laravel is a web application framework with expressive, elegant syntax. We’ve already laid the foundation for your next big idea — freeing you to create without sweating the small things.]
* Laravel独自のフレームワーク部分のソースコード: [https://github.com/laravel/framework laravel/framework: The Laravel Framework.]
* Laravel独自のフレームワーク部分のソースコード: [https://github.com/laravel/framework laravel/framework: The Laravel Framework.]
* API: [https://laravel.com/api/6.x/ Namespaces | Laravel API]


== Getting Started ==
== Getting Started ==
30行目: 31行目:


つまり、基本は設定値の参照はconfig関数で行う。
つまり、基本は設定値の参照はconfig関数で行う。
===== Additional Directory =====
[https://stackoverflow.com/questions/29226956/laravel-5-config-sub-folder php - Laravel 5 - Config sub-folder - Stack Overflow]
configディレクトリーに、ディレクトリーを追加することもできる。DotEnvライブラリーの作法と思われる。
config('subfolder.myfile.var');
上記のような命名で、ディレクトリーも解釈される。config('directory')を実行すると、ディレクトリー内の全部が連想配列で取得できる。
===== Array in .env =====
* [https://stackoverflow.com/questions/69703492/defining-an-array-as-an-env-variable-in-laravel-8 php - Defining an array as an .env variable in laravel 8 - Stack Overflow]
.envは環境変数の設定ファイル。そのままだと配列は使えない。
* コンマ区切り
* json
やるとしたら上記で記述しておいて、読込後にデコードする。
===== .env format =====
* [https://github.com/vlucas/phpdotenv GitHub - vlucas/phpdotenv: Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.]
* [https://github.com/bkeepers/dotenv bkeepers/dotenv: A Ruby gem to load environment variables from `.env`.]
.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'
<nowiki>;</nowiki><nowiki>
                                      \"}}"</nowiki>


====Directory Structure====
====Directory Structure====
67行目: 105行目:


コードがモジュール化されて、保守しやすく整理される。
コードがモジュール化されて、保守しやすく整理される。
=== Other ===
==== 独自ライブラリーの追加 ====
[https://ameblo.jp/itboy/entry-12067906451.html Laravelで独自に作ったライブラリファイルへのパスの通し方 | A Day In The Boy's Life]
自前のライブラリーを追加したくなった。追加方法がいくつかある。
composerを使う方法が簡単。


== The Basics ==
== The Basics ==
264行目: 311行目:
*logger: Log::debug相当。引数を空にするとloggerインスタンスを取得できて、そこから個別のエラーレベルに出力もできる。
*logger: Log::debug相当。引数を空にするとloggerインスタンスを取得できて、そこから個別のエラーレベルに出力もできる。
Log::info/Log::debugはuse宣言が必要で面倒なので、info/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=====
300行目: 350行目:


  <nowiki><div class="message">
  <nowiki><div class="message">
                                                              <p class="msg_title">{{$msg_title}}</nowiki><nowiki></p></nowiki>
                                                                                                                  <p class="msg_title">{{$msg_title}}</nowiki><nowiki></p></nowiki>
   <nowiki><p class="msg_content">{{$msg_content}}</nowiki><nowiki></p></nowiki>
   <nowiki><p class="msg_content">{{$msg_content}}</nowiki><nowiki></p></nowiki>
  <nowiki> </nowiki><nowiki>{{$slot}}</nowiki>
  <nowiki> </nowiki><nowiki>{{$slot}}</nowiki>
392行目: 442行目:
JavaScript変数として、配列をJSONとして描画したいことがあるだろう。
JavaScript変数として、配列をJSONとして描画したいことがあるだろう。
  <nowiki><script>
  <nowiki><script>
          var app = <?php echo json_encode($array); ?>;
                                                              var app = <?php echo json_encode($array); ?>;
          var app = {{json_encode($array)}}</nowiki>;
                                                              var app = {{json_encode($array)}}</nowiki>;
       var app = {!! json_encode($array) !!};
       var app = {!! json_encode($array) !!};
  <nowiki> </nowiki>    var array = JSON.parse('<nowiki>{{ json_encode($theArray) }}</nowiki>');
  <nowiki> </nowiki>    var array = JSON.parse('<nowiki>{{ json_encode($theArray) }}</nowiki>');
422行目: 472行目:
  @verbatim
  @verbatim
  <nowiki> </nowiki>  <nowiki><div class="container">
  <nowiki> </nowiki>  <nowiki><div class="container">
                                                                        Hello, {{ name }}</nowiki>.
                                                                                                                              Hello, {{ name }}</nowiki>.
  <nowiki> </nowiki>  <nowiki></div></nowiki>
  <nowiki> </nowiki>  <nowiki></div></nowiki>
  @endverbatim
  @endverbatim
603行目: 653行目:


コンポーネントやテンプレート固有で必要なものを、同じファイルに記載しておいて、反映だけstackでまとめてできる。これが利点だろう。
コンポーネントやテンプレート固有で必要なものを、同じファイルに記載しておいて、反映だけstackでまとめてできる。これが利点だろう。
[https://laravel.com/docs/9.x/blade#the-once-directive Blade Templates - Laravel 9.x - The PHP Framework For Web Artisans]
[https://laravel.com/docs/7.x/blade#the-once-directive Blade Templates - Laravel 7.x - The PHP Framework For Web Artisans]
Laravel 9.xで@pushOnce。Laravel 7.xで@onceがある。
componentにcssやscriptを含めたい場合に@pushを使う。
=====Component=====
=====Component=====
コンポーネントのパターンがある。
コンポーネントのパターンがある。
615行目: 673行目:
  <nowiki> </nowiki>  fields = [['label' => field1, 'class' => <nowiki>''</nowiki>, 'style' => <nowiki>''</nowiki>], ['label' => field1, 'class' => <nowiki>''</nowiki>, 'style' => <nowiki>''</nowiki>]]
  <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>  items = [['label' => <nowiki>''</nowiki>, 'class' => <nowiki>''</nowiki>, 'style' => <nowiki>''</nowiki>], ['label' => <nowiki>''</nowiki>, 'class' => <nowiki>''</nowiki>, 'style' => <nowiki>''</nowiki><nowiki>]]
                                              --}}</nowiki>
                                                                                                  --}}</nowiki>
  <nowiki><table class="table table-sm table-bordered">
  <nowiki><table class="table table-sm table-bordered">
                                                  <thead class="bg-info text-center">
                                                                                                        <thead class="bg-info text-center">
                                                      @foreach ($fields as $field)
                                                                                                            @foreach ($fields as $field)
                                                          @if (!is_array($field))
                                                                                                                @if (!is_array($field))
                                                              <th>{{ $field }}</nowiki><nowiki></th></nowiki>
                                                                                                                    <th>{{ $field }}</nowiki><nowiki></th></nowiki>
  <nowiki> </nowiki>          @else
  <nowiki> </nowiki>          @else
  <nowiki> </nowiki>              <nowiki><th class="{{empty($field['class']) ? '' : $field['class']}}"
  <nowiki> </nowiki>              <nowiki><th class="{{empty($field['class']) ? '' : $field['class']}}"
                                                                  style="{{empty($field['class']) ? '' : $field['style']}}">
                                                                                                                        style="{{empty($field['class']) ? '' : $field['style']}}">
                                                                  {{empty($field['label']) ? '' : $field['label']}}</nowiki><nowiki></th></nowiki>
                                                                                                                        {{empty($field['label']) ? '' : $field['label']}}</nowiki><nowiki></th></nowiki>
  <nowiki> </nowiki>          @endif
  <nowiki> </nowiki>          @endif
  <nowiki> </nowiki>      @endforeach
  <nowiki> </nowiki>      @endforeach
  <nowiki> </nowiki>  <nowiki></thead></nowiki>
  <nowiki> </nowiki>  <nowiki></thead></nowiki>
  <nowiki> </nowiki>  <nowiki><tbody>
  <nowiki> </nowiki>  <nowiki><tbody>
                                                      @foreach ($items as $row)
                                                                                                            @foreach ($items as $row)
                                                          <tr></nowiki>
                                                                                                                <tr></nowiki>
  <nowiki> </nowiki>              @foreach ($row as $column)
  <nowiki> </nowiki>              @foreach ($row as $column)
  <nowiki> </nowiki>                  @if (!is_array($column))
  <nowiki> </nowiki>                  @if (!is_array($column))
636行目: 694行目:
  <nowiki> </nowiki>                  @else
  <nowiki> </nowiki>                  @else
  <nowiki> </nowiki>                      <nowiki><td class="{{empty($column['class']) ? '' : $column['class']}}"
  <nowiki> </nowiki>                      <nowiki><td class="{{empty($column['class']) ? '' : $column['class']}}"
                                                                          style="{{empty($column['style']) ? '' : $column['style']}}"></nowiki>
                                                                                                                                style="{{empty($column['style']) ? '' : $column['style']}}"></nowiki>
  <nowiki> </nowiki>                          <nowiki>{{empty($column['label']) ? '' : $column['label']}}</nowiki><nowiki></td></nowiki>
  <nowiki> </nowiki>                          <nowiki>{{empty($column['label']) ? '' : $column['label']}}</nowiki><nowiki></td></nowiki>
  <nowiki> </nowiki>                  @endif
  <nowiki> </nowiki>                  @endif
688行目: 746行目:
  php artisan make:command SendEmails
  php artisan make:command SendEmails
app/Console/Commandsに指定したコマンド名でファイルが作られる。
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」になっている。


===== Command Structure =====
===== Command Structure =====
742行目: 802行目:


最低限コマンド名 (例だとemail:send) は記載する。
最低限コマンド名 (例だとemail:send) は記載する。
コロンはなくてもいい。signature='some' だとphp artisan someで実行できる。


===== Arguments =====
===== Arguments =====
753行目: 815行目:
? =がないと、必須引数。
? =がないと、必須引数。


==== Writing Output ====
===== Options =====
コンソールへの出力には、line/info/comment/question/errorメソッドを使う。
オプションはハイフン2個--を前置して指定する。オプション引数の有無で2系統ある。オプション引数がない場合、スイッチのような意味合い。
protected $signature = 'email:send {user} {--queue}';
上記の例では--queueがスイッチ系のオプション。--queueが渡されたらtrueになる。それ以外はfalse。


* info: 緑色。
引数がある場合、末尾を=にする。
* error: 赤色。
protected $signature = 'email:send {user} {--queue=}';
* line: 色なし。
以下のように実行する。
php artisan email:send 1 --queue=default
=の後にデフォルト値の指定も可能。
email:send {user} {--queue=default}
短縮形。


=== Task Scheduling ===
オプションの短縮形も指定できる。
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


==== Introduction ====
Description:
cronでタスクスケジューリングできるが、cronでやる場合、タスクが増えるたびに設定が必要になる。
  楽楽販売との連携コマンド。ガス基幹システムから楽楽販売にデータをインポートする。
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


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


タスクスケジュールはapp/Console/Kernel.phpのscheduleメソッドで定義できる。
===== Retrieving Input =====
引数やオプションはそれぞれargument/arguments|option/optionsで参照できる。
$userId = $this->argument('user');
$arguments = $this->arguments();
$queueName = $this->option('queue');
$options = $this->options();
なお、これらのメソッドはインスタンスメソッドなので、__constructでは使用不能。


===== Starting The Scheduler =====
options()は以下のような利用可能なオプション一覧とその値を返す。
スケジューラー使用時に、以下の項目をcrontabに登録する。
array (
  * * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
  '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()))


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


===== Defining Schedules =====
* info: 緑色。
App\Console\Kernelクラスのscheduleメソッドに、全タスクを定義する。
* error: 赤色。
<?php
* line: 色なし。
 
 
namespace App\Console;
==== Other ====
 
 
use Illuminate\Support\Facades\DB;
===== コマンド =====
use Illuminate\Console\Scheduling\Schedule;
 
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
* [https://magecomp.com/blog/executing-shell-script-file-from-laravel-application/?srsltid=AfmBOopfaX3lSORTemqAiUqOwM3UfUSia1O8or1wX3kO3AnSmoKj308f Executing a Shell Script File from a Laravel Application - MageComp]
 
* [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?]
class Kernel extends ConsoleKernel
* [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]
    /**
 
      * The Artisan commands provided by your application.
外部プログラム類を実行したいことがある。
      *
 
      * @var array
storage以下に配置する。か、app_pathなどでパスを参照する。
      */
 
    protected $commands = [
* shell_exec
        //
* $schedule->exec
    ];
* Symfony\Component\Process\Process
 
 
    /**
同じ場所に配置して実行すればいいと思う。
      * Define the application's command schedule.
 
      *
===== グループ =====
      * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
 
      * @return void
* [https://stackoverflow.com/questions/52257533/laravel-artisan-command-with-sub-methods Laravel artisan command with sub methods - Stack Overflow]
      */
* [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]
    protected function schedule(Schedule $schedule)
 
    {
Artisanコマンドはartisan some:function {argument} {--option}の書式で定義される。
        $schedule->call(function () {
 
            DB::table('recent_users')->delete();
が、別にコロンはなくてもいい。コロンをつけると、同じプレフィクスのコマンドをグループ化して、ヘルプなどで取り扱ってくれる。わかりやすい。
        })->daily();
 
    }
処理を共通化したければ、クラスにして継承するか、Traitにする。
}
 
callメソッドで、PHPコードを指定できる。
シンプルなものなら、オプションか引数で分けるというのもありだろう。


この例では、毎日深夜にClosureを実行している。Closureでテーブルを削除している。__invokeを定義済みのinvokableオブジェクトなら以下でシンプルにかける。
=== File Storage ===
$schedule->call(new DeleteRecentUsers)->daily();
[https://laravel.com/docs/5.8/filesystem File Storage - Laravel 5.8 - The PHP Framework For Web Artisans]


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


その場合、commandメソッドを使う。
==== File ====
$schedule->command('emails:send Taylor --force')->daily();
 
$schedule->command(EmailsCommand::class, ['Taylor', '--force'])->daily();
Artisanコマンドの場合、コマンド名かクラスを指定する。


===== Scheduling Shell Commands =====
* [https://www.sejuku.net/blog/63671 【PHP/Laravel】ファイル操作時には欠かせない!Fileクラスの使い方 | 侍エンジニアブログ]
execメソッドはシェルコマンドの発行に使える。
* [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]
$schedule->exec('node /home/forge/script.js')->daily();
* [https://laravel.com/api/6.x/Illuminate/Support/Facades/File.html Illuminate\Support\Facades\File | Laravel API]


===== Schedule Frequency Options =====
公式マニュアルに記載がないが、Fileファサードが存在する。これは、PHPのファイル入出力処理のラッパー。Storageと異なり、ディスクシステムの考慮などはしていない。比較的シンプルなファイルIO用。
[https://stackoverflow.com/questions/47224505/laravel-schedule-hourly-daily-understanding-when-exactly-start php - Laravel schedule - hourly, daily - understanding when exactly start - Stack Overflow]
 
侍エンジニアブログくらいしか解説がない。


dailyは ["0 0 * * *" ] 相当。
vendor/laravel/framework/src/illuminate/Filesystem/Filesystem.phpがソースコード。


== Database ==
Illuminate\Support\Facades\Fileが名前空間。
LaravelのDBの取得結果は、以下のような行ごとの連想配列になっている。
[
['column1' => 1, 'column2' => 2],
['column1' => 3, 'column2' => 4],
]


====Getting Started====
* File::extension
[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]
* File::size
* File::get
* File::put
* File::copy
* File::delete
* File::move
* File::isDirectory
* File::copyDirectory: 中も全部。
* File::deleteDirectory: 中も全部。


クエリービルダー
=== Helpers ===
use Illuminate\Support\Facades\DB;
上記のクラスメソッドで生SQLを使えeる。
=====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を使う。
==== Paths ====
DB::statement('drop table users');
パス取得関係のヘルパー関数がいくつかある。
なお、DB::statementは1文ずつしか実行できない。複数実行する場合、1文ごとにDB::statementを記載する。


=== Query Builder ===
* app_path: appの絶対パス。
[https://laravel.com/docs/5.8/queries Database: Query Builder - Laravel 5.8 - The PHP Framework For Web Artisans]
* base_path: アプリのルートパス。
* config_path: config
* database_path: database
* mix
* public_path
* resource_path
* storage_path


==== Retrieving Results ====
特にbase_pathは重要に感じる。


===== Aggregates =====
=== Task Scheduling ===
集約メソッドがある。
[https://laravel.com/docs/5.8/scheduling Task Scheduling - Laravel 5.8 - The PHP Framework For Web Artisans]


* count: レコード数を返す。テーブルの行数などの把握で頻出。データの可否確認には、exists/doesntExistもある。がcountでいい気もする。
==== Introduction ====
* max
cronでタスクスケジューリングできるが、cronでやる場合、タスクが増えるたびに設定が必要になる。
* min
* avg
* sum


=====Joins=====
LaravelではLaravelのスケジュールコマンドを1個cronに追加するだけで、Laravelのタスクを全部管理できる。
$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 ====
タスクスケジュールはapp/Console/Kernel.phpのscheduleメソッドで定義できる。
重要。いくつか書き方がある。
*->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]


問題ない。
===== Starting The Scheduler =====
public function getStatuses($dates)
スケジューラー使用時に、以下の項目をcrontabに登録する。
{
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
    $query = DB::table('tickets');
これで毎分実行される。
    if ($dates['from'])
 
        $query->where('from', $dates['from']);
==== Defining Schedules ====
    if ($dates['to'])
 
        $query->where('to', $dates['to']);
===== Defining Schedules =====
    $query->select('Active');
App\Console\Kernelクラスのscheduleメソッドに、全タスクを定義する。
    return $query->get()->toArray();
<?php
}
whereの戻り値を変数に入れて流用しても問題ない。
====Transaction====
*[https://laravel.com/docs/5.8/database Database: Getting Started - Laravel 5.8 - The PHP Framework For Web Artisans]
*[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();
namespace App\Console;
    return true;
 
  });
use Illuminate\Support\Facades\DB;
  } catch (Exception $e) {
use Illuminate\Console\Scheduling\Schedule;
     Log::error($e->getMessage());
  use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
    return;
 
}
class Kernel extends ConsoleKernel
2引数でデッドロック用のリトライ回数を指定できる。基本はいらない?
  {
 
     /**
無名関数内でreturnすると、返せる。DB::transactionで失敗時の処理を個別にしたい場合、内部でthrowしてtry-catchをする。
      * The Artisan commands provided by your application.
====Migrations====
      *
[https://laravel.com/docs/5.8/migrations Database: Migrations - Laravel 5.8 - The PHP Framework For Web Artisans]
      * @var array
 
      */
マイグレーションはデータベースのバージョン管理のようなもの。データベーススキーマを簡単に修正・共有を可能にする。
    protected $commands = [
=====Generating Migrations=====
        //
make:migrationコマンドでマイグレーションファイルを作れる。
    ];
  php artisan make:migration create_users_table
 
CRUD_テーブル名_tableなどのような命名規則にしておくとわかりやすい。
    /**
      * 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コードを指定できる。


実行すると、database/migrationsディレクトリーにマイグレーションファイルが生成される。
この例では、毎日深夜にClosureを実行している。Closureでテーブルを削除している。__invokeを定義済みのinvokableオブジェクトなら以下でシンプルにかける。
$schedule->call(new DeleteRecentUsers)->daily();


オプションで--create=テーブル名、--table=テーブル名がある。
===== Scheduling Artisan Commands =====
ArtisanコマンドやOSのコマンドも指定できる。


現在のテーブルの状況などで、マイグレーションファイルのひな形が変わる。ある程度マイグレーション名から推測してくれるが、日本語などが入るときかなくなる。オプションを指定したほうが無難。
その場合、commandメソッドを使う。
=====Migration Structure=====
$schedule->command('emails:send Taylor --force')->daily();
コマンド実行で生成されたテンプレートには、upとdownメソッドが用意されている。ここにテーブルの更新と、巻戻の処理を記載する。
 
=====Running Migrations=====
  $schedule->command(EmailsCommand::class, ['Taylor', '--force'])->daily();
以下のコマンドで用意済みのマイグレーションを実行する。
Artisanコマンドの場合、コマンド名かクラスを指定する。
  php artisan migrate
 
=====Command=====
===== Scheduling Shell Commands =====
*migrate:rollback: マイグレーションをロールバックする。デフォルトだと最後の1回分。--step=Nで回数を指定できる。
execメソッドはシェルコマンドの発行に使える。
*migrate:reset: 全部ロールバックする。
$schedule->exec('node /home/forge/script.js')->daily();
*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テーブルにデータを手動で登録しておくと、マイグレーション済みと認識できる。
===== 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]


作ってあげるデータは、id,migration, batchを持つデータで、idは1からインクリメント、migrationはマイグレーションのファイル名(.phpは除く)、batchを1(たぶんこれは何度マイグレーションを実行したかを保っておくもので状態を1つ戻すときに使われるのでは?)に設定すればよい。
dailyは ["0 0 * * *" ] 相当。
{| class="wikitable"
{| class="wikitable"
!id
!Method
!migration
!Description
!batch
|-
|-
|1
|<code>->cron('* * * * *');</code>
|2017_09_01_134421_create_prefectures_table
|Run the task on a custom Cron schedule
|1
|-
|<code>->everyMinute();</code>
|Run the task every minute
|-
|<code>->everyFiveMinutes();</code>
|Run the task every five minutes
|-
|<code>->everyTenMinutes();</code>
|Run the task every ten minutes
|-
|<code>->everyFifteenMinutes();</code>
|Run the task every fifteen minutes
|-
|<code>->everyThirtyMinutes();</code>
|Run the task every thirty minutes
|-
|<code>->hourly();</code>
|Run the task every hour
|-
|<code>->hourlyAt(17);</code>
|Run the task every hour at 17 mins past the hour
|-
|<code>->daily();</code>
|Run the task every day at midnight
|-
|<code>->dailyAt('13:00');</code>
|Run the task every day at 13:00
|-
|<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
|-
|-
|2
|<code>->timezone('America/New_York');</code>
|2017_09_01_134422_create_cities_table
|Set the timezone
|1
|}
|}SQLだと以下相当。
daily()=dailyAt('00:00')
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でのマイグレーション=====
[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。
===== Preventing Task Overlaps =====
class AddColumnsToUsersTable extends Migration
デフォルトだと、直前のタスクが実行中でも無視して次のタスクが実行される。withoutOverlappingを使うと、排他処理で他のタスクをさせない。
{
    /**
      * 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。
====Seeding====
[https://laravel.com/docs/5.8/seeding Database: Seeding - Laravel 5.8 - The PHP Framework For Web Artisans]


マイグレーションで用意したDBのデフォルトデータの用意の話。
実行時間が大幅に異なるタスクがあって、終了予定時間が予測できない場合に便利。


デフォルトデータをシードと呼んでおり、シードを用意する機能をシーディングと呼んでいる。
デフォルトだと24時間でロックは解除される。引数で分単位で解除時間を指定できる。


シードを作成するためのスクリプト (シーダー)  ファイルを生成して、記述して実行する流れ。
タスクが異常終了したらロックファイルが残る。
=====Writing Seeders=====
以下のコマンドでシーダーを作成する。
php artisan make:seeder UsersTableSeeder
database/seedsにファイルが作成される。


中身はrunメソッドがあるだけ。run内でinsertするだけ。
[https://blog.e2info.co.jp/2023/01/07/laravel_task_scheduke_lock/ Laravelタスクスケジュールの多重起動制御のロックを解除する - ハマログ]
=====Calling Additional Seeders=====
php artisan schedule:clear-cache
追加したシーダーはそのままでは認識されない。


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で指定するイメージ。
====Error====
=====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]
===== Running Tasks On One Server =====
memcachedかredisでキャッシュサーバーと同期していることが前提。


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
これが必要。


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


====Getting Started====
== Database ==
[https://laravel.com/docs/5.8/eloquent Eloquent: Getting Started - Laravel 5.8 - The PHP Framework For Web Artisans]
LaravelのDBの取得結果は、以下のような行ごとの連想配列になっている。
[
['column1' => 1, 'column2' => 2],
['column1' => 3, 'column2' => 4],
]
 
===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]


Eloquent ORM。
クエリービルダー
=====Defining Models=====
  use Illuminate\Support\Facades\DB;
make:modelコマンドでモデルインスタンスを作成できる。
上記のクラスメソッドで生SQLを使えeる。
  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]


Eloquentのモデルでは、デフォルトでクラス名を複数形のスネークケースにしたものをテーブル名とみなす。
====Running Raw SQL Queries====
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'your_db.your_models' doesn't exist (SQL: select * from `your_models`)
[https://qiita.com/alpha_z/items/3d2e3c283b4cd8dbc955 LaravelのクエリビルダでSQL文を直接実行(select,insert,update,delete,その他) #Laravel - Qiita]
不在の場合、上記のエラーが出る。


デフォルト以外のテーブル名を使いたければ、$tableにテーブル名を指定する。
DB::select/insert/update/deleteなどよく使うCRUD操作はメソッドがある。そういうのとは関係なしに、SQLを直実行する場合DB::statementを使う。
  <?php
  DB::statement('drop table users');
 
なお、DB::statementは1文ずつしか実行できない。複数実行する場合、1文ごとにDB::statementを記載する。
namespace App;
 
=== Query Builder ===
use Illuminate\Database\Eloquent\Model;
[https://laravel.com/docs/5.8/queries Database: Query Builder - Laravel 5.8 - The PHP Framework For Web Artisans]
 
 
class Flight extends Model
==== Retrieving Results ====
{
 
    /**
===== Aggregates =====
      * The table associated with the model.
集約メソッドがある。
      *
 
      * @var string
* count: レコード数を返す。テーブルの行数などの把握で頻出。データの可否確認には、exists/doesntExistもある。がcountでいい気もする。
      */
* max
    protected $table = 'my_flights';
* min
}
* avg
プロジェクトでテーブル名に日本語を使う場合など、クラスメイとテーブル名が同一なら、以下のような親クラスを使って、これを継承すると同じ処理を記載しなくていい。
* sum
<?php
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]
=====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();


デフォルトで、Eloquentはcreated_atとupdated_atカラムを期待する。使っていないならば、以下のエラーが出る。
==== Where Clauses ====
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]
*->where('vote','=', 100);
$timestamps=falseにしておく。
*->where('vote', 100);: 演算子を省略すると=扱い。
<?php
*->where([['status', '=', '1'], ['subscribed', '<>', '1'],]);: 二次元配列にすれば一度に複数条件渡せる。キー・演算子・バリューのペア。
 
=====分割実行=====
  namespace App;
[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]
 
 
use Illuminate\Database\Eloquent\Model;
問題ない。
 
  public function getStatuses($dates)
class Flight extends Model
  {
  {
     /**
     $query = DB::table('tickets');
      * Indicates if the model should be timestamped.
    if ($dates['from'])
      *
        $query->where('from', $dates['from']);
      * @var bool
    if ($dates['to'])
      */
        $query->where('to', $dates['to']);
     public $timestamps = false;
     $query->select('Active');
    return $query->get()->toArray();
  }
  }
whereの戻り値を変数に入れて流用しても問題ない。


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


つまり、whereなどを使うなら、最後の取得はgetメソッドを使う必要がある。
===== Upsert =====
Laravel 8からupsertメソッドが登場した。
Laravel 8未満の場合、自分でDB::statementなどで行うしかない。


Eloquentのモデルはクエリービルダーなので、クエリービルダーの全メソッドが使用可能。
「[https://github.com/yadakhov/insert-on-duplicate-key GitHub - yadakhov/insert-on-duplicate-key]」が例。
 
====Transaction====
モデル名::メソッドの他、select/where/findなども同じものを返すので、メソッドチェーンで呼び出せる。
*[https://laravel.com/docs/5.8/database Database: Getting Started - Laravel 5.8 - The PHP Framework For Web Artisans]
=====Retrieving Single Models / Aggregates=====
*[https://qiita.com/shimizuyuta/items/dc69fb30d9f8600592db 新卒がLaravelのトランザクション実装について学んだこと #初心者 - Qiita]
モデルからデータを取得するいくつか主要なメソッドがある。
*[https://www.happylifecreators.com/blog/20220513/ Laravelのトランザクションとエラーハンドリグについて | Happy Life Creators]
*find: プライマリーキーから値を探す。
*[https://www.yui-web-beginner.net/laravel-transaction/ Laravelのトランザクション処理を2パターン徹底解説 | 現役プログラマーYuiの開発ブログ]
*all: 全項目を取得する。
*[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]
*where:
DB::transactionに処理の無名関数を指定してやると、DB処理が失敗したら自動的にロールバックする。
*findOrFail
try {
*findMany
$result = DB::transaction(function () use ($a, $b) {
メソッドチェーン
    DB::table('users')->update(['votes' => 1]);
*first: 先頭項目を取得する。
 
*firstOrFail
    DB::table('posts')->delete();
*count
    return true;
*max
});
[https://qiita.com/gone0021/items/951cd63a7e591e18cd2a 【laravel】データベースの操作:Eloquent ORM編 #Laravel6 - Qiita]
} catch (Exception $e) {
    Log::error($e->getMessage());
    return;
}
2引数でデッドロック用のリトライ回数を指定できる。基本はいらない?
 
無名関数内でreturnすると、返せる。DB::transactionで失敗時の処理を個別にしたい場合、内部でthrowしてtry-catchをする。
 
=== Migrations ===
[https://laravel.com/docs/5.8/migrations Database: Migrations - Laravel 5.8 - The PHP Framework For Web Artisans]


Eloquentのfindは扱いが特殊。引数が配列なら配列で返してくれる。
マイグレーションはデータベースのバージョン管理のようなもの。データベーススキーマを簡単に修正・共有を可能にする。


配列で結果が欲しければ、findManyを使う。findManyで常に配列で返すようにしたほうが、型が揃って扱いやすいかもしれない。
==== Generating Migrations ====
=====Inserting & Updating Models=====
make:migrationコマンドでマイグレーションファイルを作れる。
======Insert======
php artisan make:migration create_users_table
モデルインスタンスを作成し、属性をセットし、最後にsaveを呼ぶ。
  <?php
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 // 相当と思われる
    
    
  namespace App\Http\Controllers;
  // 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
    
    
  use App\Flight;
  php artisan migrate:fresh --seed
  use Illuminate\Http\Request;
 
  use App\Http\Controllers\Controller;
==== Tables ====
 
===== Renaming/Dropping Tables =====
  Schema::rename($from, $to);
 
  Schema::drop('users');
    
    
  class FlightController extends Controller
  Schema::dropIfExists('users');
{
 
    /**
==== Columns ====
      * 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 ======
===== Creating Columns =====
[https://laravel.com/docs/5.8/eloquent#updates Eloquent: Getting Started - Laravel 5.8 - The PHP Framework For Web Artisans]
{| class="wikitable"
$flight = App\Flight::find(1);
!Command
 
!Description
$flight->name = 'New Flight Name';
|-
 
|<code>$table->bigIncrements('id');</code>
$flight->save();
|Auto-incrementing UNSIGNED BIGINT (primary key) equivalent column.
saveメソッドはinsertだけでなく、updateにも使える。updated_atも自動で更新される。
|-
 
|<code>$table->bigInteger('votes');</code>
クエリーのupdateで配列で複数レコードも一括更新できる。
|BIGINT equivalent column.
 
|-
注意点として、updateの一括更新時は、eloquentで本来発動するsaving/saved/updating/updatedのイベントは発動しない。
|<code>$table->binary('data');</code>
 
|BLOB equivalent column.
======Mass Assignment======
|-
[https://qiita.com/yyy752/items/820260163d4883efb132 Add [] to fillable property to allow mass assignment onの解決方法 #初心者 - Qiita]
|<code>$table->boolean('confirmed');</code>
Add [_token] to fillable property to allow mass assignment on [App\].  
|BOOLEAN equivalent column.
こんなエラーが出る。
|-
 
|<code>$table->char('name', 100);</code>
プロパティーの代入で1個ずつ設定するほかに、まとめて1行での保存もできる。ただし、一括割り当ては危険なので、保護されており、fillable属性かguarded属性の指定が必要。
|CHAR equivalent column with an optional length.
 
|-
fillメソッドは連想配列でデータをモデルにセットする。
|<code>$table->date('created_at');</code>
$form = $request->all();
|DATE equivalent column.
unset($form['_token']);
|-
$flight->fill(['name' => 'Flight 22']);
|<code>$table->dateTime('created_at');</code>
フォームの項目と対応させておけばそのままセットできる。
|DATETIME equivalent column.
======fillable======
|-
まず、最初に一括割り当て可能な属性を定義する。
|<code>$table->dateTimeTz('created_at');</code>
 
|DATETIME (with timezone) equivalent column.
fillableプロパティーで、一括割り当て可能なカラムを定義できる。
|-
  <?php
|<code>$table->decimal('amount', 8, 2);</code>
 
|DECIMAL equivalent column with a precision (total digits) and scale (decimal digits).
namespace App;
|-
 
|<code>$table->double('amount', 8, 2);</code>
  use Illuminate\Database\Eloquent\Model;
|DOUBLE equivalent column with a precision (total digits) and scale (decimal digits).
 
|-
class Flight extends Model
|<code>$table->enum('level', ['easy', 'hard']);</code>
{
|ENUM equivalent column.
    /**
|-
      * The attributes that are mass assignable.
|<code>$table->float('amount', 8, 2);</code>
      *
|FLOAT equivalent column with a precision (total digits) and scale (decimal digits).
      * @var array
|-
      */
|<code>$table->geometry('positions');</code>
    protected $fillable = ['name'];
|GEOMETRY equivalent column.
}
|-
======Guarding Attributes======
|<code>$table->geometryCollection('positions');</code>
$fillableはホワイトリスト方式。$guardedも使用できる。一括割り当て可能にしたくない属性を指定する。ブラックリスト。 $fillableと$guardedのどちらかが最低限必要。
|GEOMETRYCOLLECTION equivalent column.
|-
|<code>$table->increments('id');</code>
|Auto-incrementing UNSIGNED INTEGER (primary key) equivalent column.
|-
|<code>$table->integer('votes');</code>
|INTEGER equivalent column.
|-
|<code>$table->ipAddress('visitor');</code>
|IP address equivalent column.
|-
|<code>$table->json('options');</code>
|JSON equivalent column.
|-
|<code>$table->jsonb('options');</code>
|JSONB equivalent column.
|-
|<code>$table->lineString('positions');</code>
|LINESTRING equivalent column.
|-
|<code>$table->longText('description');</code>
|LONGTEXT equivalent column.
|-
|<code>$table->macAddress('device');</code>
|MAC address equivalent column.
|-
|<code>$table->mediumIncrements('id');</code>
|Auto-incrementing UNSIGNED MEDIUMINT (primary key) equivalent column.
|-
|<code>$table->mediumInteger('votes');</code>
|MEDIUMINT equivalent column.
|-
|<code>$table->mediumText('description');</code>
|MEDIUMTEXT equivalent column.
|-
|<code>$table->morphs('taggable');</code>
|Adds <code>taggable_id</code> UNSIGNED BIGINT and <code>taggable_type</code> VARCHAR equivalent columns.
|-
|<code>$table->uuidMorphs('taggable');</code>
|Adds <code>taggable_id</code> CHAR(36) and <code>taggable_type</code> VARCHAR(255) UUID equivalent columns.
|-
|<code>$table->multiLineString('positions');</code>
|MULTILINESTRING equivalent column.
|-
|<code>$table->multiPoint('positions');</code>
|MULTIPOINT equivalent column.
|-
|<code>$table->multiPolygon('positions');</code>
|MULTIPOLYGON equivalent column.
|-
|<code>$table->nullableMorphs('taggable');</code>
|Adds nullable versions of <code>morphs()</code> columns.
|-
|<code>$table->nullableUuidMorphs('taggable');</code>
|Adds nullable versions of <code>uuidMorphs()</code> columns.
|-
|<code>$table->nullableTimestamps();</code>
|Alias of <code>timestamps()</code> method.
|-
|<code>$table->point('position');</code>
|POINT equivalent column.
|-
|<code>$table->polygon('positions');</code>
|POLYGON equivalent column.
|-
|<code>$table->rememberToken();</code>
|Adds a nullable <code>remember_token</code> VARCHAR(100) equivalent column.
|-
|<code>$table->set('flavors', ['strawberry', 'vanilla']);</code>
|SET equivalent column.
|-
|<code>$table->smallIncrements('id');</code>
|Auto-incrementing UNSIGNED SMALLINT (primary key) equivalent column.
|-
|<code>$table->smallInteger('votes');</code>
|SMALLINT equivalent column.
|-
|<code>$table->softDeletes();</code>
|Adds a nullable <code>deleted_at</code> TIMESTAMP equivalent column for soft deletes.
|-
|<code>$table->softDeletesTz();</code>
|Adds a nullable <code>deleted_at</code> TIMESTAMP (with timezone) equivalent column for soft deletes.
|-
|<code>$table->string('name', 100);</code>
|VARCHAR equivalent column with a optional length.
|-
|<code>$table->text('description');</code>
|TEXT equivalent column.
|-
|<code>$table->time('sunrise');</code>
|TIME equivalent column.
|-
|<code>$table->timeTz('sunrise');</code>
|TIME (with timezone) equivalent column.
|-
|<code>$table->timestamp('added_on');</code>
|TIMESTAMP equivalent column.
|-
|<code>$table->timestampTz('added_on');</code>
|TIMESTAMP (with timezone) equivalent column.
|-
|<code>$table->timestamps();</code>
|Adds nullable <code>created_at</code> and <code>updated_at</code> TIMESTAMP equivalent columns.
|-
|<code>$table->timestampsTz();</code>
|Adds nullable <code>created_at</code> and <code>updated_at</code> TIMESTAMP (with timezone) equivalent columns.
|-
|<code>$table->tinyIncrements('id');</code>
|Auto-incrementing UNSIGNED TINYINT (primary key) equivalent column.
|-
|<code>$table->tinyInteger('votes');</code>
|TINYINT equivalent column.
|-
|<code>$table->unsignedBigInteger('votes');</code>
|UNSIGNED BIGINT equivalent column.
|-
|<code>$table->unsignedDecimal('amount', 8, 2);</code>
|UNSIGNED DECIMAL equivalent column with a precision (total digits) and scale (decimal digits).
|-
|<code>$table->unsignedInteger('votes');</code>
|UNSIGNED INTEGER equivalent column.
|-
|<code>$table->unsignedMediumInteger('votes');</code>
|UNSIGNED MEDIUMINT equivalent column.
|-
|<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 =====
カラム種類に加え、いくつかのカラム修飾がある。
  Schema::table('users', function (Blueprint $table) {
    $table->string('email')->nullable();
  });
以下が特に。
 
* after
* 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 =====
既存カラムの改名や、データ型変更も行えるが、癖がある。


====== Other Creation Methods ======
なお、int(10) のような数値型の幅などは、MySQLの独自拡張で、DB独自拡張はマイグレーションのAPIで未対応。
insertもupdateもやることはほぼ同じなので、コードの冒頭で以下のような式でモデル生成部分だけ変更したらよいだろう。
 
        $model = empty($request['オーナーコード'])
[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]
            ? new 楽楽販売_オーナーマスタ : 楽楽販売_オーナーマスタ::find($request['オーナーコード']);
と思っていたが、もっといいのがあった。


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


firstOrNewは自分でsaveできるように、モデルインスタンスを返してくれる (new モデル相当)。
====== Prerequisites ======
// Retrieve flight by name, or create it if it doesn't exist...
事前に依存関係を追加する。
$flight = App\Flight::firstOrCreate(['name' => 'Flight 10']);
  composer require doctrine/dbal
 
// 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
====== Updating Column Attributes ======
  // If there's a flight from Oakland to San Diego, set the price to $99.
既存のカラムを別の型に変更する場合、changeメソッドを使う。
  // If no matching model exists, create one.
  Schema::table('users', function (Blueprint $table) {
  $flight = App\Flight::updateOrCreate(
    $table->string('name', 50)->change();
     ['departure' => 'Oakland', 'destination' => 'San Diego'],
  });
    ['price' => 99, 'discounted' => 1]
 
  );
  Schema::table('users', function (Blueprint $table) {
1番目の配列の情報で検索して、2番目の配列で更新する。1番目の情報で不在なら、全部の情報で更新する。
     $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メソッドもある ([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から削除するだけで、実テーブルからは削除しない。使うことはほぼない。


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


====== Upserts ======
===== Creating Indexes =====
[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');
$table->index(['account_id', 'created_at']);
$table->unique('email', 'unique_email');
$table->unique(['account_id', 'email']);
$table->dropUnique(['account_id', 'email']);
複合UNIQUE制約をつけたい場合、uniqueメソッドの第一引数を配列にする。


* [https://laravel.com/docs/8.x/eloquent#upserts Eloquent: Getting Started - Laravel 8.x - The PHP Framework For Web Artisans]
==== Other ====
* [https://nextat.co.jp/staff/archives/333 Eloquentのupsert() 知ってると便利ですよ。1つのクエリで複数のレコードにアプローチしよう。|Laravel|PHP|開発ブログ|株式会社Nextat(ネクスタット)]


Laravel 8からupsertメソッドが登場して、一括更新が可能になった。以前から、updateOrCreateがあった。
===== Command =====
====Relationships====
*migrate:rollback: マイグレーションをロールバックする。デフォルトだと最後の1回分。--step=Nで回数を指定できる。
[https://laravel.com/docs/5.8/eloquent-relationships Eloquent: Relationships - Laravel 5.8 - The PHP Framework For Web Artisans]
*migrate:reset: 全部ロールバックする。
=====Defining Relationships=====
*migrate:refresh: rollback+migrate。
======One To One======
*migrate:fresh: ロールバックではなく、テーブルを削除してから戻す。
<?php
 
 
===== 既存DBの途中からの管理 =====
namespace App;
*[https://gamushiros.hatenablog.com/entry/2019/04/23/000000 Laravelのmigrateを途中から実行する - mk-toolブログ]
 
*[https://teratail.com/questions/362984 Laravel 既存DBをマイグレーションする方法]
use Illuminate\Database\Eloquent\Model;
*[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]
 
今後の変更分だけ管理するということもできる。
class User extends Model
{
    /**
      * Get the phone record associated with the user.
      */
    public function phone()
    {
        return $this->hasOne('App\Phone');
    }
}
主テーブルに外部キーが埋め込まれていて、シンプルな場合、上記のように外部テーブルをメソッド名にしてhasOneを実行すると全部返せる。非常にシンプル。


プロパティーとしてアクセス可能になる。1対1だから、以下のようにプライマリーキーなどで参照できる。
なお、中途半端にLaravelのマイグレーションファイルがある場合、migrationsテーブルにデータを手動で登録しておくと、マイグレーション済みと認識できる。
$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');
    }
=====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を使う。


ただ、このwherehasはコールバックなどを使わないといけなくて、少々面倒くさい。
作ってあげるデータは、id,migration, batchを持つデータで、idは1からインクリメント、migrationはマイグレーションのファイル名(.phpは除く)、batchを1(たぶんこれは何度マイグレーションを実行したかを保っておくもので状態を1つ戻すときに使われるのでは?)に設定すればよい。
 
{| class="wikitable"
joinしてフラットにして、カラム名にプレフィクスをつけたほうがわかりやすい。フラットにしなかったら、入れ子になって扱いが面倒になる。が、たいしてプレフィクスをつけるのと手間は変わらないから。
!id
=====Eager Loading=====
!migration
Eloquentのリレーションにプロパティーでのアクセス時、リレーションデータは "lazy loaded" になる。これはプロパティーへのアクセスまで、実際には読み込まないことを意味する。つまり、アクセスのたびに読み込みが発生する。Eager Loadが可能になる。
!batch
 
|-
Eagerは、熱心な、せっかちなという形容詞。
|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でのマイグレーション =====
[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]


例えば、N件のデータを全部表示させたい場合、以下のようなコードを記述する。
upメソッドにDB::updateなどで生のSQLをそのまま書けばOK。
<?php
  class AddColumnsToUsersTable extends Migration
 
namespace App;
 
use Illuminate\Database\Eloquent\Model;
 
  class Book extends Model
  {
  {
     /**
     /**
       * Get the author that wrote the book.
       * Run the migrations.
      *
      * @return void
       */
       */
     public function author()
     public function up()
     {
     {
         return $this->belongsTo('App\Author');
         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。


$books = App\Book::all();
===== Cannot declare class TableName, because the name is already in use =====
 
 
  foreach ($books as $book) {
* [https://zoo200.net/laravel-migration-error-cannot-declare-class-tablename/ Laravelのマイグレーションで Cannot declare class TableNameのエラー | zoo200's MemoMemo]
    echo $book->author->name;
* [https://teratail.com/questions/112058 Laravelでmigrateができない]
  }
* [https://qiita.com/takepan/items/86c59f15600c1f32a942 Laravelで「Cannot declare class UpdateUsersTable」ほか #laravel5.5 - Qiita]
最初に全取得+1、N件のデータを取得+Nで、合計1+N回のクエリーが発行される。
 
マイグレーションファイル作成時に生成されるクラスが、既存のマイグレーションファイルのクラス名と被っているのが原因の一つ。
 
マイグレーションのファイル名/クラス名は固有にしないといけない。
 
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 {
  //
  };
 
===== Doctrine\DBAL\Exception  : Unknown column type "mediuminteger" requested. =====


Eager loadによりこれを2回のクエリー発行で収めることができる。withメソッドを使う。
* [https://stackoverflow.com/questions/77359070/laravel-migration-to-change-an-unsignedmediuminteger-column eloquent - Laravel migration to change an unsignedMediumInteger column - Stack Overflow]
$books = App\Book::with('author')->get();
* [https://laracasts.com/discuss/channels/eloquent/unsupported-laravel-datatypes-in-doctrinedbal Unsupported laravel datatypes in doctrine/dbal]
 
 
foreach ($books as $book) {
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.
    echo $book->author->name;
Laravel 5.8のmigrationでmediumintergerが使えない。Doctrineが対応していないから。しかたないからintegerにする。
}
 
これは具体的には以下のSQLの実行になる。
[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以上で解決しているとのこと。
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じゃなくて、複数のモデルが入っている場合。
=== Seeding ===
[https://laravel.com/docs/5.8/seeding Database: Seeding - Laravel 5.8 - The PHP Framework For Web Artisans]


Laravel 8.42からLatestOfManyなどのメソッドが追加されており、これを使えば回避できる。これを使わない場合、JOINとMAXなどを駆使する必要があった。
マイグレーションで用意したDBのデフォルトデータの用意の話。


ひとまず、hasManyとか、性能を見て、問題あったらeager loadingしたり工夫することにする。
デフォルトデータをシードと呼んでおり、シードを用意する機能をシーディングと呼んでいる。
*[https://zenn.dev/yuzuyuzu0830/articles/cf4aedd9e9c08b58c8f2 withを使用した様々なデータの取得方法]
*[https://qiita.com/HuntingRathalos/items/5a7cae35a49a2795e8f2 [Laravel]withメソッドを理解する #初心者 - Qiita]
複数のテーブルと結合したい場合、withに複数指定する。必要な列が決まっているなら、withのテーブル名の後に:で列名指定でOK。
======Constraining Eager Loads======
=====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.*')
 
*アクセサー
==== Writing Seeders ====
get/selectでやるのがよさそう。
以下のコマンドでシーダーを作成する。
====Other====
php artisan make:seeder UsersTableSeeder
=====列の存在確認=====
database/seedsにファイルが作成される。
*[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
*結合を含んだり、大量データの処理が必要な複雑な場合にクエリービルダー。
こういう方針で使い分けるといい。結合とかになると、モデルの範囲外になる。


レコードセットなのか、モデルなのかを意識する。
中身はrunメソッドがあるだけ。run内でinsertするだけ。
=====列名の取得=====
*[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()
==== Calling Additional Seeders ====
            ->getSchemaBuilder()
追加したシーダーはそのままでは認識されない。
            ->getColumnListing('colaborators');
===== ::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]
EloquentのORMのモデルを使用する際は、<Model>::findなどのメソッドを使う。


が、インスタンスを生成してから、条件に応じて処理を実行したい場合など、都合が悪いことがある。
DatabaseSeederに登録する。DatabaseSeeder.phpのrunのcallメソッド内に追加する。
public function run()
{
    $this->call([
        UsersTableSeeder::class,
        PostsTableSeeder::class,
        CommentsTableSeeder::class,
    ]);
}


インスタンス生成用に<Model>::query()がある。<Model>::query()->findなどのように、メソッドの前に挟める。
==== Running Seeders ====
追加したシーダーを認識させるために以下のコマンドでautoloadを更新する。
composer dump-autoload
シーダーを用意したらコマンドを実行して作成する。
php artisan db:seed
--classでシーダー名を指定もできる。あとからシーダーを追加する場合は--classで指定するイメージ。


これを使う形で書いておくと、コードの書き方がきれいになったり、コード補完が効きやすかったりする。
=== Error ===
=====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]
==== Handling ====


Eloquentのwhere関数内でSQL関数は使えない。工夫が必要。
* [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]


[https://laravel.com/docs/5.8/queries#raw-expressions Database: Query Builder - Laravel 5.8 - The PHP Framework For Web Artisans]
DB関係の処理で、失敗した場合の扱いが、公式文書に記載がなくて混乱する。


DB::rawで書く。
基本は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();
    }
}


あるいは、selectRaw/whereRawなどを使う。
==== 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://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クエリーのオブジェクトを配列にしたい。
[https://qiita.com/b-coffin/items/8103583efe3767b6748e PHPのPDOをDockerコンテナ内で使おうとしたところ、"No such file or directory" エラーが発生した話 #docker-compose - Qiita]


Eloquentを使わずに、配列にしたい場合、mapやtransformで変換必要。
dockerで.envのDB_HOSTの指定間違い。dockerのservice名をホスト名に指定する必要がある。
$response['data'] = DB::table('customers') 
    // query conditions, etc
    ->get()
    ->map(function ($item, $key) {
        return (array) $item;
    })
    ->all();
最後のallも必要。これでEloquentと共通化できる。


== Testing ==
==== 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
これが必要。


====Database Testing====
== Eloquent ==
*[https://laravel.com/docs/5.8/database-testing Database Testing - Laravel 5.8 - The PHP Framework For Web Artisans]
*[https://nebikatsu.com/8431.html/ 簡単!Laravelで日本語の仮データをDBに自動生成(factoryとseeder) - ネビ活 | ネットビジネス生活]
シーダーでデータの自動登録方法を整理した。ただ、たくさんデータを登録してチェックする場合、ダミーデータを自動生成したい。Factoryというのでダミーデータを作成できる。
=====Generating Factories=====
php artisan make:factory PostFactory
database/factoriesに生成される。


--modelオプションで使用するモデル (テーブル名) を指定しておくと、テンプレートも作成してくれる。
====Getting Started====
[https://laravel.com/docs/5.8/eloquent Eloquent: Getting Started - Laravel 5.8 - The PHP Framework For Web Artisans]


== Other ==
Eloquent ORM。
 
=====Defining Models=====
====Facade====
make:modelコマンドでモデルインスタンスを作成できる。
[https://qiita.com/minato-naka/items/095f2a1beec1d09f423e 【Laravel】ファサードとは?何が便利か?どういう仕組みか? #初心者 - Qiita]
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]
 
Eloquentのモデルでは、デフォルトでクラス名を複数形のスネークケースにしたものをテーブル名とみなす。
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'your_db.your_models' doesn't exist (SQL: select * from `your_models`)
不在の場合、上記のエラーが出る。


インスタンスメソッドをstaticメソッドのように使うための仕組み。ややこしいだけなのでいらないかなと思う。
デフォルト以外のテーブル名を使いたければ、$tableにテーブル名を指定する。
====Controllerの共通処理====
<?php
*[https://stackoverflow.com/questions/29343176/common-logic-between-various-laravel-controllers-method php - Common logic between various Laravel controllers method - Stack Overflow]
 
*[https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q11254710971 Laravelの実装について、複数のControllerから扱... - Yahoo!知恵袋]
namespace App;
*[https://note.com/watarunakayama/n/n4fb2794c3514 Laravel: 色んなModelで共通のメソッドをTraitで定義する|Wataru]
*[https://programing-school.work/laravel-another-controller-method/ Laravelでコントローラから別のコントローラのメソッドを使う|共通処理のメソッドはTraitにする]
use Illuminate\Database\Eloquent\Model;
*[https://kaki-note-02.netlify.app/2020/11/22/ 物流エンジニアが本気出して考えた Laravel のアーキテクチャ:リポジトリパターン - 1 | かきノート]
 
*[https://kaki-note-02.netlify.app/2020/11/25/ 物流エンジニアが本気出して考えた Laravel のアーキテクチャ:リポジトリパターン - 2 | かきノート]
class Flight extends Model
*[https://zenn.dev/mpyw/articles/ce7d09eb6d8117 5年間 Laravel を使って辿り着いた,全然頑張らない「なんちゃってクリーンアーキテクチャ」という落としどころ]
{
    /**
      * 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======
[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]
 
デフォルトで、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
[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のどちらかが最低限必要。
 
====== 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番目の情報で不在なら、全部の情報で更新する。
 
ただし、これらのメソッド類は、一度に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を使う。
 
ただ、このwherehasはコールバックなどを使わないといけなくて、少々面倒くさい。
 
joinしてフラットにして、カラム名にプレフィクスをつけたほうがわかりやすい。フラットにしなかったら、入れ子になって扱いが面倒になる。が、たいしてプレフィクスをつけるのと手間は変わらないから。
=====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。
======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]
]);
 
=====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でやるのがよさそう。
====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
*結合を含んだり、大量データの処理が必要な複雑な場合にクエリービルダー。
こういう方針で使い分けるといい。結合とかになると、モデルの範囲外になる。
 
レコードセットなのか、モデルなのかを意識する。
=====列名の取得=====
*[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()
            ->getSchemaBuilder()
            ->getColumnListing('colaborators');
===== ::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]
EloquentのORMのモデルを使用する際は、<Model>::findなどのメソッドを使う。
 
が、インスタンスを生成してから、条件に応じて処理を実行したい場合など、都合が悪いことがある。
 
インスタンス生成用に<Model>::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が配列を返すのでダメ。
 
== 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がある。
 
==== 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を忘れずに呼ぶこと。
 
=== 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 ===
*[https://laravel.com/docs/5.8/database-testing Database Testing - Laravel 5.8 - The PHP Framework For Web Artisans]
*[https://nebikatsu.com/8431.html/ 簡単!Laravelで日本語の仮データをDBに自動生成(factoryとseeder) - ネビ活 | ネットビジネス生活]
シーダーでデータの自動登録方法を整理した。ただ、たくさんデータを登録してチェックする場合、ダミーデータを自動生成したい。Factoryというのでダミーデータを作成できる。
=====Generating Factories=====
php artisan make:factory PostFactory
database/factoriesに生成される。
 
--modelオプションで使用するモデル (テーブル名) を指定しておくと、テンプレートも作成してくれる。
 
== Other ==
 
====Facade====
[https://qiita.com/minato-naka/items/095f2a1beec1d09f423e 【Laravel】ファサードとは?何が便利か?どういう仕組みか? #初心者 - Qiita]
 
インスタンスメソッドをstaticメソッドのように使うための仕組み。ややこしいだけなのでいらないかなと思う。
====Controllerの共通処理====
*[https://stackoverflow.com/questions/29343176/common-logic-between-various-laravel-controllers-method php - Common logic between various Laravel controllers method - Stack Overflow]
*[https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q11254710971 Laravelの実装について、複数のControllerから扱... - Yahoo!知恵袋]
*[https://note.com/watarunakayama/n/n4fb2794c3514 Laravel: 色んなModelで共通のメソッドをTraitで定義する|Wataru]
*[https://programing-school.work/laravel-another-controller-method/ Laravelでコントローラから別のコントローラのメソッドを使う|共通処理のメソッドはTraitにする]
*[https://kaki-note-02.netlify.app/2020/11/22/ 物流エンジニアが本気出して考えた Laravel のアーキテクチャ:リポジトリパターン - 1 | かきノート]
*[https://kaki-note-02.netlify.app/2020/11/25/ 物流エンジニアが本気出して考えた Laravel のアーキテクチャ:リポジトリパターン - 2 | かきノート]
*[https://zenn.dev/mpyw/articles/ce7d09eb6d8117 5年間 Laravel を使って辿り着いた,全然頑張らない「なんちゃってクリーンアーキテクチャ」という落としどころ]
例えば、/city/nycで都市都市の一覧表示。/city/nyc/streetで該当都市の通りの一覧表示。こういうタイプの処理を都市ごとに実装するというようなことがよくある。
例えば、/city/nycで都市都市の一覧表示。/city/nyc/streetで該当都市の通りの一覧表示。こういうタイプの処理を都市ごとに実装するというようなことがよくある。


ただし、表示処理は共通処理があったりする。
ただし、表示処理は共通処理があったりする。
 
 
この共通処理をどうまとめて実装するか?いくつか方法がある。
この共通処理をどうまとめて実装するか?いくつか方法がある。
*親Controller
*親Controller
*ファサード/トレイト (関数クラス)/サービスクラス
*ファサード/トレイト (関数クラス)/サービスクラス
*middleware
*middleware
*モデルに共通処理を定義して呼び出し。
*モデルに共通処理を定義して呼び出し。
*validation/FormRequest
*validation/FormRequest
Controllerの前後に挟み込む感じならmiddleware、Controllerの処理の中でやりたいなら親Controllerか、ファサード/トレイト/サービスクラス、DB周りならModel?
Controllerの前後に挟み込む感じならmiddleware、Controllerの処理の中でやりたいなら親Controllerか、ファサード/トレイト/サービスクラス、DB周りならModel?
 
基本はmiddlewareの模様。ビジネスロジックになるなら、サービスクラスを使うのがいい。トレイトでやる場合、app/traitsに格納する感じ。
 
やっぱりサービスクラスに出すのいい。
====Coding Style Guide====
*[https://laravel.com/docs/11.x/contributions Contribution Guide - Laravel 11.x - The PHP Framework For Web Artisans]
PSR-2/4に準拠。
 
ローカル変数名やプロパティーの記法の指定がない。
*[https://github.com/laravel/framework/blob/1a1a61068bc3c5594376a559e424ae09ec3fe64a/src/Illuminate/Foundation/resources/exceptions/renderer/components/trace.blade.php#L31 framework/src/Illuminate/Foundation/resources/exceptions/renderer/components/trace.blade.php at 1a1a61068bc3c5594376a559e424ae09ec3fe64a · laravel/framework]
*[https://github.com/laravel/framework/blob/1a1a61068bc3c5594376a559e424ae09ec3fe64a/src/Illuminate/Foundation/Console/ServeCommand.php#L26 framework/src/Illuminate/Foundation/Console/ServeCommand.php at 1a1a61068bc3c5594376a559e424ae09ec3fe64a · laravel/framework]
Laravel本体のソースコードを見るとcamelCaseになっているのでこれに合わせる。
 
Bladeファイル名 [[https://stackoverflow.com/questions/61808504/laravel-naming-convention-for-blade-files Laravel naming convention for blade files - Stack Overflow]]
 
チェインケースかcamelCaseを推奨。特に決まってはいない。
 
「[https://github.com/laravel/framework/blob/1a1a61068bc3c5594376a559e424ae09ec3fe64a/src/Illuminate/Foundation/resources/exceptions/renderer/components/theme-switcher.blade.php framework/src/Illuminate/Foundation/resources/exceptions/renderer/components/theme-switcher.blade.php at 1a1a61068bc3c5594376a559e424ae09ec3fe64a · laravel/framework]」にあるように、Laravel本体はチェインケースなので、チェインケースがよいだろう。
 
component類のslot名はcamelCase [[https://github.com/laravel/framework/blob/7d26b7ee454a0ccc339db92a641487f668b44331/tests/View/Blade/BladeComponentTagCompilerTest.php#L105 framework/tests/View/Blade/BladeComponentTagCompilerTest.php at 7d26b7ee454a0ccc339db92a641487f668b44331 · laravel/framework]]。
 
コマンド


基本はmiddlewareの模様。ビジネスロジックになるなら、サービスクラスを使うのがいい。トレイトでやる場合、app/traitsに格納する感じ。
[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]


やっぱりサービスクラスに出すのいい。
* 名詞:動詞
====Coding Style Guide====
* 名詞動詞Command.php
*[https://laravel.com/docs/11.x/contributions Contribution Guide - Laravel 11.x - The PHP Framework For Web Artisans]
PSR-2/4に準拠。
 
ローカル変数名やプロパティーの記法の指定がない。
*[https://github.com/laravel/framework/blob/1a1a61068bc3c5594376a559e424ae09ec3fe64a/src/Illuminate/Foundation/resources/exceptions/renderer/components/trace.blade.php#L31 framework/src/Illuminate/Foundation/resources/exceptions/renderer/components/trace.blade.php at 1a1a61068bc3c5594376a559e424ae09ec3fe64a · laravel/framework]
*[https://github.com/laravel/framework/blob/1a1a61068bc3c5594376a559e424ae09ec3fe64a/src/Illuminate/Foundation/Console/ServeCommand.php#L26 framework/src/Illuminate/Foundation/Console/ServeCommand.php at 1a1a61068bc3c5594376a559e424ae09ec3fe64a · laravel/framework]
Laravel本体のソースコードを見るとcamelCaseになっているのでこれに合わせる。
 
Bladeファイル名 [[https://stackoverflow.com/questions/61808504/laravel-naming-convention-for-blade-files Laravel naming convention for blade files - Stack Overflow]]
 
チェインケースかcamelCaseを推奨。特に決まってはいない。
 
「[https://github.com/laravel/framework/blob/1a1a61068bc3c5594376a559e424ae09ec3fe64a/src/Illuminate/Foundation/resources/exceptions/renderer/components/theme-switcher.blade.php framework/src/Illuminate/Foundation/resources/exceptions/renderer/components/theme-switcher.blade.php at 1a1a61068bc3c5594376a559e424ae09ec3fe64a · laravel/framework]」にあるように、Laravel本体はチェインケースなので、チェインケースがよいだろう。
 
component類のslot名はcamelCase [[https://github.com/laravel/framework/blob/7d26b7ee454a0ccc339db92a641487f668b44331/tests/View/Blade/BladeComponentTagCompilerTest.php#L105 framework/tests/View/Blade/BladeComponentTagCompilerTest.php at 7d26b7ee454a0ccc339db92a641487f668b44331 · laravel/framework]]。


ViewCacheCommand/ViewClearCommandなど1コマンド1ファイル。


ただし、make:command/CommandMakeCommandなど一部命名規則に外れるものがある。
[[Category:PHP]]
[[Category:PHP]]

2024年11月1日 (金) 09: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インスタンス経由でアクセスする。

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系のアクション実行用に、バリデーションというデータの検証の仕組みがある。

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

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

上記の書式で使う。

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

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

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

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

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)}}で良いと思われる。

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

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

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

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>
Other
Bladeのレンダー結果の取得

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

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

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

パスの取得

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

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

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

子ビューの変数の使用

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

$slotの型

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

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

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();

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をする。

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で指定するイメージ。

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

これが必要。

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のどちらかが最低限必要。

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番目の情報で不在なら、全部の情報で更新する。

ただし、これらのメソッド類は、一度に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を使う。

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

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

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。

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]
]);
Other
リレーションの列名エイリアス

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

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

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

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

Other

列の存在確認

いくつか方法がある。

  • use Illuminate\Support\Facades\Schema; Schema::hasColumn('テーブル名', '列名'): これが一番シンプル。
  • $model['列名']: インスタンス生成後ならこれ。
Eloquent vs. Query Builder

ORMとクエリービルダー。どこでどう使い分けるか?最初の2個の記事が参考になる。

  • ユーザーフォームのような基本的で簡単なCRUD処理は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');
::query

EloquentのORMのモデルを使用する際は、<Model>::findなどのメソッドを使う。

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

インスタンス生成用に<Model>::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が配列を返すのでダメ。

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がある。

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を忘れずに呼ぶこと。

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など一部命名規則に外れるものがある。