作って学ぶLaravel入門講座  by WEBOTV / WEBプログラム学習チャンネル チャンネル で学んだ

前に作成した記事を誤って別の記事で上書きしてしまった。
前の記事は余計な事をせずに動画を忠実に模写したので、反響が本当に凄かった。
今回も、余計なことをせず、動画を模写するよう心掛けようと思った。
前回、lineについてのloginではメールアドレスの取得ができなかったので、今回はそれも併せて掲載してます。


https://www.youtube.com/watch?v=Br_Tx619k9E&list=PL3B2bjwrmhfSg8wVfjW-ldH8zNu_xf7zB&index=1&ab_channel=WEBOTV%2FWEB%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E5%AD%A6%E7%BF%92%E3%83%81%E3%83%A3%E3%83%B3%E3%83%8D%E3%83%AB

https://qiita.com/motuo/items/5c9b24606ef75535f882

下の拡張機能をいれていると、use時ファイルを検索してくれます。

https://marketplace.visualstudio.com/items?itemName=bmewburn.vscode-intelephense-client

# 第1回 インストール＆初期設定編

#### laravelをインストール

```:#最新versionがインストール
laravel new Bookmark
```

```:laravel8のインストール#versionを指定してインストール
composer create-project "laravel/laravel=8.*" Bookmark
```
#### 初期設定編
##### config\app.phpの変種

```php5:config\app.php
    /*
    |--------------------------------------------------------------------------
    | Application Timezone
    |--------------------------------------------------------------------------
    |ここで、アプリケーションのデフォルトのタイムゾーンを指定することができます。
    |は、PHP の日付関数と日付時刻関数で使用されます
    */
    'timezone' => 'Asia/Tokyo',
    // 'timezone' => 'UTC',
    /*
    |--------------------------------------------------------------------------
    | Application Locale Configuration
    |--------------------------------------------------------------------------
    | アプリケーションロケールとは、アプリケーションで使用されるデフォルトのロケールを決定する
    | 翻訳サービスプロバイダによって。この値は自由に設定することができます
    | アプリケーションでサポートされる任意のロケールに設定します。
    */
    'locale' => 'ja',
    // 'locale' => 'en',
    /*
    |--------------------------------------------------------------------------
    | Faker Locale
    |--------------------------------------------------------------------------
    | このロケールは、Faker PHP ライブラリがフェイクを生成する際に使用されます。
    | データベースのシードのためのデータです。例えば、これは
    | ローカライズされた電話番号、住所情報など。
    */

    'faker_locale' => 'ja_JP',
    // 'faker_locale' => 'en_US',
```
##### envファイルの編集

```c:env
#アプリケーション名をBookmarkAppに変更
APP_NAME=BookmarkApp

#データベースをmySqlからsqliteに変更
DB_CONNECTION=sqlite
# DB_CONNECTION=mysql
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=
```
##### sqliteを作成

```:win
type nul > database/database.sqlite
```
```:mac
touch database/database.sqlite

```
メモ：sqliteのファイル名とパスを変更する場合

```php5:config\database.php
    'connections' => [

        'sqlite' => [
            'driver' => 'sqlite',
            'url' => env('DATABASE_URL'),
//パスと名前を変更できる。
            'database' => env('DB_DATABASE', database_path('database.sqlite')),
            'prefix' => '',
            'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
        ],
//省略
]
```
##### ローカルサーバーを起動

```
php artisan serve
```
切断は ctr+c

```:port番号を指定する場合
php artisan serve --port=8080
```
# 第2回 アプリケーションのデモ
login認証が必要なアプリ＋laravel socialiteを用いてgithubアカウントでのログイン認証が可能
更に追加でgoogle,yahooJp,line(メール取得)を追記してます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/fdbc3fa5-3495-1609-293d-06ac0fc4e719.png)


 1. bookmarkの登録/編集/削除
 1. tag付け機能があります。
 1. tagの登録/編集/削除 機能もあるアプリです。
 1. ページネーション機能

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/7047f0e7-eb24-1589-17ef-a930f7f36292.png)


# 第3回 Router編

routes\web.phpで設定する

```php5:routes\web.php
//http://127.0.0.1:8000/test
Route::get('/test',function(){
    return 'Hello Laravel';
});

//変数を使用する
//http://127.0.0.1:8000/test/hello_laravel
Route::get('/test/{buff}',function($buff){
    return $buff;
});
//正規表現を使用する
Route::get('/test/{id}',function($id){
    return $id;
})->where('id','[0-9]+');

//コントローラーを指定する
//ブックマークコントローラーのindex関数,ルート名はbookmarks.index
Route::get('/bookmarks', [App\Http\Controllers\BookmarkController::class, 'index'])->name('bookmarks.index');

//エイリアスを使用した場合
use App\Http\Controllers\BookmarkController;
Route::get('/', [BookmarkController::class,'index'])->name('bookmarks.index');
```

# 第4回 データベース編
laravelではマイグレーションファイルやモデル、コントローラーファイルなどのファイル作成は
`php artisan mkae`コマンドで作成していきます。
どのようなファイルが作成可能かは下記のコマンドで確認が可能です。

```
php artisan make
```
今回は、マイグレーションファイルだけではなく、モデルやコントローラーも
一括で作成しますので、下記のコマンドを実行します。

##### モデルとその他ファイルを一括で作成する

```
php artisan make:model Bookmark -a
```
その他：オプション

| option | 概要 |
|:-:|:-|
| -m  |モデルとマイグレーションファイルを作成する|
| -f  |モデルとファクトリーファイルを作成する |
| -r  |モデルとindex()やshow()等のリソースのついたコントローラーが作成される |
| -a  |-m-f-r全部|

##### bookmarksテーブルを作成する

```php5:database\migrations\xxx_create_bookmarks_table.php
    public function up()
    {
        Schema::create('bookmarks', function (Blueprint $table) {
            $table->id();
  //+
            $table->string('title');
            $table->string('url');
            $table->string('description')->nullable();
            
            $table->timestamps();
        });
    }
```
テーブルを作成する

```:
php artisan migrate
```
その他、laravel付属のusersテーブルとかも作成される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/f20bdb34-bee9-dedf-71dc-5d6e3ab87cb7.png)

##### ダミーデータを作成する
factoryファイルは
先ほど、`php artisan make:model Bookmark -a`で`-a`で一緒に作成してくれている。

```php5:database\factories\BookmarkFactory.php

    public function definition()
    {
        return [
//UserFactoryファイルをコピペして編集する。
            'title' => $this->faker->realText($this->faker->numberBetween(10,25)),
            'url' => $this->faker->url(),
            'description' => $this->faker->realText($this->faker->numberBetween(50,200)),
        ];
    }
```
```php5:database\seeders\DatabaseSeeder.php
    public function run()
    {
        // \App\Models\User::factory(10)->create();
//上を書き換える
        \App\Models\Bookmark::factory(100)->create();
    }
```
ダミーデータを作成する

```
php artisan db:seed
```
# 第5回 データベースのデータを表示しよう

その前に、認証システムのファイルを作成する。

login画面やregister画面,home画面などのbladeファイルが作成されるため、
そのコードをコピペして使用します。
今回はbootstrapのuiで作成します。他にvueやreact等があります。


また、認証必須のアプリですので、後に使用します。

```:3.3以上はjqueryがデフォルトで削除されている。
composer require laravel/ui:^3.3
```
```
php artisan ui bootstrap --auth
```
```
npm install && npm run dev
```
```
npm run dev
```
login画面やregister画面、認証後のhome画面など認証に関する機能が全部作成される。
bookmarkの画面作成にはそのコードを利用して作成します。

#### データをテーブルから取得する
1.ルートを作成する

```php5:routes\web.php
Route::get('/bookmarks', [App\Http\Controllers\BookmarkController::class, 'index']);
```
2.コントローラーで取得する
  
  1. dd()を使用して変数の中身を確認できます。

```php5:app\Http\Controllers\BookmarkController.php
    public function index()
    {
        $bookmarks = Bookmark::all();
        dd($bookmarks);
    }
```
[bookmark.index](http://localhost/Bookmark/public/bookmarks)(xammpを使用してます。)
にアクセスすると下の画面のように
変数の中身が確認できるようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/d9511c07-d182-1a28-a010-c9c1ca461ef8.png)


#### viewsのbookmarks.index.blade.phpにデータを渡して表示する

bookmarks.index.blade.phpの作成

- 1.viewsディレクトリの下にbookmarksディレクトリを作成
   - resources\views\bookmarks
- 2.その下にindex.blade.phpファイルを作成する。
   - resources\views\bookmarks\index.blade.php

home画面をコピーして編集する

```php5:resources\views\bookmarks\index.blade.php
@extends('layouts.app')

@section('content')
<div class="container">
<div class="row justify-content-center">
    <div class="col-md-8">
        <div class="card">
            <div class="card-header">ブックマーク一覧</div>
            <div class="card-body">
{{-- + --}}
                <table class="table table-light">
                    <thead class="thead-light">
                        <tr>
                            <th>id</th>
                            <th>タイトル</th>
                        </tr>
                    </thead>
                    <tbody>

                        @foreach ($bookmarks as $bookmark)
                            <tr>
                                <td>{{ $bookmark->id }}</td>
                                <td>{{ $bookmark->title }}</td>
                            </tr>
                        @endforeach

                    </tbody>
                </table>

            </div>
        </div>
    </div>
</div>
</div>
@endsection
```
作成したbladeにデータを渡して表示させる。views()を使用する。

```php5:app\Http\Controllers\BookmarkController.php
    public function index()
    {
        
        $bookmarks = Bookmark::all();
        //dd($bookmarks);
//+ディレクトリの区切りは'.'を使用し、`blade.php`は省略する。
        return view('bookmarks.index', compact('bookmarks'));
    }
```
[bookmark.index](http://localhost/Bookmark/public/bookmarks)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/71ea2df5-1eae-b1c1-2dee-ff69e9d36eaa.png)


#### ページネーションを設定する

https://qiita.com/morohoshi/items/2a8635eb0d9dd06613bf

```php5:app\Http\Controllers\BookmarkController.php
    public function index()
    {
        // $bookmarks = Bookmark::all();

//+
        $bookmarks = Bookmark::paginate(10);
        return view('bookmarks.index', compact('bookmarks'));
    }
```
laravel8のデフォルトのページネーションはTailwindを使用しているため、
bootstrap4を使用するよう明示する必要があります。そのための設定です。

```php5:app\Providers\AppServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;


// +
use Illuminate\Pagination\Paginator;

class AppServiceProvider extends ServiceProvider
{
    public function register()
    {
    }

    public function boot()
    {
// +
        Paginator::useBootstrap();
    }
}
```
```php5:resources\views\bookmarks\index.blade.php
@extends('layouts.app')

@section('content')
<div class="container">
<div class="row justify-content-center">
    <div class="col-md-8">
        <div class="card">
            <div class="card-header">ブックマーク一覧</div>
            <div class="card-body">
                <table class="table table-light">
               {{-- 省略 --}}
                </table>

{{-- +テーブル要素の直下に追加 --}}
                    {{ $bookmarks->links() }}

            </div>
        </div>
    </div>
</div>
</div>
@endsection
```
bookmark.index
ページネーションが表示された。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/3d6eaf3d-21cf-1e20-9533-8b5872a3c306.png)

# 第6回 詳細ページの作成
resources\views\bookmarks\show.blade.phpを作成する

```php5:resources\views\bookmarks\show.blade.php
@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">ブックマーク詳細</div>
                <div class="card-body">
                    <table class="table table-bordered">
                        <tbody>
                            <tr>
                                <th class="text-nowrap">タイトル</th>
                                <td>{{ $bookmark->title }}</td>
                            </tr>
                            <tr>
                                <th>URL</th>
                                <td>{{ $bookmark->url }}</td>
                            </tr>
                            <tr>
                                <th>概要</th>
                                <td>{!! nl2br(e($bookmark->description)) !!}</td>
                            </tr>
                            <tr>
                                <th>作成日</th>
                                <td>{{ $bookmark->created_at->format('Y年m月d日 h:i:s') }}</td>
                            </tr>
                        </tbody>
                    </table>
                <a class="btn btn-primary" href="{{ route('bookmarks.index') }}">一覧にもどる</a>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection
```
```php5:app\Http\Controllers\BookmarkController.php
//Bookmark $bookmark は一旦コメントアウト
    // public function show(Bookmark $bookmark)
    public function show($id)
    {
//findOrFail()見つからない場合
//404 HTTPレスポンスをクライアントへ自動的に返す
        $bookmark = Bookmark::findOrFail($id);
        return view('bookmarks.show',compact('bookmark'));
    }
```
ルートを作成する

```php5:routes\web.php
Route::get('/bookmarks/{id}', [App\Http\Controllers\BookmarkController::class, 'show'])->where('id','[0-9]+');
```
リンクを作成する

```php5:resources\views\bookmarks\index.blade.php
{{-- +タイトルにaタグをwrapする --}}
                <td>
{{-- + --}}
                    <a href="./bookmarks/{{ $bookmark->id }}">{{ $bookmark->title }}</a>
                </td>
```
bookmarks.indexとbookmarks.show画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/7512a0d4-96b0-1595-9a1f-185c3bafde9d.png)


#### ルートに名前を付ける

コントローラーの関数を直接指定する場合--action()関数

```php5:resources\views\bookmarks\index.blade.php
<a href="{{ action([App\Http\Controllers\BookmarkController::class,'show'],$bookmark->id) }}">{{ $bookmark->title }}</a>
```
ルートにネームを付けた場合---route()関数

```php5:routes\web.php
use App\Http\Controllers\BookmarkController;
Route::get('/bookmarks/{id}', [BookmarkController::class, 'show'])->where('id','[0-9]+')->name('bookmarks.show');
```
route()を使用して、urlを作成することができる。

```html:resources\views\bookmarks\index.blade.php
<a href="{{ route('bookmarks.show',$bookmark->id) }}">{{ $bookmark->title }}</a>
```
また、先ほど、$idをgetリクエストしてbookmarksテーブルを検索して取得しましたが、
本来は、そのまま、$bookmarkを流して表示させます。

```php5:app\Http\Controllers\BookmarkController.php
//Bookmarkをnewすることなく$bookmark変数が使用できます。
    public function show(Bookmark $bookmark)
    {
//getリクエストで送られてきた$bookmarkをそのまま渡す。
        return view('bookmarks.show', compact('bookmark'));
    }
```
```html:resources\views\bookmarks\index.blade.php
{{-- <a href="{{ route('bookmarks.show',$bookmark->id) }}">{{ $bookmark->title }}</a> --}}
{{-- 変更 --}}
<a href="{{ route('bookmarks.show',$bookmark) }}">{{ $bookmark->title }}</a>
```
```php5:routes\web.php
//ルートは上から取得されるため、必ずコメントアウトしてください。
//Route::get('/bookmarks/{id}', [BookmarkController::class, 'show'])->where('id','[0-9]+')->name('bookmarks.show');
Route::get('/bookmarks/{bookmark}', [BookmarkController::class, 'show'])->name('bookmarks.show');
```
結果は先ほどと同じです。リンクから詳細画面が表示されればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/45f7f357-71b0-230d-cace-63d9f90fdc66.png)


# 第7回 レコード追加機能の作成

bookmarkのルートをresourceに変更する


laravelではcrudに必要な関数を予め用意してくれています。

1. index()で一覧表示
1. show()で詳細表示
1. create()で登録画面の表示
1. store()でテーブルにデータを登録
1. edit()で編集画面
1. update()でテーブルのデータを編集
1. destroy()でテーブルのデータを消去

上記の、ルートを一括で作成するのが
Route::resource()です。

```php5:routes\web.php
//+
use App\Http\Controllers\BookmarkController;

// Route::get('/bookmarks', [App\Http\Controllers\BookmarkController::class, 'index']);
// Route::get('/bookmarks/{bookmark}', [App\Http\Controllers\BookmarkController::class, 'show'])->name('bookmarks.show');
//+
Route::resource('bookmarks', BookmarkController::class);
```
resourceで作成されるルート

|Method|URI|Name|Middleware|
|:-:|:-:|:-:|:-:|
|POST|bookmarks|bookmarks.store|web|
|GET|bookmarks|bookmarks.index|web|
|GET|bookmarks/create|bookmarks.create|web|
|DELETE|bookmarks/{bookmark}|bookmarks.destroy|web|
|PUT|bookmarks/{bookmark}|bookmarks.update|web|
|GET|bookmarks/{bookmark}|bookmarks.show|web|
|GET|bookmarks/{bookmark}/edit|bookmarks.edit|web|

#### 新規登録フォームを作成する
create.blade.phpを作成する
//login画面をコピーして編集する

```php:resources\views\bookmarks\create.blade.php
@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">ブックマークを登録する</div>

                    <div class="card-body">
                        <form method="POST" action="{{ route('bookmarks.store') }}" novalidate>
//GET送信以外はトークンが必要です。@csrfでトークンを生成して送信してくれます。
                            @csrf
                            <div class="row mb-3">
                                <label for="title" class="col-md-2 col-form-label text-md-end">タイトル</label>
                                <div class="col-md-10">
                                    <input id="title" type="text" class="form-control @error('title') is-invalid @enderror"
                                        name="title" value="{{ old('title', $bookmark->title ?? '') }}" required
                                        autocomplete="title" autofocus>

                                    @error('title')
                                        <span class="invalid-feedback" role="alert">
                                            <strong>{{ $message }}</strong>
                                        </span>
                                    @enderror
                                </div>
                            </div>

                            <div class="row mb-3">
                                <label for="url" class="col-md-2 col-form-label text-md-end">URL</label>

                                <div class="col-md-10">
                                    <input id="url" type="url" class="form-control @error('url') is-invalid @enderror"
                                        name="url" value="{{ old('url', $bookmark->url ?? '') }}" required
                                        autocomplete="current-url">

                                    @error('url')
                                        <span class="invalid-feedback" role="alert">
                                            <strong>{{ $message }}</strong>
                                        </span>
                                    @enderror
                                </div>
                            </div>

                            <div class="row mb-3">
                                <label for="description" class="col-md-2 col-form-label text-md-end">概要</label>

                                <div class="col-md-10">
                                    <textarea name="description" id="description" cols="30" rows="10"
                                        class="form-control @error('description') is-invalid @enderror" required
                                        autocomplete="description"
                                        autofocus>{{ old('description', $bookmark->description ?? '') }}</textarea>

                                    @error('description')
                                        <span class="invalid-feedback" role="alert">
                                            <strong>{{ $message }}</strong>
                                        </span>
                                    @enderror
                                </div>
                            </div>
                    </div>
                </div>

                <div class="row mb-0">
                    <div class="col-md-10 offset-md-2 d-flex justify-content-end">

                        <a class="btn btn-secondary me-3" href="{{ route('bookmarks.index') }}">
                            一覧に戻る
                        </a>
                        <button type="submit" class="btn btn-primary">
                            投稿する
                        </button>
                    </div>
                </div>
                </form>
            </div>
        </div>
    </div>
    </div>
    </div>
@endsection
```
create()を作成する

```php5:app\Http\Controllers\BookmarkController.php
    public function create()
    {
                return view('bookmarks.create');
    }
```
新規登録ボタンを作成する

```haml:resources\views\bookmarks\index.blade.php
{{-- .card-bodyの直下 --}}
            <div class="card-body">
                <a href="{{ route('bookmarks.create') }}" class="btn btn-primary mb-3">新規登録</a>
```
bookmarks.index画面とbookmarks.create画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/2dd4d6a4-6dcb-56af-81a7-9f72ac1ee5d6.png)

#### store()を作成してテーブルにデータを登録
最新のlaravelから自動でフォームリクエスト機能がかかるようになりました。
`public function store(StoreBookmarkRequest $request)`
そのため、フォームリクエスト機能は後に解説されていますので、いったんここではコメントアウトしてください。

```php5:app\Http\Controllers\BookmarkController.php
//+
    use Illuminate\Http\Request;

//一旦コメントアウト
    //public function store(StoreBookmarkRequest $request)
    public function store(Request $request)
    {
//+
//create()を使用するとホワイトリストに登録したデータだけ登録してくれます。滅茶苦茶安全です。
//戻り値もクリエイトしたデータを戻してくれます。$bookmark->idで登録したデータのidを取得できる。
        $bookmark = Bookmark::create($request->all());
//redirect()->route()でルートにアクセスできます。
//views()ではありません。views()だとそのまま、index.blade.phpにアクセスされます。
        return redirect()->route('bookmarks.index');
    }
```
<details><summary>redirect()についての注意事項</summary><div>
[ララジャパン-abort()を使ってリダイレクト](https://www.larajapan.com/2020/07/25/abort%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%83%AA%E3%83%80%E3%82%A4%E3%83%AC%E3%82%AF%E3%83%88/)

 - 要約
      - rediret()はreturnと一緒にしか使えない。
      - returnが使えないときはabort(redirect()->route(''));を使用する。
</div></details>

ホワイトリストを登録する

```php5:app\Models\Bookmark.php
//Userモデルからコピペして編集
    protected $fillable = [
        'title',
        'url',
        'description',
    ];
```
降順表示に変更する

```php5:app\Http\Controllers\BookmarkController.php
    public function index()
    {
        // $bookmarks = Bookmark::all();
        // $bookmarks = Bookmark::paginate(10);
        $bookmarks = Bookmark::orderBy('id','desc')->paginate(20);
        return view('bookmarks.index', compact('bookmarks'));
    }
```
bookmarks.create画面とbookmarks.index画面
登録したデータが表示されていれば、OKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/4a613d26-4f60-2cd1-a32f-02e442429333.png)


# 第8回 レコード更新機能の作成

#### 編集画面を作成する

formタグをcreate.blade.phpと共通化するため
コンポーネントを使用する

 1. viewsディレクトリの下にcomponentsディレクトリを作成
 1. componentsディレクトリの下にbookmarksディレクトリを作成
 1. bookmarksディレクトリの下にform.blade.phpを作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/5c27a574-aded-7546-8e99-9bb6b5be4e19.png)

bookmarks.create.blade.phpのformタグをカットして<x-bookmarks.form/>に変更

```php5:resources\views\bookmarks\create.blade.php
@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">ブックマークを登録する</div>

                    <div class="card-body">

 {{-- +コンポーネントファイルは <x-ファイル名/> 値をコンポーネントに渡すこともできる--}}
                        <x-bookmarks.form route="store" />
                        
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection
```
formタグをresources\views\components\bookmarks\form.blade.phpに貼り付ける

```php5:resources\views\components\bookmarks\form.blade.php
{{-- $routeで値を受け取る でも、普通はRoute::is() --}}
@if ($route == 'store')
<form method="POST" action='{{ route("bookmarks.$route") }}' novalidate>
@else
<form method="POST" action='{{ route("bookmarks.$route",$bookmark) }}' novalidate>
@endif
    @csrf
    <div class="row mb-3">
        <label for="title" class="col-md-2 col-form-label text-md-end">タイトル</label>
        <div class="col-md-10">
            <input id="title" type="text" class="form-control @error('title') is-invalid @enderror"
                name="title" value="{{ old('title', $bookmark->title ?? '') }}" required
                autocomplete="title" autofocus>

            @error('title')
                <span class="invalid-feedback" role="alert">
                    <strong>{{ $message }}</strong>
                </span>
            @enderror
        </div>
    </div>

    <div class="row mb-3">
        <label for="url" class="col-md-2 col-form-label text-md-end">URL</label>

        <div class="col-md-10">
            <input id="url" type="url" class="form-control @error('url') is-invalid @enderror"
                name="url" value="{{ old('url', $bookmark->url ?? '') }}" required
                autocomplete="current-url">

            @error('url')
                <span class="invalid-feedback" role="alert">
                    <strong>{{ $message }}</strong>
                </span>
            @enderror
        </div>
    </div>

    <div class="row mb-3">
        <label for="description" class="col-md-2 col-form-label text-md-end">概要</label>

        <div class="col-md-10">
            <textarea name="description" id="description" cols="30" rows="10"
                class="form-control @error('description') is-invalid @enderror" required
                autocomplete="description"
                autofocus>{{ old('description', $bookmark->description ?? '') }}</textarea>

            @error('description')
                <span class="invalid-feedback" role="alert">
                    <strong>{{ $message }}</strong>
                </span>
            @enderror
        </div>
    </div>

<div class="row mb-0">
<div class="col-md-10 offset-md-2 d-flex justify-content-end">

<a class="btn btn-secondary me-3" href="{{ route('bookmarks.index') }}">
    一覧に戻る
</a>
<button type="submit" class="btn btn-primary">
    @if (Route::is('bookmarks.create'))
        投稿する
    @else
        編集する
    @endif
</button>
</div>
</div>
</form>
```
変わらずに表示されればOK
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/d14d3f12-d506-b9ae-6c96-49cddbb29310.png)

edit.blade.phpを作成する

```php5:resources\views\bookmarks\edit.blade.php
@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">ブックマークを編集する</div>

                    <div class="card-body">

 {{-- +コンポーネントに変数を渡すときは注意 : がいる--}}
                        <x-bookmarks.form route="update" :bookmark="$bookmark"/>
                        
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection
```
```php5:resources\views\components\bookmarks\form.blade.php
     @if ($route == 'store')
         <form method="POST" action='{{ route("bookmarks.$route") }}' novalidate>
     @else
         <form method="POST" action='{{ route("bookmarks.$route",$bookmark) }}' novalidate>
    @endif
         @csrf

{{-- +@csrfの下に追記 update()はPUT送信のため 追記してください  --}}
    @if(Route::is('bookmarks.edit')) @method('PUT') @endif

```
テーブルにアクション欄を作成して詳細と編集btnを作成する

```php5:resources\views\bookmarks\index.blade.php
                <table class="table table-light">
                    <thead class="thead-light">
                        <tr>
                            <th>id</th>
                            <th>タイトル</th>
{{-- + --}}
                            <th>アクション</th>
                        </tr>
                    </thead>
                    <tbody>
                        @foreach ($bookmarks as $bookmark)
                            <tr>
                                <td>{{ $bookmark->id }}</td>
                                <td>{{ $bookmark->title }}</td>
{{-- + @foreachの<tr>の中 --}}
                                <td class="text-nowrap">
                                    <a class="btn btn-info"
                                        href="{{ route('bookmarks.show', $bookmark) }}">詳細</a>
                                    <a class="btn btn-secondary"
                                        href="{{ route('bookmarks.edit', $bookmark) }}">編集</a>
                                </td>


                            </tr>
                        @endforeach
                    </tbody>
                </table>
```
```php5:app\Http\Controllers\BookmarkController.php
    public function edit(Bookmark $bookmark)
    {
       return view('bookmarks.edit',compact('bookmark'));
    }
```
bookmarks.indexとbookmarks.edit
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/277d8872-7bc8-8bf0-a044-8cf0c76932c7.png)

#### update()を作成する

```php5:app\Http\Controllers\BookmarkController.php
//いったんコメントアウト
    // public function update(UpdateBookmarkRequest $request, Bookmark $bookmark)
    public function update(Request $request, Bookmark $bookmark)
    {
        $bookmark->update($request->all());
        return redirect()->route('bookmarks.show',$bookmark);
    }
```
編集内容が反映されていればOk.
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/bc1ffc30-0afa-3eaf-c450-09cf5c3a3f02.png)


# 第9回 レコードの削除とフラッシュメッセージ
destroy()を作成する

```php5:app\Http\Controllers\BookmarkController.php
    public function destroy(Bookmark $bookmark)
    {
        $bookmark->delete();
        return redirect()->route('bookmarks.index');
    }
```
アクションに削除ボタンを追加
resourceのdestroy()はform method="delete" とcsrfトークンと一緒に送信する必要がある

```html:resources\views\bookmarks\index.blade.php
{{-- アクションボタンに追加 --}}
            <td class="text-nowrap">
                <a class="btn btn-info"
                    href="{{ route('bookmarks.show', $bookmark) }}">詳細</a>
                <a class="btn btn-secondary"
                    href="{{ route('bookmarks.edit', $bookmark) }}">編集</a>
{{-- + --}}
                <a class="btn btn-danger" href="#"
                onclick="event.preventDefault();
                                document.getElementById(`destroy_form.{{ $bookmark->id }}`).submit();">
                    削除
                </a>
                <form id="destroy_form.{{ $bookmark->id }}" action="{{ route('bookmarks.destroy',$bookmark) }}" method="POST" class="d-none">
                    @csrf
                    @method('delete')
                </form>
                
            </td>
```
dataが削除されればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/690dd32b-759c-b6cc-bf6e-f87bf88a8e9a.png)


削除btnのコンポーネント化する
併せて、scriptを使用するためresources\views\layouts\app.blade.phpに@stack('js')を追記

```html:resources\views\layouts\app.blade.php

        <main class="py-4">
            @yield('content')
        </main>
    </div>

{{-- ＜/body＞の上に追加 --}}
    @stack('js')
</body>
```

 1. 先ほど作成したcomponentsディレクトリの下に
 1. その下に`del_btn.blade.php`を作成する

```php:resources\views\components\del_btn.blade.php
<a class="btn btn-danger" href="javascript:void(0)" onclick='del_bookmark(event);' id="{{ $id }}">
    削除
</a>

{{-- 1回だけレンダリングしてくれる。 --}}
@once
    @push('js')
        <form name="destroy_form" action="" method="POST" class="d-none">
            @method('delete')
            @csrf
        </form>

        <script>
{{-- 値は$routeIsで取得 $route_isなどのキャメルはNG --}}
            destroy_url = `{{ route("$routeIs.destroy", '') }}`;

            function del_bookmark(e) {
                e.preventDefault();
                del_form = document.destroy_form;
                del_form.setAttribute("action", `${destroy_url}/${e.target.id}`);
                document.destroy_form.submit()
            }
        </script>
    @endpush
@endonce
```
```php5:resources\views\bookmarks\index.blade.php
            <td class="text-nowrap">
                <a class="btn btn-info"
                    href="{{ route('bookmarks.show', $bookmark) }}">詳細</a>
                <a class="btn btn-secondary"
                    href="{{ route('bookmarks.edit', $bookmark) }}">編集</a>
{{-- 変更 :bookmark_id など キャメルはNG --}}
               <x-del_btn routeIs="bookmarks" :id="$bookmark->id" />
            </td>
```
先ほどと変わらす、削除されればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/371c6df2-16bd-4d2b-2800-8814b701682a.png)


#### flashメッセージを作成する

```php5:app\Http\Controllers\BookmarkController.php
    public function store(Request $request)
    {
        $bookmark=Bookmark::create($request->all());
//->with('status','登録しました')
        return redirect()->route('bookmarks.index')->with('status','登録しました');
    }

    public function update(Request $request, Bookmark $bookmark)
    {
        $bookmark->update($request->all());
//->with('status','更新しました')
        return redirect()->route('bookmarks.show',$bookmark)->with('status','更新しました');
    }
    public function destroy(Bookmark $bookmark)
    {
        $bookmark->delete();
//->with('status','削除しました')
        return redirect()->route('bookmarks.index')->with('status','削除しました');
    }
```
 1. viewsの下にコンポーネントディレクトリを作成
 1. その下にalert.blade.php

```php5:resources\views\components\alert.blade.php
@if (session()->has('status'))
<div class="alert alert-success" role="alert">
    {{ session('status') }}
</div>
@endif
```
```php5:resources\views\bookmarks\index.blade.php
        <div class="card-header">ブックマーク一覧</div>
        <div class="card-body">
{{-- +.card-bodの下 --}}
            <x-alert></x-alert>
```
```php5:resources\views\bookmarks\show.blade.php
            <div class="card">
                <div class="card-header">ブックマーク詳細</div>
                <div class="card-body">
{{-- +.card-bodの下 --}}
                    <x-alert></x-alert>

```
# 第10回 入力フォームのチェック機能（バリデーション）
フォームリクエストクラス
最新のlaravel8ではresource作成時、すでにリクエストクラス等も作成される。
また、FormRequestはコントローラーメソッドに勝手にDIされている。

```:artisanで作成する場合
php artisan make:request StoreBookmark
```
BookmarkControllerコントローラー

```php5:app\Http\Controllers\BookmarkController.php
//コメントアウトを解除して変更してください。
//store(StoreBookmarkRequest $request) 作成したクラスはDIして使用する。
    public function store(StoreBookmarkRequest $request)
    {
        $bookmark=Bookmark::create($request->all());
        return redirect()->route('bookmarks.index')->with('status','登録しました');
    }

//StoreBookmarkRequestクラスに変更してください。
    public function update(StoreBookmarkRequest $request, Bookmark $bookmark)
    {
        $bookmark->update($request->all());
        // return redirect()->route('bookmarks.show',$bookmark);
        return redirect()->route('bookmarks.show',$bookmark)->with('status','更新しました');
    }
```
再度、登録してみてください。そうすると図のようにエラー表記(403 THIS ACTION IS UNAUTHORIZED)になります。
これは、登録する権限がfalseになっているためです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/c4717391-0b20-60d4-e25d-118da7030f94.png)

StoreBookmarkRequestリクエストクラスにルールを記述

```php5:app\Http\Requests\StoreBookmarkRequest.php
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;

class StoreBookmarkRequest extends FormRequest
{
    //*
    // ユーザーがこの要求を行う権限があるかどうかを判断します。
    //*
    public function authorize()
    {
        //return false
//accessする権限があるかどうかをチェックできる。trueは必須
//例えば、編集権限があるかどうか、１日の投稿件数を超えていた場合、falseにするとか
//ここをtrueにすることで403 THIS ACTION IS UNAUTHORIZEDを解除できる
        return true;
    }
    //*
    // リクエストに適用されるバリデーションルールを取得します。
    //*
    public function rules()
    {
//バリデーションルールを
        return [
            'title' => 'required',
            'url'   => 'required|url',
            'description' => 'max:500',
        ];
    }
}
```
入力がルールに抵触すると英語のエラーメッセージが返る
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/b58a1eb4-5dae-01a8-6cfa-c353bcbb3d40.png)
そうでないときは先ほどはエラーメッセージがでたが、ちきんと登録される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/7eeafcca-bc81-0f4b-4dd0-34598479a9ef.png)

その他解説
viewsでのバリデーションのエラーの判定は`@error('name名') @enderro`でできる。

```php5:
        <div class="col-md-10">
// @error('title') is-invalid @enderror titleがエラーなら is-invalid を表示
            <input id="title" type="text" class="form-control @error('title') is-invalid @enderror" name="title" value="{{ old('title',$bookmark->title??'') }}" required autocomplete="title" autofocus>

            @error('title')
                <span class="invalid-feedback" role="alert">
//$messageで最初のエラーメッセージを取得できる。@error('title')で囲まれているためtitleのエラーメッセージが取得される
                    <strong>{{ $message }}</strong>
                </span>
            @enderror
        </div>

//複数のメッセージを取得する場合
@if ($errors->has('title'))
  @foreach($errors->get('title') as $message)
  <strong> {{ $message }} </strong>
  @endforeach
@endif
```
入力した値を取得する
old()で取得　第一引数に項目名、第二引数にデフォルトの値(edit.blade.phpの時とか)

```php:
        <div class="col-md-10">
            <input id="title" type="text" class="form-control @error('title') is-invalid @enderror" name="title"

//第一引数に項目名、第二引数にデフォルトの値($bookmark->title ?? '')
                value="{{ old('title', $bookmark->title ?? '') }}" required autocomplete="title" autofocus>

```
その他解説　終了

エラーメッセージを日本語に変更
StoreBookmarkRequestクラスにmessages()関数を追加

```php5:app\Http\Requests\StoreBookmarkRequest.php
//方法1
    public function messages()
{
    return [
        'title.required' => 'タイトルは必須やねん',
        'url.required' => 'urlがぬけているねん',
        'description.max:500' => '概要は500文字以内に書いて欲しいねん',
    ];
}
//方法2
    public   function   messages()
    {
        return   [
            "required"    => ":attributeは必須項目です。",
            "email"       => ":attributeはメールアドレスの形式で入力してください。",
            "max:500"     => ":attributeは500文字以内で入力してください。",
        ];
    }
    public function attributes()
    {
        return[
            'title'=> 'タイトル',
            'description'   => '概要',
        ];
    }
```
レジスター画面やログイン画面のメッセージも日本語に変更する場合
普通はこっちで一括して変更する。かち合った場合は上記が優先される。

https://qiita.com/mitashun/items/160784ce352cf529f853

```
php -r "copy('https://readouble.com/laravel/5.6/ja/install-ja-lang-files.php', 'install-ja-lang.php');"
```
```
php -f install-ja-lang.php 
```
```
php -r "unlink('install-ja-lang.php');"
```
ja.jsonファイルを作成


```
type nul > resources/lang/ja.json
```
作成された空のファイルに下のコードをコピペ

```json:resources\lang\ja.json
{
    "Register": "ユーザー登録",
    "Name": "氏名",
    "E-Mail Address": "メールアドレス",
    "Password": "パスワード",
    "Confirm Password": "パスワード (確認用)",
    "Login": "ログイン",
    "Logout": "ログアウト",
    "Remember Me": "ログインしたままにする",
    "Forgot Your Password?": "パスワードを忘れた方はこちら",
    "Reset Password": "パスワード再設定",
    "Send Password Reset Link": "パスワード再設定用のリンクを送る",
    "Verify Your Email Address": "メールアドレス認証を行ってください",
    "A fresh verification link has been sent to your email address.": "ユーザー登録の確認メールを送信しました。",
    "Before proceeding, please check your email for a verification link.": "メールに記載されているリンクをクリックして、登録手続きを完了してください。",
    "If you did not receive the email,": "メールが届かない場合、",
    "click here to request another.": "こちらをクリックして再送信してください。",
    "Please confirm your password before continuing.": "続行するにはパスワードを入力してください。"
}
```
```php5:resources\lang\ja\passwords.php
-------------------省略----------------
    'user' => "メールアドレスに一致するユーザーは存在していません。",
//+
    'throttled' => 'しばらく待ってから再度お試しください。',
```
```php5:resources\lang\ja\validation.php
    'attributes' => [
//+
        "name" => "名前",
        "password" => "パスワード",
        "password_confirmation" => "パスワード(確認用)",
        "email" => "メールアドレス",

//+
        'title'=>'タイトル',
        'description' => '概要',
    ],

];
```
エラーメッセージが日本語化されていればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/191c2be5-448f-7805-f94d-7d0f44368d62.png)

# 第11回 ユーザー登録・認証機能の作成

もうすでに認証関係は導入済み。

```c:#第4回データベース編でusersテーブルも一緒に作成
#usersテーブルはdefaultで用意されているため。削除しなければ、一緒に作成される。
php artisan migrate
```
```c:第５回の時に--authで認証機能を導入
#--authでコントローラーもveiwsファイルも全部作成
php artisan ui bootstrap --auth
```

#### login後のリダイレクト先を変更する

```php5:app\Providers\RouteServiceProvider.php
//homeから'/'に変更
    // public const HOME = '/home';
    public const HOME = '/';
```
bookmarks.indexのルートを`/`に変更する

```php5:routes\web.php
//
Route::get('/', [BookmarkController::class, 'index']);
Route::resource('bookmarks', BookmarkController::class);
```
レジスター登録を確認する
登録して、登録後、bookmarks.index画面に遷移されればOKです。
併せて、バリデーションが日本語になっているかも確認してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/a125ebc2-75c9-c6d5-c73e-37f39c9e596e.png)


#### bookmarkに認証機能を追加
認証されたユーザーだけアクセスできるように変更

```php5:routes\web.php
//->middleware('auth')
//    Route::get('/', [BookmarkController::class, 'index'])->name('bookmarks.index')->middleware('auth');

Route::group(['middleware' => 'auth'], function () {
    Route::get('/', [BookmarkController::class, 'index'])->name('bookmarks.index');
    Route::resource('bookmarks', BookmarkController::class);
});
```
一旦ログアウトして、再度bookmarks.index画面にアクセスしてみてください。ロゴのリンクが
bookmarks.indexのリンクに変更されています。
login画面にリダイレクトされればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/a43a6f88-25bc-576b-c6a9-761f2869e92f.png)


# 第12回 タグCRUD（作成・読み込み・更新・削除）機能の作成

#### tagsテーブルを作成する

モデル等も一括して作成する

```c:
php artisan make:model Tag -a
```
tagsテーブルを編集する

```php5:database\migrations\xxx_create_tags_table.php
    public function up()
    {
        Schema::create('tags', function (Blueprint $table) {
            $table->id();
            //+
            $table->string('title');
            
            $table->timestamps();
        });
    }
```
```
php artisan migrate
```
#### ホワイトリストを登録する

```php5:app\Models\Tag.php
    protected $fillable = [
        'title',
    ];
```

#### ダミーデータを作成する

```php5:database\factories\TagFactory.php
    public function definition()
    {
        static $id=1;
        return [
            'title' => 'タグ'.$id++,
        ];
    }
```
```php5:database\seeders\DatabaseSeeder.php
        // \App\Models\User::factory(10)->create();
//先ほどのBookmarkはコメントアウトする。
        // \App\Models\Bookmark::factory(100)->create();

        \App\Models\Tag::factory(10)->create();
```
```
php artisan db:seed
```
#### バリデーションを作成する

```php5:app\Http\Requests\StoreTagRequest.php

class StoreTagRequest extends FormRequest
{
    public function authorize()
    {
//+
        return true; //false
    }
    public function rules()
    {
        return [
//+
            'title' => 'required|max:300|unique:tags,title,,',
        ];
    }
}
```
#### TagControllerを編集する
->BookmarkControllerの関数群をコピペして変更する
->BookmarkをTagにbookmarkをtagに変更すればOK

```php5:
    public function index()
    {
        $tags = Tag::paginate(10);
        return view('tags.index',compact('tags'));
    }

    public function create()
    {
        return view('tags.create');
    }

    public function store(StoreTagRequest $request)
    {
        Tag::create($request->all());
        return redirect()->route('tags.index')->with('status','登録しました');
    }

    public function show(Tag $tag)
    {
        return view('tags.show',compact('tag'));
    }

    public function edit(Tag $tag)
    {
        return view('tags.edit',compact('tag'));
    }

    public function update(StoreTagRequest $request, Tag $tag)
    {
        $tag->update($request->all());
        return redirect()->route('tags.index')->with('status','更新しました');
    }

    public function destroy(Tag $tag)
    {
        $tag->delete();
        return redirect()->route('tags.index')->with('status','削除しました');
    }
```
viewsの作成

 1. resources\views\bookmarksディレクトリをそのままコピして
 1. resources\views\tagsディレクトリに変更する。
 1. 以下Bookmark仕様からtag使用へ変更する

```php5:resources\views\tags\index.blade.php
@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">タグ一覧</div>
                    <div class="card-body">
                        <a href="{{ route('tags.create') }}" class="btn btn-primary mb-3">新規登録</a>
                        <x-alert></x-alert>

                        <table class="table table-light">
                            <thead class="thead-light">
                                <tr>
                                    <th>id</th>
                                    <th>タイトル</th>
                                    <th>アクション</th>
                                </tr>
                            </thead>
                            <tbody>

                                @foreach ($tags as $tag)
                                    <tr>
                                        <td>{{ $tag->id }}</td>
                                        <td><a
                                                href="{{ route('tags.show', $tag) }}">{{ $tag->title }}</a>
                                        </td>

                                        <td class="text-nowrap">
                                            <a class="btn btn-info"
                                                href="{{ route('tags.show', $tag) }}">詳細</a>
                                            <a class="btn btn-secondary"
                                                href="{{ route('tags.edit', $tag) }}">編集</a>
                                            <x-del_btn routeIs="tags" :id="$tag->id" />
                                        </td>
                                    </tr>
                                @endforeach
                            </tbody>
                        </table>
                        {{ $tags->links() }}
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection
```
```php5:resources\views\tags\show.blade.php
@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">タグ詳細</div>
                <div class="card-body">
                    <table class="table table-bordered">
                        <tbody>
                            <tr>
                                <th class="text-nowrap">タイトル</th>
                                <td>{{ $tag->title }}</td>
                            </tr>
                            <tr>
                                <th>作成日</th>
                                <td>{{ $tag->created_at->format('Y年m月d日 h:i:s') }}</td>
                            </tr>
                        </tbody>
                    </table>
                    <a class="btn btn-primary" href="{{ route('tags.index') }}">一覧に戻る</a>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection
```
```php5:resources\views\tags\create.blade.php
@extends('layouts.app')

@section('content')
@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">ブックマークを登録する</div>

                    <div class="card-body">

 {{-- +コンポーネントファイルは <x-ファイル名/> 値をコンポーネントに渡すこともできる--}}
                        <x-tags.form route="store" />

                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection
```
```php5:resources\views\tags\edit.blade.php
@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">ブックマークを編集する</div>

                    <div class="card-body">

 {{-- +コンポーネントに変数を渡す--}}
                        <x-tags.form route="update" :tag="$tag"/>
                        
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

```
resources\views\components\bookmarks\form.blade.phpをフォルダごとコピーして
resources\views\components\tags\form.blade.php名前を変更して作成する。

```php5:resources\views\components\tags\form.blade.php
{{-- $routeで値を受け取る でも、普通はRoute::is() --}}
@if ($route == 'store')
<form method="POST" action='{{ route("tags.$route") }}' novalidate>
@else
<form method="POST" action='{{ route("tags.$route",$tag) }}' novalidate>
@endif
    @csrf
{{-- +  --}}
    @if(Route::is('tags.edit')) @method('PUT') @endif
    
    <div class="row mb-3">
        <label for="title" class="col-md-2 col-form-label text-md-end">タイトル</label>
        <div class="col-md-10">
            <input id="title" type="text" class="form-control @error('title') is-invalid @enderror"
                name="title" value="{{ old('title', $tag->title ?? '') }}" required
                autocomplete="title" autofocus>

            @error('title')
                <span class="invalid-feedback" role="alert">
                    <strong>{{ $message }}</strong>
                </span>
            @enderror
        </div>
    </div>
</div>
</div>

<div class="row mb-0">
<div class="col-md-10 offset-md-2 d-flex justify-content-end">

<a class="btn btn-secondary me-3" href="{{ route('tags.index') }}">
    一覧に戻る
</a>
<button type="submit" class="btn btn-primary">
    @if (Route::is('tags.create'))
        投稿する
    @else
        編集する
    @endif
</button>
</div>
</div>
</form>
```
#### ナビにbookmarkとtagのリンクを作成する
nemeで判断
class="nav-link `{{ request()->route()->named('bookmarks*')? 'active': '' }}`"
bootstrapのナビを使用しているため .active　をつけると、active表示してくれる。
urlで判断
他に`Request::is('bookmark/*')?'active':''`

```php5:resources\views\layouts\app.blade.php
    <!-- Left Side Of Navbar -->
            <ul class="navbar-nav me-auto">
{{-- +レフトサイドに追記する --}}
                <li><a class="nav-link {{ request()->route()->named('bookmarks*')? 'active': '' }}"
                        href="{{ route('bookmarks.index') }}">ブックマーク</a></li>
                <li><a class="nav-link {{ request()->route()->named('tags*')? 'active': '' }}"
                        href="{{ route('tags.index') }}">タグ</a></li>

            </ul>
```
#### ルートを設定する

```php5:routes\web.php
Route::group(['middleware' => 'auth'], function () {
    Route::get('/', [BookmarkController::class, 'index'])->name('bookmarks.index')->middleware('auth');
    Route::resource('bookmarks', BookmarkController::class, ['except' => ['index']]);

//+
    Route::resource('tags', TagController::class);
});
```
tagのCRUDが確認できればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/a7e98ddd-c3d0-780d-d36b-1b697eb07fef.png)


# 第13回 データベースのリレーション設定（ブックマークとタグの関連付け）

#### ブックマークとタグをリレーションさせる

 - ブックマークとタグは多対多の関係にあるため中間テーブルを作成する
 - 中間テーブルは直接CRUDしないのでモデルは必要ない
 - そのため、中間テーブルを操作するためのメソッドが用意されている。

 - laravel 中間テーブル 命名規則
     1. 2つのテーブルをアルファベット順に並べる
     1. 2つのテーブル名 (単数形) を_ (アンダーバー) で繋げる
 

```c:
php artisan make:migration create_bookmark_tag_table
```
```php5:
    public function up()
    {
        Schema::create('bookmark_tag', function (Blueprint $table) {
            $table->id();
            //中間テーブルの時は定型文
            $table->foreignId('bookmark_id')
// ->references('id')->on('bookmarks')の意味,
            ->constrained()
//deleteされたら一緒にdelete
            ->cascadeOnDelete()
//updateされたら一緒にupdate・・idがupdateされたら一緒にupdateする。
            ->cascadeOnUpdate();

            $table->foreignId('tag_id')
            ->constrained()
            ->cascadeOnDelete()
            ->cascadeOnUpdate();
            
            $table->timestamps();
        });
    }
```
```
php artisan migrate
```
bookmarkとtagをリレーションさせる

```php5:app\Models\Bookmark.php
    public function tags()
    {
      return $this->belongsToMany(Tag::class);
    }
```
```php5:app\Models\Tag.php
    public function bookmarks()
    {
        return $this->belongsToMany(Bookmark::class);
    }
```
これだけで、リレーションは完了です。後は、取得して表記するだけです。
これから、下記の図のようなbookmarks.create画面にtagを登録させるためのformを追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/32aff043-35b8-a20e-5c7b-bf77a24d5aa3.png)

Bookmarkコントローラーを編集する

```php5:
//+
use App\Models\Tag;

    public function create()
    {
//+
                //id(key)とタイトル(value)の1次元連想配列で取得する
                $tags = Tag::pluck('title','id')->toArray();

                return view('bookmarks.create',compact('tags'));
    }

    public function edit(Bookmark $bookmark)
    {
//+
        //タグテーブルのid(key)とタイトル(value)を1次元連想配列で取得する
        $tags = Tag::pluck('title','id')->toArray();
        return view('bookmarks.edit',compact('bookmark','tags'));
    }
```


```php:resources\views\components\bookmarks\form.blade.php
<!-- buttonの上に追記 -->

<!-- ＋ここから -->
<div class="row mb-3">
    <label for="tags" class="col-md-2 col-form-label text-md-end">tags</label>
    <div class="col-md-10 d-flex flex-wrap align-content-around p-2">
        @foreach ($tags as $key => $tag)
            <div class="form-check mr-3">
                <input class="form-check-input" type="checkbox" value="{{ $key }}" id="tag{{ $key }}"
                    name="tags[]" 
{{-- //$bookmark->tagsに$keyが含まれているかどうか判定してる --}} 
{{-- //$bookmark->tagsが3.5.7で$keyが3ならtrue判定してくる結果、3.5.7にcheckedがつく --}} 
       @if (isset($bookmark->tags) && $bookmark->tags->contains($key))
                checked
        @endif
        >
        <label class="form-check-label" for="tag{{ $key }}">
            {{ $tag }}
        </label>
    </div>
    @endforeach

    @error('tags')
        <span class="invalid-feedback" role="alert">
            <strong>{{ $message }}</strong>
        </span>
    @enderror
</div>
</div>
<!--ここまで -->


<div class="row mb-0">
    <div class="col-md-10 offset-md-2 d-flex justify-content-end">

        <a class="btn btn-secondary me-3" href="{{ route('bookmarks.index') }}">
            一覧に戻る
        </a>
        <button type="submit" class="btn btn-primary">
            @if (Route::is('bookmarks.create'))
                投稿する
            @else
                編集する
            @endif
        </button>
    </div>
</div>
</form>
```
```php5:resources\views\bookmarks\create.blade.php
 {{-- +コンポーネントに$tagsを渡す--}}
                        <x-bookmarks.form route="store" :tags="$tags"/>
```
```php5:resources\views\bookmarks\edit.blade.php
 {{--+コンポーネントに$tagsを渡す--}}
                        <x-bookmarks.form route="update" :bookmark="$bookmark" :tags="$tags"/>
```
bookmarks.createとbookmarks.editに表示されればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/0f607ecd-0593-e42b-0f31-d6bbcabff8d5.png)


#### 中間テーブルを操作して、bookmarkとtagをリレーションさせる。

https://www.wakuwakubank.com/posts/387-laravel-relation-3/

|メソッド	 |insert	|update	 |delete|
|:-----------|:-----------:|:----------:|:-----------:|
| attach	 | ○| × | × |
| detach    | × | × | ○ |
| updateExistingPivot| × | ○ | × |
| sync        |○ | ○ | ○ |
| syncWithoutDetaching | ○ | ○ | ×|
| toggle| ○| ×| ○|
			

```php5:app\Http\Controllers\BookmarkController.php
    public function store(StoreBookmarkRequest $request)
    {
        $bookmark=Bookmark::create($request->all());
//sync()は登録時更新時、中間テーブルにcreate update deleteしてくる
//1,2,3を登録して2,5,7に更新すると1,3をdeleteして5,7を新しく登録してくれる。
        $bookmark->tags()->sync($request->tags);
        return redirect()->route('bookmarks.index')->with('status','登録しました');
    }
    public function update(UpdateBookmarkRequest $request, Bookmark $bookmark)
    {
        $bookmark->update($request->all());

        $bookmark->tags()->sync($request->tags);

        return redirect()->route('bookmarks.show',$bookmark)->with('status','更新しました');
    }

    public function destroy(Bookmark $bookmark)
    {
        $bookmark->delete();
//sql時onDleteCascade設定しているため本来的に不要だが、
//そうでない場合detach()で中間テーブルから削除できる。
        $bookmark->tags()->detach();
        return redirect()->route('bookmarks.index')->with('status','削除しました');
    }
```
database.sqliteのデータを確認する。拡張機能を導入する

https://marketplace.visualstudio.com/items?itemName=qwtel.sqlite-viewer

bookmark_tagテーブルにデータが登録されていればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/fac558b7-4d4d-2743-deb4-b78c26fac649.png)

# 第14回 リレーション（タグ）の表示とデバッグツールについて
#### デバックツールを導入する

```Laravel　Debugbarをインストール１
composer require barryvdh/laravel-debugbar --dev
```
```Laravel　Debugbarをインストール２
php artisan vendor:publish --provider="Barryvdh\Debugbar\ServiceProvider"
```
下にデバッグバーが表示される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/b1fe776c-6b6f-c933-e4ae-246aa959ab79.png)


本番環境でデバッグバーが見えてしまうとDBの中身も見えたりしてまずいので.envを設定します。APP_DEBUGの設定をfalseにすればデバッグバーが出ない設定になります。

http://www.code-magagine.com/?p=10486

```c:env
APP_DEBUG=false
```

#### bookmark.index画面にタグも一緒に表示

```php5:resources\views\bookmarks\index.blade.php
    <table class="table table-light">
        <thead class="thead-light">
            <tr>
                <th>id</th>
                <th>タイトル</th>
{{-- + --}}
                <th>タグ</th>
                <th>アクション</th>
            </tr>
        </thead>
        <tbody>
            @foreach ($bookmarks as $bookmark)
                <tr>
                    <td>{{ $bookmark->id }}</td>
                    <td>{{ $bookmark->title }}</td>
{{-- +新しく<td>を追加 --}}
                    <td>
                        @foreach ($bookmark->tags as $tag)
                            <a
                                href="{{ route('tags.show', $tag) }}">{{ $tag->title }}</a>
{{-- ループのlastでないならカンマさせる --}}
                                @unless ($loop->last)
                                ,
                                @endunless
                        @endforeach
                    </td>

                    <td class="text-nowrap">
                        <a class="btn btn-info"
                            href="{{ route('bookmarks.show', $bookmark) }}">詳細</a>
                        <a class="btn btn-secondary"
                            href="{{ route('bookmarks.edit', $bookmark) }}">編集</a>
                        <x-del_btn action="bookmarks" :bookmarkId="$bookmark->id" />
                    </td>
                </tr>
            @endforeach
        </tbody>
    </table>
```
先ほど、bookmarkに登録したタグが一緒に表示された。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/3be68828-f1c9-c32d-bc6e-bb0a3d0ee5e1.png)

発行されたクエリを確認すると23個も発行されており、tagsテーブルに何度もアクセスされている。
これは、繰り返しクエリが発行される。N+1問題が生じている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/491a1ee0-480c-7fc5-03e2-c671a3bbc266.png)

N+1問題を解決するため遅延ロードさせる。

```php5:app\Http\Controllers\BookmarkController.php
    public function index()
    {
//with('リレーション名')　これだけでOKです。
        $bookmarks = Bookmark::with('tags')->orderBy('id','desc')->paginate(20);
        return view('bookmarks.index', compact('bookmarks'));
    }
```
クエリの発行回数が４回に激減して、tagsに対するクエリもまとめられている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/1214d2fe-621a-da5b-b09a-f81324380e67.png)

メンバ変数で設定する場合

```php5:app\Models\Bookmark.php
//+    
    protected $with = [
//リレーション名
      'tags'
    ];

    public function tags()
    {
      return $this->belongsToMany(Tag::class);
    }
```
```php5:app\Http\Controllers\BookmarkController.php
    public function index()
    {
        //with('リレーション名')
        //$bookmarks = Bookmark::with('tags')->orderBy('id','desc')->paginate(20);
//自動で遅延ロードしてくれる。
        $bookmarks = Bookmark:orderBy('id','desc')->paginate(20);
        return view('bookmarks.index', compact('bookmarks'));
    }
```
#### tags.show画面でbookmarkの一覧を表示させる

```php5:app\Http\Controllers\TagController.php
    public function show(Tag $tag)
    {
        $bookmarks = $tag->bookmarks()->paginate(3);
        return view('tags.show',compact('tag','bookmarks'));
    }
```

```php5:resources\views\tags\show.blade.php
@extends('layouts.app')

@section('content')
<div class="container">
<div class="row justify-content-center">
    <div class="col-md-8">
        <div class="card">
            <div class="card-header">タグ詳細</div>
            <div class="card-body">
                <table class="table table-bordered">
                    <tbody>
                        <tr>
                            <th class="text-nowrap">タイトル</th>
                            <td>{{ $tag->title }}</td>
                        </tr>
                        <tr>
                            <th>作成日</th>
                            <td>{{ $tag->created_at->format('Y年m月d日 h:i:s') }}</td>
                        </tr>
                    </tbody>
                </table>

{{-- + bookmarks一覧 --}}
                <table class="table table-light">
                    <thead class="thead-light">
                        <tr>
                            <th>id</th>
                            <th>タイトル</th>
                            <th>タグ</th>
                            <th>アクション</th>
                        </tr>
                    </thead>
                    <tbody>
                        @foreach ($bookmarks as $bookmark)
                            <tr>
                                <td>{{ $bookmark->id }}</td>
                                <td>{{ $bookmark->title }}</td>
                                <td>
                                    @foreach ($bookmark->tags as $tag)
                                        <a class="me-1"
                                            href="{{ route('tags.show', $tag) }}">{{ $tag->title }}</a>
                                    @endforeach
                                </td>

                                <td>
                                    <a class="btn btn-info"
                                        href="{{ route('bookmarks.show', $bookmark) }}">詳細</a>
                                    <a class="btn btn-secondary"
                                        href="{{ route('bookmarks.edit', $bookmark) }}">編集</a>
                                    {{-- + --}}
                                    <x-del_btn action="bookmarks" :bookmarkId="$bookmark->id" />
                                </td>
                            </tr>
                        @endforeach
                    </tbody>
                </table>
                <div class="d-flex flex-column align-items-center">
                    {{ $bookmarks->links() }}
                </div>



            </div>
        </div>
    </div>
</div>
</div>
@endsection
```
何個か登録して確認できればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/6eef2cbf-7047-55b1-1c91-f9a0afe9b295.png)

#### bookmarksテーブルをコンポーネント化する

 1. componentsディレクトリの下にbookmarksディレクトリを作成する
 1. その下にtable.blade.phpを作成する

```php5:resources\views\components\bookmarks\table.blade.php
{{-- bookmarks.index.blade.phpのテーブルをそのままカットして貼り付け --}}

<table class="table table-light">
    <thead class="thead-light">
        <tr>
            <th>id</th>
            <th>タイトル</th>
            <th>タグ</th>
            <th>アクション</th>
        </tr>
    </thead>
    <tbody>

        @foreach ($bookmarks as $bookmark)
            <tr>
                <td>{{ $bookmark->id }}</td>
                <td><a href="{{ route('bookmarks.show', $bookmark) }}">{{ $bookmark->title }}</a>
                </td>
                {{-- + --}}
                <td>
                    @foreach ($bookmark->tags as $tag)
                        <a href="{{ route('tags.show', $tag) }}">{{ $tag->title }}</a>
                        {{-- ループのlastでないならカンマさせる --}}
                        @unless($loop->last)
                            ,
                        @endunless
                    @endforeach
                </td>

                <td class="text-nowrap">
                    <a class="btn btn-info" href="{{ route('bookmarks.show', $bookmark) }}">詳細</a>
                    <a class="btn btn-secondary" href="{{ route('bookmarks.edit', $bookmark) }}">編集</a>
                    {{-- + --}}
                    {{-- 変更 :bookmark_id など キャメルはNG --}}
                    <x-del_btn routeIs="bookmarks" :id="$bookmark->id" />
                </td>

            </tr>
        @endforeach
    </tbody>
</table>

```
```php5:resources\views\bookmarks\index.blade.php
@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">ブックマーク一覧</div>
                    <div class="card-body">
                        <x-alert></x-alert>
                        <a href="{{ route('bookmarks.create') }}" class="btn btn-primary mb-3">新規登録</a>
   {{-- tebleをcutして変更 --}}
               <x-bookmarks.table :bookmarks="$bookmarks"/>

                        <div class="d-flex flex-column align-items-center">
                            {{ $bookmarks->links() }}
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

@endsection
```
```php5:resources\views\tags\show.blade.php
@section('content')
<div class="container">
<div class="row justify-content-center">
    <div class="col-md-8">
        <div class="card">
            <div class="card-header">タグ詳細</div>
            <div class="card-body">
                <table class="table table-bordered">
                    <tbody>
                        <tr>
                            <th class="text-nowrap">タイトル</th>
                            <td>{{ $tag->title }}</td>
                        </tr>
                        <tr>
                            <th>作成日</th>
                            <td>{{ $tag->created_at->format('Y年m月d日 h:i:s') }}</td>
                        </tr>
                    </tbody>
                </table>

{{-- + tableをcutして変更 :bookmarks="$bookmarks"--}}
                <x-bookmarks.table :bookmarks="$bookmarks" />
                    

                <div class="d-flex flex-column align-items-center">
                    {{ $bookmarks->links() }}
                </div>
            </div>
        </div>
    </div>
</div>
</div>
@endsection
```
そのまま表記されていればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/6432ea9e-e1ea-95c1-eacd-e9a3a83df600.png)

リレーション先でN+1問題が生じています。tagsクエリが繰り返し発行されている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/cb126928-d876-970e-dfee-7347c5b810c8.png)


```php5:原因
コントローラー
        $bookmark = $tags->bookmark->paginate(3);

conmponents.bookmarks.table.blade.php
        <td>

//此処でfrom tagクエリが繰り返し発行される。
        @foreach ($bookmark->tags as $tag)
            <a href="{{ route('tags.show', $tag) }}">{{ $tag->title }}</a>
            {{-- ループのlastでないならカンマさせる --}}
            @unless($loop->last)
                ,
            @endunless
        @endforeach
        </td>
```
遅延ロードさせる。

```php5:app\Http\Controllers\TagController.php
    public function show(Tag $tag)
    {

//->with()を追加するだけ
        $bookmarks = $tag->bookmarks()->with('tags')->paginate(3);
        return view('tags.show',compact('tag','bookmarks'));
    }
```
結果　N＋１問題が解決される。
発行されるクエリの数が激減しているのを確認してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/3f91c184-3d24-051d-e66f-e39d2f899dbd.png)

```php5:app\Http\Controllers\TagController.php
    public function show(Tag $tag)
    {

//->paginate(10)に変更してください
        $bookmarks = $tag->bookmarks()->with('tags')->paginate(10);
        return view('tags.show',compact('tag','bookmarks'));
    }
```
発行されるクエリの数が維持されているのと、tagsがまとめてselectされていればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/48eb10d2-277d-1006-df17-805a8f975a66.png)



# 第15回 Laravel Socialiteを使ってSNSアカウントでログインできるようにしよう

#### laravel socialiteをインストール

```c:
composer require laravel/socialite
```

#### gitHubアカウントを使用してログインする。
環境構築
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/5e6ec8a1-f8b6-993d-20e7-06ba0186c6ef.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/28f359ec-49b2-00b6-55af-2bb2d3e1b219.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/2b25deb9-b4f7-4fc7-498e-b2a7a2aaaedf.png)
上から
`BookmarkApp`
`http://localhost`
`bookmark`
`http://localhost/Bookmark/public/auth/github/callback`(xammp)
`http://localhost:8000/auth/github/callback`(php artisan serve時)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/c2571496-a3f4-317d-c88c-ab141decf78b.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/e759d1be-d405-9a54-8818-aace318c0d4e.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/d34e4c57-ac3f-fb97-8595-2475e5d34ed5.png)


```php5:config\services.php
return [
    'mailgun' => [
        'domain' => env('MAILGUN_DOMAIN'),
        'secret' => env('MAILGUN_SECRET'),
        'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
    ],

    'postmark' => [
        'token' => env('POSTMARK_TOKEN'),
    ],

    'ses' => [
        'key' => env('AWS_ACCESS_KEY_ID'),
        'secret' => env('AWS_SECRET_ACCESS_KEY'),
        'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
    ],
//+
    'github' => [
        'client_id' => env('GITHUB_CLIENT_ID'),
        'client_secret' => env('GITHUB_CLIENT_SECRET'),
        'redirect' => env('GITHUB_CALLBACK_URL'),
    ],
]
```
```:env
GITHUB_CLIENT_ID=fafadfadfja;dfkaffaw
GITHUB_CLIENT_SECRET=fadfap:fawkeflasdl;ffa
GITHUB_CALLBACK_URL=http://localhost/Bookmark/public/auth/github/callback
```
ログインコントローラーを設定する

まずはユーザー情報を取得してみる。

```php5:app\Http\Controllers\Auth\LoginController.php
//+
use Laravel\Socialite\Facades\Socialite;

//この引数$proveiderはルートの{proveider}のこと
//
public function redirectToProvider($provider){

    //登録してあるsocialiteのログインサイトに自動でアクセスしてくれる。
    return Socialite::driver($provider)->redirect();

}

public function handleProviderCallback($provider)
{

//socialiteがこのルートに直接user情報を送信してくるので、
//そのuser情報はこの関数で取得できる。
       $user = Socialite::driver($provider)->user();
       dd($user);
}
```
ルートを用意する

```php5:routes\web.php
//+
use App\Http\Controllers\Auth\LoginController;

Route::get('/auth/{provider}/redirect', [LoginController::class, 'redirectToProvider'])->name('social_login');

//ridirectに登録したURIを作成する。ここにアカウントのuser情報が返ってくる。{provider}は
//変数にして汎用性を高めてある。
Route::get('/auth/{provider}/callback', [LoginController::class, 'handleProviderCallback']);
```
githubログインbtnを用意する

```php5:resources\views\auth\login.blade.php
                            <div class="col-md-8 offset-md-4">
                                <button type="submit" class="btn btn-primary">
                                    {{ __('Login') }}
                                </button>

                                @if (Route::has('password.request'))
                                    <a class="btn btn-link" href="{{ route('password.request') }}">
                                        {{ __('Forgot Your Password?') }}
                                    </a>
                                @endif
  {{-- //+パスワードリセットボタンの@endifの下に追記 --}}
                    <hr>
                    <p>SNSアカウントでログイン</p>
  {{-- //+ --}}
                    <a class="btn btn-secondary" href="{{ route('social_login','github') }}">
                        Github
                    </a>
                           </div>
```
githubのログイン画面をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/f2061727-75ac-bd9a-e479-ee45c798a6d7.png)
githubのログインサイトにアクセスして緑のbtnをクリックしてuser情報が取得できていればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/9c189ed5-f5a0-e63f-73a0-df30fc7f34d6.png)

#### user情報を用いてユーザー登録とログインを完了させる

usersテーブルを修正する
passwordやemailをnull_ableに設定する必要があります。
返ってきた情報はprovider_idとprovider_nameとsocialite_emailに追加します。

```php5:
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique()->nullable();

            $table->string('provider_name')->nullable();
            $table->string('provider_id')->nullable();
            $table->string('provider_email')->nullable();


            $table->timestamp('email_verified_at')->nullable();
            $table->string('password')->nullable();
            
            $table->rememberToken();
            $table->timestamps();
        });
    }
```
ホワイトリストを登録する

```php5:app\Models\User.php
    protected $fillable = [
        'name',
        'email',
        'password',
 //+       
        'provider_id',
        'provider_name',
        'provider_email',
    ];
```
```
php artisan migrate:fresh
```
新しくダミーデータを作成する

```php5:database\seeders\DatabaseSeeder.php
        \App\Models\Tag::factory(10)->create();

        \App\Models\Bookmark::factory(100)->create()->each(function (\App\Models\Bookmark $bookmark) {
            $array = range(1, 10);
            shuffle($array);
            $tag_ids = array_slice($array, 0, rand(0, 4));
            $bookmark->tags()->sync($tag_ids);
        });
```
```
php artisan db:seed
```

https://blog.capilano-fw.com/?p=43

```php5:app\Http\Controllers\Auth\LoginController.php
//+
use App\Models\User;
//+
use Illuminate\Support\Facades\Auth;

    public function handleProviderCallback($provider)
    {
            try {
                //socialiteがこのルートに直接user情報を送信してくるので、
                //そのuser情報はこの関数で取得できる。
                // $user = Socialite::driver($provider)->user();
                // dd($user);
        //+
                $socialiteUser = Socialite::driver($provider)->user();
        
            } catch (\Throwable $th) {
                dd($th);
                return  redirect()->route('login');
            }
        //+今回久保さんのコードを採用しましたが、採用したコードはセキュリティー的にどうかと思った。
        //前回gitHubで登録したユーザーが今回gogoleで登録した時、同じメールを使用していた場合、
        //当然ながら前回のgithubでのユーザーとして認証される。
        //つまり、googleのアカウントが乗っ取られた場合、そのほかのgithubで認証したサイトの情報も乗っ取られてしまう可能性がある。
        //oath認証って滅茶苦茶怖いですね。
            // $user = User::where([
            //     'provider_name'=> $provider,
            //     'provider_id'=> $socialiteUser->getId(),
            // ])->first();
        
        //+ 返されたuser情報でusersテーブルに照会する
            $user = User::where(function ($query) use ($socialiteUser, $provider) {
                $query->where('provider_name', $provider)
                    ->where('provider_id', $socialiteUser->id);
            })
                ->orWhere('provider_email', $socialiteUser->email)
                ->first();

            //+ user情報がなかったらcreateする。
            if(!$user){
               $user = User::create([
                    'name'=> $socialiteUser->getNickname()??$socialiteUser->getName(),
                    'provider_id'=> $socialiteUser->getId(),
                    'provider_name'=> $provider,
                    'provider_email'=> $socialiteUser->getEmail(),
                ]);
            }
            //ログインする。
            Auth::login($user, true);
            return redirect()->route('bookmarks.index');
        }
```
loginができているのを確認してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/ff5b1b9b-0133-4278-f053-983e8254bda3.png)
usersテーブルにデータが作成されていればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/9ae3eb3b-8c03-b18a-b9b9-f0d862454102.png)


以上完了です。

#### googleでログインしてみる.
リファレンスサイト(ここに全部書いてくれています)

https://zenn.dev/naoki_oshiumi/articles/80ab178d958447

環境構築

https://console.cloud.google.com/?hl=ja

手順

 1. https://console.cloud.google.com/?hl=ja
 1. GOOGLE_CLIENT_ID=取得
 1. GOOGLE_CLIENT_SECRET=取得
 1. GOOGLE_CALLBACK_URL=http://localhost/Bookmark/public/auth/google/callback
 1. その他にも、OATH認証の登録が最初に１回必要だったと思いますが、2度目からは要求されませんでした。

 1のサイトで,2,3を取得し4を登録すれば環境構築は完了
手順
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/6c21cb83-3f82-b24b-cc72-0fcfc367e08a.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/47a8c4b9-edff-aeb1-097d-cab057cc3333.png)
4の承認済みリダイレクトURLはコールバックURLの意味です。web.phpのルートで作成したルートを登録します。
名前：`BookmarkApp`
承認済みJavaScript生成元に:`http://localhost`
承認済みリダイレクトURL:`http://localhost/Bookmark/public/auth/google/callback`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/58b123ec-7264-2722-b286-1f452fe51957.png)
クライアントIDとシークレットIDをここでメモっておいて下さい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/268066de-f1f8-42e3-53ba-9321acb26f02.png)
以上で、環境構築は終了です。
手順1

```:env
GOOGLE_CLIENT_ID=106fadfadfad47181-dpfadklfjaogo4hm2krvb.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=fasdfafX-fadfafPb_3mfadfdc-B-OYI5Is
GOOGLE_CALLBACK_URL=http://localhost/Bookmark/public/auth/google/callback
```
手順2

```php5:config\services.php
    'github' => [
        'client_id' => env('GITHUB_CLIENT_ID'),
        'client_secret' => env('GITHUB_CLIENT_SECRET'),
        'redirect' => env('GITHUB_CALLBACK_URL'),
    ],

// 追記する
    'google' => [
        'client_id' => env('GOOGLE_CLIENT_ID'),
        'client_secret' => env('GOOGLE_CLIENT_SECRET'),
        'redirect' => env('GOOGLE_CALLBACK_URL'),
    ],
```
手順3
ルートとコントローラーは既に作成済みなので、OK

手順4
リンクの作成

```php5:resources\views\auth\login.blade.php
                    <p>SNSアカウントでログイン</p>
                    <a class="btn btn-secondary" href="{{ route('social_login','github') }}">
                        Github
                    </a>
  {{-- //+ --}}
                    <a class="btn btn-danger" href="{{ route('social_login','google') }}">
                        google
                    </a>
                            </div>
                        </div>
                    </form>
```
googleアカウントでログインしてみる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/89e530d3-643f-321a-51ec-a91a821f8315.png)
usersテーブルにデータが入力されていたらOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/efbbd6d4-9706-1184-6117-57173e8bbb81.png)


githubのメールがgoogleアカウントと同じ場合、googoleアカウントでloginしても、
githubで登録したuser情報でログインできるようにしています。
新しく登録したい場合はuserの取得方法を変更してください。

```php5:app\Http\Controllers\Auth\LoginController.php
//+
            $user = User::where([
                'provider_name'=> $provider,
                'provider_id'=> $socialiteUser->getId(),
            ])->first();
        
        //+ 返されたuser情報でusersテーブルに照会する
            // $user = User::where(function ($query) use ($socialiteUser, $provider) {
            //     $query->where('provider_name', $provider)
            //         ->where('provider_id', $socialiteUser->id);
            // })
            //     ->orWhere('provider_email', $socialiteUser->email)
            //     ->first();
```

#### yahooアカウントで登録してみる
リファレンスサイト

https://qiita.com/zaburo/items/25ebbb3d1b580a0df9f3

↓のサイトのコードを丸々使用しました。

https://qiita.com/msht0511/items/779961ef3204abac8dd8

環境構築の手順は先ほどのgithub,googleと同じです。
コールバックURLの登録に癖があります。

https://developer.yahoo.co.jp/yconnect/v2/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/ed37fabf-e212-ad83-d02b-aa651ae1de91.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/55f00638-875b-7f72-7dd6-0eb1fe8d243e.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/3713c15f-35f2-61be-8877-6a79d834603c.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/9fcd8989-c4f5-bea9-1ddd-4ffe57e1bc41.png)
ここでクライアントIDとシークレットIDを取得
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/3b600cf2-cfde-4885-51de-a31b4e8f7ede.png)
上の画像の３のアプリケーションの詳細リンクをクリックしてコールバックURLを登録
`http://localhost/Bookmark/public/auth/yahoojp/callback`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/8e7dfa5a-cab6-7661-605f-b35539250c17.png)

環境構築は終了です。

```:env
YahooJp_ID=fdkalfjaodfija;dlfafadfamVfadsklfjafadf-
YahooJp_SECRET=fadklsfjadlfja;ldfkjafdafiweofjaf
YahooJp_CALLBACKURL=http://localhost/Bookmark/public/auth/yahoojp/callback
```
```php5:config\services.php

    'github' => [
        'client_id' => env('GITHUB_CLIENT_ID'),
        'client_secret' => env('GITHUB_CLIENT_SECRET'),
 //githubのredirectに登録したURL
        'redirect' => env('GITHUB_CALLBACK_URL'),
    ],

    
    'google' => [
        'client_id' => env('GOOGLE_CLIENT_ID'),
        'client_secret' => env('GOOGLE_CLIENT_SECRET'),
        'redirect' => env('GOOGLE_CALLBACK_URL'),
    ],
// 追記する
    'yahoojp' => [
        'client_id'     => env('YahooJp_ID'),
        'client_secret' => env('YahooJp_SECRET'),
        'redirect'      => env('YahooJp_CALLBACKURL'),
    ],
```
リンクを作成する

```php5:
                    <p>SNSアカウントでログイン</p>
                    <a class="btn btn-secondary" href="{{ route('social_login','github') }}">
                        Github
                    </a>
                    <a class="btn btn-danger" href="{{ route('social_login','google') }}">
                        google
                    </a>
  {{-- //+ --}}
                    <a class="btn btn-danger" href="{{ route('social_login','yahoojp') }}">
                        yahoo
                    </a>
                            </div>
                        </div>
                    </form>
```
ルートとコントローラーは既に作成済みです。
本来なら、これでOKなのですが、Laravel SocialiteはyahooJpには対応していませんので、
Laravel Socialiteを拡張する必要があります。

手順1

 1. appディレクトリの下にapp\Socialiteディレクトリを作成
 1. その下に、2つのファイルと１つのディレクトリを作成してください
     1. app\Socialite\MySocialManager.php
     1. app\Socialite\MySocialServiceProvider.php
     1. Twoディレクトリを作成その下に
        1.app\Socialite\YahooJpProvider.php 

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/3ce0fc5e-8d8a-8a99-e52a-2d2ea8ec225a.png)

```php5:app\Socialite\MySocialServiceProvider.php
<?php

namespace App\Socialite;

use Laravel\Socialite\SocialiteServiceProvider;
use Laravel\Socialite\Contracts\Factory;


class MySocialServiceProvider extends SocialiteServiceProvider
{
    public function register()
    {
        //シングルトンとしてMySocialManagerを生成
        $this->app->singleton(Factory::class, function ($app) {
            return new MySocialManager($app);
        });
    }
}
```

```php5:app\Socialite\MySocialManager.php
<?php

    namespace App\Socialite;

    use Laravel\Socialite\SocialiteManager;

    class MySocialManager extends SocialiteManager{

//+
        protected function createYahooJpDriver()
        {
            //services.phpの設定情報を読む
            $config = $this->config['services.yahoojp'];
            //設定情報と共にプロバイダを生成
            return $this->buildProvider(
                'App\Socialite\Two\YahooJpProvider',$config
            );
        }
```
```php5:app\Socialite\Two\YahooJpProvider.php
<?php

namespace App\Socialite\Two;

use Laravel\Socialite\Two\AbstractProvider;
use Laravel\Socialite\Two\ProviderInterface;
use Laravel\Socialite\Two\User;

class YahooJpProvider extends AbstractProvider implements ProviderInterface
{

    //scopeの区切り文字設定
    protected $scopeSeparator = ' ';

    //スコープ設定
    protected $scopes = [
        'openid',
        'profile',
        'email',
    ];

    //認証用URL($stateはオプション)
    protected function getAuthUrl($state)
    {
        return $this->buildAuthUrlFromBase('https://auth.login.yahoo.co.jp/yconnect/v2/authorization', $state);
    }

    //token取得用URL
    protected function getTokenUrl()
    {
        return 'https://auth.login.yahoo.co.jp/yconnect/v2/token';
    }

    //Token取得の際のオプション
    //Basic認証と必要なPOSTパラメータを送付
    public function getAccessToken($code)
    {
        $basic_auth_key = base64_encode($this->clientId.":".$this->clientSecret);

        $response = $this->getHttpClient()->post($this->getTokenUrl(), [
            //認証
            'headers' => [
                'Authorization' => 'Basic '.$basic_auth_key,
            ],
            // 'form_params' => $this->getTokenFields($code),
            // 直接記述
            'form_params' => [
                'grant_type' => 'authorization_code',
                'code' => $code,
                'redirect_uri' => $this->redirectUrl
            ],
        ]);

        return $this->parseAccessToken($response->getBody());
    }

    //ユーザー情報取得
    protected function getUserByToken($token)
    {
        //url + schema=openidが必要だった
        $response = $this->getHttpClient()->get('https://userinfo.yahooapis.jp/yconnect/v2/attribute?schema=openid', [
            'headers' => [
                // 'Authorization' => 'Bearer '.$token['access_token'],
                'Authorization' => 'Bearer '.$token,
            ],
        ]);
        return json_decode($response->getBody(), true);
    }

    //Userにパラメーターをマップ（必要に応じてその他のパラメータ追加）
    protected function mapUserToObject(array $user)
    {
        return (new User())->setRaw($user)->map([
            'id' => $user['sub'],
            'name' => $user['name']??$user['nickname'],
            'email' => $user['email'],
            
        ]);
    }

    //token parse用の関数
    protected function parseAccessToken($body)
    {
        return json_decode($body, true);
    }
}
```
#### 作成したproviderの登録

```php5:config\app.php
    'providers' => [

        /*
         * Laravel Framework Service Providers...
         */
        Illuminate\Auth\AuthServiceProvider::class,
        Illuminate\Broadcasting\BroadcastServiceProvider::class,
        Illuminate\Bus\BusServiceProvider::class,
        /*
         * Package Service Providers...
         */

        /*
         * Application Service Providers...
         */
        App\Providers\AppServiceProvider::class,
        App\Providers\AuthServiceProvider::class,
        // App\Providers\BroadcastServiceProvider::class,
        App\Providers\EventServiceProvider::class,
        App\Providers\RouteServiceProvider::class,
//+
        App\Socialite\MySocialServiceProvider::class,
    ],
```
```c:
composer update
```
composer updateしてもコードのエラー表記が消えない場合で、気になる場合は、
vscodeを再起動してください。


#### lineアカウントでログイン
リファレンスサイト

http://blog.lesimo.jp/archives/174

https://qiita.com/LowSE01/items/25be61b1039e54bc722b

https://blog.ef-4.co.jp/laravel%e3%82%a2%e3%83%97%e3%83%aa%e3%82%b1%e3%83%bc%e3%82%b7%e3%83%a7%e3%83%b3%e3%81%abline%e3%83%ad%e3%82%b0%e3%82%a4%e3%83%b3%e3%82%92%e5%ae%9f%e8%a3%85%e3%81%97%e3%82%88%e3%81%86%ef%bc%81/

滅茶苦茶勉強になったサイト
正直laravel Socialiteを使用するとOath認証の仕組みを理解することは困難だと思います。
laravel Socialiteを使用せずにsocialite loginの解説をしてくれています。

https://nori-life.com/laravel-line-login/

↓こっちをメインに↑を参考にすると凄くいい。↓は↑を参考にしている。因みに私は、上下共に頻繁に見てます。

https://biz.addisteria.com/laravel_line_integration/

https://frostmoon.net/?p=450

今回は、laravel Socialiteを使用して実装します。

環境構築

https://developers.line.biz/ja/
lineログインをクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/782a2ae1-2807-bd31-1eec-c575078599b1.png)
今すぐはじめようをクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/bef7ab63-f2eb-c0c0-e559-3cb0a358023c.png)
各種入力
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/83bd68b0-1213-6410-8d82-cefac27f8fdc.png)
チャンネルIDとチャンネルシークレットがクライアントIDとシークレットIDになります。
上の方の<font color="red">非公開タグを公開タグ</font>に忘れずに設定してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/7ed7b5a4-733e-b55e-129c-8a233b06c02f.png)

コールバックURLの登録
`http://localhost/Bookmark/public/auth/line/callback`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/14577e91-00b9-6987-b886-3fd6d22c6bbd.png)
メールアドレス取得の設定です。この設定をしていないとメールアドレスは取得できません。
スクリーンショットはログイン画面をスクショして保存しました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/f6f56554-33cf-f948-a4c3-53db6e9859e6.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/bbfdbc82-3a0c-4aa5-37a5-1146f2bdaa3c.png)
申請済みになれば、完了です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/c0ca933d-03d0-2c60-8c83-3abf4883b997.png)
環境構築は終了です。
大まかな手順はyahooJpと同じですが、emailの取得方法が独特です。
下記のサイトでくわしく書いてくれいます。画像は下記のサイトのスクショです。

http://blog.lesimo.jp/archives/174

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/d64e22f1-a5e1-4a15-1f44-1ca6eb94f3df.png)
上記サイトでは２の方法をで記載してくれていますので、１の方法で記載します。
１の方法がスタンダードだと思います。

```:env
LINE_CLIENT_ID=fdafjadlsffadf
LINE_CLIENT_SECRET=dfasdfadkl;fjaoifjaafda
LINE_REDIRECT=http://localhost/Bookmark/public/auth/line/callback
```

```php5:config\services.php

    'github' => [
        'client_id' => env('GITHUB_CLIENT_ID'),
        'client_secret' => env('GITHUB_CLIENT_SECRET'),
        'redirect' => env('GITHUB_CALLBACK_URL'),
    ],


    'google' => [
        'client_id' => env('GOOGLE_CLIENT_ID'),
        'client_secret' => env('GOOGLE_CLIENT_SECRET'),
        'redirect' => env('GOOGLE_CALLBACK_URL'),
    ],

    'yahoojp' => [
        'client_id'     => env('YahooJp_ID'),
        'client_secret' => env('YahooJp_SECRET'),
        'redirect'      => env('YahooJp_CALLBACKURL'),
    ],

// 追記する
    'line'   => [
        'client_id'     => env('LINE_CLIENT_ID'),
        'client_secret' => env('LINE_CLIENT_SECRET'),
        'redirect'      => env('LINE_REDIRECT'),
    ],
```
ルートとコントローラーは既に作成済み。

```php5:resources\views\auth\login.blade.php
                    <p>SNSアカウントでログイン</p>
                    <a class="btn btn-secondary" href="{{ route('social_login','github') }}">
                        Github
                    </a>
                    <a class="btn btn-danger" href="{{ route('social_login','google') }}">
                        google
                    </a>
                    <a class="btn btn-danger" href="{{ route('social_login','yahoojp') }}">
                        yahoo
                    </a>
  {{-- //+ --}}
                    <a class="btn btn-success" href="{{ route('social_login','line') }}">
                        line
                    </a>
                            </div>
                        </div>
                    </form>
```


```php5:app\Socialite\MySocialManager.php
<?php

    namespace App\Socialite;

    use Laravel\Socialite\SocialiteManager;

    class MySocialManager extends SocialiteManager{

        protected function createYahooJpDriver()
        {
            //services.phpの設定情報を読む
            $config = $this->config['services.yahoojp'];
            //設定情報と共にプロバイダを生成
            return $this->buildProvider(
                'App\Socialite\Two\YahooJpProvider',$config
            );
        }
        
//+
        protected function createLineDriver()
        {
            $config = $this->config['services.line'];

            return $this->buildProvider('App\Socialite\Two\LineProvider', $config);
        }
    }
```
```php5:app\Socialite\Two\LineProvider.php
<?php
namespace App\Socialite\Two;

use Laravel\Socialite\Two\AbstractProvider;
use Laravel\Socialite\Two\ProviderInterface;
use Laravel\Socialite\Two\User;
use Laravel\Socialite\Facades\Socialite;
use Illuminate\Support\Arr;

class LineProvider extends AbstractProvider implements ProviderInterface
{
    // protected $parameters = [
    //     'nonce' => null
    // ];
    protected $scopeSeparator = ' ';

    protected $scopes = [
        'openid',
        'profile',
        'email',
    ];
 
    protected function getAuthUrl($state)
    {
        // 'nonce' パラメータ値の生成//正直これの使い方が不明だった。勉強になる。
        // $this->parameters['nonce'] = (string) uniqid('PREFIX', true);
 
        // 'nonce' パラメータ値をセッション変数に保存
        // session()->put(['nonce' => $this->parameters['nonce']]);
 
        return $this->buildAuthUrlFromBase('https://access.line.me/oauth2/v2.1/authorize', $state);
    }
    protected function getTokenUrl()
    {
        return 'https://api.line.me/oauth2/v2.1/token';
    }

    public function getAccessTokenResponse($code)
    {
        $response = $this->getHttpClient()->post($this->getTokenUrl(), [
            'headers' => [
                'Content-Type' => 'application/x-www-form-urlencoded',
            ],
            'form_params' => [
                'grant_type' => 'authorization_code',
                'code' => $code,
                'redirect_uri' => $this->redirectUrl,
                'client_id' => $this->clientId,
                'client_secret' => $this->clientSecret,
            ],
        ]);
        return json_decode($response->getBody(),true);
    }

    protected function getUserByToken($token)
    {
        $response = $this->getHttpClient()->get('https://api.line.me/v2/profile', [
            'headers' => [
                'Accept' => 'application/json',
                'Authorization' => 'Bearer '.$token,
            ],
        ]);
 
        return json_decode($response->getBody(), true);
    }

    public function user()
    {
        if ($this->user) {
            return $this->user;
        }

        if ($this->hasInvalidState()) {
            throw new InvalidStateException;
        }

        $response = $this->getAccessTokenResponse($this->getCode());

        $id_token = Arr::get($response, 'id_token');

        $json = $this->curl_line($id_token);

        $this->user = $this->mapUserToObject($this->getUserByToken(
            $token = Arr::get($response, 'access_token')
        ));

// 戻り値: User にセット (map() でメールアドレスをセット)
        return $this->user->setToken($token)
                    ->setRefreshToken(Arr::get($response, 'refresh_token'))
                    ->setExpiresIn(Arr::get($response, 'expires_in'))
                    ->map(['email' => $json->email]);
    }

    protected function mapUserToObject(array $user)
    {
        return (new User())->setRaw($user)->map([
            'id' => $user['userId'],
            'name' => $user['displayName'],
            // 'avatar_original' => $user['pictureUrl'],
        ]);
    }

    public function curl_line($id_token){
        $headers = [ 'Content-Type: application/x-www-form-urlencoded' ];
        $post_data = array(
       //シークレットキー
       'id_token' => $id_token,
       //POST されたトークンの値
       'client_id' => $this->clientId,
        );
        $url = 'https://api.line.me/oauth2/v2.1/verify';

        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST');
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($post_data));
        $res = curl_exec($curl);
        curl_close($curl);
    
        $json = json_decode($res);
        // dd($json);
        return $json;
    }
}
```
以上、終了です。