【初心者用】Laravelのお勉強

# これは何？
Laravelについて勉強した備忘録です。
何かの参考になれば幸いです。



# 全体のファイル構造
|フォルダ名 or ファイル名|内容|
|:--|:--|
| app |ビジネスロジック|
| config |アプリケーションの設定|
| database |データベース作成やデフォルトデータ作成|
| public |アクセスした時に最初に参照されるエントリーポイント(index.php)|
| resources |html、cssなど見た目に関するファイル群|
| route |ルーティング設定|
| vendor |サードパーティのライブラリ群|
| .env |(**重要**) 認証情報など環境固有の設定|
| composer.json |phpファイルの依存関係解決|
| package.json |javaScript、フロントエンドの依存関係解決|



# Artisan Command
Laravelが用意しているコマンドラインインターフェイス

使えるコマンドを確認する
```php
php artisan list
```

自分のコマンドを作成する
```php
php artisan make:command コマンド名
```

作成されるクラスの中身
|プロパティ or メソッド|内容|
|:--|:--|
|signature|コマンドの名前|
|description|コマンドの説明|
|handle|実際に実行されるコマンドの中身| 

### Tinker
PsySHパッケージを搭載したLaravelフレームワークのREPL
対話的にデータベースなどを参照できる

実行コマンド
```php
php artisan tinker
```
:::note info
終了するときはexitかctrl+c
デフォルトだと色がつかないので、色をつける場合の対処法は下記の2通り
- `php artisan tinker --ansi`を実行する
- `vendor/psy/psysh/src/configuration.php`の下記を`disabled`から`auto`にする
```php
const COLOR_MODE_DISABLED = 'disabled';
```
:::



# ルーティング

静的なものとパラメータなどを持つ動的なものがある。

#### web.php と api.php の違い
web.php と api.php に分かれているのは、要件が違うから。
api はjson形式のフォーマットのみを返す。
また、一般的なwebサイトはcookieやsessionに依存するが、apiはRateLimit（回数制限?）が必要な場合がある。

#### ルーティングの例
```php
Route::get('/recent-posts/{days_ago?}', function ($daysAgo = 20) {
    return 'Post from ' . $daysAgo . 'days ago';
})->name('posts.recent.index ')->middleware('auth');
```
:::note info
?をつけないと必須パラメータになり、その場合、パラメータがないと404が返る
$daysAgo = 20 のように、パラメータにデフォルト値を設定できる
nameをつけると他のファイルでその名前を使える、名前をつけるの推奨
:::

#### ルーティングの例2
```php
Route::get('/post/{id}', function ($id) use($posts) {
    return view('posts.show', ['post' => $posts[$id]]);
})->where([
    'id' => '[0-9]+'
])->name('posts.show'); 
```
:::note info
whereでパラメータに制約を付けることができる
whereには**配列を渡す**
制約には正規表現を使うことができる
:::

#### ルーティングの例3
```php
Route::prefix('/fun')->name('fun.')->group(function() use($posts) {
    Route::get('responses', function() use($posts) {
```
:::note info
group でルートのグルーピングができる
:::

#### ルーティイングの例4
```php
Route::get('/', [HomeController::class, 'home'])->name('home.index')
```
:::note info
controllerを指定する場合は[]で囲う
第1引数にコントローラのクラス名、第2引数にコントローラのメソッド名
:::

#### RouteServiceProviderで共通の制約をつける

RouteServiceProviderのbootメソッド内に記述

```php
Route::pattern('id', '[0-9]+');
```
:::note info
Route::patternメソッドで、共通の制約を付けることができる
個別のrouteメソッドにいちいち記載しなくて良いので便利
上記の場合は、idだったら常に1つ以上の数字でないと404になる
:::



# ビュー
ルーティングファイルで、下記のように記述することでviewに遷移できる
```php
return view('home.index',[]);
```

:::note info
第2引数には**配列で**viewに渡すパラメータを入れられる
:::

ルーティングファイルでviewに変数を渡す
```php
// $postsは$idをキーに持った配列
Route::get('/post/{id}', function ($id) use($posts) {
    abort_if(!isset($posts[$id]), 404);
    return view('posts.show', ['post' => $posts[$id]]);
})
```
:::note info
上記の場合、postがviewで使える変数の名前になる
abort_ifは条件に当てはまったらエラーを返すヘルパ関数、よく使う
:::

Bladeテンプレートで変数を表示
```php
<h1>{{ $post['title'] }}</h1>
```
:::note info
ダブルプレース構文（ヒゲ）に通された変数は、エスケープ処理のためのphpのhtmlspecialchars関数を通る
:::

ルーティングファイルで変数を持たないviewに遷移
```php
Route::view('/contact', 'home.contact')->name('home.contact');
```


### Bladeテンプレート

Blade directive(指令)を使うことで、効率的にviewを作成できる

#### directiveの例

`resources/views/layouts/app.blade.php`を継承
```php
@extends('layouts.app')
```

継承したテンプレート（親ファイル）の title に Home page を表示
```php
@section('title', 'Home page')
```

セクションディレクティブの中身が長い場合は、下の書き方
```php
@section('content')
<h1>Welcome to Laravel</h1>
<div>This is the content of the main page</div>
@endsection
```

親ファイルに記述
継承先のテンプレート（子ファイル）からsectionディレクティブで指定された title をもらう
```php
@yield('title')
```

phpの if, elseif , else のように使える
```php
@if($post['is_new'])
<div>A new blog post! Using if</div>
@else
<div>Blog post is old! Using elseif/else</div>
@endif
```

〜でないとき、ifの逆
```php
@unless($post['is_new'])
<div>It is an old post... using unless</div>
@endunless
```

値がセットされているかどうかの判定
```php
@isset($post['has_comments'])
<div>The post has some comments...</div>
@endisset
```

ループ処理
```php
@if(count($posts))
    @foreach($posts as $key => $post)
        <div>{{ $key }}. {{ $post['title'] }}</div>
    @endforeach
@else
    No posts found!
@endif
```

foreachループ処理の中では`$loop`変数が最初から使える
使えるプロパティについては[Laravel公式](https://readouble.com/laravel/9.x/ja/blade.html#the-loop-variable)を参照

if + foreachの簡略化
```php
@forelse ($posts as $key => $post) 
    @include('posts.partials.post')
@empty
   No posts found!
@endforelse
```

スキップ（continue）と終了（break）
```php
@foreach ($users as $user)
    @continue($user->type == 1)

    <li>{{ $user->name }}</li>

    @break($user->number == 5)
@endforeach
```

phpのforと一緒
```php
@for ($i = 0; $i < 10; $i++)
    現在の値は、{{ $i }}
@endfor
```

phpのwhileと一緒
```php
@while (true)
    <p>無限ループ中です。</p>
@endwhile
```

phpの記述をする
```php
@php
    $isActive = false;
    $hasError = true;
@endphp
```

部分的に他のファイルを表示する
```php
@include('posts.partials.post')
```
:::note info
postsフォルダ → partialsフォルダ → post.blade.php の中身を表示
変数は全てinclude先のファイルに継承される
:::

追加データを渡すこともできる
```php
@include('posts.partials.post', ['status' => 'complete'])
```

＠include + loop を同時にする
```php
@each('posts.partials.post', $posts, 'post')
```
:::note info
第1引数は表示するビュー
第2引数は反復対象のコレクション、もしくは配列
第3引数はビュー内で表示する反復要素の変数名
追加データを渡すことはできない
`$loop`変数も使えない
:::

### ビューコンポーザ(view composer)
ビューをレンダするときに呼び出すコールバックまたはクラスメソッドのこと。
ビューをレンダする際に常に同じデータを渡す場合は、ビューコンポーザでロジックを集約できる。

ビューコンポーザの作成（ディレクトリはどこでもOK）
```php
use Illuminate\View\View;

class ActiveComposer
{
    public function compose(View $view)
    {
        // 省略
        $view->with('most_commented', $most_commented);
        $view->with('most_active', $most_active);
        $view->with('most_active_last_month', $most_active_last_month);
    }
}
```
:::note info
レンダされる際に実行される`compose`メソッドを定義する
`$view->with()`でviewをレンダする時に常にデータを渡すようにする
:::

`AppServiceProvider.php`でViewComposerを記述
```php
public function boot()
{
    view()->composer(['posts.index', 'posts.show'], ActiveComposer::class);
    // view()->composer('*', ActiveComposer::class);
}
```
:::note info
`boot`メソッド内で`view`ヘルパ関数、`composer`メソッドを呼ぶ
第1引数には、対象のviewファイル、すべてを対象にする場合はアスタリスクを記述
第2引数には、定義したview composerクラス
:::






# リクエストとレスポンス

### レスポンス

ヘルパ関数でレスポンスオブジェクトの作成
```php
response($posts, 201)
```

:::note info
第1引数に内容、第2引数にhttpステータス
:::

responseオブジェクトはviewメソッド、headerメソッド、cookieメソッドが使える
```php
response($posts, 201)
->view('/contact', 'home.contact')
->header('Content-Type', 'application/json')
->cookie('MY-COOKIE', 'HOGE HOGE', 3600);
```
:::note info
header の第2引数には html か json
cookie の第3引数は有効期限、3600なら1時間
:::

リダイレクトしたい場合は、redirectメソッド
```php
return redirect('/contact')
```

リダイレクト時にURIではなく、ルートを指定することもできる
```php
return redirect()->route('posts.show', ['id' => 1])
```

自分のサイト以外にリダイレクトしたい場合
```php
return redirect()->away('https://google.com')
```

直前のページに戻したい場合
```php
return back()
```

htmlではなくjson形式のデータを返したい場合
```php
return response()->json($posts);
```

ファイルをダウンロードさせたい場合
```php
return response()->download(public_path('/daniel.jpg', 'face.jpg'))
```
:::note info
public フォルダへのアクセスは public_path メソッド
第3引数にヘッダー情報を配列で渡す
:::

### リクエスト
リクエストへのアクセスは2通り
```php
request()
```
```php
use Illuminate\Http\Request;

function (Request $request)
```

リクエストの中身すべてへアクセス
```php
request()->all()
```
:::note info
配列が返ってくる
リクエストボディとクエリ文字列（URLの末尾に付けたす変数）も表示される
クエリ文字列は文字列で取得されるので数字などはcastする必要がある
:::

リクエストの一部へアクセス
```php
request()->input('page', 1)
```
:::note info
第1引数にアクセスしたいキーを指定
第2引数に入力値が存在しない場合のデフォルト値を指定できる
:::

リクエストのクエリ文字列へアクセス
```php
request()->query('page', 1)
```

チェックボックスなどの真偽値を受け取る場合
```php
request()->boolean('archived');
```
:::note info
1、"1"、true、"true"、"on"、"yes"にtrueを返す
:::

受け取りたいパラメータを複数指定する
```php
request()->only('username', 'password');
```

受け取りたくないパラメータを指定する
```php
request()->except('credit_card')
```

リクエストに指定された値がすべて存在するか確認する
```php
request()->has(['name', 'email'])
```

リクエストに値が存在した場合の実行処理
```php
request()->whenHas('name', function ($input) {
    // "name"が存在する場合の処理…
}, function () {
    // "name"が存在しない場合の処理…
});
```

リクエストに指定された値のいずれかが存在するか確認する
```php
request()->hasAny(['name', 'email'])
```

リクエストに値が存在し、空でないことを確認する
```php
request()->filled('name')
```

リクエストに値が存在し、空でない場合の実行処理
```php
request()->whenFilled('name', function ($input) {
    // "name"の値が空でない場合の処理…
}, function () {
    // "name"の値が空の場合の処理…
});
```



# ミドルウェア
controllerに入る前と入った後の処理
ミドルウェアにはhandleメソッドのみ存在する
3種類のミドルウェアがある

1. グローバルなミドルウェア、すべてに適用される
1. webとapiのルートのみに適用される、`kernel.php`で設定
1. 特定のルートグループのみに適用される、controllerで設定

`kernel.php`での記述
```php
// すべてに適用されるミドルウェア
protected $middleware = [
    //
];

// webとapiに適用されるミドルウェア
protected $middlewareGroups = [
    'web' => [
        //
    ],
    'api' => [
        //
    ],
];

// 特定のルートに適用されるミドルウェア
protected $routeMiddleware = [
    'auth' => \App\Http\Middleware\Authenticate::class,
];
```

controllerで特定のルートにミドルウェアを適用
```php
Route::get('/', [HomeController::class, 'home'])
    ->name('home.index')
    ->middleware('auth');
```



# コントローラ

ルートからビューに渡す前に何か処理をしたいときに記述する

コントローラの作成
```php
php artisan make:controller 作成したいコントローラ名 --resource
```
:::note info
resourceオプションを指定するとCRUD(create, read, update, delete)のメソッドを自動で作ってくれる
:::

リソースコントローラのルート設定
```php
Route::resource('posts', PostsController::class);
```

CRUDの一部だけを使いたい場合
```php
Route::resource('posts', PostsController::class)->only([
    'index', 'show'
]);
```
```php
Route::resource('photos', PhotoController::class)->except([
    'create', 'store', 'update', 'destroy'
]);
```

メソッドが一つだけのシングルコントローラ
```php
class AboutController extends Controller
{
    public function __invoke() 
    {
        return 'Single';
    }
}
```
:::note info
__invokeメソッドを使う
:::



# 設定（config と .env）
Laravelの設定ファイルはconfigフォルダ配下にある

Laravelがどの設定を使うか決める流れ
![Laravel-Environment-Variable@2x (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2516185/c510c8ef-ed12-3f20-0229-17f1e6bd43fe.png)

:::note warn
githubなどを使う場合、.envはセキュリティの観点から、コミットしてはいけない
コミットする場合は、.env.exampleに**変数のみ**を記載してコミットすること
:::

例えば、データベースに関する設定は`database.php`に記述がある

デフォルトで使うRDMSの設定
```php
'default' => env('DB_CONNECTION', 'mysql'),
```
:::note info
第1引数は.envファイルの変数名を記載
第2引数は、.envファイルに記述がない場合のデフォルト値
:::



# データベースとMigration
データベースへのアクセスの種類
![Databases@2x (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2516185/39619a47-e008-bc48-f47e-83366cbc1f66.png)

マイグレーション（データベース作成）ファイルの概要
![Laravel+Migrations@2x (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2516185/cd63f348-c602-1e7f-1ede-7b108f67e382.png)

マイグレーションファイルの例
```php
class CreateBlogPostsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('blog_posts', function (Blueprint $table) {
            $table->id();
            $table->timestamps();

            $table->string('title')->default('');
            $table->text('content');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('blog_posts');
    }
}
```
:::note info
主キー（id）は必ず必要
timestampsでcreated_atとupdated_atのカラムを作成する
:::

マイグレーションファイルで使えるカラムタイプ一覧
[(Laravel公式)マイグレーション　利用可能なカラムタイプ](https://readouble.com/laravel/9.x/ja/migrations.html#available-column-types)

モデルとマイグレーションファイルを一緒に作成する
```php
php artisan make:model BlogPost -m
```





# ORM(eloquent)とモデル（Model）
クエリをphpライクに記述することができる方法
modelなどにも簡単にアクセスができる

:::note info
**(重要)**
**返り値はEloquentモデルオブジェクト**(`Illuminate\Database\Eloquent\Model`)だが、**複数返ってくる場合はEloquentコレクション**
:::

:::note warn
Eloquentコレクション(`Illuminate\Database\Eloquent\Collection`)と基本コレクション(`Illuminate\Support\Collection`)は別物、使えるメソッドが違うので注意
:::

参照：[【Laravel】Eloquentを理解する](https://www.whizz-tech.co.jp/6984/)

保存する
```php
$posts = new BlogPost();
$posts->title = 'I am title';
$posts->content = 'I am content';
$posts->save();
```

idで探す
```php
$posts = BlogPost::find(1);
```
:::note info
該当のIDが存在しない場合はnullを返す
:::

複数のidを探す場合は配列を渡す
```php
$posts = BlogPost::find([1, 2, 3]);
```
:::note info
見つかったidのみ返す、エラーは出さない
:::

idで探して、idが存在しない場合exceptionを返す
```php
$posts = BlogPost::findOrFail(1000);
```

すべてのデータを取得する
```php
$posts = BlogPost::all();
```
:::note info
戻り値はeloquentコレクション
取得する値はデフォルトではidの古い順になっている（他のメソッドも）
:::

コレクションから特定の項目だけ取得する
```php
$posts = BlogPost::all()->pluck('id');
```
:::note info
pluckはeloquentコレクションではなく基本コレクションのインスタンスを返す
:::

コレクションへのアクセス方法は配列と同じ
```php
$posts[0]
```

コレクションの最初の値を取得する
```php
$posts->first();
```

コレクションの数をカウントする
```php
$posts->count();
```



# クエリビルダ（QueryBuilder）
sqlに近い形でクエリを記述できるようにしたもの
クエリビルダの実態は`Illuminate\Database\Query\Builder`
**モデルクラスにはクエリビルダが組み込まれている**

SQLのキーワードを下記のような形で使える
```php
Model::where(...)
Model::select(...)
Model::join(...)
```

#### クエリビルダの例
```php
User::where('id', '>=', 2)->orderBy('id', 'desc')->take(5)->get();
```
:::note info
クエリビルダは静的に呼びだし、ビルダクラスの新しいインスタンスが生成される
**（重要）返り値はコレクションオブジェクト**(`Illuminate\Support\Collection`)
takeメソッドはsqlのlimit句と一緒
takeをgetの後に追加した場合は取得したコレクションに対して実行されるORMの扱い
:::



# フォーム
ユーザーが入力した情報を送信する

ポストした時のルーティングを指定
```php
<form action="{{ route('posts.store') }}" method="POST">
```

CSRF（クロスサイトリクエストフォージェリ）から守る
```php
@csrf
```
:::note info
hidden でユニークなトークンをセッションに保存する
フォーム送信処理のルートで送信されたトークンと生成されたトークンが一致するか調べる
`app/Http/Middleware/VerifyCsrfToken.php`で確認
:::

:::note warn
クロスサイトリクエストフォージェリとは…
悪意のあるウェブサイトのURLをクリックすると、クリックした時点で、正規のサイトに登録されているパスワードなどを更新する。本来であれば、正規のサイト側で上書きを拒否する仕組みが必要。
:::

:::note warn
ハッキング回避のため、データ送信の method には GET ではなく POST を指定する
:::

#### バリデーション・検証
送信されるデータについて検証をかけることができる

バリデーションの例
```php
$request->validate([
    'title' => 'bail|required|min:5|max:100',
    'content' => 'required|min:10',
]);
```
:::note info
bailはバリデーションに引っかかった場合、残りのバリデーションルールをチェックしない
上記の場合、titleのmin:5で失敗すると、max:100は検証されない
:::

:::note info
バリデーションルールで失敗すると、以下の2通りのいずれかになる
- フォームから送信された場合、最後のページにリダイレクトされる（同時にエラーの内容をセッションに保存する）
- Ajaxで呼び出された場合、エラーとともにレスポンスでアドレスを返す
:::

#### エラーメッセージ
ミドルウェアの`ShareErrorsFromSession`クラスのおかげで、bladeテンプレート内なら、`$errors`変数を使ってエラー内容にいつでもアクセスできる

`app/Http/Kernel.php`に記述がある
```php
protected $middlewareGroups = [
    'web' => [
        // 省略
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        // 省略
    ],
```

Blade内ですべてのエラーをまとめて表示する
```php
@if($errors->any())
    <div class="mb-3">
        <ul class="list-group">
            @foreach ($errors->all() as $error)
                <li class="list-group-item list-group-item-danger">{{ $error }}</li>
            @endforeach
        </ul>
    </div>
@endif
```
:::note info
$errorsを定義していなくてもアクセスできる
:::

Blade内で個別の項目にエラーを表示する
```php
@error('title')
    <div class="alert alert-danger">{{ $message }}</div>
@enderror
```
:::note info
＠errorディレクティブ内でメッセージ内容が格納されている$messageにアクセスできる
:::

エラー内容を各項目ごとに表示する例
```php
<div class="form-group">
    <label>Password</label>
    <input name="password" required type="password"
        class="form-control {{ $errors->has('password') ? 'is-invalid' : '' }}"/>

    @if ($errors->has('password'))
    <span class="invalid-feedback">
        <strong>{{ $errors->first('password') }}</strong>
    </span>
    @endif
</div>
```
:::note info
html の type = password型で入力時にアスタリスクなどに変換してくれる
errors->has('password')でパスワードの項目にエラーがあるかをチェック
errors->first('password)でエラーの最初の内容を取得
class="invalid-feedback"はbootstrapの書き方でエラー表示に使う
:::


#### バリデーションルールをカプセル化する
フォームリクエストクラスの作成
```php
php artisan make:request StorePostRequest(クラス名)
```

`authorize`メソッドでフォームを送信できるユーザーかどうかをチェック

バリデーションルールは`rules`メソッドに記載
```php
public function rules()
{
    return [
        'title' => 'bail|required|min:5|max:100',
        'content' => 'required|min:10',
    ];
}
```

controllerでバリデーション後の値を取得
```php
public function store(StorePostRequest $request)
{
    $validated = $request->validated();
    //
}
```
:::note info
フォームリクエストクラスをタイプヒントし、`validated`メソッドを呼び出せばOK
フォームリクエストはコントローラメソッドが呼び出される前にバリデーションを行う
:::

バリデーションが適用された後の値へアクセス
```php
$post->title = $validated['title'];
```

#### フラッシュメッセージ
セッションに一度だけ使用するメッセージを保存できる
```php
$request->session()->flash('status', 'The blog post was created!');
```
:::note info
第1引数には使いやすい変数の名前を書く（なんでもよい）
:::

Blade内でフラッシュメッセージを表示
```php
@if(session('status'))
    <div class="alert alert-success">
        {{ session('status') }}
    </div>
@endif
```

#### 直前に入力された値を消さずに画面に表示する
Blade内でoldメソッドを使用すると、バリデーションで失敗しても入力した値が消えない
```php
<input type="text" name="username" value="{{ old('title', optional($post ?? null)->title) }}">
```
:::note info
第2引数にデフォルト値を設定できる
optionalメソッドを使うと、指定したオブジェクトがnullだった場合、nullを返す
:::

#### 複数代入（MassAssignment）対策
複数代入の脆弱性対策として、`$fillable`または`$guarded`をモデルに設定する

複数代入可能な`$fillable`の設定
```php
class BlogPost extends Model
{
    /**
     * 複数代入可能な属性
     */
    protected $fillable = [
        'title', 
        'content'
    ];
}
```

複数代入不可能な`$guarded`の設定
```php
class BlogPost extends Model
{
    /**
     * 複数代入不可能な属性
     */
    protected $guarded = [
        'password', 
    ];
}
```
:::note warn
複数代入（MassAssignment）とは…
アプリケーションの意図しない複数のカラムにデータを入れること
> 例えば、passwordだけ更新出来る画面が有ったとして、ブラウザに表示されるのはpasswordフィールドだけですが、ブラウザをちょっといじれば入力項目を追加できます。これが複数代入です。大抵、他の項目も更新出来てしまう問題を回避するために、複数の項目に代入出来ない様にしています。

参照：[「複数代入」ってなに？-猿でも分かるlaravel解説-](https://qiita.com/hirohirohero/items/e4a5c5e7d81bbdc53372)
:::


割り当てを適用する3つの方法

1 `create`
```php
$validated = $request->validated();
BlogPost::create($validated);
```
:::note info
新しいモデルインスタンスを作成し、全てのプロパティに入力値を入れ、DBにすぐに保存する
すべてのプロパティに入力値が取得できる場合に使用
引数にはphpのarrayをとる
:::

2 `make`
```php
$post = BlogPost::make([
    'title' => 'BlogPost2'
]);
$post->save();
```
:::note info
新しいモデルインスタンスを作成し、プロパティに入力値を入れるが、DBに保存しない
DBに保存するにはsaveメソッドを呼ぶ必要がある
makeを呼んだ時点では取得できていない関連モデルやプロパティがある場合に使用
saveの引数にはEloquendモデルインスタンスをとる
:::

3 `fill`(create, makeと違い、静的メソッドではない)
```php
$post->fill([
    'title' => 'BlogPost3'
]);
```
:::note info
すでにインスタンスが存在する場合や、既存のモデルを修正したい場合に使用
:::



# update と delete
#### update
updateする場合はputするがhtmlではgetとpostしかできないため、methodディレクティブを使用する
```php
<form action="{{ route('posts.update', ['post' => $post->id]) }}" method="POST">
    @csrf
    @method('PUT')
    //
</form>
```

#### delete
deleteもupdateと同じでmethodディレクティブを使用する
```php
<form class="d-inline" action="{{ route('posts.destroy', ['post' => $post->id]) }}" method="POST">
    @csrf
    @method('DELETE')
    <input type="submit" value="Delete!" class="btn btn-primary">
</form>
```

controller側でdeleteの設定をする
```php
public function destroy($id)
{
    $post = BlogPost::findOrFail($id);
    $post->delete();

    session()->flash('status', 'Blog post was deleted!');

    return redirect()->route('posts.index');
}
```
:::note info
sessionヘルパ関数を使うことで、sessionにアクセスできる
Requestクラスをタイプヒントすることでsessionにアクセスする方法もある
:::




# Assets & Styles(JavaScript, CSS, Laravel MIX, BootStrap)
![Laravel+Mix@2x (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2516185/a0b9c8c9-3a8d-d082-5bae-b8398ace5e92.png)

#### 使っているツール
|ツール名|説明|
|:--|:--|
|Node.js|CSSやJavaScriptの管理をするツール、サーバサイドのJavaScript実行環境やクライアントサイドJavaScriptの開発環境として利用される|
|NPM|Node.jsのパッケージマネージャー、PHPのcomposerのようなもの、フロントエンドのライブラリを取得し管理する|
|WebPack|JavaScriptのモジュールバンドラー(module bundler) 、複数のモジュールの依存関係を解決して1つにまとめる、CSSプリプロセッサのコンパイルやminifyも行う、Node.jsで作られたパッケージの１つ|
|Laravel Mix|webpackビルド手順を定義する流暢なAPIを提供するもの|

参照：[（Laravel公式）Laravel 9.x JavaScriptとCSSスカフォールド](https://readouble.com/laravel/9.x/ja/frontend.html)


#### フロントエンドライブラリ
使用するフロントエンドライブラリはpackage.jsonに記載がある。
PHPのcomposer.jsonのようなもの。

フロントエンドライブラリのインストール
```php
npm install
```
:::note info
インストールするとnode_modulesファイルとpackage-lock.jsonファイルができる
:::

コンパイルの実行
```php
npm run dev
```
:::note info
publicフォルダ配下にapp.cssファイルとapp.jsファイルが作られる
:::

package.jsonにはnpmで使えるコマンドが記述されている
```javaScript
"scripts": {
    // 開発環境用のコンパイル。minifyしないのでデバッグがしやすい。
    "dev": "npm run development",
    "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --config=node_modules/laravel-mix/setup/webpack.config.js",
    // devと一緒だが、コマンド実行後、変更を監視し、変更があったら自動で再コンパイルしてくれる。
    "watch": "npm run development -- --watch",
    "watch-poll": "npm run watch -- --watch-poll",
    "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --disable-host-check --config=node_modules/laravel-mix/setup/webpack.config.js",
    // 本番環境用のコマンド。minifyするので早いが、デバッグには向かない。
    "prod": "npm run production",
    "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --config=node_modules/laravel-mix/setup/webpack.config.js"
},
```

Blade内でassetsの読み込み
```html
<link rel="stylesheet" href="{{ asset('css/app.css') }}">
<script src="{{ asset('js/app.js') }}" defer></script>
```
:::note info
asset関数は、現在のリクエストのスキーマ（HTTPかHTTPS）を使い、アセットへのURLを生成
deferをつけるとページがレンダリングされてから、javaScriptが読み込まれる
:::

#### バージョニング／キャッシュの破棄
コンパイルしたアセット(cssファイル、jsファイル)にタイムスタンプまたは一意のトークンの接尾辞を付けて、提供してきたコードの古くなったコピー（キャッシュ）の代わりに、ブラウザに新しいアセットをロードするように強制することができる。
Laravelではwebpack.mix.jsで簡単にバージョニングができる。

コンパイルしたすべてのファイルのファイル名に一意のハッシュを追加する
```javascript
mix.js('resources/js/app.js', 'public/js');

if (mix.inProduction()) {
    mix.version();
}
```
:::note info
`version`メソッドで、コンパイルしたすべてのファイル名に一意のハッシュを追加
`inProduction`メソッドは`npm run prod`の実行中のみtrueを返す
バージョン付きファイルは開発では不要なため、本番環境でのみ適用されるようにしておく
:::

Bladeの`asset`ヘルパ関数を`mix`ヘルパ関数にする
```html
<link rel="stylesheet" href="{{ mix('css/app.css') }}">
<script src="{{ mix('js/app.js') }}" defer></script>
```
:::note info
`mix`関数は、ハッシュされた(suffixがついた)ファイルの現在の名前を自動的に判別
:::

参照：[（Laravel公式）Laravel 9.x アセットのコンパイル（Mix）](https://readouble.com/laravel/9.x/ja/mix.html)

#### Bootstrap
UIコンポーネントにはプリセットという概念があり、その1つがbootstrap。
Bootstrapの機能はレイアウト、コンポーネント、ユーティリティの3つに大別される。
![Laravel+Bootstrap+Overview@2x (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2516185/442cf5dd-379d-1f19-83b3-46b07c620bcb.png)
|名前|内容|
|:--|:--|
|レイアウト|レスポンシブデザインを作るのに役立つ。コンテンツを行と列に配置する。|
|コンポーネント|ボタン、ドロップダウン、フォームなど一般的に使用されるWeb要素の構成。|
|ユーティリティ|フレックスボックスや色の適用。CSSをカスタムしなくてもすぐに使える。|

laravel/uiパッケージをインストールする
```php
composer require laravel/ui 3.0.0
```
:::note info
バージョン指定がなくてもOK
:::

基本的なスカフォールドを生成
```php
php artisan ui bootstrap
```
:::note info
スカフォールド(scaffold)とはアプリケーションの足場(雛形)を自動生成して簡単に作ることができる機能
:::

(bootstrapとは直接関係ないけど)認証用コントローラの作成
```php
php artisan ui:controllers
```

#### Carbon
LaravelはCarbon（PHPのDateTimeクラスをオーバーラップした日付操作ライブラリ）を使用しているため、すべての日付はcarbonオブジェクトのインスタンスが返るようになっている

参照：https://carbon.nesbot.com/

```php
<p>Added {{ $post->created_at->diffForHumans() }}</p>
@if(now()->diffInMinutes($post->created_at) < 5)
<div class="alert alert-info">New!</div>
@endif
```
:::note info
日付を人が理解しやすい形に変換
`now`は現時点を表す新しい`Illuminate\Support\Carbonインスタンス`を生成
:::




# テスト
#### 単体テスト(UnitTest)と機能テスト(FeatureTest)
テストは単体テストと機能テストの2つに大別される。

|名前|説明|
|:--|:--|
|単体テスト|1つのクラスや1つのメソッドを機能するか確認するテスト|
|機能テスト|ブログの記事を投稿したら保存される、というような機能単位のテスト。実行中のアプリケーションにリクエストを送ることが多い|

テストに関連するファイルはtestsディレクトリ配下にある。
すべてのテストファイルは`tests/TestCase.php`の**TestCaseクラスを継承する必要がある**。
単体テストのファイルは`tests/Unit`ディレクトリ配下、機能テストのファイルは`tests/Feature`ディレクトリ配下。ただし、`tests/Unit`ディレクトリ配下のテストは、アプリケーションを起動しないため、データベースやその他のフレームワークにアクセスができない。
またテストクラスの**メソッド名はtestから始まる必要がある**。

:::note info
リクエスト関連のテストでは`assert...`のメソッドが大量に用意されている。
参照：[（Laravel公式）HTTPテスト](https://readouble.com/laravel/9.x/ja/http-tests.html#available-assertions)
:::


#### テストの実行
テストをターミナルで実行（簡易的な結果）
```
./vendor/bin/phpunit 
```
テストをターミナルで実行（詳細な結果）
```php
php artisan test
```

#### 典型的なテストの形
1. Arrangeパート（テストをするためのオブジェクトの初期化）
1. Actパート（オブジェクトに紐づくメソッドの実行）
1. Assertパート（操作の結果が期待通りかどうかの検証）
:::note info
Arrangeパートは常にあるわけではないが、ActとAssertは常に必要
:::

#### テストに関する設定
`phpunit.xml`ファイルに設定の記述がある。テストはローカル環境とは別の環境で実行される。
また使用しているデータベースに直接影響がないように設定できる。

データベースをsqliteのインメモリ機能を使う
```php
// phpunit.xmlに記述
<env name="DB_CONNECTION" value="sqlite_testing"/>
```
```php
// database.phpに記述
'connections' => [
    //
    'sqlite_testing' => [
        'driver' => 'sqlite',
        'database' => ':memory:'
    ],
    //
]
```
:::note info
`phpunit.xml`に`<env name="DB_DATABASE" value=":memory:"/>`でもOK
`:memory:`はメモリ上にデータベースを作成するインメモリの指定
:::

参照：[（Qiita記事）【Laravel7】PHPUnitの設定をしてテストを実行してみる](https://qiita.com/phper_sugiyama/items/26672e3a11af6bd980db)

:::note warn
dockerを使っている場合はforceを付け加える必要あり
テスト前に`php artisan cache:clear`でキャッシュをクリアすること
:::

参照：[（Qiita記事）【Laravel】.env.testingの使用方法と注意点](https://qiita.com/akkino_D-En/items/024661836b7570d78704)
参照：[(Laravel公式)設定キャッシュ](https://readouble.com/laravel/9.x/ja/configuration.html#configuration-caching)

#### テストファイルの作成
```php
php artisan make:test HomeTest
```
:::note info
HomeTestの部分は作成するファイル名
:::

テスト例1（トップページにアクセスした時に表示されるテキストの確認）
```php
class HomeTest extends TestCase
{
    public function testHomePageIsWorkingCorrectly()
    {
        $response = $this->get('/');

        $response->assertSeeText('Welcome to Laravel!');
    }
}
```
:::note info
メソッド名は長くても良いので、何をテストしているのかわかりやすい名前をつける
:::

テスト例2(データベース関連のテスト)
```php
class PostTest extends TestCase
{
    use RefreshDatabase;

    public function test1BlogPostWhenThereIs1withNoComments()
    {
        // Arrange
        $post = $this->createDummyBlogPost();

        // Act
        $response = $this->get('/posts');

        // Assert
        $response->assertSeeText('New title');
        $response->assertSeeText('No comments yet');

        $this->assertDatabaseHas('blog_posts', [
            'title' => 'New title',
        ]);
    }
}
```
:::note info
`RefreshDatabase`を使うことで、テストで使ったデータが保存されたままにならない
`assertDatabaseHas`メソッドで、DBに値を保持しているかチェック
:::

テスト3(フォームからポストした時のテスト)
```php
public function testStoreValid()
{
    // Arrange
    $params = [
        'title' => 'Valid title',
        'content' => 'At least 10 characters'
    ];

    // Act & Assert
    $this->post('/posts', $params)
        ->assertStatus(302)
        ->assertSessionHas('status');

    // Assert
    $this->assertEquals(session('status'), 'The blog post was created!');
}
```
:::note info
`assertStatus(302)`でリダイレクトが正しく動作するかの確認
:::

テスト例4(updateした時のテスト)
```php
public function testUpdateValid()
{
    // Arrange
    $post = $this->createDummyBlogPost();

    $this->assertDatabaseHas('blog_posts', $post->toArray());

    $params = [
        'title' => 'A new named title',
        'content' => 'Content was changed',
    ];

    // Act & Assert
    $this->put("/posts/{$post->id}", $params)
    ->assertStatus(302)
    ->assertSessionHas('status');

    // Assert
    $this->assertEquals(session('status'), 'Blog post was updated!');
    $this->assertDatabaseMissing('blog_posts', $post->toArray());
    $this->assertDatabaseHas('blog_posts', [
        'title' => 'A new named title',
    ]);
}
```
:::note info
`put`を使っているが、`php artisan route:list`で何を使うか調べること
:::




# リレーション(1対1, 1対多, Eloquent)
モデルとマイグレーションファイルの作成
```php
php artisan make:model Author --migration
```

#### 1対1のリレーションのモデル
Author（親モデル）がprofile（子モデル）を持つ場合
```php
class Author extends Model
{
    use HasFactory;

    public function profile()
    {
        return $this->hasOne('App\Models\Profile');
    }
}
```
```php
class Profile extends Model
{
    use HasFactory;

    public function author()
    {
        return $this->belongsTo('App\Models\Author');
    }
}
```
:::note info
author側にhasoneメソッド、profile側にbelongsToメソッドを使用する
:::

profile（親モデル）をauthor（子モデル）に割り当てる方法

```php
$author = new Author();
$profile = new Profile();
$author->profile()->save($profile);
```
:::note info
`save`メソッドはモデルインスタンスを引数にとる
`create`メソッドは配列を引数にとる
:::

author（子モデル）をprofile（親モデル）に割り当てる方法1
```php
$profile = new Profile();
$author = Author::create();
$profile->author()->associate($author)->save();
```

author（子モデル）をprofile（親モデル）に割り当てる方法2
```php
$profile = new Profile();
$author = Author::create();
$profile->author_id = 4;
$profile->save();
```

デフォルトではリレーションは明示的に呼び出さない限りは読み込まれない
```php
// この時点ではprofileは読み込まれてない
$author = Author::find(2);

// profileにアクセスすることで読み込まれる
$author->$profile
```
:::note info
1度に全てのリレーションを読み込まないことをlazy loadingと言う
:::

最初からリレーションを読み込む場合
```php
$author = Author::with(['profile', 'comment'])->whereKey(1)->first();
```
:::note info
1度にリレーションまで読み込むことをeager loadingを言う
:::

#### 1対多のリレーションモデル
BlogPostが複数のCommentを持つ場合
```php
class BlogPost extends Model
{
    public function comments()
    {
        return $this->hasMany('App\Models\Comment');
    }
}
```
```php
class Comment extends Model
{
    public function blogPost()
    {
        // 基本の形
        // return $this->belongsTo('App\Models\BlogPost');

        // キー名が違う場合
        return $this->belongsTo('App\Models\BlogPost', 'post_id', 'blog_post_id');
    }
}
```
:::note info
メソッド名の末尾にidをつけたものを外部キー名とする（上の例だとblog_post_id）
外部キーが違う場合、第2引数で外部キー名を指定できる
親モデルの主キーを参照しない場合、第3引数で親モデルの別カラムを指定できる
:::

CommentをBlogPostに割り当てる方法
```php
$bp = BlogPost::find(2);
$comment = new Comment();
$comment->content = 'A first comment.';
$bp->comment()->save($comment);
```

BlogPostをCommentに割り当てる方法
```php
$bp = BlogPost::find(2);
$comment = new Comment();
$comment->content = 'A second comment.';
$comment->blogpost()->associate($bp)->save();
```

BlogPostをCommentに割り当てるもう1つの方法
```php
$comment = new Comment();
$comment->content = 'A third comment.';
$comment->blog_post_id = 2;
$comment->save();
```

複数のCommentをBlogPostに1回で割り当てる方法
```php
$bp = new BlogPost();
$comment = new Comment();
$comment->content = 'a';
$comment2 = new Comment();
$comment2->content = 'b';
$bp->comments()->saveMany([$comment, $comment2]);
```

CommentなしでBlogPostだけを取得する
```php
$post = BlogPost::all();
```

BlogPostに紐づいているCommentもまとめて取得する
```php
$post = BlogPost::with('comments')->get();
```

CommentからBlogPostをlazy loading
```php
$comment = Comment::find(1);
$comment->blogPost;
```
:::note info
blogPostはCommentモデルに記述したメソッド名
:::

#### Lazy Loading と Eager Loading
実行されるクエリログを確認するには、ファサードクラスの`Illuminate\Database\Connection`のメソッドを使用する

実行クエリを確認する
```php
// ログの有効化
DB::connection()->enableQueryLog();
// クエリ実行
$post = BlogPost::all();
// 実行クエリをログに出力
dd(DB::getQueryLog());
```

Lazy Loading
```php
$post = BlogPost::all();
foreach ($posts as $post) {
    foreach ($post->comments as $comment) {
        echo $comment->content;
    }
}
```
:::note info
上記の場合、すべてのブログポストを取得した後、各ブログポストに紐づくコメントを1つずつ取得する。そのため、コメントを取得するクエリはコメントの数だけ実行される。N+1問題
:::

Eager Loading
```php
$post = BlogPost::with('comments')->get();
foreach ($posts as $post) {
    foreach ($post->comments as $comment) {
        echo $comment->content;
    }
}
```
:::note info
withを使った場合、コメントテーブルのデータもまとめて取得するので、ブログポストを取得するクエリ + コメントを取得するクエリの2回で済む
:::

Commentを持っているBlogPostだけを取得する
```php
BlogPost::has('comments')->get();
```
:::note info
リレーション関連のメソッド(`has`)はEloquent
Eloquentリレーションはクエリビルダとしても機能する
:::

Commentを2つ以上持っているBlogPostだけを取得する
```php
BlogPost::has('comments', '>=', 2)->get();
```

より複雑なクエリを記述する場合はwhereHasメソッドで`$query`変数を使用する
```php
BlogPost::whereHas('comments', function($query) {
    $query->where('content', 'like', '%abc%');
})->get();
```

CommentがないBlogPostの取得
```php
BlogPost::doesntHave('comments')->get();
```

abcを含むCommentをもつBlogPostは取得しない
```php
BlogPost::whereDoesntHave('comments', function (Builder $query) {
    $query->where('content', 'like', '%abc%');
})->get();
```

持っているCommentの数をカウントする
```php
BlogPost::withCount('comments')->get();
```
:::note info
新しくcomments_countという属性がBlogPostに自動的に追加される
:::

別名をつけてカウントするクエリ
```php
BlogPost::withCount(['comments', 'comments as new_comments' => function($query) {
    $query->where('created_at', '>=', '2022-03-24 09:00');
}])->get();
```
:::note info
comments_countとnew_commentsの数が集計され追加される
:::



# Model Factory
データベースのテストで使用する。モデルに紐づくテーブルにダミーのデータを生成する。

#### Faker
Faker（ライブラリ）で生成されるランダムなデータを使って、特定のモデルインスタンスをいくつか生成することができる。
参照：[（github）Faker](https://github.com/fzaninotto/faker)(Fakerで生成できるデータの種類が載ってる)
参照：[(Laravel公式)モデルファクトリ](https://readouble.com/laravel/9.x/ja/database-testing#defining-model-factories)

composer.jsonの記述
```php
"require-dev": {
    "facade/ignition": "^2.5",
    "fakerphp/faker": "^1.9.1",
    "mockery/mockery": "^1.4.2",
    "nunomaduro/collision": "^5.0",
    "phpunit/phpunit": "^9.3.3"
},
```
:::note info
`composer.json`でfakerをインストールしている
:::

#### Model Factory
Factoryの作成
```php
php artisan make:factory CommentFactory --model=Comments
```
:::note info
`--model=Comments`でどのモデルのファクトリなのかを指定
`database`ディレクトリ配下にファイルが生成される
:::

`comments`のダミーデータを作成するファクトリファイル
```php
class CommentFactory extends Factory
{
    /**
     * The name of the factory's corresponding model.
     *
     * @var string
     */
    protected $model = Comment::class;

    /**
     * Define the model's default state.
     *
     * @return array
     */
    public function definition()
    {
        return [
            'content' => $this->faker->realText(50, 5)
        ];
    }
}
```
:::note info
`definition`メソッドの`return`にfakerで生成するデータを定義する
`protected $model = Comment::class;`で対応するモデルを定義
:::

factoryファイルを呼び出す
```php
Comment::factory()->count(4)->create(['blog_post_id' => 2]);
```
:::note info
`factory`メソッドでどこでも`factory`を呼び出せる
`count`メソッドで作成したい個数を指定、上記だと4つのレコードを作成
`create`メソッド内の配列でオプションのパラメータを指定
:::

特定の文字数以上のダミーデータをFactoryで作成
```php
public function definition()
{
    return [
        'title' => $this->faker->sentence(10),
        'content' => $this->faker->paragraphs(5, true)
    ];
}
```
:::note info
`sentence(10)`で10文字分の文字列を作成
`paragraphs(5, true)`で文章を5つ作成。第2引数をfalseにすると配列が返る
:::

#### Factory の State
状態操作するメソッドを定義することで、ダミーではなく任意のデータに変更できる
```php
public function newTitle()
{
    return $this->state(function() {
        return [
            'title' => 'New title',
        ];
    });
}
```
:::note info
メソッド名はわかりやすい名前にする
`$this->state()`内の`return`+ 配列で属性に任意のデータを定義する
:::

stateメソッドで定義した内容のデータを作成する
```php
BlogPost::factory()->count(5)->newTitle()->create();
```
:::note info
stateで定義されていないものは`definition`で定義したランダムデータが作成される
:::
:::note warn
`state`はあくまでモデルファクトリで使用できるもので、実際のモデルには関係がない
:::

#### afterCreating と afterMaking
モデルの作成中、または、作成後に追加のタスクを実行できるようにする。

author（親モデル）を生成しDBに保存した後にprofile（子モデル）を保存する
```php
public function createDummyProfile()
{
    return $this->afterCreating(function(Author $author) {
        $author->profile()->save(Profile::factory()->make());
    });
}
```




# 認証
認証に関するファイルを追加
```php
php artisan make:auth
```
:::note warn
既存のファイルを上書きする場合があるので注意
:::


`web.php`でルーティングが記述される
```php
Auth::routes();
```
:::note info
Auth::routes(); だけで認証に関するすべてのルーティングが設定される
登録されているルーティングを確認するには、`php artisan route:list`
:::


#### 認証に関するコードを追ってみる
`app/Http/Controllers/HomeController.php`で認証に関するミドルウェアを設定している
```php
public function __construct()
    {
        $this->middleware('auth');
    }
```


ミドルウェアは `app/Http/Kernel.php` で設定されてる
```php
protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
```

`App\Http\Middleware\Authenticate`で、認証が失敗したときのリダイレクト先を定義
```php
protected function redirectTo($request)
{
    if (! $request->expectsJson()) {
       return route('login');
    }
}
```
:::note info
APIは弾きたいので、jsonフォーマットかどうかをチェック
`Illuminate\Auth\Middleware\Authenticate`を継承している
:::

`Illuminate\Auth\Middleware\Authenticate`の`authenticate`で認証チェック
```php
public function handle($request, Closure $next, ...$guards)
{
    $this->authenticate($request, $guards);

    return $next($request);
}
```

#### アカウント登録に関するコードを追ってみる
`app/Http/Controllers/Auth/RegisterController.php`に記述がある

最初の時点では認証済みではないため、guestを使用
```php
public function __construct()
{
    $this->middleware('guest');
}
```

バリデーション（制約）の設定
```php
protected function validator(array $data)
{
    return Validator::make($data, [
        'name' => ['required', 'string', 'max:255'],
        'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
        'password' => ['required', 'string', 'min:8', 'confirmed'],
    ]);
}
```
:::note info
emailではusersテーブルでユニークかどうかをチェック
パスワード強度を上げるなど、独自のルールを追加することも可能
:::
:::note info
confirmedは、フィールドが、{field}_confirmationフィールドと一致する必要がある。たとえば、バリデーション中のフィールドが「password」の場合、「password_confirmation」フィールドが入力（html側）に存在し一致している必要がある。
:::

データをDBに保存する処理
```php
protected function create(array $data)
{
    return User::create([
        'name' => $data['name'],
        'email' => $data['email'],
        'password' => Hash::make($data['password']),
    ]);
}
```
:::note warn
パスワードは必ずハッシュ化して保存する
:::


登録全体の処理はtraitを使用している
```php
use RegistersUsers;
```
:::note info
`vendor/laravel/ui/auth-backend/RegistersUsers.php`が使用しているtrait
:::

以下、`RegistersUsers.php`（trait）の処理
登録画面の呼び出し（/registerがgetされた時）
```php
public function showRegistrationForm()
{
    return view('auth.register');
}
```

登録処理（/registerがpostされた時）
```php
public function register(Request $request)
{
    $this->validator($request->all())->validate();

    event(new Registered($user = $this->create($request->all())));

    $this->guard()->login($user);

    if ($response = $this->registered($request, $user)) {
        return $response;
    }

    return $request->wantsJson()
                ? new JsonResponse([], 201)
                : redirect($this->redirectPath());
}
```

:::note info
$request->all() でフォームから送信されたすべての値を取得
validate() でバリデーションの検証
new Registeredでユーザーの新しいインスタンスを保持
redirect(this->redirectPath())でリダイレクト
:::

リダイレクト先はtraitに記述
```php
use RedirectsUsers;
```
:::note info
`vendor/laravel/ui/auth-backend/RedirectsUsers.php`を使用している
:::

以下、`RedirectUsers.php`の処理
リダイレクト先の指定
```php
public function redirectPath()
{
    if (method_exists($this, 'redirectTo')) {
        return $this->redirectTo();
    }

    return property_exists($this, 'redirectTo') ? $this->redirectTo : '/home';
}
```
:::note info
redirectToというメソッドを定義すれば、そのメソッドが呼び出される
メソッドがない場合、redirectToプロパティで指定される
:::


#### ログインに関するコードを追ってみる
`app/Http/Controllers/Auth/LoginController.php`に記述がある
```php
use AuthenticatesUsers;

protected $redirectTo = RouteServiceProvider::HOME;

public function __construct()
{
    $this->middleware('guest')->except('logout');
}
```
:::note info
trait `vendor/laravel/ui/auth-backend/AuthenticatesUsers.php`を使用
redirectToでログインした時のリダイレクト先を定義
exceptで認証が必要ないものを除外
（上記だとlogout以外はguestの認証でOK (logoutはauthの認証が必要のため)）
:::


以下、`AuthenticatesUsers.php`の処理

リダイレクト先をtraitで記述
```php
use RedirectsUsers
```

ログインフロー
```php
public function login(Request $request)
{
    $this->validateLogin($request);

    if (method_exists($this, 'hasTooManyLoginAttempts') &&
        $this->hasTooManyLoginAttempts($request)) {
        $this->fireLockoutEvent($request);

        return $this->sendLockoutResponse($request);
    }

    if ($this->attemptLogin($request)) {
        return $this->sendLoginResponse($request);
    }

    return $this->sendFailedLoginResponse($request);
}
```
:::note info
validateLoginでバリデーションのチェック
$this->attemptLoginで何回ログインを試したかをカウント
:::


何回ログインをしたかのカウント
```php
protected function attemptLogin(Request $request)
{
    return $this->guard()->attempt(
        $this->credentials($request), $request->filled('remember')
    );
}

protected function credentials(Request $request)
{
    return $request->only($this->username(), 'password');
}
```
:::note info
guard()->attemptで与えられた認証情報でログインをしようとしている
credentialsメソッドでユーザー名とパスワードを取得
:::

`login`メソッド内の`sendLoginResponse`メソッドでレスポンスを送信

```php
protected function sendLoginResponse(Request $request)
{
    $request->session()->regenerate();

    $this->clearLoginAttempts($request);

    if ($response = $this->authenticated($request, $this->guard()->user())) {
        return $response;
    }

    return $request->wantsJson()
                ? new JsonResponse([], 204)
                : redirect()->intended($this->redirectPath());
}
```
:::note info
session()->regenerateでセッションを再生成
clearLoginAttemptsでログインに失敗した回数を消去
:::

#### ガードコンポーネントの解説
認証するとき、どのようにユーザーの情報を取ってくるかの設定
`auth.php`に記述がある

```php
'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],

    'api' => [
        'driver' => 'token',
        'provider' => 'users',
        'hash' => false,
    ],
],

'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model' => App\Models\User::class,
    ],

'passwords' => [
    'users' => [
        'provider' => 'users',
        'table' => 'password_resets',
        'expire' => 60,
        'throttle' => 60,
    ],
],
```
:::note info
コンポーネントが分かれていることで、複数の認証方法を使ったコンポーネントを作成できる
guard の driver は session か token から選ぶ（apiはtoken）
providers の driver は eloquent か database から選ぶ
passwordsでパスワードのリセットを設定
　tableでリセットにどのテーブルを使うかを指定
　expireで有効期限を60分に指定
　throttleでログイン失敗の回数を60回に指定
:::

#### Remember Me(ログイン保持)
アカウント名に何を使うかは`vendor/laravel/ui/auth-backend/AuthenticatesUsers.php`の`username`メソッドで定義している
```php
protected function validateLogin(Request $request)
{
    $request->validate([
        $this->username() => 'required|string',
        'password' => 'required|string',
    ]);
}

public function username()
{
    return 'email';
}
```
:::note info
上記ではemailだが別のクラスでnameなどにオーバーライドすることができる
:::

htmlのnameにrememberを記述
```php
<input class="form-check-input" type="checkbox" name="remember"
    value="{{ old('remember') ? 'checked' : '' }}"/>
```

`vendor/laravel/ui/auth-backend/AuthenticatesUsers.php`の`attemptLogin`メソッド内でrememberのチェックしている
```php
protected function attemptLogin(Request $request)
{
    return $this->guard()->attempt(
        $this->credentials($request), $request->filled('remember')
    );
}
```
:::note info
`attempt`メソッドの第2引数にbool値を渡せる
trueの場合は、手動でログアウトするまで認証が継続される
:::

#### 認証済みと未認証の挙動を分ける
Blade内で出しわけ
```php
@auth('admin')
    // ユーザーは認証済み…
@endauth

@guest('admin')
    // ユーザーは認証されていない…
@endguest
```

登録ルートがある場合のみ登録画面へのリンクを表示する
```php
@if (Route::has('register'))
    <a class="p-2 text-dark" href="{{ route('register') }}">Register</a>
@endif
```

ログアウトのルートはpostしか受け付けないためpostでの実装をする
```php
<a class="p-2 text-dark" href="{{ route('logout') }}"
    onclick="event.preventDefault();document.getElementById('logout-form').submit();">
    Logout ({{ Auth::user()->name }})
</a> 
<form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
    @csrf
</form>
```
:::note info
event.preventDefault();はjavaScriptの記述でデフォルトの挙動を妨害する
上記の場合、route('logout')へのリンク遷移を妨害している
:::

#### csrfトークンの処理を追ってみる
`app/Http/Middleware/VerifyCsrfToken.php`でcsrf対策の適用を除外するURIを設定できる
```php
class VerifyCsrfToken extends Middleware
{
    /**
     * The URIs that should be excluded from CSRF verification.
     *
     * @var array
     */
    protected $except = [
        // 適用を除外するURI
    ];
}
```

`app/Http/Middleware/VerifyCsrfToken.php`は`vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php`を継承しており、`handle`メソッド内でcsrf対策の処理をしている
```php
public function handle($request, Closure $next)
{
    if (
        $this->isReading($request) ||
        $this->runningUnitTests() ||
        $this->inExceptArray($request) ||
        $this->tokensMatch($request)
    ) {
        return tap($next($request), function ($response) use ($request) {
            if ($this->shouldAddXsrfTokenCookie()) {
                $this->addCookieToResponse($request, $response);
            }
        });
    }

    throw new TokenMismatchException('CSRF token mismatch.');
}
```

`tokensMatch`メソッドでcsrfトークンのチェック
```php
protected function tokensMatch($request)
{
$token = $this->getTokenFromRequest($request);

return is_string($request->session()->token()) &&
        is_string($token) &&
        hash_equals($request->session()->token(), $token);
}
```

`getTokenFromRequest`メソッドでcsrfトークンを取得
```php
protected function getTokenFromRequest($request)
{
    $token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');

    if (! $token && $header = $request->header('X-XSRF-TOKEN')) {
        try {
            $token = CookieValuePrefix::remove($this->encrypter->decrypt($header, static::serialized()));
        } catch (DecryptException $e) {
            $token = '';
        }
    }

    return $token;
}
```
:::note info
inputかヘッダーにcsrfトークンがあるか確認している
csrfトークンでエラーが出た場合、419が返ってくる
:::

:::note warn
どこでエラーが起きているか確認する以外はvendor配下のファイルは変更しない方が良い
:::

:::note warn
セッションの書き込みエラーの場合は、`storage/framework/sessions`ディレクトリ配下のファイルに書き込み権限があるかどうかを確認する
:::

#### 認証されたユーザー情報の取得
`Auth`ファサードを使用する方法
```php
use Illuminate\Support\Facades\Auth;
```
```php
// 現在認証しているユーザーのidを取得
Auth::id();

// 現在認証しているユーザーモデルを取得
Auth::user();

// ユーザーが認証済みかチェックする
Auth::check();
```
:::note info
`Auth`ファサードクラスを見ると使用可能なメソッド一覧が確認できる
:::

認証済みユーザーでリクエストを送る場合$ruquestからも取得できる
```php
public function store(StorePost $request)
{
    $validated = $request->validated();
    $validated['user_id'] = $request->user()->id;
    // 省略
}
```
:::note info
`$request->user()`で認証ユーザーの情報を取得
:::


#### 特定のルートを認証済みユーザーしかアクセスできないようにする
route側で認証済みユーザーしかアクセスできないようにする場合
```php
Route::get('/recent-posts/{days_ago?}', function ($daysAgo = 20) {
    return 'Post from ' . $daysAgo . 'days ago';
})->name('posts.recent.index ')->middleware('auth');
```

controller側で認証済みユーザーしかアクセスできないようにする場合
```php
class PostsController extends Controller
{
    public function __construct() {
        $this->middleware('auth')
            ->only(['create', 'store', 'edit', 'update', 'destroy']);
    }
    //
}
```

#### テストで認証されたユーザーを使用する場合
`tests/TestCase.php`クラスに`user`メソッドを追加
```php
abstract class TestCase extends BaseTestCase
{
    use CreatesApplication;

    protected function user()
    {
        return User::factory()->create();
    }
}
```

テストする箇所で`user`メソッドを呼び出す
```php
$this->actingAs($this->user())
    ->post('/posts', $params)
    ->assertStatus(302)
    ->assertSessionHas('status');
```
:::note info
`actingAs`ヘルパ関数で指定したユーザーを現在のユーザーとして認証
:::




# Database Seeding
#### 外部キーの追加
既存のデータベースに外部キーを追加する場合、外部キーを追加するだけのmigrationファイルを実行すると、既存レコードの外部キーに何を入れればよいかLaravelが判断できないため、エラーが発生する。

エラーが出るmigrationファイル
```php
public function up()
{
    Schema::table('blog_posts', function (Blueprint $table) {
        $table->unsignedInteger('user_id');
        $table->foreign('user_id')->references('id')->on('users');
    });
}
```

上の簡易的な書き方
```php
public function up()
{
    Schema::table('blog_posts', function (Blueprint $table) {
        $table->foreignId('user_id')->constrained('users');
    });
}
```
:::note info
カラムタイプは実際のテーブルの外部キーと一致すること。上記の場合、users.idがunsignedBigIntegerなので、そのように設定している。
:::

解決方法1
nullを許容する
```php
$table->unsignedInteger('user_id')->nullable();
```

解決方法2
開発環境ならmigrate:refreshを実行する
```php
php artisan migrate:refresh
```
:::note info
すべてのデータベースが1度ロールバックされた後、再度テーブルが作成される
本番環境では使用しない
:::

#### seeder
データベースにデータを一括で登録できるもの。

seederの実行
```php
php artisan db:seed
```

`database/seeders/DatabaseSeeder.php`に記述された内容が実行される
```php
class DatabaseSeeder extends Seeder
{
    public function run()
    {
        DB::table('users')->insert([
            'name' => 'yuta saito',
            'email' => 'saito@laravel.test',
            'email_verified_at' => now(),
            'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
            'remember_token' => Str::random(10),
        ]);
    }
}
```
:::note info
`run`メソッド内が実行される、上記の例だとusersテーブルにレコードを1つ追加している
もう1度seederを実行すると重複したレコードを登録できないためエラーになる
その場合は`php artisan migrate:fresh --seed`を実行するとエラーにならない
:::

seeder内でfactoryを使う
```php
class DatabaseSeeder extends Seeder
{
    public function run()
    {
        $users = User::factory(10)->create();
        // $usersが10こ生成されるか確認
        dd($users->count());
    }
}
```
:::note info
コレクションはphpの配列に関する関数(countとか)をそのまま使える
:::

外部キーをもつmodelのseeder
```php
public function run()
{
    $takauji = User::factory()->takaujiAshikaga()->create();
    $else = User::factory(20)->create();

    $users = $else->concat([$takauji]);

    $blog_posts = BlogPost::factory(50)->make()->each(function ($blog_post) use ($users) {
        $blog_post->user_id = $users->random()->id;
        $blog_post->save();
    });

    Comment::factory(150)->make()->each(function ($comment) use ($blog_posts) {
        $comment->blog_post_id = $blog_posts->random()->id;
        $comment->save();
    });
}
```
:::note info
`make`を使っている点に注意

以下、コレクションで使えるメソッド
`concat` 配列またはコレクションを別のコレクションに加える
`each` 反復処理し、各アイテムをクロージャに渡す
`random` コレクションの中からランダムに1つ返す
:::

個別のseederファイルを作成する
```php
php artisan make:seeder UsersTableSeeder(ファイル名)
```

作成したseederファイルが実行されるようにする
```php
// database/seeders/DatabaseSeeder.phpに記述
public function run()
{
    $this->call([
        UsersTableSeeder::class,
        BlogPostsTableSeeder::class,
        CommentsTableSeeder::class,
    ]);
}
```
:::note warn
新しいseederファイルを作成したら必ず下記コマンドを実行すること
`composer dump-autoload`
:::

作成した個別のseederを実行する
```php
php artisan db:seed --class= UsersTableSeeder
```

#### seeder実行時のオプションを設定する
`php artisan db:seed`を実行した際にdatabaseを作り直すか確認するように設定
```php
if ($this->command->confirm('Do you want to refresh the database?', true)) {
    $this->command->call('migrate:refresh');
    $this->command->info('Database was refreshed');
}
```
:::note info
継承元の`vendor/laravel/framework/src/Illuminate/Database/Seeder.php`に`$command`プロパティが設定されている
`$command`のメソッドとしてaritsanコンソール関連のメソッドを使用できる
`confirm`メソッドでyes, no で回答する質問が表示される、第2引数がtrueの場合はデフォルト値がyesになる
:::

参照：[(Laravel公式)artisanコンソール](https://readouble.com/laravel/9.x/ja/artisan.html?#command-io)

作成するuser数のデフォルト値を設定する
```php
class UsersTableSeeder extends Seeder
{
    public function run()
    {
        $user_count = max($this->command->ask('How many users would you like?', 20), 1);
        User::factory($user_count)->create();
    }
}
```
:::note info
`max`で与えられた引数の中で最も大きな数を返す、入力値が0だった場合「0, 1」になり1が返る
`ask`メソッドの第2引数はデフォルト値
:::

`-n`オプションを付けるとデフォルト値ですべて進めてくれる
```php
php artisan db:seed -n
```






# 物理削除と論理削除（ソフトデリート）
デフォルトだと関連テーブルは自動で削除してくれないので、設定する必要がある。

#### 関連テーブルの削除方法1
Eloquentモデルはイベントをディスパッチする。
`delete`されたときには、`deleting` と `deleted`のイベントがディスパッチされる。
上記イベントをディスパッチするために、モデルに`booted`メソッドを定義する。
```php
// BlogPostを削除したとき、紐づいたCommentsも一緒に削除されるよう設定
public static function booted()
{
    static::deleting(function (BlogPost $blogPost) {
        $blogPost->comments()->delete();
    });
}
```
:::note info
deletingの場合、削除される直前に処理が走る
commentsではなくcomments()としているのは、特定のコメントではなくすべてのコメントを取得するため
:::

#### 関連テーブルの削除方法2
migrationファイルでテーブルを作成するときに、関連テーブルも削除されるように設定する
```php
public function up()
{
    Schema::table('comments', function (Blueprint $table) {
        // 外部キーのblog_post_idが削除されたとき、commentsも削除する
        $table->dropForeign(['blog_post_id']);
        $table->foreign('blog_post_id')
            ->references('id')
            ->on('blog_posts')
            ->onDelete('cascade');
    });
}
```
:::note info
`dropForeign`で既存の外部キーを削除、カラム名を指定するときは配列を渡す
`dropForeign('comments_blog_post_id_foreign')`のように外部キーの名前を渡す場合は、配列ではなく文字列で渡す
`onDelete('cascade')`で外部キーのレコードが削除されたときに紐づくレコードが削除される
:::

#### ソフトデリート
ソフトデリート(論理削除)するには`Illuminate\Database\Eloquent\SoftDeletes`を使う
```php
use Illuminate\Database\Eloquent\SoftDeletes;

class BlogPost extends Model
{
    use SoftDeletes;
    //
}
```

migrationファイルでテーブル作成時にdeleted_atカラムを追加する
```php
public function up()
{
    Schema::table('blog_posts', function (Blueprint $table) {
        $table->softDeletes();
    });
}

public function down()
{
    Schema::table('blog_posts', function (Blueprint $table) {
        $table->dropSoftDeletes();
    });
}
```
:::note info
関連テーブルも論理削除したい場合は、関連テーブルにもSoftDeletesを使う
:::

ソフトデリートされたレコードを取得する
```php
$all = BlogPost::withTrashed()->get()->pluck('id');
```
:::note info
ソフトデリートされたものはall()など通常のクエリでは取得対象でなくなる
:::

ソフトデリートされたレコードだけを取得する
```php
$trashed = BlogPost::onlyTrashed()->where('id', '>', 3)->get();
```
:::note info
`withTrashed`や`onlyTrashed`はクエリビルダのインスタンスを返す
:::

取得したモデルがソフトデリートされているかをチェックする
```php
$post = BlogPost::withTrashed()->find(3);
// bool値が返ってくる
$post->trashed();
```

ソフトデリートしたものを復元する
```php
$post = BlogPost::withTrashed()->find(3);
$post->restore();
```

関連テーブルも一緒に復元させる場合は`booted`メソッド内に`restoring`メソッドをあらかじめ記述
```php
static::restoring(function (BlogPost $blogPost) {
    $blogPost->comments()->restore();
});
```

論理削除(ソフトデリート)が有効な場合に、レコードを物理削除する方法
```php
$post = BlogPost::has('comments')->get()->first();
$post->forceDelete();
```




# 認可 (Gate, Policy)
あるユーザーが特定の行動をできるように許可することを認可という。
Laravelでの認可の方法は、GateとPolicyの2つがある。

### Gate
Gateとは、ユーザーが特定のアクションを実行することを許可されているかどうかを判断するクロージャのこと。`Illuminate\Support\Facades\Gate`を使用する。
`App\Providers\AuthServiceProvider`でGateを使用することで、認可の定義ができる。

自分で投稿したBlogPostだけ編集できるようにする
```php
use App\Models\BlogPost;
use App\Models\User;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;

class AuthServiceProvider extends ServiceProvider
{
    public function boot()
    {
        $this->registerPolicies();

        Gate::define('update-post', function (User $user, BlogPost $post) {
            return $user->id === $post->user_id;
        });
    }
}
```
:::note info
`boot`メソッド内にGateを定義する
`define`の第1引数でわかりやすいアクション名をつける
クロージャの第1引数は必ず`$user`
:::

編集するときに認可されているかを判定
```php
// Controllerに記述
public function edit($id)
{
    $post = BlogPost::findOrFail($id);

    if (Gate::denies('update-post', $post)) {
        abort(403, "You can't edit this blog post.");
    }

    return view('posts.edit', ['post' => $post]);
}
```
:::note info
`Gate::denies`で`update-post`が認可されていない場合を判定
`abort`はヘルパ関数、第2引数で表示するメッセージを定義
:::

認可されていない判定を簡単に記述する
```php
// Controllerに記述
public function edit($id)
{
    $post = BlogPost::findOrFail($id);

    $this->authorize('update-post', $post);

    return view('posts.edit', ['post' => $post]);
}
```
:::note info
Controllerの場合、`$this`で`authorize`ヘルパ関数が使える
`authorize`でアクションが認可判定し、認可されていない場合は403を返す
:::

認証済みユーザー以外のユーザーが特定の行動を認可されているか確認する
```php
$user = User::find(21);

// 認可されている場合、trueが返る
Gate::forUser($user)->allows('update-post', $post);

// 認可されていない場合、trueが返る
Gate::forUser($user)->denies('update-post', $post);
```

adminのようにすべての権限を認可する場合
```php
Gate::before(function ($user, $ability) {
    if ($user->is_admin && in_array($ability, ['update-post'])) {
        return true;
    }
});
```
:::note info
`before`メソッドで他の認可チェックが入る前のクロージャを定義できる
第2引数の`$ability`は他の認可チェックで定義しているアクション名
null以外が返った場合、認可チェックの結果と見なされる
上記の場合、userがadminでupdate-postのアクションをする場合は認可される
同じようなものに`after`メソッドがある
:::

### Policy
特定のモデルまたはリソースに関する認可ロジックを集めたクラスのこと。
認可対象が増えてきたときにGateですべて対処しようと思うと、複雑になるのでPolicyを使用する。
特に特定のモデルやリソースに紐づく場合に使用するとベター。

Policyの作成
```php
php artisan make:policy BlogPostPolicy --model=BlogPost
```
:::note info
オプションでモデルを指定することでCRUDのメソッドを自動で生成してくれる
:::

Policyファイルに認可条件を定義
```php
public function update(User $user, BlogPost $blog_post)
{
    return $user->id === $blog_post->user_id;
}
```

AuthServiceProviderで定義したPolicyを使用する
```php
Gate::define('update-post', [BlogPostPolicy::class, 'update']);
```

モデルを紐づけた場合はresourceメソッドを使うこともできる
```php
Gate::resource('posts', BlogPostPolicy::class);
```
:::note info
view、create、update、deleteを定義しているのと一緒
第1引数の名前はなんでも良いのでわかりやすい名前をつける
:::

モデルとポリシーの対応を定義することで**Gateを定義しなくても認可が反映**される
```php
// AuthServiceProviderに記述
protected $policies = [
    BlogPost::class => BlogPostPolicy::class,
];
```

#### リソースコントローラでの認可

モデルを紐づけてPolicyを作成しており、コントローラもモデルを紐づけて作成されている場合、アクション名なしでPolicyを反映できる
```php
public function update(StorePost $request, $id)
{
    $post = BlogPost::findOrFail($id);
    $this->authorize($post);
    // 省略
}
```
:::note info
`$this->authorize($post)`だけでPolicyのupdateが呼ばれる
:::

参照：[（Laravel公式）リソースコントローラの認可](https://readouble.com/laravel/9.x/ja/authorization#authorizing-resource-controllers)

リソースポリシーとリソースコントローラを使っている場合は、コントローラのコンストラクターで`authorizeResource`メソッドを使用できる
```php
// Controllerに記述
public function __construct()
{
    $this->middleware('auth');
    $this->authorizeResource(User::class, 'user');
}
```
:::note info
`authorizeResource `の第1引数にモデルのクラス名、第2引数にモデルのIDを含むルート/リクエストパラーメーターの名前を入れることで、自動的に下記のポリシーメソッドが適用されるようになる。
:::

|コントローラメソッド|ポリシーメソッド|
|:--|:--|
| index | viewAny |
| show | view |
| create | create |
| store | create |
| edit | update |
| update | update |
| destroy | delete |

#### Bladeでの認可

Bladeで認可されているかどうかを判定する
```php
@can('update', $post)
    <a href="{{ route('posts.edit', ['post' => $post->id]) }}" class="btn btn-primary">Edit</a>
@endcan
```
:::note info
`can`ディレクティブで認可の設定ができる
コントローラと同じようにアクション名と変数を渡す
:::

Bladeで認可されていない時のアクションを設定する
```php
@cannot('delete', $post)
    <p>You cannot delete this post.</p>
@endcannot
```
:::note info
`cannnot`ディレクティブで認可されていない時の設定ができる
:::

#### ミドルウェアでの認可
```php
// AuthServiceProviderに記述
Gate::define('home.secret', function (User $user) {
    return $user->is_admin;
});
```
```php
// Routeに記述
Route::get('/secret', [HomeController::class, 'secret'])
    ->name('home.secret')
    ->middleware('can:home.secret');
```
:::note info
`middleware`の中で`can`を使うことで認可の設定ができる
:::





# クエリスコープ(Query Scopes)
クエリの制約をつけることができる。
グローバルスコープとローカルスコープがある。

### グローバルスコープ
特定のモデルのすべてのクエリに制約を追加できる。
スコープ専用の特定ディレクトリはデフォルトでないため、自分で作成する。

グローバルスコープの作成
```php
<?php

namespace App\Scopes;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;

class LatestScope implements Scope
{
    public function apply(Builder $builder, Model $model)
    {
        $builder->orderby($model::CREATED_AT, 'desc');
    }
}
```
:::note info
`$model::CREATED_AT`でモデルの作成日を指定
:::

グローバルスコープの適用
```php
// app/Models/BlogPost.phpに記述
public static function booted()
{
    static::addGlobalScope(new LatestScope);
}
```
:::note info
`addGlobalScope`の引数にはインスタンスだけをとる
:::
:::note warn
元々Laravelの機能で`latest`メソッドがあるので上の例はあくまで練習
:::

Softdeleteを特定のクエリで適用されないようにする
```php
class DeletedAdminScope implements Scope
{
    public function apply(Builder $builder, Model $model)
    {
        if (Auth::check() && Auth::user()->is_admin) {
            $builder->withoutGlobalScope(SoftDeletingScope::class);
        }
    }
}
```
```php
// app/Models/BlogPost.phpに記述
public static function boot()
{
    static::addGlobalScope(new DeletedAdminScope);
    
    parent::boot();
}
```
:::note info
`Auth::check`でユーザーが認証済みかチェック
`$builder->withoutGlobalScope()`で適用されるはずのScopeを削除
:::



### ローカルスコープ
特定の場合にのみ再利用できる共通のクエリ制約を設定できる。

タイトルで並び替える
```php
// app/Models/BlogPost.phpに記述
public function scopeTitle(Builder $query)
{
    return $query->orderby('title', 'asc');
}
```
:::note info
ローカルスコープのメソッド名は必ず`scope`から始める
:::

適用
```php
// app/Http/Controllers/PostsController.phpに記述
return view(
    'posts.index',
    ['posts' => BlogPost::title()->withCount('comments')->get()]
);
```
:::note info
設定したローカルスコープのscopeの文字を消したものがメソッド名として使用できる
:::

複雑な場合はクロージャを使うケースもある
```php
// app/Models/Comment.phpに記述
public function scopeContent(Builder $query)
{
    return $query->orderby('content', 'asc');
}
```
```php
// app/Http/Controllers/PostsController.phpに記述
return view('posts.show', [
    'post' => BlogPost::with(['comments' => function ($query) {
        return $query->content();
    }])->findOrFail($id),
]);
```
:::note info
上記はBlogPostを取得するとき、コメントの内容をアイウエオ順に並び替えて取得
:::




# BladeComponents
componentはBladeの要素をまとめて再利用しやすくするもの。

### クラスベースのcomponent
componentの作成
```php
php artisan make:component Alert
```
:::note info
`App\View\Components`の下にクラスのファイルが作成される
`resources/views/components`ディレクトリの下にビュー用のファイルが作成される
:::

コンポーネントを使用する
```php
// resources/views/components/alert.blade.phpに記述
<div class="alert alert-{{ $type }}">
    {{ $message }}
</div>
```
```php
// app/view/components/Alert.phpに記述
class Alert extends Component
{
    public $type;

    public $message;

    public function __construct($type, $message)
    {
        $this->type = $type;
        $this->message = $message;
    }

    public function render()
    {
        return view('components.alert');
    }
}
```
```php
// 表示したいBladeに記述
<x-alert type="primary" :message="$message"/>
```
:::note info
変数を渡すときは`:`をつける
`x-`でコンポーネントを使える
クラスには使いたい変数をすべて定義する必要がある
:::

### スロット
コンポーネントにプレーンテキストを渡すときは`$slot`を使用する
```php
// resources/views/components/alert.blade.phpに記述
<span class="alert-title">{{ $title }}</span>

<div class="alert alert-danger">
    {{ $slot }}
</div>
```
```php
<x-alert>
    <x-slot:title>
        Server Error
    </x-slot>

    <strong>Whoops!</strong> Something went wrong!
</x-alert>
```
:::note info
`$slot`に`<strong>Whoops!</strong> Something went wrong!`が渡される
`x-slot:title`で`$title`に値を渡す
:::

### 匿名コンポーネント
関連するクラスのない単一のコンポーネントファイル。
`resources/views/components`の下にファイルを作成するだけでOK。
基本的な使い方はクラスベースコンポーネントと一緒。
ただし`components`ディレクトリの下にファイルを置かない場合は、`AppServiceProvider.php`でエイリアスを設定してあげる必要がある。

```php
public function boot()
{
    Blade::component('posts.components.badge', 'badge');
}
```
:::note info
上記だと`resources/views/posts/components/badge.blade.php`を`x-badge`で呼び出せるようにしている
:::




# キャッシュ
Laravelのキャッシュとは、一時的にメモリにデータを保存することです。
メモリからデータを読み込むことで、ディスクへアクセスすることなく高速にデータを取得できます。
キャッシュするデータは通常、MemcachedやRedisなどの非常に高速なデータストアに保存します。

### 設定
キャッシュの設定は`config/cache.php`、`.env`のファイルに記述されている。

`config/cache.php`で使用するキャッシュの種類を指定
```php
'default' => env('CACHE_DRIVER', 'file'),
```
:::note info
memcachedやredisについても設定されている
redisの詳細は`config/database.php`にも設定がある
:::

### キャッシュのメソッド
`Illuminate\Support\Facades\Cache`を使えばキャッシュに関するメソッドを使用できる。

キャッシュを設定
```php
Cache::put('data', 'Hello World!', 5);
```
:::note info
第1引数がキー名、第2引数が値、第3引数が保存する時間（秒）
第3引数がない場合は永遠に保存された状態になる
:::

キャッシュの値を取得
```php
Cache::get('data');
```

キャッシュが設定されているか確認
```php
Cache::get('data');
```
:::note info
ある場合はtrue、ない場合はfalseが返る
:::

キャッシュ内の値をインクリメント
```php
Cache::increment('xxx', $data);
```
:::note info
第2引数で増減させる値を設定できる、$dataが2なら'xxx'を2増やす
:::

キャッシュがない場合だけキャッシュを設定
```php
$most_commented = Cache::remember('blog-post-commented', 60, function () {
    return BlogPost::mostCommented()->take(5)->get();
}); 
```
:::note info
第1引数はキー名、第2引数はキャッシュの保存時間(秒)、第3引数はクロージャでキーがない場合に取得する値
第2引数はcarbonなどでも指定できる、またない場合は永遠に保存される
:::

キャッシュを削除する
```php
Cache::forget("blog-post-{$blogPost->id}")
```

### キャッシュタグ
キャッシュタグはMemcachedやRedisなどで使用できる。fileやmysqlでは使用できない。
キャッシュ内の関連アイテムにタグを付けてから、特定のタグが割り当てられているすべてのキャッシュ値を削除できる。
参照：[[ Docker ] Redis をDocker化する & LaravelからSessionを保存する](https://zenn.dev/takuyanagai0213/articles/8a125ff0b14b01)
参照：[DockerでRedisのコンテナを作成しLaravelと接続する](https://zenn.dev/marushin/articles/4903cd0bbbcee0)

キャッシュタグをつける
```php
Cache::tags(['people', 'artists'])->put('John', 'Hello Im John', 60);
Cache::tags(['people', 'authors'])->put('Ann', 'Hello Im Ann', 60);
```

キャッシュタグへのアクセス
```php
Cache::tags(['people', 'artists'])->get('John');
// "Hello Im John"が表示される
```

タグ付きキャッシュアイテムの削除
```php
Cache::tags(['people'])->flush();
```
:::note info
['people']の場合は、'people'のタグがついたすべてのキャッシュが削除される
['artists']の場合は、'artists'のタグがついたもの（'John'だけ）が削除される
:::


# リレーション(多対多、Eloquent)
blog_postsテーブル、tagsテーブル、中間テーブルのblog_post_tagsテーブルを定義する

modelで関係性を定義
```php
// app/Models/BlogPost.phpで記述
public function tags()
{
    return $this->belongsToMany(Tag::class)->withTimestamps();
}
```
```php
// app/Models/Tag.phpで記述
public function blogPosts()
{
    return $this->belongsToMany(BlogPost::class)->withTimestamps();
}
```
:::note info
`withTimestamps`メソッドをつけることで中間テーブルのcreated_atと
updated_atに値が入るようになる
:::


関連付けをする
```php
$blog_post = BlogPost::find(1);
$blog_post->tags()->attach([$tag1->id, $tag2->id]);
```
:::note info
`attach`メソッドで紐付けをする、中間テーブル(pivotテーブル)にレコードが生成される
配列で複数の値を渡すことができる
`attach`メソッドは重複を気にせず関連付けしてしまうので注意
:::

関連付けを解除する
```php
$blog_post->tags()->detach($tag1);
$blog_post->tags()->detach([$tag1->id, $tag2->id]);
```
:::note info
$tag1に紐づくレコードが中間テーブルから削除される
複数渡す場合は、配列で渡してあげる
引数を指定しない場合は、すべての紐付けが解除される（レコードすべて削除）
:::

すべての関連付けを指定した値に同期させる
```php
$blog_post->tags()->sync([$tag1->id, $tag2->id]);
```
:::note info
`sync`メソッドで指定されていない値は関連付けが解除される
上記の場合、$tag3などの関連付けがあれば解除され、レコードが削除される
重複は発生しない
:::

関連付けを解除せずに指定した値に同期させる
```php
$blog_post->tags()->syncWithoutDetaching([$tag1->id ,$tag2->id]);
```
:::note info
`syncWithoutDetaching`メソッドでは指定した値以外のものも、そのまま残る
上記の場合、$tag3などの関連付けがあってもそのまま残り、レコードも削除されない
重複は発生しない
:::

中間テーブルのデータにアクセスする
```php
$tags = App\Models\Tag::find(1);
$post = $tags->blogPosts->first();
$post->pivot->created_at;
```
:::note info
中間テーブルには`pivot`でアクセスできる
:::

中間テーブルの属性名に別名をつける
```php
// app/Models/Tag.phpに記述
public function blogPosts()
{
    return $this->belongsToMany(BlogPost::class)
        ->as('tagged')
        ->withTimestamps();
}
```
:::note info
`as`メソッドで別名をつけることでpivot以外の名前にすることができる
ただし、両方のmodelで定義しない場合、定義した側からしかその名前でアクセスできない
:::

### ネストしたリレーションへのアクセス
```php
BlogPost::with(['comments', 'tags', 'user', 'comments.user'])->findOrFail($id);
```
:::note info
`comments.user`でネストした`comments`の`user`へアクセスできる
:::

### 複数のリレーションへのアクセス
複数のリレーションはwithの引数に配列で渡してあげればOK
```php
BlogPost::with(['comments', 'tags', 'user', 'comments.user'])->findOrFail($id);
```





# ファイルストレージ
ファイルをどこに保存するか、どこからダウンロードするかの設定。
ファイルストレージの設定は`filesystem.php`に記述がある。
`storage_path`メソッドでどこのディレクトリに保存するかを指定している。
アクセスする場合は、どの設定でも`Illuminate\Support\Facades\Storage`を使う。

```php
'default' => env('FILESYSTEM_DRIVER', 'local'),


'disks' => [

    'local' => [
        'driver' => 'local',
        'root' => storage_path('app'),
    ],

    'public' => [
        'driver' => 'local',
        'root' => storage_path('app/public'),
        'url' => env('APP_URL').'/storage',
        'visibility' => 'public',
    ],

    's3' => [
        'driver' => 's3',
        'key' => env('AWS_ACCESS_KEY_ID'),
        'secret' => env('AWS_SECRET_ACCESS_KEY'),
        'region' => env('AWS_DEFAULT_REGION'),
        'bucket' => env('AWS_BUCKET'),
        'url' => env('AWS_URL'),
        'endpoint' => env('AWS_ENDPOINT'),
    ],
```
:::note info
.envに設定がない場合のデフォルトの設定は`local`になってる
:::

### ファイルのアップロード
アップロードボタンの作成
```php
<form action="{{ route('posts.store') }}" method="POST" enctype="multipart/form-data">
    @csrf
    <div class="form-group">
        <label for="thumbnail">Thumbnail</label>
        <input id="thumbnail" type="file" name="thumbnail" class="form-control-file">
    </div>
    <div>
        <input type="submit" value="Create" class="btn btn-primary btn-block">
    </div>
</form>
```
:::note info
`form`に`enctype="multipart/form-data"`を指定する
:::

リクエストにファイルがあるか確認して、あったら保存する
```php
$has_file = $request->hasFile('thumbnail');

if ($has_file) {
    $file = $request->file('thumbnail');
    $file->store('thumbnails');
}
```
:::note info
`hasFile`メソッドでリクエストに対象のファイルがあるか確認
`$request->file('thumbnail')`でファイルにアクセス
`$file->store('thumbnails')`でファイルを保存、thumbnailsというディレクトリ配下にファイルが作成される
:::

ファイル名の確認
```php
$file->getClientMimeType();
// もしくは
$file->hashName(); 
```

ファイル拡張子の確認
```php
$file->getClientOriginalExtension();
// もしくは
$file->extension();
```

:::note warn
悪意のあるユーザーによりファイル名や拡張子が改竄される可能性があるため、`getClientMimeType `や`getClientOriginalExtension `は使わない方が良い
:::

Storageファサードを使う書き方
```php
Storage::disk('s3')->put('thumbnails', $file);
// もしくは
Storage::putFile('thumbnails', $file)
```
:::note info
`disk`メソッドを使用すると特定のディスクを指定できる
`putFile`メソッドで一意のファイル名を生成する、第2引数には`Illuminate\Http\File`または`Illuminate\Http\UploadedFile`を引数にとる
:::

ファイル名を指定する場合
```php
$file->storeAs('thumbnails', $post->id . $file->extension());
// もしくは
Storage::putFileAs('thumbnails', $file, $post->id . $file->extension());
```
:::note info
`storeAs`メソッドの第2引数として、ファイル名を指定
`putFileAs`メソッドの第2引数で保存するファイル、第3引数でファイル名を指定
:::

アップロード先のURLを取得する
```php
$name1 = $file->storeAs('thumbnails', $post->id . '.' . $file->guessExtension());
$name2 = Storage::disk('local')->putFileAs('thumbnails', $file, $post->id . '.' . $file->extension());

Storage::url($name1);
// 結果：http://localhost/storage/thumbnails/62.jpg
Storage::disk('local')->url($name2);
// 結果：/storage/thumbnails/62.jpg
```
:::note info
`Storage::url()`でURLを取得する
publicフォルダ配下のものはhttpからURLが表示される
diskでlocalを指定している場合は、storage/app配下に格納されるため、httpからのURLではない
:::

publicアップロードした先のファイルにアクセスできるようにする
```php
php artisan storage:link
```
:::note warn
publicディレクトリにapp/storage/public配下のファイルのシンボリックリンクを作成する
これをしないとアクセスができないので注意
:::

画面上にファイルを表示する
```php
// app/Models/Image.phpに記述
public function url()
{
    return Storage::url($this->path);
}
```
```php
// Bladeに記述
<img src="{{ $post->image->url() }}"/>
```

### ファイルを削除する
`delete`メソッドを使う
```php
Storage::delete($post->image->path);
```

### ファイルにバリデーションをかける
リクエストクラスに記述
```php
public function rules()
{
    return [
        'title' => 'bail|required|min:5|max:100',
        'content' => 'required|min:10',
        'thumbnail' => 'image|mimes:jpg,jpeg,png,gif,svg|max:1024|dimensions:min_height=500',
    ];
}
```
:::note info
imageで画像ファイルのみを指定
mimesで拡張子を指定
maxでファイルサイズを指定
dimensionsでパラメータで高さなどを指定
:::







# ポリモーフィックリレーション（Polymorphic relation）
ポリモーフィック(多相型)な関係は、一つのモデルが複数のモデルに紐づくものをいう。
例えば、画像(image)がブログ記事(Post)にもユーザー(User)にも紐づく場合、ポリモーフィックな関係と言える。

ポリモーフィックなテーブルを作成するmigrationファイル
```php
public function up()
 {
    Schema::table('images', function (Blueprint $table) {
        // $table->unsignedBigInteger('imageable_id');
        // $table->string('imageable_type');
        // 上記2つを`morphs`メソッド1つで代替できる

        $table->morphs('imageable');
    });
}
```
:::note info
`imageable_id`と`imageable_type`でPostとUserのどちらの画像なのかを判定する
`morphs`メソッドで、`{column}_id`と`{column}_type`を生成できる
:::

```php
public function down()
{
    Schema::table('images', function (Blueprint $table) {
        $table->dropMorphs('imageable');
    });
}
```
:::note info
`dropMorphs`メソッドで`morphs`メソッドで作成したカラムの削除
:::

作成されるカラムのデフォルト文字数をあらかじめ`AppServiceProvider`で定義しておく
```php
public function boot()
{
    Schema::defaultStringLength(191);
}
```
:::note info
`defaultStringLength`メソッドでmigrationで作ったstringのデフォルト文字数を定義
:::

### 1対1のポリモーフィックな関係の定義
```php
// app/Models/Image.phpに記述
public function imageable()
{
    return $this->morphTo();
}
```
```php
// app/Models/BlogPost.php と app/Models/User.phpに記述
public function image()
{
    return $this->morphOne(Image::class, 'imageable');
}
```

imageを保存
```php
$post->image()->save(
    Image::make(['path' => $path])
);
```
:::note info
`$post->image()`だけでアクセスができる
:::









# ライフサイクル

1. autoloadを読み込んで、requireしなくても名前空間を指定するだけで良くなる
1. Applicationインスタンスを作成（サービスコンテナ）、サービスプロバイダも読み込まれる
1. HttpKernelインスタンスを作成
1. Requestインスタンスを作成
1. HttpKernelがリクエストを処理してResponseを取得
1. レスポンスを送信
1. terminate()でリクエストとレスポンスを削除



# サービスコンテナ

依存関係を自動で解決してくれる。
依存関係のさらに先の依存関係まで全部解決してくれる。
app()はサービスコンテナを呼び出すヘルパ関数。

コンテナへの登録
```php
app()->bind()

// 1度しか依存関係を解決しなくて良い場合
app()->singleton()
```

登録されているものを使う
```php
app()->make()

// 他の書き方
app('登録したメソッド名');
app()->resolve();
App::make();
```



# サービスプロバイダ（提供者）

サービスコンテナにサービスを登録する。

サービスプロバイダの作成
```php
php artisan make:provider サービスプロバイダ名（アッパーキャメルケース）
```

登録
```php
register()
```

登録後に実行したい処理
```php
boot()
```