Laravel7 わたしのインデックス

##はじめに
Laravel7学習の際に「これ絶対忘れそうだな〜」とか「どっかで使ったけどどこに書いてたっけ？」とか、対象となりそうなところに絞って内容をまとめていきます。
自分の備忘録であり、忘れそうなところをまとめたインデックスです。
Laravel7では、ルーティング、様々な重要処理、Viewの継承に分けてまとめていきます。

###対象読者
「routeのRoute::resource、Auth::Routes()の動きを忘れそうな人」
「ミドルウェア・アクセサの使い方がいまいちわからない人」
「laravelの簡易ログイン作成方法を知りたい人」
「フォームが一つでそれぞれ別のテーブルに記録したい人(実際はフォームが一つはあまり良くないらしい。。。)」
「viewの継承の使い方を知りたい人」

というプログラミング初心者です。

プログラミングはじめて八ヶ月程度でフレームワークを使い始めたのも最近なので、温かい目で見て頂けたらと。。

ご質問、ご指摘ありましたら遠慮なくご連絡下さい。

では、書いていきます。

##参考文献（個人的な物も含む)
入門Laravelチュートリアル (1) イントロダクションと環境構築
https://www.hypertextcandy.com/laravel-tutorial-introduction/
Laravel入門 - 使い方チュートリアル -
https://qiita.com/sano1202/items/6021856b70e4f8d3dc3d

Laravel（EC2）CircleCIによるデプロイ（deploy）の自動化
https://noumenon-th.net/programming/2020/05/02/circleci-laravel-ec2-deploy/
作成に関係ありませんが、個人的にaws、circleciの構築に大変役に立ったので残しておきます。

Laravel 7.x で Auth を利用する
https://qiita.com/kapibarasensei/items/c6b40366505f94103c35
Dockerでphpコンテナとかにnpmをインストールするときのメモ
https://tsyama.hatenablog.com/entry/docker-not-found-npm
上記2つはlaravel7のnpmをdockerfileに追加してAuthを利用可能にするまで

##ルーティング

###ルートの命名

```web.php
Route::get('/record', 'RecordController@index')->name('record');
Route::post('/record', 'RecordController@post');
```
```index.blade.php
{{ route('record') }}
```
今回ディレクトリの指定は/recordとしています。
Route::get()->name();とすることで
getでもpostでも{{ route('') }}で呼び出すことができます。
名前をつけてあとで呼び出せるのは URL だけなので、同じ URL で HTTP メソッド違い(delete,putなど）のルートがいくつかある場合はどれか一つに名前をつければ OK です。

###Auth::routes();
make:authで下記のようなRouteが生成されます。

```web.php
Auth::routes(); /これだけで下記のroute取得/
php artisan route:list
+--------+----------+-------------------------+------+-----------------------------------------------------------------+------------+
| Domain | Method   | URI                     | Name | Action                                                          | Middleware |
+--------+----------+-------------------------+------+-----------------------------------------------------------------+------------+
|        | GET|HEAD | /                       |      | Closure                                                         |            |
|        | GET|HEAD | home                    |      | App\Http\Controllers\HomeController@index                       | web,auth   |
|        | GET|HEAD | login                   |      | App\Http\Controllers\Auth\AuthController@showLoginForm          | web,guest  |
|        | POST     | login                   |      | App\Http\Controllers\Auth\AuthController@login                  | web,guest  |
|        | GET|HEAD | logout                  |      | App\Http\Controllers\Auth\AuthController@logout                 | web        |
|        | POST     | password/email          |      | App\Http\Controllers\Auth\PasswordController@sendResetLinkEmail | web,guest  |
|        | POST     | password/reset          |      | App\Http\Controllers\Auth\PasswordController@reset              | web,guest  |
|        | GET|HEAD | password/reset/{token?} |      | App\Http\Controllers\Auth\PasswordController@showResetForm      | web,guest  |
|        | GET|HEAD | register                |      | App\Http\Controllers\Auth\AuthController@showRegistrationForm   | web,guest  |
|        | POST     | register                |      | App\Http\Controllers\Auth\AuthController@register               | web,guest  |
+--------+----------+-------------------------+------+-----------------------------------------------------------------+------------+
```
php artisan route:listで現在のrouteリストが確認できます。
ちょっとわかりづらいのでget,postでまとめてControllerを省略します。

```web.php
//トップページ
Route::get('/home', 'HomeController@index');
//登録
Route::get('/register', 'AuthController@showRegistrationForm');
Route::post('/register ', 'AuthController@register');
//ログイン
Route::get('/login', 'AuthController@showLoginForm');
Route::post('/login ', 'AuthController@login');
//ログアウト
Route::get('/logout', 'AuthController@logout');
//パスワードメール送信
Route::post('password/email', 'PasswordController@sendResetLinkEmail');
//パスワードリセット
Route::get('password/reset/{token?}', 'PasswordController@showResetForm');
Route::post('password/reset', 'PasswordController@reset');
```
順番を入れ替えて、それぞれ分けて書いてみました。
登録・ログイン・リセットのgetメソッドがshow{}Formになっていたのは新しい発見。
HomeController以外、App\Http\Controllers\Auth以下に存在します。

```~Controller.php
namespace App\Http\Controllers\Auth;
```
上記の記述がControllerに書かれています。
軽くググったらnamespaceは「名前空間：ファイルの居場所を示す」だそうです。なるほど。

```index.blade.php
{{ route('register') }}
{{ route('login') }}
{{ route('logout') }}
{{ route('password.email') }}
{{ route('password.update') }}
```
上記でget,postともに呼び出せます。（'password/reset/{token?}'は例外)
~~パスワードリセットpost('password/reset')なのに{{ route('password.update') }}なのわかりにくい。~~

###Restfulリソースコントローラ

```web.php
Route::resource('book','BookController');
```
下記のRestfulなルーティングを行うことができます。

```web.php
Route::get('/book', 'BookController@index');  //一覧画面の表示
Route::get('/book/{book}', 'BookController@show'); //詳細画面の表示
Route::get('/book/create', 'BookController@create'); //登録画面の表示
Route::post('/book', 'BookController@store'); //登録処理
Route::get('/book/{book}/edit', 'BookController@edit'); //編集画面の表示
Route::put('/book/{book}', 'BookController@update'); //編集処理
Route::delete('/book/{book}', 'BookController@destroy'); //削除処理
```
Route::resource();にルート命名はできないので'/book~'で呼び出します。

```index.php
<a href="/book"></a>
```
この場合、getでindexメソッドを呼び出し、一覧画面の表示をします。

##様々な重要処理
###ミドルウェア
認証を求めるミドルウェアはデフォルトで用意されていますので、routes/web.php でルートにミドルウェアを適用します。

```web.php
Route::group(['middleware' => 'auth'], function() {
    // いままで定義してきたルート
});
```
これで認証ユーザーしか「いままで定義してきたルート」内にアクセスできません。
ですが、その中で「指定したユーザーしかアクセスできないようにする」ためには別でポリシークラスを設定する必要があります。

コマンドラインから以下のコマンドでポリシークラスを作成できます。

```
php artisan make:policy FolderPolicy
```
app/Policies/FolderPolicy.php を以下の内容で編集します。

```FolderPolicy.php
<?php

namespace App\Policies;

use App\Folder;
use App\User;

class FolderPolicy
{
    /**
     * フォルダの閲覧権限
     * @param User $user
     * @param Folder $folder
     * @return bool
     */
    public function view(User $user, Folder $folder)
    {
        return $user->id === $folder->user_id;
    }
}
```
これで「ユーザーidとフォルダーuser_idが結びついてるときのみ許可する」という認証ルールが作成されました。
フォルダーを作成する時にユーザーid=>フォルダーuser_idとするので、「作成したフォルダーを保有しているのはuser_idである」とテーブルに**必ず**記録しておきます。

作成したポリシーは AuthServiceProvider に登録します。

```AuthServiceProvider.php
<?php
namespace App\Providers;

use App\Folder; // ★ 追加
use App\Policies\FolderPolicy; // ★ 追加

protected $policies = [
    Folder::class => FolderPolicy::class,
];
```
Folder モデルに対する処理への認可には FolderPolicy ポリシーを使用する、という意味です。
簡単に言うと、Folderを取り出すときには「FolderPolicyを通りますよー」ということです。

では作成したポリシーを使用します。
いくつかの方法がありますが、今回はミドルウェアから呼び出して使用する方法を紹介します。

```web.php
Route::group(['middleware' => 'can:view,folder'], function() {
 Route::get('/folders/{folder}/tasks', 'TaskController@index')->name('tasks.index');
});
```
（view,folder）はカンマ区切りになっていて、カンマの左側が認可処理の種類、右側がポリシーに渡すルートパラメーター（URL の変数部分）を示します。
canが適切な認可処理を判定して、trueなら後続処理、falseならコード403でレスポンスを返します。

###リレーションが存在しない
次にリレーションが存在しない（テーブル同士の関係性がない）パターンについて考えます。
おそらくポリシークラスでも同様に作成できますが、コード403(権限がない)で返すため、そのフォルダー（今回でいえばタスク）は存在すると知られてしまうので、コード404(存在しない)で返すようにします。

```TaskController.php
public function showEditForm(Folder $folder, Task $task)
{
    $this->checkRelation($folder, $task);

    // 以下略
}

public function edit(Folder $folder, Task $task, EditTask $request)
{
    $this->checkRelation($folder, $task);

    // 以下略
}

private function checkRelation(Folder $folder, Task $task)
{
    if ($folder->id !== $task->folder_id) {
        abort(404);
    }
}

```
チェックの処理をメソッドに切り出します。
これもポリシークラスと同様に「checkRelationを通りますよー」という形です。
条件も同様で、フォルダーidがタスクfolder_idと異なっているならコード404を返す処理になっています。
メソッドを切り出したら、$this->checkRelation(,);で使用できます。

###エラー画面404(403、500)作成

```resources/views/errors/404.blade.php
@extends('layout')

@section('content')
  <div class="container">
    <div class="row">
      <div class="col col-md-offset-3 col-md-6">
        <div class="text-center">
          <p>お探しのページは見つかりませんでした。</p>
          <a href="{{ route('home') }}" class="btn">
            ホームへ戻る
          </a>
        </div>
      </div>
    </div>
  </div>
@endsection
```
これで作成した画面がエラー画面として表示されます。

###アクセサ
アクセサとは、モデルクラスが本来持つデータを加工した値を、さもモデルクラスのプロパティであるかのように参照できる Laravel の機能です。
簡単に言うと、「本来持つプロパティのデータを加工して、新しいデータを変更・追加したりした後もともとプロパティに存在するように振る舞う」という感じですかね。
人からノート見せて貰って写したけど、ちょっと内容変えて自分の名前で提出したみたいなｗ。

今回は独自カラムを作成したので、その方法を紹介します。
protected $appends = array();によって、「もともとプロパティに存在するように振る舞う」ことができるようになります。
今回作成したのがこちらです。

```Gorecord.php
  /**
   * 状態定義
   */
  const RECORD = [
    'flg' => '出勤',
    'class' => 'text-primary'
  ];

  // 独自カラム
  protected $appends = array('go_flg','go_class');

  // 独自カラムのアクセサ
  public function getGoFlgAttribute()
  {
      return self::RECORD['flg'];
  }

  // 独自カラムのアクセサ
  public function getGoClassAttribute()
  {
      return self::RECORD['class'];
  }
```

これで'go_flg','go_class'を呼び出せば、RECORD['flg']、RECORD['class']を取得することができます。
このメリットは、テーブルに余計なプロパティを追加する必要がなくなるので、**テーブルを汚さずに**追加したい要素を作成できます。
コントローラー側で下記を追記することでカラムを追加できます。

```RecordController.php
// 独自カラムを追加する
$gorecords->append('go_flg','go_class')->toArray();
```
これでプロパティを呼び出せば、画面に表示されます。

###簡易ログイン
laravel簡易登録・簡易ログインと調べても出てこなかったので、ここで書いておこうと思います。
時間かかったわりに簡単でしたｗ。

```Controller.php
// 認証処理をパスしてログイン
$user = User::find($user_id);
Auth::login($user,true);
//こっちは一行で書ける
Auth::loginUsingId(1, true);
```
Auth::login();で認証処理をパスしてログインできます。
ユーザーidの指定、第２引数にtrueをつけることでログイン保持ができます。
Auth::loginUsingId();とすれば一行で記述できます。（今回管理者を指定したいので第一引数に1を指定しています)

###1つのフォームでテーブルを分けて保存する
Controller側で分岐処理を入れる形です。送信ボタンにname属性を持たせます。
今回であればbladeから送信するnameはgo、leaveです。

```RecordController.php
  public function post(Request $request){

    if ($request->input('go')){

      if($request->name == null){

        $message = ['ユーザー名を選択して下さい'];

        return redirect()->route('record')->withInput($message);
      }

      $message = ['出勤しました'];

      // Gorecordモデルのインスタンスを作成する
      $gorecord = new Gorecord();
      // 送信されたユーザー情報を代入する
      $gorecord->user_name = $request->name;
      $gorecord->record_date = Carbon::now();
      $gorecord->record_time = Carbon::now();
      // インスタンスの状態をデータベースに書き込む
      $gorecord->save();

      return redirect()->route('record')->withInput($message);

    }elseif ($request->input('leave')){

      if($request->name == null){

        $message = ['ユーザー名を選択して下さい'];

        return redirect()->route('record')->withInput($message);
      }

      $message = ['退勤しました'];

      // Leaverecordモデルのインスタンスを作成する
      $Leaverecord = new Leaverecord();
      // 送信されたユーザー情報を代入する
      $Leaverecord->user_name = $request->name;
      $Leaverecord->record_date = Carbon::now();
      $Leaverecord->record_time = Carbon::now();
      // インスタンスの状態をデータベースに書き込む
      $Leaverecord->save();

      return redirect()->route('record')->withInput($message);
    }
  }
```
$requestにinput('go')であればそのまま処理、input('leave')であればelseif下の処理が実行されます。
**補足**ですが、格納した変数を->withInput();でリダイレクトに送ることができます。
リダイレクト側は $request->old(); で変数を受け取れます。

##Viewの継承
###@yield('')
まず親ビューとなるtemplate.blade.phpを作成します。

```template.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <!-- viewport meta -->
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <省略>
</head>
<body>
  <!-- ヘッダー -->
  <header>
    <nav class="navbar navbar-light bg-lightblue fixed-top font-small font-weight-bold" id="header">
    <div class="container-fluid">
    @guest
      <a class="header_title " href="/">
    @else
      <a class="header_title " href="/user">
    @endguest
        <i class="fas fa-door-open"></i>
        {{ config('app.name', 'Attendance') }}
      </a>

      <!-- ハンバーガーメニュー -->
      <div id="js-nav-toggle">
        <div class="nav-toggle">
          <span></span>
          <span></span>
          <span></span>
        </div>
      </div>
      <nav class="header_nav">
        <ul id="nav-toggle-2">
        @guest
        <省略>
        @else
        <省略>
        @endguest
        </ul>
      </nav>
    </div>
    </nav>
  </header>

  @yield('content')

  <!-- フッター -->
  <footer>
    <div class="container-fluid bg-lightblue font-small" id="footer">
      <div class="row text-center pt-2">
        <!-- 各種操作一覧 -->
        @guest
        <省略>
        @else
        <省略>
        @endguest
      </div>
    </div>
    <!-- Copyright -->
    <div class="container-fluid  bg-brown text-white" id="copyright">
      <div class="row text-center">
        <div class="col-12">
          Copyright(C) Attendance.All Rights Reasearved.
        </div>
      </div>
    </div>
  </footer>
</body>
```
親ビューの@yield('content')に小ビューの@section('content')~@endsectionで囲った部分が挿入されます。
親の持っている機能を子が使うというイメージですね。

なお、**@guest**は認証ユーザーでなければそのまま処理、そうでなければ@else後続の処理が実行されます。
逆にしたい場合は @if(Auth::check())~@else~@endifを使用します。

```index.blade.php
@extends('layouts.template')
@section('content')
<!-- メインメニュー -->
<h1 id="js-show-msg" class="msg-slide text-center font-weight-bold" style="display: none;">
{{ $message[0] }}
</h1>
<div class="container-fluid padding" id="main">
<省略>
@endsection
```
今回ファイルの場所がviews/layouts/templateなので@extends('layouts.template');とすることで親ビューを継承できます。
###@include('', ['target' => ''])
@include('', ['target' => ''])を使用すると他のファイルを簡単に取得できます。
targetは下記のように記述します。

```form.blade.php
@if($target == 'store')
<form action="/book" method="post">
@elseif($target == 'update')
<form action="/book/{{ $book->id }}" method="post">
<input type="hidden" name="_method" value="PUT">
@endif
```
@elseif($target == '')のtargetを増やせば、いくつでも表示の変更が可能です。
（増えすぎるとコードが見にくくなるので、2,3つだけにするべき)

###CSRF対策

```index.blade.php
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">
<中略>
<form action="/record" method="post">
@csrf
```
<!-- CSRF Token -->を設定すると@csrfでCSRF対策を使える。

#最後に
書いてたらこれも忘れそう、あれも必要かもと色々脱線してしまいました。。
色々書きたい記事は出てきたので、また忘れそうなことをアウトプットしていきます！
長い記事（わたしのインデックス)を読んでくださりありがとうございました！