20191023のlaravelに関する記事は10件です。

【サルが書く】Laravel 基本情報編

Laravelのとくちょう

わきゃ
最近Laravelに触る機会ができたので、基本的なところをまとめたいと思います。

1. 学習コストが低い

Laravelの特徴として、学習コストが低いことが挙げられます。
その特徴として、 ファサード を使用することで、PHPのスタティックなクラスメソッドを呼び出すように各機能を使用することができます。
Laravel 5.5 ファサード

Laravelのファサードはすべて、Illuminate\Support\Facades 名前空間下で定義されているので。
使いたいときは use Illuminate\Support\Facades\Cache;のようにuseで指定して使うようですね。

ファサードを使用するときの注意点としては、ファサードを使用しすぎてクラスの肥大化しないように気をつけること。クラスの責任範囲を小さくするようにファサードを使用しすぎない。

2. symfonyベース

Laravelはsymfonyベースで作られています。
symfonyのコアはModel View Controller(MVC)フレームワークである Mojavi、オブジェクトリレーショナルマッピング (ORM) である Propel、そして Ruby on Rails のテンプレートヘルパーなどがベースとなっています。

3. 多機能

Laravelはフルスタックフレームワークであり、様々な機能を持っています。
ルーティングやコントローラー、ビュー、ORMなどの基本機能の他に、認証機能、UT (ユニットテスト)、などの応用的な機能も備えています。

4. 積極的なバージョンアップ

Laravelは積極的なバージョンアップを常に行っています。
現時点では半年ごとにマイナーリリースが行われています。PHPのフレームワークの中では早めのリリースサイクルを保っています。そのため、2年毎にLTS(長期サポート版)がリリースされます。

最近 (2019/09/11) ではLaravel6のリリースが発表されました。
https://laravel-news.com/laravel-6

5. 高い拡張性

Laravelではディレクトリ構成に高い拡張性を備えています。ディレクトリ構成は開発者が自由に決めることができます。そのためMVCパターン、ADR、レイヤードアーキテクチャ等の様々なアーキテクチャを採用することができます。

逆に言えば、自由なディレクトリ構造をできるので、ディレクトリの管理ができなくなりやすいのかなとも思います。

phpのフレームワークでCakePHPがありますが、そちらはLaravelとは逆にディレクトリ構成を規約で縛ることにより、崩れにくいディレクトリ管理をすることができます。

参考文献

PHPフレームワーク Laravel Webアプリケーション開発 バージョン5.5 LTS対応

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LaradockでLaravel環境を構築する流れとMySQL接続について

LaradockでMySQLに接続したい!!

Laradockでlaravelの開発環境を構築する方法については、公式ドキュメントや多くのQiitaの記事で説明されており、基本的にはそれに従って行けば開発環境を立ち上げることができると思います。しかし、自分が公式ドキュメントや記事通りに作業していく中でハマった点が2つあり、その解決法を探すのに時間がかかったので、ハマった点とその対処法について記録を残しておくものです

ハマった点

1 MySQLのバージョンについて

MySQLの認証方法がLaravelにサポートされてないのでエラーが出る。
MySQL 8.0.4からデフォルトの認証方式が変わり、セキュリティが強化されました。しかし、Laravel側でサポートされていないためphp artisan migrateを打っても「The server requested authentication method unknown to the client」というエラーが出ます

2 .envの編集について

Laradock,Laravelの.envの編集すれば良いか、MySQLのユーザー登録をどのようにすれば良いかがわからない

1 Laradockのインストール、env編集

今回はこのようなディレクトリ構造で作業を行なっていきます

  • (root)
    • sampleapp
      • Laradock(Laradock側)
      • Laravel
        • wikiLearns(Laravel側)

まずは公式ドキュメント通り

$ git clone https://github.com/laradock/laradock.git
$ cd laradock
$ cp env-example .env

ここで.envファイルを編集します

laradock.env
### MYSQL ################################################

MYSQL_VERSION=5.7 //latest
MYSQL_DATABASE=default
MYSQL_USER=default
MYSQL_PASSWORD=secret
MYSQL_PORT=3306
MYSQL_ROOT_PASSWORD=root
MYSQL_ENTRYPOINT_INITDB=./mysql/docker-entrypoint-initdb.d

MySQLのバージョンをlatestから5.7にしました
なのでハマった点1の答えは「認証方法変更前のMySQL5.7を使おう」です。現在インストールしているMySQLも8.0.4 -> 5.7に変更しました

それともう一つ

laradock.env
### NGINX ################################################

NGINX_HOST_HTTP_PORT=8888 // 80
NGINX_HOST_HTTPS_PORT=443
NGINX_HOST_LOG_PATH=./logs/nginx/
NGINX_SITES_PATH=./nginx/sites/
NGINX_PHP_UPSTREAM_CONTAINER=php-fpm
NGINX_PHP_UPSTREAM_PORT=9000
NGINX_SSL_PATH=./nginx/ssl/

NGINXのポート番号を80 -> 8888に変更しました
Apacheのポート番号とダブらないようにするためです

2 コンテナの起動

コンテナを起動します

docker-compose up -d nginx mysql phpmyadmin

ここで8.0 -> 5.7にバージョンを変えた場合、MySQLのコンテナだけ起動されないという不具合が発生します。その時はDATA_PATH_HOSTで設定したフォルダを綺麗にして、イメージを作成し直す必要があります

対処法
① パス確認
  $ cat .env | grep DATA_PATH_HOST
  自分の場合は DATA_PATH_HOST=~/.laradock/data が表示されました
② パス、イメージ、コンテナを消去
  rm -rf ~/.laradock/data/mysql
  docker rmi laradock_mysql -f
  docker rmi mysql -f
③ mysqlをビルドし直し
  docker-compose build --no-cache mysql
④ docker ps でちゃんと動いているか確認しましょう

3 Laravelプロジェクトの作成

workspaceに入って

$ docker exec -it laradock_workspace_1 bash

Laravelプロジェクトの作成
生成後、sampleapp/Laravel/wikiLearns 内にディレクトリwikiLearnsが出来るので、これをsample/Laravelに置き直してください(何かここ方法があるはず)

# composer create-project laravel/laravel wikiLearns(任意のアプリ名)

workspaceから出ます

# exit

設定の変更をするために一時停止

$ docker-compose stop

APP_CODE_PATH_HOST=../をAPP_CODE_PATH_HOST=../laravel/wikiLearns(自分の作ったディレクトリに対するLaradock起点での任意のパス)に書き換えてください

laradock.env
APP_CODE_PATH_HOST=../laravel/wikiLearns // ../

コンテナ再起動

$ docker-compose up -d nginx mysql

ブラウザでhttp://localhost:8888/ につなぐと例の画面が出てくるはずです。(出てこない場合は多分APP_CODE_PATH_HOSTのパス間違い)

4 MySQLへの接続

ハマった点2の説明です

LaradockのMySQLに接続したい時、

Laravel.env
DB_CONNECTION=mysql
DB_HOST=mysql // 127.0.0.1
DB_PORT=3306
DB_DATABASE=wikiLearns
DB_USERNAME=homestead
DB_PASSWORD=secret

LaravelのDB_HOSTを127.0.0.1 -> mysqlに変更すれば接続できるはず...なんですが、PDOException::("SQLSTATE[HY000] [2002] Connection refused")が出てきます。このエラーはMySQLに認証されていないユーザーで接続しようとした時に発生します。MySQLのコンテナに入って新たにユーザー生成しましょう

1 ユーザー生成

「laradockのworkspaceコンテナからmysqlコンテナに接続」します。そうするとworkspaceからMySQLに接続してマイグレーション等ができるようになります
まずdocker inspectでworkspaceのIPアドレスを確認しましょう

$ docker-compose up -d nginx mysql
$ docker ps (これでworkspaceのコンテナIDを見られる)
$ docker inspect be68295513a3(workspaceのIPアドレス)

下の方にIPアドレスが書いてあります

"Gateway": "172.20.0.1",
"IPAddress": "172.20.0.3", <- これ
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:14:00:03",
"DriverOpts": null

MySQLコンテナに入ります

$ docker-compose exec mysql bash
# mysql -u root -p
# (パスワードはLaradockの.envのMYSQL_ROOT_PASSWORDを見る
    デフォルトはroot)

データベースを作成

> create database wikiLearns(任意の名前);

ユーザー作成

> create user 'root(任意の名前)'@'172.20.0.3(workspaceのIPアドレス)'identified by 'secret(任意のパスワード)';
> grant all privileges on . to 'root'@'172.20.0.3';

2 .env編集

MySQLを出てLaradock、Laravelの.env編集

laradock.env
MYSQL_VERSION=5.7
MYSQL_DATABASE=wikiLearns(作ったデータベース名)
MYSQL_USER=root(作ったユーザー名)
MYSQL_PASSWORD=secret(設定したパスワード)
MYSQL_PORT=3306
MYSQL_ROOT_PASSWORD=root
MYSQL_ENTRYPOINT_INITDB=./mysql/docker-entrypoint-initdb.d
Laravel.env
DB_CONNECTION=mysql
DB_HOST=mysql // 127.0.0.1 → mysql
DB_PORT=3306
DB_DATABASE=dockapp(Laradockと同じに)
DB_USERNAME=root(Laradockと同じに)
DB_PASSWORD=secret(Laradockと同じに)

dockerを停止して再起動すればマイグレーションができます

$ docker-compose up -d nginx mysql
$ docker exec -it laradock_workspace_1 /bin/bash
$ php artisan migrate

これでdockerコンテナを立ち上げてlaravelを利用できるようになりました

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel で 開始と終了 時刻 でのバリデーション

Laravel6を使用して動作を確認しています。

画面

form.blade.php
<input type="text" placeholder="00:00" name="start_time" class="form-control">
@error('start_time') {{ $message }} @enderror
<input type="text" placeholder="00:00" name="end_time" class="form-control">
@error('end_time') {{ $message }} @enderror

SC000002.JPG

バリデーション

FormRequestで行います

php artisan make:make:request TimeRequest
TimeRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class TimeRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'start_time' => 'required|date_format:H:i|',
            'end_time' => 'required|date_format:H:i|after:start_time',
        ];
    }
}


これで適当なControllerのメソッドへformをpostするようにして、引数にTimeRequestを指定します

TekitoController
<?php

namespace App\Http\Controllers;

use App\Http\Requests\TimeRequest;

class TekitoControllerextends Controller
{
    public function some(TimeRequest $request)
    {
        //バリデーション済み$requestが渡ってくる
    }
}

(日本語化済みの表示サンプル)
SC000003.JPG

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vue-component内のinput-textタグのvalueにBlade上の変数を入れる

親コンポーネントから子コンポーネントへ単方向データバインディングさせる際、
v-modelディレクティブ名は

  • 親側:ケバブケース (例:foo-bar)
  • 子側:キャメルケース (例:fooBar)

で記述する必要がある(みたい)。

以下、サンプルコード。

/resources/js/components/SamplComponent.vue
<template>
    <div>
        <div class="input-group">
            <input type="text" class="form-control" name="text1" v-model="hoge">
        </div>
        ...(省略)
    </div>
</template>

<script>
export default {
    props: {
        inputVal: {
            type: String,
        }
    },
    data() {
        return {
            hoge: this.inputVal,    // プロパティのイミュータブル制約回避
            ...
        }
    },
    ...
}
</script>
/resources/js/app.js
require('./bootstrap');

window.Vue = require('vue');

Vue.component('sample-component', require('./components/SamplComponent.vue').default);

const app = new Vue({
    el: '#app',
});

$ npm run dev等でコンパイルし、

/resources/views/sample.blade.php
<body>
    <div id="app">
        ...
        <sample-component input-val="{{ old('hogehoge') }}"></sample-component>
     <!-- ↑例:oldヘルパーを使って入力値を保持 -->
        ...
    </div>
</body>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

laravelのマルチauthで認証済みの独自定義ユーザを取得する方法

php artisan make:authで作成できるユーザの認証状態はAuth::user()で取得できますが、
マルチauthでの独自に定義したユーザは上記の方法では認証状態を取得することはできません。

そこで、認証済み独自ユーザを取得する方法をadminという独自ユーザを例に解説します。
早速ですが、以下コードになります。

Auth::guard('admin')->user()

Authファサードのguardメソッドを使って、使用したい独自ユーザのガードを指定することで、
マルチauthでの認証済の独自ユーザの取得を実現しています。

今回はadminを指定しているので、認証済adminの取得になります。

利用例

実際に使うときは、routesのapi.phpなどで以下のように利用しています。

Route::get('/admin', function () {
  return Auth::guard('admin')->user();
})->name('admin');

マルチauth作成で参考にさせていただいたページ

https://qiita.com/PKunito/items/a8300db38ce7d6949106

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelのマルチauthで認証済みの独自定義ユーザを取得する方法

php artisan make:authで作成できるユーザの認証状態はAuth::user()で取得できますが、
マルチauthでの独自に定義したユーザは上記の方法では認証状態を取得することはできません。

そこで、認証済み独自ユーザを取得する方法をadminという独自ユーザを例に解説します。
早速ですが、以下コードになります。

Auth::guard('admin')->user()

Authファサードのguardメソッドを使って、使用したい独自ユーザのガードを指定することで、
マルチauthでの認証済の独自ユーザの取得を実現しています。

今回はadminを指定しているので、認証済adminの取得になります。

利用例(Laravel × Nuxt)

実際に使うときは、routesのapi.phpなどで以下のように利用しています。

Route::get('/admin', function () {
  return Auth::guard('admin')->user();
})->name('admin');

Nuxtでログイン状態の維持のために以下のように使えます。

/store/index.js

export const actions = {
  async nuxtServerInit ({ commit }, { app }) {
    await app.$axios.$get('/admin')
      .then(user => commit('auth/setAdmin', admin))
      .catch(() => commit('auth/setAdmin', null))
  }
}

SSR時のログインチェックを行わせることで、Nuxt側で認証をstoreにセットして、ログイン状態を維持できるわけですね。

LaravelとNuxtでの認証周りの開発の参考になれば幸いです。

マルチauth作成で参考にさせていただいたページ

https://qiita.com/PKunito/items/a8300db38ce7d6949106

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelのマルチauthで認証済みの独自定義ユーザを取得する方法・Nuxt SSR時のログイン状態維持

php artisan make:authで作成できるユーザの認証状態はAuth::user()で取得できますが、
マルチauthでの独自に定義したユーザは上記の方法では認証状態を取得することはできません。

そこで、認証済み独自ユーザを取得する方法をadminという独自ユーザを例に解説します。
早速ですが、以下コードになります。

Auth::guard('admin')->user()

Authファサードのguardメソッドを使って、使用したい独自ユーザのガードを指定することで、
マルチauthでの認証済の独自ユーザの取得を実現しています。

今回はadminを指定しているので、認証済adminの取得になります。

利用例(Laravel × Nuxt)

実際に使うときは、routesのapi.phpなどで以下のように利用しています。

Route::get('/admin', function () {
  return Auth::guard('admin')->user();
})->name('admin');

Nuxtでログイン状態の維持のために以下のように使えます。

/store/index.js

export const actions = {
  async nuxtServerInit ({ commit }, { app }) {
    await app.$axios.$get('/admin')
      .then(admin => commit('auth/setAdmin', admin))
      .catch(() => commit('auth/setAdmin', null))
  }
}

SSR時のログインチェックを行わせることで、Nuxt側で認証をstoreにセットして、ログイン状態を維持できるわけですね。

LaravelとNuxtでの認証周りの開発の参考になれば幸いです。

マルチauth作成で参考にさせていただいたページ

https://qiita.com/PKunito/items/a8300db38ce7d6949106

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

laravelのlogin処理のソースコードリーディング

login処理のソースコードリーディングをした過程を書いたのですが、ログイン周りをカスタムする必要性が出てきたら勉強すればよいと思います。

自分の場合は軽い気持ちで読んでみただけなので、カスタムする必要性が出てきてからでよかったなと思ってます。

本当にカスタムしようとすると、GuardだったりUserProviderも勉強しないといけないのでアレなんですけどね。

前提知識

認証を理解するにあたって知っておきたい知識を簡単に説明します。

Guard

認証情報の操作をするものです。このクラスはIlluminate\Contracts\Auth\Guardを実装する必要があります。

デフォルトではセッションを利用するものが使われます。

UserProvider

実際に認証処理を行うクラスです。idなどを利用してユーザーのデータを取得して返却します。

Illuminate\Contracts\Aurh\UserProviderを実装する必要があります。

デフォルトではDBからユーザー情報の取得を行い、DB接続に利用するものとしてeloquentかdatabaseの2つが用意されており、デフォルトではeloquentが利用されます。

Authenticatable

UserProviderでは引数や返り値としてIlluminate\Contracts\Auth\Authenticatableを実装したクラスを扱います。

デフォルトで使用されるUser.phpもextends先でこれを実装しています。

login処理

まずはじめに注意点として、掲載するコードは今回の説明に関係ないところを省略して書いていたりします。完全なコードが知りたい場合はlaravelのコードを見てください。

LoginControllerにはloginメソッドはなく、AuthenticatesUsersトレイトに実装されています。

public function login(Request $request)
{
    $this->validateLogin($request);

    // If the class is using the ThrottlesLogins trait, we can automatically throttle
    // the login attempts for this application. We'll key this by the username and
    // the IP address of the client making these requests into this application.
    if (method_exists($this, 'hasTooManyLoginAttempts') &&
        $this->hasTooManyLoginAttempts($request)) {
        $this->fireLockoutEvent($request);

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

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

    // If the login attempt was unsuccessful we will increment the number of attempts
    // to login and redirect the user back to the login form. Of course, when this
    // user surpasses their maximum number of attempts they will get locked out.
    $this->incrementLoginAttempts($request);

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

validateLoginは入力された認証情報(標準ではメールアドレスとパスワード)をバリデーションしています。

attemptLoginの箇所以外はログイン試行回数が規定回数を超えたらロックするといった機能を実現するものなので、今回は割愛します。

二個目のif文の中の$this->attemptLoginを見ていきます。

protected function attemptLogin(Request $request)
{
    return $this->guard()->attempt(
        $this->credentials($request), $request->filled('remember')
    );
}

guard()メソッドを見ていきます。

protected function guard()
{
    return Auth::guard();
}

Authファサードでguardメソッドを呼び出しています。

Authファサードはauthという名前でサービスコンテナから解決をします。
config/app.phpからAuthServiceProviderが登録されているのがわかるので、その中身を見ればどの具象クラスが返ってくるかがわかります。

AuthファサードではIlluminate\Auth\AuthManagerのインスタンスを返却するので、その中のguardメソッドを呼び出していることになります。

なので上記のコードは以下のコードとほぼ同義になります。

protected function guard()
{
    return (new AuthManager())->guard();
}

ではguardメソッドの中身を見ていきましょう。

public function guard($name = null)
{
    $name = $name ?: $this->getDefaultDriver();

    return $this->guards[$name] ?? $this->guards[$name] = $this->resolve($name);
}

guardには引数を渡していないので、$this->getDefaultDriverが呼ばれます。

public function getDefaultDriver()
{
    return $this->app['config']['auth.defaults.guard'];
}

ここのコードの意味はconfig/auth.phpの中身のdefaults => guardの値を取り出すといった意味です。

auth.php
defaults' => [
    'guard' => 'web',
    'passwords' => 'users',
],

標準ではこのように記載されているので、webといった文字列を返却することになります。
なので$name = "web"となります。

次は$this->resolve($name)を見ていきます。

protected function resolve($name)
{
    $config = $this->getConfig($name);

    $driverMethod = 'create'.ucfirst($config['driver']).'Driver';

    if (method_exists($this, $driverMethod)) {
        return $this->{$driverMethod}($name, $config);
    }
}

getConfigは先程のgetDefaultDriverと同じ用にauth.phpから値を取ってきます。

['driver' => 'session', 'provider' => 'users']といった配列が返却されます。

$driverMethod = createSessionDriverとなります。if文の中でこの名前のメソッドを呼び出しています。
($this->createSessionDriver($name, $config))

public function createSessionDriver($name, $config)
{
    $provider = $this->createUserProvider($config['provider'] ?? null);

    $guard = new SessionGuard($name, $provider, $this->app['session.store']);

    return $guard;
}

$this->createUserProviderでUserProviderを取得し、それらを利用してSessionGuardを生成しています。

attemptLoginメソッド内の$this->guard()SessionGuardクラスのインスタンスを返すことがわかりました。

いろんなところにとびすぎたのでattemptLoginメソッドを再掲します。

protected function attemptLogin(Request $request)
{
    return $this->guard()->attempt(
        $this->credentials($request), $request->filled('remember')
    );

    // 以下と同値
    return (new SessionGuard()->attempt(
       $this->credentials($request), $request->filled('remember')
   );
}

Illuminate\Auth\SessionGuardattemptメソッドを見ていきます。

public function attempt(array $credentials = [], $remember = false)
{
    $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);

    if ($this->hasValidCredentials($user, $credentials)) {
        $this->login($user, $remember);

        return true;
    }
}

$this->provider->retrieveByCredentials($credentials)はメールアドレスを元にユーザー情報をDBから取得しています。

$this->hasValidCredentialsは主にパスワードの照合をしています。パスワードが一致すればloginメソッドが呼ばれます。

public function login(AuthenticatableContract $user, $remember = false)
{
    $this->updateSession($user->getAuthIdentifier());

    // If the user should be permanently "remembered" by the application we will
    // queue a permanent cookie that contains the encrypted copy of the user
    // identifier. We will then decrypt this later to retrieve the users.
    if ($remember) {
        $this->ensureRememberTokenIsSet($user);

        $this->queueRecallerCookie($user);
    }

    // If we have an event dispatcher instance set we will fire an event so that
    // any listeners will hook into the authentication events and run actions
    // based on the login and logout events fired from the guard instances.
    $this->fireLoginEvent($user, $remember);

    $this->setUser($user);
}

loginメソッドでは、セッションにユーザーIDを格納したり、remember_tokenをDBにセットしたりしています。

そうすることでログインという状態を維持しているわけです。

loginメソッドのあとにtrueを返却することで、一番最初のloginメソッドに戻り、ログインイベントが発火されるわけです。

if ($this->attemptLogin($request)) {
    return $this->sendLoginResponse($request);
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel6.0で新規作成・編集・一覧・ページネーションを作成したメモ

概要

前回の続き。
Laravel 6.0 基本のタスクリストを参考に、CRUD操作の一部を作成した。
Bootstrapが4であったりという点が異なっている。

データベース

タグを今回は作成する

php artisan make:migration create_tags_table 
whitemap/database/migrations/create_tags_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateTagsTable extends Migration
{

    public function up()
    {
        Schema::create('tags', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->integer('create_user_id')->unsigned()->comment('作成者ID');
            $table->string('name');
            $table->integer('value')->default(0);
            $table->boolean('is_display')->default(true);
            $table->timestamp('created_at')->useCurrent();
            $table->timestamp('updated_at')->default(DB::raw('CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP'));

            $table->foreign('create_user_id')->references('id')->on('users');
        });
    }
    public function down()
    {
        Schema::dropIfExists('tags');
    }
}

アプリケーション

モデル

php artisan make:model Models/Tag 

上記コマンドで以下が作成される。
規約通りに作ると、Eloquentが自動的にO/Rマッピングしてくれる。

whitemap/app/Models/Tag.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;

class Tag extends Model
{
    //
}

コントローラ

php artisan make:controller Admin/TagController --resource --model=Models/Tag 

上記コマンドでモデルと紐づいたコントローラが作成される。
ただし、今回は新規作成画面や編集画面をそれぞれ作らない予定なので、createeditメソッドは割愛。

whitemap/app/Http/Controllers/Admin/TagController.php
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Tag;
use App\Http\Requests\Admin\StoreTagPost;
use Illuminate\Http\Request;

class TagController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        $tags = Tag::paginate(config('const.Paginator.PER_PAGE'));
        return view('admin/tags', [
            'tags' => $tags
        ]);
    }

    /**
     * Store a newly created resource in storage.
     * 新規作成
     *
     * @param  App\Http\Requests\Admin\StoreTagPost  $request
     * @return \Illuminate\Http\Response
     */
    public function store(StoreTagPost $request)
    {
        $user = $request->user();
        $tag = new Tag();
        $tag->name = $request->name;
        $tag->create_user_id = $user->id;
        $tag->value = $request->value;
        $tag->save();

        return redirect('/tag');
    }

    /**
     * Update the specified resource in storage.
     * 変更の保存
     * Laravelはタイプヒントされた変数名とルートセグメント名が一致する場合、
     * ルートかコントローラアクション中にEloquentモデルが定義されていると、自動的に依存解決する。
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \App\Models\Tag  $tag
     * @return App\Http\Requests\Admin\StoreTagPost
     */
    public function update(StoreTagPost $request, Tag $tag)
    {
        $tag->name = $request->name;
        $tag->value = $request->value;
        $tag->save();

        return redirect('/tag');
    }
}

ページネーション用に、1ページに何ページ表示するかの数字を定数にしている。

whitemap/config/const.php
<?php

return [
    // Couponsで使う定数
    'Coupons' => [
        'TYPE_GET' => 1,
        'TYPE_USE' => 2,
    ],
+    'Paginator'=>[
+        'PER_PAGE'=>30
+    ]
];

バリデーション

php artisan make:request Admin/StoreTagPost

バリデーションの設定を行う。
タイプヒントで指定することでコントローラに使用を伝える。
例:update(StoreTagPost $request)

whitemap/app/Http/Requests/Admin/StoreTagPost.php
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;

class StoreTagPost extends FormRequest
{
    public function authorize()
    {
        // そもそも管理者しかタグの更新は行わないためここでは判定しない
        return true;
    }

    public function rules()
    {
        return [
            'name' => 'required|max:255',
            'value' => 'required|integer'
        ];
    }

    public function messages()
    {
        return [
            'name.required' => '名前は必須です',
            'name.max' => '名前は255文字以内で入力してください',
            'value.required'  => '値は必須です',
        ];
    }
}

ルーティング

{tag}にプライマリーキーを受け取ることを想定して、コントローラで$tagで受け取ることで暗黙的なモデルバインディングを実現している。

whitemap/routes/web.php
Route::group(['middleware' => ['auth', 'can:admin-access']], function () {
    Route::get('/admin', function (Illuminate\Http\Request $request) {
        return view('admin/dashboard');
    });


+    Route::get('/tag','Admin\TagController@index');
+    Route::post('/tag','Admin\TagController@store');
+    Route::put('/tag/{tag}','Admin\TagController@update');
});

ビュー

fontawesomeは公式の<link href="https://use.fontawesome.com/releases/v5.11.2/css/all.css" rel="stylesheet">よりもjsdelivrのほうが数段早い。

whitemap/resources/views/layoutes/app.blade.php
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="description" content="異世界漂流TRPG ドリフトサヴァイブはサバイバルをして文明を築き上げるTRPGです。" />
    <meta name="keywords" content="Laravel,laradock,gcp" />
    <meta name="robots" content="index" />
    @yield('meta')
    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <title>{{ config('app.name', 'Laravel') }} - @yield('title')</title>
+    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.11.2/css/all.min.css" integrity="sha256-+N4/V/SbAFiW1MPBCXnfnP9QSN3+Keu+NlB+0ev/YKQ=" crossorigin="anonymous">
    @yield('css')
    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
+    <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha256-pasqAKBDmFT4eHoN2ndd6lN370kFiGUFyTiUHWhU7k8=" crossorigin="anonymous"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
+    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script src="{{ mix('js/common/app.js') }}"></script>
    @yield('head-scripts')
    <!-- Google Tag Manager -->
    <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
    new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
    j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
    'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
    })(window,document,'script','dataLayer','GTM-PBSJTDV');</script>
    <!-- End Google Tag Manager -->
  </head>
  <body>
    <!-- Google Tag Manager (noscript) -->
    <noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-PBSJTDV" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
    <!-- End Google Tag Manager (noscript) -->
    <div id="app">
        <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
                @guest
                @else
                    <a class="navbar-brand user-icon" href="{{ url('/') }}">
                      <i class="user-icon">
                        <img src="{{\Auth::user()->twitter_profile_image_url_https}}">
                      </i>
                    </a>
                @endguest
            <!-- <a class="navbar-brand" href="{{ url('/') }}">{{ config('app.name', 'Laravel') }}</a> -->
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarSupportedContent">
                <ul class="navbar-nav mr-auto">
                    <li class="nav-item {{str_replace(url('/'),'',url()->current()) === '/'     ? 'active' : ''}}"><a class="nav-link" href="{{ url('/') }}">トップ</span></a></li>
                    <li class="nav-item {{str_replace(url('/'),'',url()->current()) === '/home' ? 'active' : ''}}"><a class="nav-link" href="{{ url('/home') }}">マイページ</span></a></li>
                    <li class="nav-item dropdown">
                        <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                        サイト情報
                        </a>
                        <div class="dropdown-menu" aria-labelledby="navbarDropdown">
                            <a class="dropdown-item" href="/agreement">利用規約</a>
                            <a class="dropdown-item" href="/privacy-policy">プライバシーポリシー</a>
                            <div class="dropdown-divider"></div>
                            <a class="dropdown-item" href="https://github.com/hibohiboo/whitemap">github</a>
                        </div>
                    </li>
                </ul>
            </div>
            @guest
              <div class="my-2 my-lg-0">
                <ul class="nav navbar-nav navbar-right">
                    <li class="nav-item"><a href="{{ route('login') }}" class="nav-link">ログイン</a></li>
                </ul>
              </div>
            @else

            @endguest

        </nav>
        @yield('content')
    </div>
    <!-- Scripts -->
    <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js" integrity="sha256-VeNaFBVDhoX3H+gJ37DpT/nTuZTdjYro9yBruHjVmoQ=" crossorigin="anonymous"></script>
    @yield('scripts')
  </body>
</html>

bootstrap3のpanelがbootstrap4ではcardになっているなど微修正。

whitemap/resouces/vies/admin/tags.blade.php
@extends('layouts.app')

@section('content')
    {{-- このコメントはレンダ後のHTMLには現れない --}}
    {{-- Bootstrapは一度に1つのモーダルウィンドウしかサポートしない入れ子になったモーダルはユーザー経験が乏しいと思われるためサポートされていない--}}
    {{-- 可能であれば他の要素からの干渉を避けるためにモーダルHTMLを最上位に配置すること --}}
    <div id="editModal" class="modal fade" tabindex="-1" role="dialog">
      <form id="edit-form" action="{{ url('tag')}}" method="POST">
        @csrf
        @method('PUT')
        <div class="modal-dialog" role="document">
          <div class="modal-content">
            <div class="modal-header">
              <h5 class="modal-title">編集</h5>
              <button type="button" class="close" data-dismiss="modal" aria-label="閉じる">
                <span aria-hidden="true">&times;</span>
              </button>
            </div>{{-- /.modal-header --}}
            <div class="modal-body">
              {{-- タグ名 --}}
              <div class="form-group">
                <label for="edit-tag-name" class="col-sm-3 control-label">タグ名</label>
                <div class="col-sm-6">
                    <input type="text" name="name" id="edit-tag-name" class="form-control" value="{{ old('tag') }}">
                </div>
              </div>
              {{-- タグ値 --}}
              <div class="form-group">
                <label for="edit-tag-value" class="col-sm-3 control-label"></label>
                <div class="col-sm-6">
                    <input type="number" name="value" id="edit-tag-value" class="form-control" value="{{ old('tag') }}">
                </div>
              </div>
            </div>{{-- /.modal-body --}}
            <div class="modal-footer">
              <button type="button" class="btn btn-secondary" data-dismiss="modal">閉じる</button>
              <button type="submit" class="btn btn-primary">変更を保存</button>
            </div>{{-- /.modal-footer --}}
          </div>{{-- /.modal-content --}}
        </div>{{-- /.modal-dialog --}}
      </form>
    </div>{{-- /.modal --}}
    <main class="container">
        <div class="col-sm-offset-2 col-sm-8">
            <div class="card">
                <div class="card-header">新しいタグ</div>
                <div class="card-body">
                    {{-- バリデーションエラーの表示 --}}
                    @include('common.errors')

                    {{-- 新タグフォーム --}}
                    <form action="{{ url('tag')}}" method="POST" class="form-horizontal">
                        @csrf
                        {{-- タグ名 --}}
                        <div class="form-group">
                            <label for="tag-name" class="col-sm-3 control-label">タグ名</label>

                            <div class="col-sm-6">
                                <input type="text" name="name" id="tag-name" class="form-control" value="{{ old('tag') }}">
                            </div>
                        </div>

                        {{-- タグ値 --}}
                        <div class="form-group">
                            <label for="tag-value" class="col-sm-3 control-label"></label>

                            <div class="col-sm-6">
                                <input type="number" name="value" id="tag-value" class="form-control" value="{{ old('tag') }}">
                            </div>
                        </div>

                        {{-- タグ追加ボタン --}}
                        <div class="form-group">
                            <div class="col-sm-offset-3 col-sm-6">
                                <button type="submit" class="btn btn-primary">
                                    <i class="fa fa-btn fa-plus"></i> タグ追加
                                </button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>

            @if (count($tags) > 0)
                <div class="card">
                    <div class="card-header">タグ一覧</div>
                    <div class="card-body">
                        <table class="table table-striped tag-table">
                            {{-- テーブルヘッダ --}}
                            <thead>
                                <tr>
                                    <th>タグ</th>
                                    <th></th>
                                    <th>&nbsp;</th>
                                </tr>
                            </thead>
                            {{-- テーブル本体 --}}
                            <tbody>
                              @foreach ($tags as $tag)
                                <tr>
                                  <td class="table-text">
                                      <div>{{ $tag->name }}</div>
                                  </td>
                                  <td class="table-text">
                                      <div>{{ $tag->value }}</div>
                                  </td>
                                  <td>
                                    <button type="button" class="btn" data-toggle="modal" data-target="#editModal" 
                                            data-action="{{ url('tag/' . $tag->id) }}" data-name="{{$tag->name}}" data-value="{{$tag->value}}"
                                    >
                                    <i class="fa fa-btn fa-edit"></i>
                                  </button>
                                  </td>
                                </tr>
                              @endforeach
                            </tbody>
                        </table>
                    </div>
                </div>
            @endif
            {{ $tags->links() }}
        </div>
    </div>
@endsection
@section('scripts')
  <script src="{{ mix('js/admin/tag/index.js') }}"></script>
@endsection

mixで呼び出すjs用の設定が以下。externalsで外部からリソースを取得することを表す。

npm install --save-dev @types/bootstrap
whitemap/webpack.mix.js
const mix = require('laravel-mix');
mix.ts('resources/ts/common/app.ts', 'public/js/common')
    .sass('resources/sass/app.scss', 'public/css')
    .version();

mix.ts('resources/ts/welcome/index.ts', 'public/js/welcome').version();
mix.ts('resources/ts/home/index.ts', 'public/js/home').version();
mix.ts('resources/ts/login/index.ts', 'public/js/login').version();
mix.webpackConfig({
    externals: {
+        jquery: 'jQuery',
        firebase: 'firebase',
        firebaseui: 'firebaseui',
        axios: 'axios',
        lodash: 'lodash',
+        bootstrap: 'bootstrap',
+        popper: 'popper.js'
    }
});

+ mix.ts('resources/ts/admin/tag/index.ts', 'public/js/admin/tag').version();
resouces/admin/tag/index.ts
import { ModalEventHandler } from 'bootstrap';
$('#editModal').on('show.bs.modal', function(event: ModalEventHandler<HTMLElement>) {
    const target = event.relatedTarget;
    console.log('modal', target);
    if (target === undefined) {
        return;
    }
    const $button = $(target); // モーダル切替えボタン
    const action = $button.data('action'); // data-* 属性から情報を抽出
    const name = $button.data('name');
    const value = $button.data('value');

    // モーダルの内容を更新。ここではjQueryを使用するが、代わりにデータ・バインディング・ライブラリまたは他のメソッドを使用することも可能
    const $modal = $(this);
    $modal.find('#edit-tag-name').val(name);
    $modal.find('#edit-tag-value').val(value);
    $modal.find('#edit-form').attr('action', action);
});
npm run watch-poll

参考

Laravel 6.0 基本のタスクリスト
bootstrap3 -> 4
@types/bootstrap
fontawesome は公式より jsdeliver のほうが早い
FontAwesome の読み込み速度を公式サイトと CDN サービスで比較してみた
暗黙のモデル結合
Laravel 6.0 データベース:ペジネーション
Laravel 6.0 コントローラ
Laravel 6.0 バリデーション

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel6.0で管理者用画面を作ったメモ

概要

前回の続き。
今回は、ログインユーザに管理者権限を与えて、管理者専用画面を表示する。

データベース

ER図

今回、DBは以下のようにする。
管理者クーポンが発行されたユーザが管理者権限を持つとする。

image.png

マイグレーション

以下のコマンドでマイグレーションファイルを作成できる。

php artisan make:migration create_coupons_table

上記で作成したファイルに含まれる$table->timestamps();はnullableなcreated_atupdated_atを作成する。
DBに作ってもらいたかったので、作成時、更新時の時刻を挿入する設定にしている。

whitemap/database/migrations/create_coupons_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateCouponsTable extends Migration
{
    public function up()
    {
        Schema::create('coupons', function (Blueprint $table) {
            $table->string('id');
            $table->integer('type')->default(config('const.Coupons.TYPE_GET', 1))->comment('1:取得, 2:使用');
            $table->string('name');
            $table->boolean('is_display')->default(true);
            $table->timestamp('created_at')->useCurrent();
            $table->timestamp('updated_at')->default(DB::raw('CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP'));
        });
    }
    public function down()
    {
        Schema::dropIfExists('coupons');
    }
}

クーポン種別は定数で定義してみた。

whitemap/config/const.php
<?php

return [
    // Couponsで使う定数
    'Coupons' => [
        'TYPE_GET' => 1,
        'TYPE_USE' => 2,
    ],
];

ユーザとクーポンの紐づけテーブルでは外部キーを設定し、
ユーザテーブルやクーポンテーブルにないものは登録できないようにした。

whitemap/database/create_user_coupons_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUserCouponsTable extends Migration
{
    public function up()
    {
        Schema::create('user_coupons', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('coupon_id')->comment('クーポンID');
            $table->integer('subscribe_user_id')->unsigned()->comment('受取ユーザID');
            $table->integer('publish_user_id')->unsigned()->comment('発行ユーザID');
            $table->dateTime('expire')->nullable()->comment('利用期限');
            $table->timestamp('created_at')->useCurrent();
            $table->timestamp('updated_at')->default(DB::raw('CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP'));
            $table->softDeletes();

            $table->foreign('subscribe_user_id')->references('id')->on('users');
            $table->foreign('publish_user_id')->references('id')->on('users');
            $table->foreign('coupon_id')->references('id')->on('coupons');
        });
    }

    public function down()
    {
        Schema::dropIfExists('user_coupons');
    }
}

php artisan migareteでデータベース更新。

シーダの設定

php artisan make:seeder UsersTableSeeder 

システム用のユーザと初期ユーザを作成。

whitemap/database/seeds/UserSeeder.php
<?php
use Illuminate\Database\Seeder;

class UserSeeder extends Seeder
{
    public function run()
    {
        $user = [
            'id' => 1,
            'firebase_uid' => 'system',
            'name' => 'システム管理者',
            'twitter_screen_name' => '',
            'twitter_profile_image_url_https' => '',
        ];
        DB::table('users')->insert($user);
        $user = [
            'id' => 2,
            'firebase_uid' => env('FIRST_USER_FIREBASE_UID'),
            'name' => env('FIRST_USER_NAME'),
            'twitter_screen_name' => env('FIRST_USER_TWITTER_SCREEN_NAME'),
            'twitter_profile_image_url_https' => env('FIRST_USER_TWITTER_PROFILE_IMAGE_URL'),
        ];
        DB::table('users')->insert($user);
    }
}

管理者クーポンを追加。

whitemap/database/seeds/Couponseeder.php
<?php

use Illuminate\Database\Seeder;
use App\Enums\Coupon\CouponIds;

class CouponsSeeder extends Seeder
{
    public function run()
    {        
        $coupon = [
            'id' => CouponIds::ADMIN(),
            'name' => '管理者クーポン',
            'is_display' => false,
        ];
        DB::table('coupons')->insert($coupon);
    }
}

クーポンIDはEnumで登録してみた。

composer require myclabs/php-enum
whitemap/app/Enums/Coupon.php
<?php
namespace App\Enums\Coupon;
use MyCLabs\Enum\Enum;

class CouponIds extends Enum
{
    const ADMIN = 'admin';
}

システムユーザから初期ユーザに向けて管理者クーポンを発行

whitemap/database/seeds/UserCouponsSeeder.php
<?php
use Illuminate\Database\Seeder;
use App\Enums\Coupon\CouponIds;

class UserCouponsSeeder extends Seeder
{
    public function run()
    {
        $user_coupon = [
            'id' => 1,
            'coupon_id' => CouponIds::ADMIN(),
            'subscribe_user_id' => 2,
            'publish_user_id' => 1,
        ];
        DB::table('user_coupons')->insert($user_coupon);
    }
}

実行するSeederを指定

whitemap/database/seeds/DatabaseSeeder.php
<?php
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    public function run()
    {
        // $this->call(UsersTableSeeder::class);
        $this->call([
            UserSeeder::class,
            CouponsSeeder::class,
            UserCouponsSeeder::class,
        ]);
    }
}

シーダの実行

php artisan db:seed 

アプリケーション

モデルの設定

php artisan make:model Models/UserCoupon
whitemap/app/Models//UserCoupon.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;

class UserCoupon extends Model
{
    protected $table = 'user_coupons';
}

subscribe_user_idでユーザクーポンとユーザを紐づける。

whitemap/app/User.php
<?php
namespace App;
use Laravel\Passport\HasApiTokens; // 追加
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use App\Enums\Coupon\CouponIds;

class User extends Authenticatable
{
    use Notifiable, HasApiTokens; // HasApiTokens を追加
    public function __construct(array $attributes = []){
    }

    protected $fillable = [
        'name', 'twitter_screen_name','twitter_profile_image_url_https', 'firebase_uid'
    ];
    protected $hidden = [
        'password', 'remember_token',
    ];
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];


+    public function userCoupons(){
+        return $this->hasMany('App\Models\UserCoupon',  'subscribe_user_id');
+    }
}

ゲートの設定

whitemap/app/Gate/AdminAccess
<?php 
namespace App\Gate;
use App\User;
use App\Enums\Coupon\CouponIds;

final class AdminAccess
{
    public function __invoke(User $user): bool 
    {
        // 管理者用のクーポンを持っているかDBに問い合わせる。
        return $user->userCoupons()->where('coupon_id',CouponIds::ADMIN())->exists();
    }
}

上記で設定したゲートをadmin-accessの名前で登録する。

whitemap/app/Providers/AuthServiceProvider.php
<?php
namespace App\Providers;
use App\Auth\MySessionGuard; 
use App\Auth\MyEloquentUserProvider; 
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
use Auth; 
use App\Gate\UserAccess; 
use App\Gate\AdminAccess; 
use \Psr\Log\LoggerInterface;

class AuthServiceProvider extends ServiceProvider
{
    protected $policies = [
        // 'App\Model' => 'App\Policies\ModelPolicy',
    ];

    public function boot(LoggerInterface $logger)
    {
        $this->registerPolicies();

        Auth::provider('my_eloquent', function($app, array $config) {
            return new MyEloquentUserProvider($app['hash'], $config['model']);
        });


+        // 認可
+        Gate::define('admin-access', new AdminAccess);

        // 認可の前にロギング
        Gate::before(function ($user, $ability) use ($logger) {
            // Log::info("Hello my log,");
            $logger->info($ability, ['firebase_uid'=>$user->getAuthIdentifier()]);
        });
    }
}

ルーティング

whitemap/routes/web.php
Route::group(['middleware' => ['auth', 'can:admin-access']], function () {
    // この中は管理者権限の場合のみルーティングされる
    Route::get('/admin', function (Illuminate\Http\Request $request) {
        return view('admin/dashboard');
    });
});

管理者ページへのリンク

管理者権限があるときだけ、管理者ページへのリンクを表示。

whitemap/resources/views/home.blade.php
@extends('layouts.app') 
@section('title') マイページ @endsection
@section('head-scripts')
@if(Auth::check())
<script src="https://www.gstatic.com/firebasejs/7.2.0/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.2.0/firebase-auth.js"></script>
<script src="{{ mix('js/home/index.js') }}"></script>
@endif
@endsection

@section('content')
<main role="main" class="container">
<div class="starter-template">
    <h1>マイページ</h1>
    @if(Auth::check())
      <ul>
+        @can('admin-access')
+          <li><a href="/admin">管理者画面へ</a></li>
+        @endcan
        <li><a href="#" id="logout">ログアウト</a></li>
      </ul>
    @else 
      こんにちは!  ゲストさん <br />
      <a href="/login">ログイン</a>
    @endif
</div></main>
@endsection

参考

Laravel で定数をつかうよ
Laravel で Enum を使う
Laravel 6.0 Artisan コンソール
全 68 種類!Laravel 5.6 の artisan コマンドまとめ
【メモ】【Laravel】外部キー制約付き Migrate がさっぱり動かないときのチェック・ポイント(Mysql)
Laravel の DB migration で日付のデフォルトを指定
Laravel(Eloquent)の save メソッドを使ったら MySQL の timestamp 型で謎な挙動が発生した話
管理者クーポンによるタグ画面の制御
blade テンプレートでの切替
Laravel 6.0 基本のタスクリスト
laravel
readouble laravel
【Laravel】 認証や認可に関する補足資料
Laravel 6.0 認可
Laravel 6.0 ルーティング
Laravel 6.0 ミドルウェア
Laravel の Gate(ゲート)機能で権限(ロール)によるアクセス制限を実装する
Laravel 6.0 バリデーション

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む