LoginSignup
5
4

More than 3 years have passed since last update.

Laravel7 わたしのインデックス

Last updated at Posted at 2020-08-31

はじめに

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 の機能です。
簡単に言うと、「本来持つプロパティのデータを加工して、新しいデータを変更・追加したりした後もともとプロパティに存在するように振る舞う」という感じですかね。
人からノート見せて貰って写したけど、ちょっと内容変えて自分の名前で提出したみたいなw。

今回は独自カラムを作成したので、その方法を紹介します。
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簡易登録・簡易ログインと調べても出てこなかったので、ここで書いておこうと思います。
時間かかったわりに簡単でしたw。

Controller.php
// 認証処理をパスしてログイン
$user = User::find($user_id);
Auth::login($user,true);
//こっちは一行で書ける
Auth::loginUsingId(1, true);

Auth::login();で認証処理をパスしてログインできます。
ユーザーidの指定、第2引数に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対策を使える。

最後に

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

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4