20201020のlaravelに関する記事は20件です。

Laravelのmigrate:freshでDBのviewも削除する

Laravelのコマンドはコードを読んでみると実はいろんなオプションがついていたりします。

db:wipeコマンド

db:wipeコマンドでdbのtableをdropできるのですが、実はdb:wipe --drop-viewsとするとviewも削除することができます。

migrate:freshコマンド

またmigrateを最初からやり直すmigrate:freshコマンドがありますが、viewを作っている場合はviewの作成も最初からやり直さないとviewだけ残ってしまいます。

これを解決するにはmigrate:fresh --drop-viewsとすることでviewも削除することができます。

コードを読んでみると

FreshCommand.php
<?php

namespace Illuminate\Database\Console\Migrations;

use Illuminate\Console\Command;
use Illuminate\Console\ConfirmableTrait;
use Symfony\Component\Console\Input\InputOption;

class FreshCommand extends Command
{
    use ConfirmableTrait;

    /**
     * The console command name.
     *
     * @var string
     */
    protected $name = 'migrate:fresh';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Drop all tables and re-run all migrations';

    /**
     * Execute the console command.
     *
     * @return void
     */
    public function handle()
    {
        if (! $this->confirmToProceed()) {
            return;
        }

        $database = $this->input->getOption('database');

        $this->call('db:wipe', array_filter([
            '--database' => $database,
            '--drop-views' => $this->option('drop-views'),
            '--drop-types' => $this->option('drop-types'),
            '--force' => true,
        ]));

        $this->call('migrate', array_filter([
            '--database' => $database,
            '--path' => $this->input->getOption('path'),
            '--realpath' => $this->input->getOption('realpath'),
            '--force' => true,
            '--step' => $this->option('step'),
        ]));

        if ($this->needsSeeding()) {
            $this->runSeeder($database);
        }
    }

    ...  ...
}

となっていて、--drop-viewがdb:wipeコマンドに渡されているのがわかりますね。

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

Laravel/PHP タグ付き投稿の検索機能(orWhereHasと無名関数、クロージャ)

やりたいこと

ジャズライブの口コミをタグ付きで投稿できるサイトを、チームで開発しています。
口コミ投稿をキーワード検索するコードをネットを参考にしながら書いたのですが、一部読み解けなかった部分を整理したいと思います。

仕様

  • キーワードが入力されると、そのキーワードを含む投稿を検索しビューに表示する。
  • 検索対象は、投稿の(1)タイトル、(2)本文、(3)投稿につけられたタグ。
  • 投稿のテーブル、タグのテーブルはEloquentでModelを利用している。
  • 投稿のテーブルとタグのテーブルが中間テーブルを挟んで多対多のリレーションでつながっている。

実装してみた

コントローラの実装例

ルート、ビューなどは省略させていただきます。もし見せて!という方いたらコメントください(いないと思いますが汗)。

SearchController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Review;
use App\Tag;

class SearchController extends Controller
{
    public function search(Request $request)
    {
        $keyword = $request->input('keyword');
        if (!empty($keyword))
        {
            $reviews = Review::where('title', 'like', '%' . $keyword . '%')
            ->orWhere('text', 'like', '%' . $keyword . '%')
            ->orWhereHas('tags', function ($query) use ($keyword){
                $query->where('tag_name', 'like', '%' . $keyword . '%');
            })
            ->paginate(10);
        } else {
            return redirect('/');
        }

        return view('reviews.search', [
            'reviews' => $reviews
        ]);
    }
}

orWhereHasからの2行がよくわからない!!ココを解明したい!!

orWhereHasとは?

            ->orWhereHas('tags', function ($query) use ($keyword){
                $query->where('tag_name', 'like', '%' . $keyword . '%');

orWhereHasはEloquentのメソッド、関数。わかってたふりしてましたが、案外うまく説明できなかったので整理。公式ドキュメント→Eloquent:存在するリレーションのクエリ
orは、2つ以上の条件をつけて「AもしくはB」としたい時に、2つめ以降の条件のメソッドにつける言葉。
Whereは、SQLのわかる方なら想像しやすいかもですが、問い合わせに条件をつけるもの。例えばidがホニャララなもの、とかcreated_atがいついつ以前のものといった条件が想定できるかと思います。
Hasはあるモデルからリレーション先のテーブルに対してレコードを探してくれるメソッド。リレーション名を第一パラメータに渡します。例えば、以下のようにするとタグの付いたレビューを取得するという命令になります。

$reviews = Review::has('tags')->get();

まとめるとorWhereHasは、リレーション先にレコードを(Has)、条件付きで(Where)、並列の条件(or)として探しにいく時に便利なEloquentのメソッドです。

orWhereHasの第二パラメータの関数は何?

            ->orWhereHas('tags', function ($query) use ($keyword){
                $query->where('tag_name', 'like', '%' . $keyword . '%');

このfunctionから始まる2行は、無名関数、またの名をクロージャというやつみたいです。公式→PHP 無名関数。名前ないですものね。無名関数はどういう時に使うかというと、プログラムの中でその場で一度しか使わないものに適切みたいです。ちなみに、Closureというクラスで実装されているそうです。紐解いていきます。

順番前後しますが、use ($keyword)は、その関数の親のスコープから変数$keyordを引き継ぐ時にuseで渡すようです。
($query)は変数ではなく引数。通常の引数を受け付けています。この無名関数の中では、「tag_nameに$keywordを含んだレコードを」という条件設定をしているwhereメソッドにアクセスしています。

総合して一言でこの第2パラメータを説明すると、親スコープの$keywordを引き継いで作成する、whereという条件設定をする関数という感じでしょうか。

最後に

もしご指摘などあれば、バシバシお願い申し上げます!

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

yps並走備忘録 Task7 Web-APIとAjax通信を利用したアプリケーションの作成(1ページアプリ)

というわけで2020/10/20日現在数名の方が沼にハマっていると思われるTask 7ですが、足跡を残すことで少しでも助けになればと思い、本記事にやったことなどをまとめていきます。

下準備:MySQLのデータを引っ張ってくる

まずはTask 3 : SQL:テーブル作成(復習)の手順と下記のQiita記事を参考にMySQL公式サイトからサンプルデータを引っ張ってきます。

参考:【SQL】MySQL公式サンプルデータベースを使う

+-----------------+
| Tables_in_world |
+-----------------+
| city            |
| country         |
| countrylanguage |
+-----------------+

アプリ構築&jsライブラリ導入

上記テーブルが作成されたらTask6同様、アプリケーションを構築してDBと連携させましょう。

アプリケーションが構築できたらいよいよ今回の目玉のjsライブラリを下記記事を参考に導入しましょう

参考: 今すぐ導入可能!JavaScriptで3Dの地球上にデータを可視化できる「Gio.js」を使ってみた!

本タスクに関してはおススメはCDNを使うか、jsファイルをwget等でDLしてpublicに配置した上で読み込むこと。
(自分はnpm使って読み込んだのですが、この方法だとjsを記述して挙動を変更した際などに反映を確認するのにいちいちLaravel Mixを使わないといけなくなってしまい、結構面倒でした)

jsファイルをBlade内のヘッダーで読み込んだら一先ず地球儀を表示してみましょう

<div id="globalArea" style="width:800px;height:420px"></div>

全画面にする場合はスタイルの指定をwidth:100vw; height:100vh;にすればおk

次に、JavaScript側からこの「divタグ」を取得して3Dの地球を生成します。
こちらもbladeに直書きが一番手っ取り早い

const container = document.getElementById( "globalArea" );
const controller = new GIO.Controller( container );

controller.init();

これで一先ず地球儀が表示されるはず。簡単ですね。

レーザービーム発射!

次はこの地球儀にデータを突っ込んでレーザービームを出してあげましょう。
three.jsとgio.jsを読み込んだ次の行で次のスクリプトを読み込んであげます。

<script src="https://raw.githack.com/syt123450/giojs/master/assets/data/sampleData.js"></script>

そのあとにcontroller.init()関数の前後に下記を記述するだけです。

controller.addData(data);

こんな感じの表示になりました。
image.png

ここまでは比較的簡単ですね。

公式ドキュメントは英語ですがザックリ説明すると輸出国(e)と輸入国(i)の2桁のISO国コードと値(v)をまとめたjsonデータをcontroller.addData()関数で渡してやるとビームが出るそうです。

なので最大問題はどうやってそんな『いい感じのデータ』を探すかなんですが、いい感じのデータを提供してくれるAPIを探すというのがこのタスク最大の沼なので、一先ずそっちは置いておいて、まずはMySQLのデータをAjax通信で取得するのを先にやっちゃうのがおススメです。

Ajax通信によるデータの取得

Ajaxはザックリ言うと『画面変遷を行わずにデータを取得し処理を実行するための技術』です。
これに関しては補足説明が出ていますので、下記の一連のツイートと参考サイトを見ながらやってみましょう。
(なお、本タスクではjQueryを使用してAjax通信を行います)

参考:
yps task7:補足説明
yps task7:補講
Ajaxの説明とJQueryによる簡易実装

とりあえずオレオレデータを使ってMySQL⇒Laravelのエンドポイント⇒Ajaxで取得までできればタスクの半分はクリアです。

最大の敵はWEB-API!

本タスクで最も大きなハマりポイントはここですね。

  1. 日本語のドキュメントのあるAPIで世界規模のデータを扱っているものが少ない
  2. 英語のドキュメントはそもそも難易度がVERY HARD

というところで、非常に難航している方が多そうです。

日本語のドキュメントがあるAPIとしてはypsの委員長ことmiyupakaさんが使っているRESAS-APIe-statの中から日本対海外の貿易関係のデータを利用するのが比較的難易度が低いかと思います。

いずれにしても今回の課題の最大の難関、WEB-APIに関しては

  1. APIのドキュメントを読んだ上で
  2. クエリパラメーターを把握し
  3. 返ってくるデータ構造を把握し
  4. 返ってきたデータをいい感じに成形(ごにょごにょ)して
  5. 最終的にフロントのcontroller.addData()関数にいい感じのjsonに変換して渡す

という一連の操作を行うプログラムを組めるかどうかのテストだという認識です。

具体的にどんな風に進めたのかはまた別記事にまとめたいと思います。
(一先ずここまで…)

以上

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

yps並走備忘録 Task7 Web-APIとAjax通信を利用したアプリケーションの作成(1ページアプリ)①

というわけで2020/10/20日現在数名の方が沼にハマっていると思われるTask 7ですが、足跡を残すことで少しでも助けになればと思い、本記事にやったことなどをまとめていきます。

下準備:MySQLのデータを引っ張ってくる

まずはTask 3 : SQL:テーブル作成(復習)の手順と下記のQiita記事を参考にMySQL公式サイトからサンプルデータを引っ張ってきます。

参考:【SQL】MySQL公式サンプルデータベースを使う

+-----------------+
| Tables_in_world |
+-----------------+
| city            |
| country         |
| countrylanguage |
+-----------------+

アプリ構築&jsライブラリ導入

上記テーブルが作成されたらTask6同様、アプリケーションを構築してDBと連携させましょう。

アプリケーションが構築できたらいよいよ今回の目玉のjsライブラリを下記記事を参考に導入しましょう

参考: 今すぐ導入可能!JavaScriptで3Dの地球上にデータを可視化できる「Gio.js」を使ってみた!

本タスクに関してはおススメはCDNを使うか、jsファイルをwget等でDLしてpublicに配置した上で読み込むこと。
(自分はnpm使って読み込んだのですが、この方法だとjsを記述して挙動を変更した際などに反映を確認するのにいちいちLaravel Mixを使わないといけなくなってしまい、結構面倒でした)

jsファイルをBlade内のヘッダーで読み込んだら一先ず地球儀を表示してみましょう

<div id="globalArea" style="width:800px;height:420px"></div>

全画面にする場合はスタイルの指定をwidth:100vw; height:100vh;にすればおk

次に、JavaScript側からこの「divタグ」を取得して3Dの地球を生成します。
こちらもbladeに直書きが一番手っ取り早い

const container = document.getElementById( "globalArea" );
const controller = new GIO.Controller( container );

controller.init();

これで一先ず地球儀が表示されるはず。簡単ですね。

レーザービーム発射!

次はこの地球儀にデータを突っ込んでレーザービームを出してあげましょう。
three.jsとgio.jsを読み込んだ次の行で次のスクリプトを読み込んであげます。

<script src="https://raw.githack.com/syt123450/giojs/master/assets/data/sampleData.js"></script>

そのあとにcontroller.init()関数の前後に下記を記述するだけです。

controller.addData(data);

こんな感じの表示になりました。
image.png

ここまでは比較的簡単ですね。

公式ドキュメントは英語ですがザックリ説明すると輸出国(e)と輸入国(i)の2桁のISO国コードと値(v)をまとめたjsonデータをcontroller.addData()関数で渡してやるとビームが出るそうです。

なので最大問題はどうやってそんな『いい感じのデータ』を探すかなんですが、いい感じのデータを提供してくれるAPIを探すというのがこのタスク最大の沼なので、一先ずそっちは置いておいて、まずはMySQLのデータをAjax通信で取得するのを先にやっちゃうのがおススメです。

Ajax通信によるデータの取得

Ajaxはザックリ言うと『画面変遷を行わずにデータを取得し処理を実行するための技術』です。
これに関しては補足説明が出ていますので、下記の一連のツイートと参考サイトを見ながらやってみましょう。
(なお、本タスクではjQueryを使用してAjax通信を行います)

参考:
yps task7:補足説明
yps task7:補講
Ajaxの説明とJQueryによる簡易実装

とりあえずオレオレデータを使ってMySQL⇒Laravelのエンドポイント⇒Ajaxで取得までできればタスクの半分はクリアです。

最大の敵はWEB-API!

本タスクで最も大きなハマりポイントはここですね。

  1. 日本語のドキュメントのあるAPIで世界規模のデータを扱っているものが少ない
  2. 英語のドキュメントはそもそも難易度がVERY HARD

というところで、非常に難航している方が多そうです。

日本語のドキュメントがあるAPIとしてはypsの委員長ことmiyupakaさんが使っているRESAS-APIe-statの中から日本対海外の貿易関係のデータを利用するのが比較的難易度が低いかと思います。

いずれにしても今回の課題の最大の難関、WEB-APIに関しては

  1. APIのドキュメントを読んだ上で
  2. クエリパラメーターを把握し
  3. 返ってくるデータ構造を把握し
  4. 返ってきたデータをいい感じに成形(ごにょごにょ)して
  5. 最終的にフロントのcontroller.addData()関数にいい感じのjsonに変換して渡す

という一連の操作を行うプログラムを組めるかどうかのテストだという認識です。

具体的にどんな風に進めたのかはまた別記事にまとめたいと思います。
(一先ずここまで…)

以上

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

Laravel+vue.jsでサービス開発する際の一連の流れ(1)〜環境構築編〜

LaravelとVue.jsを使ってサービスを開発しました。その過程でやったことと、参考にした記事などを書いていきます。備忘録兼、私と同じような初心者の道標になるような記事になれば良いなと思います。

環境
・Laravel(5.8.38)
・Vue.js
・MAMP

プロジェクト作成

開発に必要なファイル一式が揃ったフォルダを作成します。プロジェクト作成には2種類の方法があります。

  • laravel new
  • composerコマンド

laravel newを使うと最新バージョンのLaravelがインストールされるようです。僕は5.8系を使いたかったので、composerコマンドを使いました。

ターミナル
% composer create-project "laravel/laravel=(Laravelのバージョン)" (プロジェクト名)

僕の場合、実際のコマンドは下のようになりました。
MAMPを使用しているので、cd /Applications/MAMP/htdocsでhtdocsディレクトリに移動し、実行します。プロジェクト名は、laravel_exampleとします。

ターミナル
% composer create-project "laravel/laravel=5.8.*" laravel_example

すると、パッケージがズラーッとインストールされ、laravel_exampleディレクトリが作成されました。
laravel_exampleディレクトリに移り、Laravelのバージョンを確認します。

ターミナル
% php artisan -v
Laravel Framework 5.8.38

ちゃんと5.8系のLaravelがインストールされました。

参考記事:【Laravel入門】プロジェクト作成から起動まで

ディレクトリ構造

先ほどインストールしたディレクトリの配下に、appディレクトリとかconfigディレクトリとか、いろいろ入ってます。
最初はあまり気にしなくていいと思うし、覚える必要もないと思います。開発をしていくと勝手にこの辺には何が入ってる、というのはわかってきます。一応、公式の解説ページを載せときます。
Laravel 5.8 ディレクトリ構造
ちなみに、この公式のドキュメント、開発するにあたってめちゃくちゃお世話になります。簡潔にまとまっている分初心者にはちょっと難しく感じられ、読むのを避けがちです。
しかし、ググっても解決できず、気が進まないながらも公式ドキュメントを読んだら簡単に解決できた、ということが少なからずありました。

データベース(mysql)の設定

データベースの作成

まず、今回の開発で使うデータベースを作成しましょう。
プロジェクトのrootディレクトリ直下にて(ここでなくても良いかもしれないが)

ターミナル
% mysql -u root -p
Enter password: 

すると、パスワードの入力を求められます。僕の場合、特に何も設定していなかってのでパスワードはrootです(ユーザー名もroot)。入力するとmysqlが起動し、ターミナルの表示が%からmysql>に変わります。
既に存在するデータベース一覧はSHOW DATABASES;で表示できます。最後の「 ; 」を忘れないようにしましょう。

ターミナル
mysql> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| performance_schema |
| mysql              |
| php_sample01       |
| sample             |
| sample2            |
| sys                |
| wordpress          |
+--------------------+
8 rows in set (0.03 sec)

データベースを作成するコマンドは

ターミナル
mysql> CREATE DATABASE (データベース名);

僕の場合、下のようになりました。データベース名もlaravel_exampleにしました。なんでも良いと思います。

ターミナル
mysql> CREATE DATABASE laravel_example;
Query OK, 1 row affected (0.00 sec)

もう一度データベースの一覧を見てみると、laravel_exampleが作成されていることが確認できます。

ターミナル
mysql> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| laravel_example    |
| mysql              |
| performance_schema |
| php_sample01       |
| sample             |
| sample2            |
| sys                |
| wordpress          |
+--------------------+
9 rows in set (0.00 sec)

参考記事:MySQLをMacのターミナルで操作するときのメモ

Laravelのデータベース設定

作成したデータベースをLaravelで使えるように設定します。先ほど作ったディレクトリ(僕ならlaravel_exampleディレクトリ)の中にあるファイルを修正します。

  • .env (プロジェクトのルートディレクトリ直下にあります。)
  • database.php (configディレクトリにあります。)

.env

.envファイルとは?

.envファイルを使って開発環境と本番環境を切り替えたり、データベースなどの接続情報の変更を行うことができます。つまりLaravelにとって重要な設定変更このは.envファイルを介することで簡単に行うことができます。例えば開発環境では簡易に利用できるsqliteのデータベースに接続し、本番環境ではmysqlのDBに接続といった変更が可能です。
参考記事:入門者のためのLaravel .envファイルの基礎と理解

とりあえずは、この.envファイルに情報を入力していけば、開発環境の設定ができます。
.envの中でデータベースに関わる部分は以下の通りです。

.env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel  (データベース名)
DB_USERNAME=root     (ユーザー名)
DB_PASSWORD=         (パスワード)

これを修正していきましょう。
DB_CONNECTION、DB_HOST等の項目の意味は下の記事を参考に。
参考記事:保存版!Laravelの.envでできること大全

僕の場合、mysqlの設定は変更していないので、ユーザー名もパスワードもrootのままでした。一応、MAMPを使っている場合はサーバーをオンにした後に自動で表示されるページにも載っています。(下の画像の赤枠で囲んだところです)
データベース名はさっきCREATE DATABASEで作成したものを記入します。

スクリーンショット 2020-10-19 12.57.42.png

.env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_example
DB_USERNAME=root
DB_PASSWORD=root

画像をよく見ると、Socket /Applications/MAMP/tmp/mysql/mysql.sockとも書いてありますね。これも.envに追加しておきましょう。
ちなみに、下記のsqlコマンドを打っても、socketを取得できます。

ターミナル
mysql> SHOW VARIABLES LIKE '%sock%';
+-----------------------------------------+-----------------------------------------+
| Variable_name                           | Value                                   |
+-----------------------------------------+-----------------------------------------+
| performance_schema_max_socket_classes   | 10                                      |
| performance_schema_max_socket_instances | -1                                      |
| socket                                  | /Applications/MAMP/tmp/mysql/mysql.sock |
+-----------------------------------------+-----------------------------------------+
3 rows in set (0.00 sec)
.env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_example
DB_USERNAME=root
DB_PASSWORD=root
DB_SOCKET=/Applications/MAMP/tmp/mysql/mysql.sock

これで.envファイルの設定、つまり、開発環境の設定がほぼ完了しました。

database.php

開発環境の設定が「ほぼ」完了した、と言いましたが、それは.envに設定した内容をLaravelに反映させる必要があるからです。
configディレクトリ内にあるdatabase.phpファイルで、.envに書いたデータベース情報を読み込んでいます。

※ちなみに、database.php.envのデータベース情報を読み込んでいるのと同様に、mail.phpではメールの情報を、cache.phpではキャッシュの情報を.envから読み込むといった感じになっています。

今回使うのはmysqlなので、database.php内のmysqlの部分を修正していきましょう。

database.php
'connections' => [

        'mysql' => [
            'driver' => 'mysql',
            'url' => env('DATABASE_URL'),
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '3306'),
            'database' => env('DB_DATABASE', 'forge'),
            'username' => env('DB_USERNAME', 'forge'),
            'password' => env('DB_PASSWORD', ''),
            'unix_socket' => env('DB_SOCKET', ''),
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix' => '',
            'prefix_indexes' => true,
            'strict' => true,
            'engine' => null,
            'options' => extension_loaded('pdo_mysql') ? array_filter([
                PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
            ]) : [],
        ],

env()はヘルパ関数(Laravelが用意してくれている便利メソッド)で、第1引数で.envの値を取得、第2引数は.env内に値がなかった場合のデフォルトの値を設定します。

個人的には、第2引数は全て''で良いのでは?と思います。
なぜなら、サービスを作り終えてサーバーにデプロイする際(開発環境から本番環境に切り替える際)、デフォルト値を下手に設定していたせいでエラー地獄に陥ったからです。

具体的には、本番環境ではunix_socketを設定する必要がなかったのですが、

database.php
'unix_socket' => env('DB_SOCKET', '/Applications/MAMP/tmp/mysql/mysql.sock'),

と書いていたためにデフォルト値の/Applications/MAMP/tmp/mysql/mysql.sockが設定されてしまいエラーになるなど。
デプロイ時にこうなることを避けるために、僕はデフォルト値(第2引数)は全部空にしておこうと思います。
(複数人で開発する際には、環境を合わせるためにデフォルト値を設定しておいた方が良いのかも?)

database.php
'connections' => [

        'mysql' => [
            'driver' => 'mysql',
            'url' => env('DATABASE_URL'),
            'host' => env('DB_HOST', ''),
            'port' => env('DB_PORT', ''),
            'database' => env('DB_DATABASE', ''),
            'username' => env('DB_USERNAME', ''),
            'password' => env('DB_PASSWORD', ''),
            'unix_socket' => env('DB_SOCKET', ''),
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix' => '',
            'prefix_indexes' => true,
            'strict' => true,
            'engine' => null,
            'options' => extension_loaded('pdo_mysql') ? array_filter([
                PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
            ]) : [],
        ],

これで開発環境の設定は完了です。

動作確認

実際に正しくテーブルが作成できるか試してみましょう。
プロジェクトのルートディレクトリに移動し、

ターミナル
% php artisan migrate

Laravelはusersテーブルとpassword_resetsテーブルを作成するためのファイルを予め準備してくれており、このコマンドを打つことでテーブルが作成されます。

ターミナル
laravel_example % php artisan migrate
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (0.03 seconds)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (0.03 seconds)

mysqlを確認しましょう。
use (データベース名); で使用するデータベースを選択し、show tables;でそのデータベースに登録されているテーブルを表示できます。

ターミナル
mysql>use laravel_example;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed

mysql> show tables;
+---------------------------+
| Tables_in_laravel_example |
+---------------------------+
| migrations                |
| password_resets           |
| users                     |
+---------------------------+
3 rows in set (0.00 sec)

usersテーブルとpassword_resetsテーブルがしっかり作成されています!

ちなみに、Laravelにはテーブル作成の巻き戻しをするコマンドもあります。

ターミナル
php artisan migrate:rollback

php artisan migratephp artisan migrate:rollbackはセットで覚えときましょう。後々たくさん使います。

ターミナル
laravel_example % php artisan migrate:rollback
Rolling back: 2014_10_12_100000_create_password_resets_table
Rolled back:  2014_10_12_100000_create_password_resets_table (0.02 seconds)
Rolling back: 2014_10_12_000000_create_users_table
Rolled back:  2014_10_12_000000_create_users_table (0 seconds)

コマンド入力後、テーブルを確認してみると・・・

ターミナル
mysql> show tables;
+---------------------------+
| Tables_in_laravel_example |
+---------------------------+
| migrations                |
+---------------------------+
1 row in set (0.00 sec)

usersテーブルとpassword_resetsテーブルが削除されています。
ちゃんとデータベースを操作できているようですね。

デバッグ

最後にデバッグの設定です。LarabelではデバッグのON,OFFも簡単に設定できます。
デバッグをONにするには.envファイルにて

.env
APP_DEBUG=true

とするだけです。これはconfigディレクトリのapp.phpで読み込まれています。

app.php
/*
    |--------------------------------------------------------------------------
    | Application Debug Mode
    |--------------------------------------------------------------------------
    |
    | When your application is in debug mode, detailed error messages with
    | stack traces will be shown on every error that occurs within your
    | application. If disabled, a simple generic error page is shown.
    |
    */

    'debug' => env('APP_DEBUG', false),

ここに書かれている通りで、APP_DEBUGtrueなら、作成中のアプリケーションでエラーが起きると、エラーが起きるまでの過程とエラーメッセージを画面に表示してくれます。
falseなら、シンプルなエラーページが表示されるだけです。
サービスを公開する時には、falseにするのを忘れないようにしましょう。

スタックトレース (stack trace)とは

APP_DEBUG=trueの場合
スクリーンショット 2020-10-20 20.52.14.png

APP_DEBUG=falseの場合
スクリーンショット 2020-10-20 20.55.27.png

以上で環境構築編は終了です。あとはジャンジャン開発していきましょう!
続く

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

laravel8系でVue.jsを連携するまで 備忘録

前提知識

Laravel6.0以降は自前でVue.jsをインストールし、resources/js/app.jsを編集する必要がある。(前は標準搭載

インストール→コンパイル→VueComponentの作成→app.jsで接続設定→bladeのテコ入れ→連携の確認

インストール

npm:パッケージマネージャー

package.jsonで使用しているパッケージの名前とバージョンに関する情報が書かれていて、それに基づきパッケージがinstallされる。
あと古いモノをアップデートしてくれるらしい。

ルートディレクトリで

npm install

以上でインストール完了。
ただし同じ環境を作る際は以下の視点が必要
https://blog.minimalcorp.com/users/jigen/posts/6f325dc9b8a00370b6aedf47a34cb3ce

vuejsは別にコンポーネントのコンパイルが必要

npm run dev
npm run hot

hotの場合、Hot Module Replacement(HMR)有効な状態でビルドされ、リアルタイムで編集が反映される

Vueコンポーネントを作成

resources/jsディレクトリ配下にcomponentsフォルダ作成しそこにVueコンポーネントを作成
以下例

resources/js/components/Sample.vue
<template>
    <div>
        <h1>Sample Component</h1>
    </div>
</template>

<script>
    export default {
        mounted() {
            console.log('Component mounted.')
        }
    }
</script>

app.jsの編集

これまで 参考:https://www.larajapan.com/2019/10/14/app-jsとbootstrap-js/
https://qiita.com/shonansurvivors/items/1715a483ac4298162ccd

Vue.jsとVueコンポーネント(Sample.vue)を使用するよう定義。

resources/js/app.js
import './bootstrap'
import Vue from 'vue'
import Sample from './components/Sample'

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

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

memo:ネットで参考にしたrequireとwindowの方法では繋がらなかった

blade

例を見た方が早いのでwelcome.blade.phpを編集

welcome.blade.php
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="csrf-token" content="{{ csrf_token() }}">

        <title>Laravel</title>

    </head>
    <body>

        <div id="app">
            <sample-component></sample-component>
        </div>

    <script src="{{ mix('/js/app.js') }}"></script>
    </body>
</html>

ポイント

・<meta name="csrf-token" content="{{ csrf_token() }}">
・<script src="{{ asset('js/app.js')}}">
・<div id="app"><sample-component></example-component></div>

それぞれ、
・head内metaタグにcsrf対策のコードの埋め込み
・Vueで描画する領域としてdivタグを設置
・laravel mixがコンパイルする先のファイルの指定を行っている

参考:https://www.ritolab.com/entry/171

トップページにSample.Vueの内容が表示されているか確認。

http://localhost:8000/

これでLaravelとVueが連携できたことが確認できる

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

【Laravel】public/css内のCSSを読み込まないエラー 〜 net::ERR_CONNECTION_CLOSED〜

【背景】

AWSからVagrantにローカル環境を変えて、開発を行う。
Viewを作成時に、コンパイルしたcssを読み込もうしても、Viewに反映されず行き詰まる。

【状況の確認】

Bladeファイルのheadタグ内にCSSを読み込む記述がされているか確認。

 <link href="{{ secure_asset('css/app.css') }}" rel="stylesheet">
 <link href="{{ secure_asset('css/style.css') }}" rel="stylesheet">

次にchromeの検証ツールでエラー内容を確認。エラーは検証ツール画面の右上の設定マークの横にある赤いバツ❌をクリックします。
スクリーンショット 2020-09-07 17.47.18.png
net::ERR_CONNECTION_CLOSED と表示されています。
どうやら接続ができない様子。Javascriptの方もできていない。

【解決法】

Blade内のsecure_の部分を消す。

 <link href="{{ asset('css/app.css') }}" rel="stylesheet">
 <link href="{{ asset('css/style.css') }}" rel="stylesheet">

同ファイル内のJavascriptの部分も直します。

 <script src="{{ asset('js/app.js') }}" defer></script>

これでブラウザで確認すればCSSが反映されていると思います。

【原因】

Web環境のhttpsを使った環境だとsecure_を付ける必要がある。
今回、開発環境をAWSからローカル環境のVagrantに変えたので、記述を変える必要があったようです。

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

LaravelマイグレーションでDB構築~Seederで初期データ投入するまで

マイグレーションとは

マイグレーションとは、PHP MyAdminから行うような、データベースにテーブルを追加作成したり、カラムを追加、変更、削除したり…といった作業を、コマンド一発で簡単にできる機能です。
もちろん、環境が変わった場合でも使えるので、マイグレーションを使ったことがない人には是非、覚えて欲しい機能です。

マイグレーションの手順

1. マイグレーションファイルを作成する

コマンドを使って簡単にマイグレーションファイルを作成することが出来ます。

2. 作成されたマイグレーションファイルに、処理を記述する

テーブル作成や、カラム追加、削除などの処理をPHPで記述します。
記述方法については、以下でご紹介します。

3. マイグレーションを実行する

コマンドを使って、マイグレーションを実行します。
マイグレーション実行後、データベースに変更や追加が反映されているか確認します。

Doctrine DBALライブラリーの導入

Laravelは、defaultではカラム変更のmygrationは実行出来ないため、Doctrine DBALライブラリーを追加する必要があります。

導入手順

Laravelルートディレクトリへ移動して、以下のコマンドを実行します。

composer require doctrine/dbal

※Version差異によるエラーが出ることあり

導入の確認方法

laravel/composer.jsonを開いてdoctrine/dbalが追加されていればOK

    "require": {
        "php": "^7.1.3",
        "doctrine/dbal": "^2.9",//※追加
        "fideloper/proxy": "^4.0",
        "laravel/framework": "5.8.*",
        "laravel/tinker": "^1.0",
        "nesbot/carbon": "^2.16.0",

※以下のカラムタイプのみ変更可能
bigInteger、binary、boolean、date、dateTime、dateTimeTz、decimal、integer、json、longText、mediumText、smallInteger、string、text、time、unsignedBigInteger、unsignedInteger and unsignedSmallInteger

1:マイグレーションファイル作成

Laravelでデータベースを扱うために、テーブル作成とモデルファイル作成の方法をご紹介します。

Laravelでテーブルを作成するコマンド

例:XXXテーブルを作成

php artisan make:migration [マイグレーションファイル名] --create=[テーブル名]
php artisan make:migration create_XXX_table --create=XXX

※マイグレーションファイル名はそのままクラス名になるので、分かりやすい名前をつけること
※生成出力先のパスを指定したい場合は、make:migrateコマンドの実行時に--pathオプションを付ける。(Laravelルートディレクトリからの相対パスで指定)

例:Reservationsテーブルを作成する場合
php artisan make:migration create_reservations_table --create=reservations

上記のコマンド実行すると、以下のようなファイルが生成されます。
マイグレーション用ファイル:コマンド実行日時+create_reservations_table.php
作成される場所:プロジェクトフォルダ\database\migrations

※この段階ではまだテーブルは作成されていません。

生成されたファイル

create_reservations_table.phpを開いてみます。
「CreateReservationsTable」というクラス名でファイルが生成されているのが確認できます。

upメソッド

upメソッド内にテーブル追加・変更の内容を記述していきます。
ここに記述された内容が、マイグレート(migrate)コマンドを叩いた時に、実行される処理です。

public function up()
    {
        Schema::create('flights', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name');
            $table->string('airline');
            $table->timestamps();
        });
    }
downメソッド

ここにはロールバック(rollback)した時に、実行される処理を記述します。
例えば、ロールバック時、テーブルを削除したい場合は以下のように記述します。

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

マイグレーション実行

とりあえずテーブルを作成してみたいので、マイグレーションを実行してみます。
以下のコマンドを叩きます。

php artisan migrate

以下のように「Migrated」と表示されればOK。マイグレーション実行は成功しています。

Migrating: 2020_10_19_154405_create_reservations_table
Migrated:  2020_10_19_154405_create_reservations_table (37.32ms)
実行されたSQL文を確認するコマンド

実行されたSQL文を確認したい場合は、以下のコマンドを叩きます。

php artisan migrate --pretend

全テーブルを削除してマイグレーションをやり直したい場合

php artisan migrate:reset

resetコマンドはdown メソッドを呼び出し、アプリケーション全部のマイグレーションをロールバックします。
実行後、DBのmigrationsテーブルが空になっているのが確認できます。

※resetコマンドはあくまでもロールバックのみなので、テーブル削除+マイグレーション再実行を一発で済ませたい場合は、refreshコマンドを使うと便利です。

カラムの追加、名前変更、属性変更、削除

ここまでの手順後、カラムの追加、名前変更、属性変更、削除が必要な場合は、それ用のファイルを別で作成し、再度マイグレーションを実行します。

カラム名を変更

まず、カラム変更用のマイグレーションファイルを作成するため、以下のようにコマンドを叩きます。

php artisan make:migration rename_変更前のカラム名_to_変更後のカラム名_on_テーブル名_table --table=テーブル名

作成されたファイルを開いて編集します。

カラム名を変更する:renameColumnメソッド

Schema::table('users', function (Blueprint $table) {
    $table->renameColumn('from', 'to');
});

※カラムタイプがenumのカラム名変更は、Version5.5時点ではサポートされていない

カラム属性の変更

まず、カラム属性変更用のマイグレーションファイルを作成するため、以下のようにコマンドを叩きます。

php artisan make:migration changecolumn_属性変更するカラム名_テーブル名_table --table=テーブル名

作成されたファイルを開いて編集します。

カラム属性を変更する:changeメソッド

例:nameカラムのサイズを25から50へ増やす

Schema::table('users', function (Blueprint $table) {
    $table->string('name', 50)->change();
});

カラムの追加

既存テーブルにカラムを追加する

生成されたファイルを編集

    public function up()
    {
        Schema::table('abc;', function (Blueprint $table) {
            $table->integer('test')->default(0)->index('index_test')->after('aaa')->comment('新カラム');
        });
    }

カラム追加時の修飾子

  • afterメソッド:どのカラムの後に追加するかを指定
  • nullableメソッド:null値許容
  • nullableメソッドに引数false指定:not null
  • 型指定:tinyInteger('test')
  • default null挿入:nullable($value = true)
  • TIMESTAMPカラムのデフォルト値をCURRENT_TIMESTAMPに指定:useCurrent()

※第二引数はauto_increment、第三引数はprimary keyの指定なので、String型のように第二引数は長さの指定ではないことに注意

カラムの削除

既存テーブルを削除:dropColumnメソッド

引数に配列を渡して複数削除可

Schema::table('users', function (Blueprint $table) {
    $table->dropColumn(['votes', 'avatar', 'location']);
});

DBに初期データを投入(Insert)

マイグレーションでDBを構築したら、シーディングで初期データを投入してみます。

Seedファイルの生成

投入データ用Seedファイルを生成するため、以下のコマンドを実行します。
※Reservationsテーブルへのシーダーを生成するのであれば「ReservationsTableSeeder」といったクラス名にする

php artisan make:seeder ReservationsTableSeeder

以下が表示されればOK。作成

Seeder created successfully.

データ投入の記述

プロジェクトフォルダ\database\seeders\配下にReservationsTableSeeder.phpファイルが生成されているので、開いてデータを投入する記述を追加します。

ReservationsTableSeeder.php
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;//←追加

class ReservationsTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        //ここにデータを投入する記述
    }
}

1レコード分の投入であれば以下のように記述します。複数レコードは、配列でその下に追加します。

ReservationsTableSeeder.php
       DB::table('users')->insert([
        [
            'name' => '木村',
            'email' => 'kimura@XXX.co.jp',
            'password' => '123456',
            'user_id' => 'kimura3',
            'auth_id' => 1,
            'last_login_date' => date('Y-m-d H:i:s'),
        ],

実行時に呼び出す記述を追加

シーダクラスを定義したら、このシーダクラスをシーディング実行時に呼び出す記述を追加します。
seedsディレクトリ内DatabaseSeeder.phpに以下を記述します。登録したいシーダクラスが複数ある場合は、ここに配列で追加します。

DatabaseSeeder.php
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        $this->call([
            ReservationsTableSeeder::class,
         ]);
    }

}

シーディングの実行

プロジェクトフォルダで以下コマンドを実行します。

php artisan db:seed

ファイル名を指定して実行する場合

上の手順で呼び出し用の記述をDatabaseSeeder.phpにしましたが、optionでファイル名を指定する場合は、記述しなくても実行できます。

php artisan db:seed --class=UsersTableSeeder

DB確認

テーブルにデータが作成されている事が確認できます。

LaravelでModelを作成するコマンド

例:XXXモデルを作成

php artisan make:model XXX

上記コマンド実行後、プロジェクトルート/appにXXX.phpというModelファイルが作成されているのが確認できます。

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

Laravelで超シンプルなToDoアプリを作成する

はじめに

Laravelで超シンプルなToDoアプリを作成する手順を紹介します。
ToDoアプリは、CRUD(Create, Read, Update, Delete)の操作を兼ね揃えた最もシンプルなアプリの一つと言えます。
そのため、学習したい言語やフレームワークを用いてToDoアプリを作成することで、その言語やフレームワークを用いたデータの作成・読み取り・更新・削除といった基本操作を一通り学習することができます。

Laravelを用いてToDoアプリを作成するチュートリアルは公式のものも含めて既にいくつかあると思いますが、この記事では超シンプル・必要最低限の機能だけに絞ったToDoアプリを作成する手順を紹介します。
まずは最低限のToDoアプリを作成してみてLaravelのCRUDアプリ作成の勘所を押さえ、そこから自分が欲しいと思う機能を少しずつ加えながら学習を更に進めるのが良いと思います。

作成するアプリの概要

画面構成

超シンプルにしてLaravelによるCRUD操作に内容をフォーカスしたいので、CSSはほぼ設定していません。
見た目はイケてませんが、そこは機能を実現してから各自の好みで装飾してください。

タスク一覧ページ

タスクの一覧を表示するためのページです。
このページからタスク作成ページ・タスク編集ページ・タスク削除ページへ遷移することができます。

image.png

このページのURLは、アプリのルート(/)にします。

タスク作成ページ

新しいタスクを作成するためのページです。
タスクの情報をフォームに入力し、"追加"ボタンを押すことで新しいタスクを作成することができます。

image.png

このページのURLは/create-pageにします。
また、追加ボタンが押されて実際にタスクを追加するAPIのURLは/createとします。

タスク編集ページ

選択したタスクの内容を編集するためのページです。
選択したタスクの内容が予めフォームに入力されているので、編集したい箇所を書き換えて"修正"ボタンを押すことで内容を書き換えることができます。

image.png

このページのURLは/edit-pageにします。
また、修正ボタンが押されて実際にタスクを修正するAPIのURLは/editとします。

タスク削除ページ

選択したタスクを削除するためのページです。
選択したタスクの内容が表示されるので、確認の上で"削除"ボタンを推すことでタスクを削除することができます。

image.png

このページのURLは/delete-pageにします。
また、削除ボタンが押されて実際にタスクを修正するAPIのURLは/deleteとします。

URL設計

このアプリで使用するURLは以下のとおりです。

URL HTTPリクエストメソッド 用途
/ GET アプリのトップページをレスポンスで返す
/create-page GET タスクの作成ページをレスポンスで返す
/create POST タスクの作成を実行する
/edit-page GET タスクの編集ページをレスポンスで返す
/edit POST タスクの編集を実行する
/delete-page GET タスクの削除ページをレスポンスで返す
/delete POST タスクの削除を実行する

テーブル構成

このアプリは、作成したタスクをデータベース(SQLite)に保存します。
使用するテーブルは"todos"テーブルひとつだけです。

本格的なアプリのデータベースとしてはSQLiteは力不足ですが、LaravelによるCRUDアプリの作成方法にフォーカスしたいので、まずは使うまでの準備が超簡単なSQLiteで慣れましょう。
作成したアプリをサーバで動かしたいといったステップまで進んだら、mysqlやpostgresなどのもう少し本格的なデータベースに切り替えることを検討すればよいと思います。

todosテーブルの構成

todosテーブルは以下のようなカラムを持ちます。

カラムの論理名 カラムの物理名
id id
タスクの名前 task_name
タスクの説明 task_description
担当者の名前 assign_person_name
見積時間(h) estimate_hour

開発環境を整える

PHPとcomposerのインストール

こちらのページを参考にさせていただきました。
Macの場合でもほぼ変わりありませんので、composerのインストールまで済ませておきましょう。
Laravelチュートリアル1 – 環境構築編(Windows)

上記のチュートリアルですが、2以降も大変参考になるので、Laravel初学者は一通り手を動かしてみることをお勧めします。
Laravelチュートリアル2 – ルーティングを理解しよう
Laravelチュートリアル3 -コントローラーで簡単な処理を書いてみよう
Laravelチュートリアル4 -データベースを使用して処理を書いてみよう
Laravelチュートリアル5 – フォームを扱った処理を書いてみよう

SQLite3のインストール

初期データを作成するためにSQLite3も使用します。
以下のページを参考にSQLite3もインストールしておきましょう。
Laravelでsqliteをデータベースとして使用する方法

開発の流れ

超シンプルなToDoアプリを作る流れを順を追って解説します。

Laravelプロジェクトの作成

Laravelのプロジェクトを作成したいディレクトリをターミナルで開き、以下のコマンドを実行します。

composer create-project laravel/laravel=5.7 laravel_todolist

Laravelプロジェクトの作成が成功したら、以下のコマンドで"laravel_todolist"ディレクトリに入り、開発サーバを起動します。

cd laravel_todolist
php artisan serve

http://127.0.0.1:8000へアクセスして、Laravelと表示さてれいれば、開発サーバの起動は成功しています。

データベースを用意

sqliteファイルの作成

作成した"laravel_todolist"ディレクトリで以下のコマンドを実行してsqliteファイルを作成します。

touch database/database.sqlite

LaravelプロジェクトのDB接続情報を書き換え

"laravel_todolist"ディレクトリにある.envファイルをvi等で開いて内容を書き換えます。

vi .env

書き換え対象は以下の行です。

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret

SQLiteを使用するために以下のような内容に書き換えて保存します。
#の行はSQLiteでは使わないのでコメントアウトしています)

DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=homestead
# DB_USERNAME=homestead
# DB_PASSWORD=secret

マイグレーションの実行

"laravel_todolist"ディレクトリで以下のコマンドを実行し、マイグレーションを実行してテーブルを作成します。

php artisan migrate

Laravelはデフォルトでusersテーブルとpassword_resetsテーブルのマイグレーションファイルを持っているので、migrateコマンドを実行することでusersテーブルとpassword_resetsテーブルがデータベースに作成されます。

SQLite3による接続確認

"laravel_todolist"ディレクトリで以下のコマンドを実行してデータベースに接続します。

sqlite3 database/database.sqlite

接続ができると、ターミナルが以下のような表示になります。
この画面でSQLを入力・実行することができます。

SQLite version 3.28.0 2019-04-15 14:49:49
Enter ".help" for usage hints.
sqlite>

.tableというコマンドを入力することでテーブルの一覧を見ることができます。
migrateコマンドで作成したusersテーブルとpassword_resetsテーブルが存在することを確認してください。
(migrateを実行した際に、マイグレーションの履歴を保持するためのmigrationsというテーブルも自動的に作成されています)

sqlite> .table
migrations       password_resets  users

以上を確認できたらSQLite3を終了させます。
SQLite3を終了させるにはCtrl+Dを入力します。

ToDoテーブルの作成

機能を開発する前に、データを格納するtodosテーブルをデータベースに作成しておきます。

マイグレーションファイルの作成

"laravel_todolist"ディレクトリで以下のコマンドを実行します。

php artisan make:migration create_todos_table --create=todos

マイグレーションを実行してtodosテーブルを作成

"laravel_todolist/database/migrations"ディレクトリにYYYY_MM_DD_hhmmss_create_todo_table.phpといったフォーマットの名前でファイルが作成されます。これがマイグレーションファイルです。
このファイルをviで開きます。

YYYY_MM_DD_hhmmss_create_todo_table.php
<?php                                                                                                              

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateTodoTable extends Migration

   /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('todos', function (Blueprint $table) {
            $table->increments('id');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
    */
    public function down()
    {
        Schema::dropIfExists('todos');
    }                                                                                                              
 }

upファンクションに定義されているのが、マイグレーションが実行された際にデータベースに作成されるtodosテーブルの情報です。todosテーブルには以下の情報を加えたいと思います。

カラムの論理名 カラムの物理名
id id
タスクの名前 task_name
タスクの説明 task_description
担当者の名前 assign_person_name
見積時間(h) estimate_hour

upファンクションを以下のように書き換えます。

YYYY_MM_DD_hhmmss_create_todo_table.php
<?php                                                                                                              

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateTodoTable extends Migration

   /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('todos', function (Blueprint $table) {
            $table->increments('id');
            $table->string('task_name');  // 追加
            $table->text('task_description');  // 追加
            $table->string('assign_person_name');  // 追加
            $table->string('estimate_hour');  // 追加
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
    */
    public function down()
    {
        Schema::dropIfExists('todos');
    }                                                                                                              
 }

修正が終わったら、ファイルを保存します。
"laravel_todolist"ディレクトリに戻り、以下のコマンドでマイグレーションを実行してください。

php artisan migrate

SQLite3でdatabase.sqliteを開き、todosテーブルが作成されていることを確認します。

sqlite3 database/database.sqlite
SQLite version 3.28.0 2019-04-15 14:49:49
Enter ".help" for usage hints.
sqlite> .table
migrations       password_resets  todos            users          
sqlite> 

SQLite3で初期データを登録

引き続き、SQLite3で初期データを登録します。
以下のSQLを実行します。

sqlite> INSERT INTO todos(task_name, task_description, assign_person_name, estimate_hour) VALUES("テスト", "テストです", "テストさん", "1");

以下のSQLを実行して、初期データが登録されたことを確認してください。

sqlite> SELECT * FROM todos;
1|テスト|テストです|テストさん|1||

Todoモデルクラスを実装

todosテーブルをLaravelから操作するために、Todoモデルクラスを実装します。

"laravel_todolist"ディレクトリで以下のコマンドを実行して、Todoモデルクラスを生成します。

php artisan make:model Todo

"laravel_todolist/app/Todo.php"というファイルが生成されるので、vi等で開き以下の行を追加して保存します。
ID列を$guardedで指定したので、ID列にはデータの作成時や更新時に値が入らないようになります。(ID列には、データ作成時に自動で連番を入れたいのでこのような指定を行っておきます)

Todo.php
<?php                                                                                                              

namespace App;

use Illuminate\Database\Eloquent\Model;

class Todo extends Model
{
    protected $guarded = ['id'];  // 追加
}

タスク一覧ページを実装

CRUDのR(Read)に該当する機能を実装します。

ルーティングの設定

"laravel_todolist/routes/web.php"をvi等で開きます。

web.php
<?php                                                                                                              

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

/へのルーティングが予め書かれていますが、ここを以下のように書き換えます。

web.php
<?php                                                                                                              

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', 'TodolistFormController@index');

これで、/を呼び出されたときに、TodolistFormControllerクラスのindexファンクションが実行される指定になりました。

コントローラの作成

web.phpを書き換えましたが、まだTodolistFormControllerクラスが無いので実装しましょう。

"laravel_todolist"ディレクトリで以下のコマンドを実行してTodolistFormControllerクラスを作成します。

php artisan make:controller TodolistFormController

"laravel_todolist/app/Http/Controllers/TodolistFormController.php"というファイルが作成されるので、このファイルをvi等で開きます。

TodolistFormController.php
<?php                                                                                                              

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class TodolistFormController extends Controller
{
    //
}

まだ何もファンクションが無い状態なので、ここにタスク一覧を表示するindexファンクションを追加します。

TodolistFormController.php
<?php                                                                                                              

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\ToDo;  // 追加

class TodolistFormController extends Controller
{
     // タスクを一覧で表示
     public function index()
     {
         $todos = Todo::orderBy('id', 'asc')->get();
         return view('todo_list', [
             "todos" => $todos
         ]);
     }
}

use App\ToDo;でTodoモデルクラスを読み込んでいることに注意してください。
ToDoモデルクラスのget()ファンクションを呼び出すことで、todosテーブルの値を取り出すことが出来ます。
また、orderBy()ファンクションを使用することでデータのソートも行っています。

returnでは"todo_list"ビューを使用し、$todosデータを渡すように定義しています。
次は"todo_list"ビューを作成しましょう。

ビューの作成

ビューファイルはartisanコマンドで作成できないので、touchコマンド等で直接ファイルを作成します。
"laravel_todolist/resources/views/"ディレクトリに"todo_list.blade.php"というファイルを作成し、vi等で開いて以下のように実装します。

todo_list.blade.php
<h1>ToDo List</h1>                                                                                                 
<div>
    <h2>タスクの一覧</h2>
    <a href="/create-page">タスクを追加</a>
    <table border="1">
        <tr>
            <th>タスクの名前</th>
            <th>タスクの説明</th>
            <th>担当者の名前</th>
            <th>見積時間(h)</th>
            <th colspan="2">操作</th>
        </tr>
        @foreach($todos as $todo)
        <tr>
            <td>{{$todo->task_name}}</td>
            <td>{{$todo->task_description}}</td>
            <td>{{$todo->assign_person_name}}</td>
            <td>{{$todo->estimate_hour}}</td>
            <td><a href="/edit-page/{{$todo->id}}">編集</a></td>
            <td><a href="/delete-page/{{$todo->id}}">削除</a></td>
        </tr>
        @endforeach
    </table>
</div>

見るべきポイントとしては@foreach($todos as $todo)でタスクの数だけ繰り返し表示している箇所です。コントローラからタスクの一覧が$todosという名前で渡されてくるので、これをforeachでループさせながら全件表示しています。

また、まだタスクの追加・編集・削除機能は実装していませんが、<a>タグによるリンクは先に実装してしまいます。
編集と削除のURLはどのタスクを対象とするのかをIDで判別するため、URLに{{$todo->id}}を渡しています。

動作確認

"laravel_todolist"ディレクトリで以下のコマンドを実行し、Laravelの開発サーバを起動します。

php artisan serve

ブラウザでhttp://localhost:8000/を開き、SQLiteで登録しておいた初期データが表示されることを確認してください。

image.png

これで、タスク一覧ページの実装が出来ました。
試しに、SQLite3でINSERTのSQLを実行して初期データの件数を増やすと、タスク一覧ページに表示されるタスクの件数も増えていくはずです。
次は、タスクを追加するページを実装します。

タスク追加ページを実装

CRUDのC(Create)に該当する機能を実装します。

ルーティングの設定

"laravel_todolist/routes/web.php"を編集します。
タスク追加ページを表示する/create-pageと、追加ボタンが押されたら実際にタスクを追加する/createへのルーティングを追加します。
/create-pageはGETメソッドの指定で、/createはPOSTメソッドの指定になる点に注意してください。

web.php
<?php                                                                                                              

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', 'TodolistFormController@index');
Route::get('/create-page', 'TodolistFormController@createPage');
Route::post('/create', 'TodolistFormController@create');

コントローラの作成

"laravel_todolist/app/Http/Controllers/TodolistFormController.php"を開き、TodolistFormControllerの実装に処理を追加します。

createPageファンクションの追加

"createPage"ファンクションはタスク作成ページを返すだけでよいので、"todo_create"ビューを返しているだけです。

TodolistFormController.php
    // ここより上は省略

    // タスク作成画面を表示
    public function createPage()
    {
        return view('todo_create');
    }

    // 以下省略

createファンクションの追加

"create"ファンクションはタスク作成ページで「作成」ボタンを押されたら実行されます。タスク作成ページで入力された情報がPOSTリクエストのパラメータとして渡ってくるので、その情報をもとに新しいタスクを作成しています。

Todoモデルクラスをnewしてインスタンスを生成し、タスクの情報をパラメータに詰めてsave()ファンクションを呼び出すことでtodosテーブルにレコードが保存されます。

処理の最後でreturn redirect('/')することで、タスク登録後に/ページへリダイレクトされます。

TodolistFormController.php
    // ここより上は省略

    // タスクを登録
    public function create(Request $request)
    {
        $todo = new Todo();
        $todo->task_name = $request->task_name;
        $todo->task_description = $request->task_description;
        $todo->assign_person_name = $request->assign_person_name;
        $todo->estimate_hour = $request->estimate_hour;
        $todo->save();

        return redirect('/');
    }

    // 以下省略

ビューの作成

タスク追加画面が必要なので作成します。
"laravel_todolist/resources/views/"ディレクトリに"todo_create.blade.php"というファイルを作成し、vi等で開いて以下のように実装します。

todo_create.blade.php
<h1>ToDo List</h1>                                                                                                 
<div>
    <h2>タスクを追加</h2>
    <form method="POST" action="/create">
        @csrf
        <p>
            タスクの名前<input type="text" name="task_name">
        </p>
        <p>
            タスクの説明<input type="text" name="task_description">
        </p>
        <p>
            担当者の名前<input type="text" name="assign_person_name">
        </p>
        <p>
            見積時間(h)<input type="number" name="estimate_hour">
        </p>
        <input type="submit" name="create" value="追加">
    </form>
    <a href="/">戻る</a>
</div>

<form method="POST" action="/create">としてあるので、<input type="submit" name="create" value="追加">ボタンが押されたら/createのPOSTメソッドへリクエストが飛ぶことになります。

動作確認

"laravel_todolist"ディレクトリでphp artisan serveコマンドでテストサーバを起動し、以下の手順で新しいタスクを追加できることを確認します。

image.png

タスク編集ページを実装

CRUDのU(Update)に該当する機能を実装します。

ルーティングの設定

"laravel_todolist/routes/web.php"を編集します。
タスク編集ページを表示する/edit-pageと、修正ボタンが押されたら実際にタスクを更新する/editへのルーティングを追加します。

/edit-pageはGETメソッドの指定で、/editはPOSTメソッドの指定になる点はタスク作成ページと同様です。
ただし/edit-pageへのルーティングは、「どのタスクを編集するのか」という情報をURLパラメータに加えたいため、'/edit-page/{id}'という指定になっている点に注意です。

web.php
<?php                                                                                                              

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', 'TodolistFormController@index');
Route::get('/create-page', 'TodolistFormController@createPage');
Route::post('/create', 'TodolistFormController@create');
Route::get('/edit-page/{id}', 'TodolistFormController@editPage');
Route::post('/edit', 'TodolistFormController@edit');

コントローラの作成

"laravel_todolist/app/Http/Controllers/TodolistFormController.php"を開き、TodolistFormControllerの実装に処理を追加します。

editPageファンクションの追加

"editPage"ファンクションは、編集対象のタスクのIDがURLパラメータの$idで渡ってくるので、$idを引数として受け付けます。
Todoモデルクラスのfindファンクションを用いるとテーブルのキーを用いた検索ができるので、引数で受け取った$idを使って対象のタスクを取得し、"todo_edit"ビューと合わせて返しています。

TodolistFormController.php
    // ここより上は省略

    // タスク編集画面を表示
    public function editPage($id)
    {
        $todo = Todo::find($id);
        return view('todo_edit', [
            "todo" => $todo
        ]);
    }

    // 以下省略

editファンクションの追加

"edit"ファンクションはタスク編集ページで「修正」ボタンを押されたら実行されます。タスク修正ページで入力された情報がPOSTリクエストのパラメータとして渡ってくるので、その情報をもとにタスクの情報を更新しています。

まず、Todoモデルクラスのfindファンクションを使って、リクエストパラメータに入っている$idのタスクをデータベースのtodosテーブルから取得します。
そして、updateファンクションを使って、取得したタスクの内容を書き換えます。

処理の最後でreturn redirect('/')することで、タスク編集後に/ページへリダイレクトされます。

TodolistFormController.php
    // ここより上は省略

    // タスクを更新
    public function edit(Request $request)
    {
        Todo::find($request->id)->update([
            'task_name' => $request->task_name,
            'task_description' => $request->task_description,
            'assign_person_name' => $request->assign_person_name,
            'estimate_hour' => $request->estimate_hour
        ]);
        return redirect('/');
    }

    // 以下省略

ビューの作成

タスク編集画面が必要なので作成します。
"laravel_todolist/resources/views/"ディレクトリに"todo_edit.blade.php"というファイルを作成し、vi等で開いて以下のように実装します。

todo_edit.blade.php
<h1>ToDo List</h1>                                                                                                 
<div>
    <h2>タスクの修正</h2>
    <form method="POST" action="/edit">
        @csrf
        <input type="hidden" name="id" value="{{$todo->id}}">
        <p>
            タスクの名前<input type="text" name="task_name" value="{{$todo->task_name}}">
        </p>
         <p>
             タスクの説明<input type="text" name="task_description" value="{{$todo->task_description}}">
         </p>
         <p>
             担当者の名前<input type="text" name="assign_person_name" value="{{$todo->assign_person_name}}">
         </p>
         <p>
             見積時間(h) <input type="number" name="estimate_hour" value="{{$todo->estimate_hour}}">
         </p>
         <input type="submit" name="edit" value="修正">
     </form>
     <a href="/">戻る</a>
 </div>

<form method="POST" action="/edit">としてあるので、<input type="submit" name="edit" value="修正">ボタンが押されたら/editのPOSTメソッドへリクエストが飛ぶことになります。
また、各inputのvalueには、コントローラから渡された$todoデータに格納されているタスクの情報が入ります。これによって、タスク編集画面を開いたときに、タスクの情報が予めフォームに入力された状態になります。

動作確認

"laravel_todolist"ディレクトリでphp artisan serveコマンドでテストサーバを起動し、以下の手順でタスクの編集ができることを確認します。

image.png

タスク削除ページを実装

CRUDのD(Delete)に該当する機能を実装します。

ルーティングの設定

"laravel_todolist/routes/web.php"を編集します。
タスク削除ページを表示する/delete-pageと、修正ボタンが押されたら実際にタスクを更新する/deleteへのルーティングを追加します。

/delete-pageはGETメソッドの指定で、/deleteはPOSTメソッドの指定になる点はタスク編集ページと同様です。/delete-pageへのルーティングについても、「どのタスクを削除するのか」という情報をURLパラメータに加えたいため、'/delete-page/{id}'という指定になっています。
ただし、/deleteはPOSTメソッドの指定で'/delete/{id}'となっている点は注意です。これは、削除ページではタスクの内容を確認するだけでフォームへの入力は行わないため、リクエストパラメータを使うよりもURLパラメータにIDを仕込んだほうがシンプルだと考えたためです。

web.php
<?php                                                                                                              

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', 'TodolistFormController@index');
Route::get('/create-page', 'TodolistFormController@createPage');
Route::post('/create', 'TodolistFormController@create');
Route::get('/edit-page/{id}', 'TodolistFormController@editPage');
Route::post('/edit', 'TodolistFormController@edit');
Route::get('/delete-page/{id}', 'TodolistFormController@deletePage');
Route::post('/delete/{id}', 'TodolistFormController@delete');

コントローラの作成

"laravel_todolist/app/Http/Controllers/TodolistFormController.php"を開き、TodolistFormControllerの実装に処理を追加します。

deletePageファンクションの追加

"deletePage"ファンクションは、削除対象のタスクのIDがURLパラメータの$idで渡ってくるので、$idを引数として受け付けます。
Todoモデルクラスのfindファンクションで対象のタスクを取得し、"todo_delete"ビューと合わせて返しています。

TodolistFormController.php
    // ここより上は省略

    // タスク削除画面を表示
    public function deletePage($id)
    {
        $todo = Todo::find($id);
        return view('todo_delete', [
            "todo" => $todo
        ]);
    }

    // 以下省略

deleteファンクションの追加

"delete"ファンクションはタスク削除ページで「削除」ボタンを押されたら実行されます。選択されたタスクのIDがURLパラメータとして渡ってくるので、その情報をもとにタスクの情報を削除しています。

まず、Todoモデルクラスのfindファンクションを使って、URLパラメータに入っている$idのタスクをデータベースのtodosテーブルから取得します。
そして、deleteファンクションを使って、取得したタスクをtodosテーブルから削除しています。

処理の最後でreturn redirect('/')することで、タスク編集後に/ページへリダイレクトされます。

TodolistFormController.php
    // ここより上は省略

    // タスクを削除
    public function delete(Request $id)
    {
        Todo::find($id)->delete();
        return redirect('/');
    }

    // 以下省略

ビューの作成

"laravel_todolist/resources/views/"ディレクトリに"todo_delete.blade.php"というファイルを作成し、vi等で開いて以下のように実装します。

todo_delete.blade.php
<h1>ToDo List</h1>                                                                                                 
<div>
    <h2>タスクを削除</h2>
    <form method="POST" action="/delete/{{$todo->id}}">
        @csrf
        <p>
            タスクの名前{{$todo->task_name}}
        </p>
        <p>
            タスクの説明{{$todo->task_description}}
        </p>
        <p>
            担当者の名前{{$todo->assign_person_name}}
        </p>
        <p>
            見積時間(h) {{$todo->estimate_hour}}
        <p>
        <input type="submit" name="delete" value="削除">
    </form>
    <a href="/">戻る</a>
</div>

<form method="POST" action="/delete">としてあるので、<input type="submit" name="delete" value="削除">ボタンが押されたら/deleteのPOSTメソッドへリクエストが飛ぶことになります。
削除対象のタスクの情報はコントローラから$todoという名前のデータで渡されてきているので、$todoから値を取り出してHTML上に直接(inputタグを使わずに)表示しています。

動作確認

"laravel_todolist"ディレクトリでphp artisan serveコマンドでテストサーバを起動し、以下の手順でタスクの削除ができることを確認します。

image.png

その他の機能を実装

以上で、超シンプルなToDoアプリのCRUD(Create, Read, Update, Delete)の機能が実現できました。
このままでもまぁまぁ使えるのですが、もう少し完成度を上げるために以下の2点の改善を行います。

  1. タスクに設定した見積時間の合計を表示する
  2. Validationの実装(タスク作成時と編集時に入力内容をチェックする)

見積時間の合計を表示

個々のタスクの見積時間はタスク一覧ページで表示されますが、タスクの数が増えてくるといちいち合計を手計算しなければいけないのは面倒です。
そこで、全てのタスクの見積時間を合計し、タスク一覧ページに表示しましょう。

コントローラの修正

"laravel_todolist/app/Http/Controllers/TodolistFormController.php"を開き、TodolistFormControllerのindexファンクションに処理を追加します。

TodolistFormController.php
    // ここより上は省略

    // タスクを一覧で表示
    public function index()
    {
        $todos = Todo::orderBy('id', 'asc')->get();

        // 見積時間の合計を求める
        $estimate_hour_sum = 0;
        foreach ($todos as $todo){
            $estimate_hour_sum += $todo->estimate_hour;
        }

        return view('todo_list', [                                                                                 
            "todos" => $todos,
            "estimate_hour_sum" => $estimate_hour_sum
        ]);
    }

    // 以下省略

新たに$estimate_hour_sumという変数を定義し、foreachで全タスクを回してestimate_hourを合算します。そして最後に、ビューを返す際のデータにestimate_hour_sumも加えてあげます。

ビューの修正

"laravel_todolist/resources/views/todo_list.blade.php"を開いて以下のように修正します。

todo_list.blade.php
 <h1>ToDo List</h1>
 <div>
     <h2>タスクの一覧</h2>
     <a href="/create-page">タスクを追加</a>
     <table border="1">
        <tr>
            <th>タスクの名前</th>
            <th>タスクの説明</th>
            <th>担当者の名前</th>
            <th>見積時間(h)</th>
            <th colspan="2">操作</th>
        </tr>
        @foreach($todos as $todo)
        <tr>
            <td>{{$todo->task_name}}</td>
            <td>{{$todo->task_description}}</td>
            <td>{{$todo->assign_person_name}}</td>
            <td>{{$todo->estimate_hour}}</td>
            <td><a href="/edit-page/{{$todo->id}}">編集</a></td>
            <td><a href="/delete-page/{{$todo->id}}">削除</a></td>
        </tr>                                                                                                      
        @endforeach
    </table>
    <p>
        見積時間の合計(h){{$estimate_hour_sum}}
    </p>
</div>

一番下に見積時間の合計を追加しています。

Validationの実装

現状では、タスク作成やタスク編集で、フォームの値が空の状態だった場合、QueryExceptionが発生してしまいます。

image.png

これは、todosテーブルの各カラムについてNULLを許容していないためですが、ユーザがタスク作成ページやタスク編集ページではフォームの値を埋めた状態でボタンを押してもらえればこのExceptionは回避できます。
そのような機能を実現させたい場合、Validationを実装することでフォームに入力されたデータを先にチェックし、異常なデータが有る場合は処理を中断させることができます。

コントローラの修正

"laravel_todolist/app/Http/Controllers/TodolistFormController.php"を開き、TodolistFormControllerのcreateファンクションに処理を追加します。

TodolistFormController.php
    // ここより上は省略

    // タスクを登録
    public function create(Request $request)
    {
        $validator = $request->validate([
            'task_name' => 'required',
            'task_description' => 'required',
            'assign_person_name' => 'required',
            'estimate_hour' => 'required'
        ]);

        $todo = new Todo();
        $todo->task_name = $request->task_name;
        $todo->task_description = $request->task_description;
        $todo->assign_person_name = $request->assign_person_name;
        $todo->estimate_hour = $request->estimate_hour;
        $todo->save();

        return redirect('/');
    }

    // 以下省略

Todoモデルクラスをnewする前に、HTTPリクエストで渡されてきたパラメータを$validatorを使ってチェックしています。
チェックのルールは各パラメータで個別に設定することができ、今回の場合は全てのパラメータについてrequiredを指定して必須としています。

Validationルールは他にも色々あるので、以下の公式ガイドの「使用可能なバリデーションルール」を参照してみてください。
Laravel 5.7 バリデーション

ビューの修正

"laravel_todolist/resources/views/todo_create.blade.php"を開いて以下のように修正します。

todo_create.blade.php
<h1>ToDo List</h1>                                                                                                 
<div>
    <h2>タスクを追加</h2>
    <form method="POST" action="/create">
        @csrf
        @if ($errors->any())
            <ul>
            @foreach ($errors->all() as $error)
               <li style="color:red">{{$error}}</li>
            @endforeach
            </ul>
        @endif
        <p>
            タスクの名前<input type="text" name="task_name">
        </p>
        <p>
            タスクの説明<input type="text" name="task_description">
        </p>
        <p>
            担当者の名前<input type="text" name="assign_person_name">
        </p>
        <p>
            見積時間(h)<input type="number" name="estimate_hour">
        </p>
        <input type="submit" name="create" value="追加">
    </form>
    <a href="/">戻る</a>
</div>

@if ($errors->any())で、Validationエラーがあった場合のエラーメッセージ表示領域を定義しています。
コントローラ側で$validatorのチェックルールに引っかかった場合、自動的にビューが再表示されます。その際に$errorsにエラー内容が配列で格納されているので、foreachループで回して全てのエラーメッセージを<li>タグでリスト表示しています。

日本語メッセージの表示

日本語メッセージファイルのインストール

ここまででもValidationチェックとエラーメッセージの表示はできるのですが、エラーメッセージが英文表記となっていてユーザフレンドリーとは言い難い状態です。
そのため、まずは公式の手順に沿って日本語メッセージのインストールを行います。

Laravel 5.7 validation.php言語ファイル

"laravel_todolist"ディレクトリで以下のコマンドを実行します。

php -r "copy('https://readouble.com/laravel/5.7/ja/install-ja-lang-files.php', 'install-ja-lang.php');"
php -f install-ja-lang.php
php -r "unlink('install-ja-lang.php');"

"laravel_todolist/resources/lang/ja"というディレクトリが新たに作られ、その配下にあるvalidation.phpがValidationの日本語メッセージです。

Laravelプロジェクトの言語設定を変更

"laravel_todolist/config/app.php"を開き、localeというキーの値をenからjaに変更して保存します。

app.php
    // ここより上は省略

    /*
    |--------------------------------------------------------------------------
    | Application Locale Configuration
    |--------------------------------------------------------------------------
    |
    | The application locale determines the default locale that will be used
    | by the translation service provider. You are free to set this value
    | to any of the locales which will be supported by the application.
    |
    */

    'locale' => 'ja',

    // 以下省略
カスタムバリデーション属性名の設定

ここまででメッセージのフォーム名以外は日本語になりますが、まだフォーム名はリクエストパラメータのキー名がそのまま表示されてしまいます。ここを日本語名に変換するには、カスタムバリデーション属性名を設定する必要があります。
"laravel_todolist/resources/lang/ja/validation.php"を開いて以下のように修正します。

validation.php
    // ここより上は省略

    /*
    |--------------------------------------------------------------------------
    | カスタムバリデーション属性名
    |--------------------------------------------------------------------------
    |
    | 以下の言語行は、例えば"email"の代わりに「メールアドレス」のように、
    | 読み手にフレンドリーな表現でプレースホルダーを置き換えるために指定する
    | 言語行です。これはメッセージをよりきれいに表示するために役に立ちます。
    |
    */

    'attributes' => [
        'task_name' => 'タスクの名前',
        'task_description' => 'タスクの説明',
        'assign_person_name' => '担当者の名前',
        'estimate_hour' => '見積時間',
    ],
];
動作確認

タスク追加画面でフォームの値を入力せずに追加ボタンを押すと、以下のようにValidationメッセージが表示されるようになります。

image.png

同様のExceptionはタスク編集画面でも起こりうるので、タスク編集画面とコントローラのeditファンクションにもValidationを導入しておくと良いでしょう。

ToDoアプリの機能追加例

超シンプルなToDoアプリの作成紹介は以上となります。
ここからは、各々がToDoアプリをベースにしながら付帯機能の開発を進めて、Laravelによるアプリ開発の理解を深めるのがよいと思います。
いくつか機能追加の例を示しておきます。

見積時間(h)のデータ型を文字型から数値型に変える

現状では見積時間(h)はマイグレーションファイルでStringとして定義されているため、todosテーブル上でも文字型として定義されています。
しかし、時間は数字であったほうが集計等で扱いやすいので、文字型ではなく数字型の方が良いはずです。

マイグレーションファイルを追加し、estimate_hourの定義を数値型に変更する対応をしてみると、開発途中でデータ型を変更する練習になると思います。

担当者の名前をセレクトボックスから選択できるようにする

現状では担当者の名前はテキストボックスに直接入力していますが、ここは予め決められた名前の中から選択できる方が使い勝手が良いはずです。

担当者の名前をセレクトボックスから選択できるようにするには、ざっと考えただけでも以下のような対応が必要になってきそうです。

  • 担当者の名前をデータベースに登録しておきたい
    • 担当者の名前のデータベースが必要
    • 担当者の名前のCRUD機能が必要
  • タスク登録時に担当者の名前をデータベースから取得したい
    • タスクをデータベースに保存する際の担当者の名前は、Valueではなくidを保存する
  • タスク編集時に担当者の名前をデータベースから取得したい
    • タスク登録時と似ているが、セレクトボックスには予めidに対応するvalueを選択した状態にしておきたい
  • その他考慮すべきこと
    • 担当者の名前を削除するときに、その担当者のタスクが残っている場合にどうするか?

これは、ToDoモデルに加えて、新たに担当者のモデル(AssignPersonモデル)が必要となり、モデル間の関係も考慮する練習になると思います。

ログイン機能の実装

現状ではこのToDoアプリはログイン機能を備えていないため、URLを知っていれば誰でもアクセスしてデータを操作できてしまいます。これではセキュリティ面で実用的では無いため、ログイン機能を備えるべきでしょう。

Laravelは標準でAuthの機能を備えているので、公式ガイドを参考にしながらログイン機能を追加してみるとより実用的なアプリになると思います。
Laravel 5.7 認証

また、ユーザに対して「管理者」「一般」といった権限レベルを追加し、タスクの削除ができるのは管理者のみといった権限設定を行えるようにするのも学習になると思います。

排他制御の実装

ログイン機能を追加したら、実用性を上げるためにデータ更新時の排他制御も実現させたいところです。
現状では、複数のアカウントから同時にタスクの編集を行われた場合、一方の編集内容は破棄されてしまうという問題があります。
処理の流れを示すと以下のようになります。

排他制御が行われていない場合の処理の流れ

  1. Aさんがタスク1のタスク編集画面を開く
  2. Bさんがタスク1のタスク編集画面を開く
  3. Aさんがタスク1の内容を書き換えて保存する
  4. Bさんがタスク1の内容を書き換えて保存する ※Aさんの編集内容はここには反映されていない
  5. タスク1はBさんの編集内容で保存されるため、Aさんの編集内容は破棄されてしまう

このような問題を回避するためには、排他制御としてデータ更新時の楽観ロック(更新前に読み込んだデータが、他のユーザによって先に更新されていないかチェックする仕組み)を実装する必要があります。
排他制御を実装している場合、処理の流れは以下のようになります。

排他制御が行われる場合の処理の流れ

  1. Aさんがタスク1のタスク編集画面を開く
  2. Bさんがタスク1のタスク編集画面を開く
  3. Aさんがタスク1の内容を書き換えて保存しようとしたときに、他の人が先に書き換え・保存していないかチェックする
  4. 他の人によって書き換え・保存されていないので、Aさんの書き換え内容は保存される
  5. Bさんがタスク1の内容を書き換えて保存しようとしたときに、他の人が先に書き換え・保存していないかチェックする
  6. Aさんによって書き換え・保存されているので、Bさんの書き換えは失敗する

複数アカウントでデータが操作されるアプリであれば排他制御は避けて通れないので、良い練習になると思います

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

Laravel8 CRUD処理を使った投稿アプリを作成する その4 投稿一覧ページの作成

目的

  • 投稿された内容の一覧を表示するページの作成を行う

実施環境

  • 筆者の実施環境を記載する。
  • ハードウェア環境
項目 情報
OS macOS Catalina(10.15.5)
ハードウェア MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports)
プロセッサ 2 GHz クアッドコアIntel Core i5
メモリ 32 GB 3733 MHz LPDDR4
グラフィックス Intel Iris Plus Graphics 1536 MB
  • ソフトウェア環境
項目 情報 備考
PHP バージョン 7.4.8 Homebrewを用いてこちらの方法で導入→Mac HomebrewでPHPをインストールする
Laravel バージョン 8.6.0 commposerを用いてこちらの方法で導入→Mac Laravelの環境構築を行う
MySQLバージョン 8.0.19 for osx10.13 on x86_64 Homwbrewを用いてこちらの方法で導入→Mac HomebrewでMySQLをインストールする

前提条件

  • その1の記事の内容が完了していること。

前提情報

概要

  1. ルーティング情報の記載
  2. コントローラファイルの記載
  3. ビューファイルの作成と記載
  4. 確認

詳細

  1. ルーティングの記載

    1. laravel8_crudディレクトリで下記コマンドを実行してルーティングファイルを開く。

      $ vi routes/web.php 
      
    2. 開いたファイルに下記の行を追記する。

      laravel8_crud/routes/web.php
      Route::get('/output', [ContentController::class, 'output'])->name('output');
      
    3. 追記後のルーティングファイルの内容を下記に記載する。

      laravel8_crud/routes/web.php
      <?php
      
      use Illuminate\Support\Facades\Route;
      use App\Http\Controllers\ContentController;
      
      /*
      |--------------------------------------------------------------------------
      | Web Routes
      |--------------------------------------------------------------------------
      |
      | Here is where you can register web routes for your application. These
      | routes are loaded by the RouteServiceProvider within a group which
      | contains the "web" middleware group. Now create something great!
      |
      */
      
      Route::get('/', function () {
          return view('welcome');
      });
      
      Auth::routes();
      
      Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');
      
      Route::get('/input', [ContentController::class, 'input'])->name('input');
      Route::post('/save', [ContentController::class, 'save'])->name('save');
      // 下記を追記する
      Route::get('/output', [ContentController::class, 'output'])->name('output');
      
  2. コントローラファイルの記載

    1. laravel8_crudディレクトリで下記コマンドを実行して作成したコントローラファイルを開く。

      $ vi app/Http/Controllers/ContentController.php 
      
    2. 下記の内容をクラス内に追記する。

      laravel8_crud/app/Http/Controllers/ContentController.php
      public function output()
      {
          $contents_get_query = Content::select('*');
          $items = $contents_get_query->get();
      
          return view('contents.output', [
              'items' => $items,
          ]);
      }
      
    3. すでに記載されている投稿内容を保存するアクション内のリダイレクトの記載を変更する。

      laravel8_crud/app/Http/Controllers/ContentController.php
      public function save(Request $request)
      {
          $input_content = new Content();
          $input_content->content = $request['content'];
          $input_content->save();
          //  下記を修正する
          return redirect(route('output'));
      }
      
    4. 両内容の追記後のコントローラファイルの内容を下記に記載する。

      laravel8_crud/app/Http/Controllers/ContentController.php
      <?php
      
      namespace App\Http\Controllers;
      
      use Illuminate\Http\Request;
      use App\Models\Content;
      
      class ContentController extends Controller
      {
          public function input()
          {
              return view('contents.input');
          }
      
          public function save(Request $request)
          {
              $input_content = new Content();
              $input_content->content = $request['content'];
              $input_content->save();
              //  下記を修正する
              return redirect(route('output'));
          }
      
          // 下記を追記する
          public function output()
          {
              $contents_get_query = Content::select('*');
              $items = $contents_get_query->get();
      
              return view('contents.output', [
                  'items' => $items,
              ]);
          }
          // 上記までを追記する
      }
      
  3. ビューファイルの作成と記載

    1. laravel8_crudディレクトリで下記コマンドを実行してビューファイルを作成する。

      vi resources/views/contents/output.blade.php
      
    2. 作成して開いたビューファイルに下記の内容を追記する。

      laravel8_crud/resources/views/contents/output.blade.php
      <h1>output</h1>
      
      @foreach ($items as $item)
          <hr>
          <p>{{$item['content']}}</p>
      @endforeach
      
  4. 確認

    1. laravel8_crudディレクトリで下記コマンドを実行してローカルサーバを起動する。

      $ php artisan serve
      
    2. ブラウザで下記にアクセスする。

    3. 下記の様に投稿内容が表示されることを確認する。

      127_0_0_1_8000_output_と_Zoomミーティング.png

    4. ブラウザで下記にアクセスする。

    5. テキストボックスに何かしらの文字列を入力して「送信」をクリックする。

      127_0_0_1_8000_input.png

    6. 下記の画面にリダイレクトし、入力内容が表示されれば本記事の作業は完了である。

      127_0_0_1_8000_output.png

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

Carbon で1週間の日付と曜日を取得する

PHPのCarbonで、日付と曜日(日本語)のリストを取得する方法をシェアします。

実行環境

  • Laravel Framework 6.18.23
  • "nesbot/carbon": "2.0"

取得したい結果

array: [
    0 => "10/18 (日)"
    1 => "10/19 (月)"
    2 => "10/20 (火)"
    3 => "10/21 (水)"
    4 => "10/22 (木)"
    5 => "10/23 (金)"
    6 => "10/24 (土)"
]

ソースコード

Laravelの config/app.php'locale' => 'ja' の設定しておく。

$numOfDays = 7; //日付を取得する日数

$dt = new Carbon();
$format = 'MM/DD (ddd)';

$week[0] = $dt->today()->isoFormat($format);

//Carbonのインスタンスが上書きされないようにcopy()して日付を加算
for ($i=1; $i < $numOfDays ; $i++) {
  $week[$i] = $dt->copy()->addDay($i)->isoFormat($format);
}

dump($week);

参考

https://carbon.nesbot.com/docs/
https://uiuifree.com/blog/develop/php-carbon-ja-weekday/

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

【実装編】Laravel 8(+jetstream, fortify)でマルチログイン

はじめに

この記事ではLaravel8でマルチログインの実装をしていきます。
動作環境、詳しい仕様は前回の記事をご覧ください。
前回でUserのログイン機能とAdminモデル、テーブルの作成まで完了しました。
なお、本プロジェクトはGitHubにアップしてありますのでご参考ください。

マルチログイン実装

  • 一度ルーティングを確認してみる
    • php artisan route:listでルーティングを確認してみると、すでにUserの認証に必要なルーティングが設定されています。これはLaravel Fortify内部の/{your-workspace-root}/multi-auth/vendor/laravel/fortify/routes/routes.phpで定義されています。Userの認証関係のルーティングはこちらに全て任せることにしましょう。
    • login/logout処理に関してはAuthenticatedSessionControllerが担っていますので、Adminの認証もこちらを参考にしていきます。
+--------+----------+----------------------------------+---------------------------------+---------------------------------------------------------------------------------+---------------------------------------------------------+
| Domain | Method   | URI                              | Name                            | Action                                                                          | Middleware                                              |
+--------+----------+----------------------------------+---------------------------------+---------------------------------------------------------------------------------+---------------------------------------------------------+
|        | POST     | login                            |                                 | Laravel\Fortify\Http\Controllers\AuthenticatedSessionController@store           | App\Http\Middleware\EncryptCookies                      |
|        |          |                                  |                                 |                                                                                 | Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse |
|        |          |                                  |                                 |                                                                                 | Illuminate\Session\Middleware\StartSession              |
|        |          |                                  |                                 |                                                                                 | Laravel\Jetstream\Http\Middleware\AuthenticateSession   |
|        |          |                                  |                                 |                                                                                 | Illuminate\View\Middleware\ShareErrorsFromSession       |
|        |          |                                  |                                 |                                                                                 | App\Http\Middleware\VerifyCsrfToken                     |
|        |          |                                  |                                 |                                                                                 | Illuminate\Routing\Middleware\SubstituteBindings        |
|        |          |                                  |                                 |                                                                                 | App\Http\Middleware\RedirectIfAuthenticated             |
|        | GET|HEAD | login                            | login                           | Laravel\Fortify\Http\Controllers\AuthenticatedSessionController@create          | App\Http\Middleware\EncryptCookies                      |
multi-auth/vendor/laravel/fortify/routes/routes.php
Route::group(['middleware' => config('fortify.middleware', ['web'])], function () {
    // Authentication...
    Route::get('/login', [AuthenticatedSessionController::class, 'create'])
        ->middleware(['guest'])
        ->name('login');

    $limiter = config('fortify.limiters.login');

    Route::post('/login', [AuthenticatedSessionController::class, 'store'])
        ->middleware(array_filter([
            'guest',
            $limiter ? 'throttle:'.$limiter : null,
        ]));

    Route::post('/logout', [AuthenticatedSessionController::class, 'destroy'])
        ->name('logout');
~~省略~~
multi-auth/vendor/laravel/fortify/src/Http/Controllers/AuthenticatedSessionController.php
<?php

namespace Laravel\Fortify\Http\Controllers;

use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Routing\Pipeline;
use Laravel\Fortify\Actions\AttemptToAuthenticate;
use Laravel\Fortify\Actions\EnsureLoginIsNotThrottled;
use Laravel\Fortify\Actions\PrepareAuthenticatedSession;
use Laravel\Fortify\Actions\RedirectIfTwoFactorAuthenticatable;
use Laravel\Fortify\Contracts\LoginResponse;
use Laravel\Fortify\Contracts\LoginViewResponse;
use Laravel\Fortify\Contracts\LogoutResponse;
use Laravel\Fortify\Fortify;
use Laravel\Fortify\Http\Requests\LoginRequest;

class AuthenticatedSessionController extends Controller
{
    /**
     * The guard implementation.
     *
     * @var \Illuminate\Contracts\Auth\StatefulGuard
     */
    protected $guard;

    /**
     * Create a new controller instance.
     *
     * @param  \Illuminate\Contracts\Auth\StatefulGuard
     * @return void
     */
    public function __construct(StatefulGuard $guard)
    {
        $this->guard = $guard;
    }

    /**
     * Show the login view.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Laravel\Fortify\Contracts\LoginViewResponse
     */
    public function create(Request $request): LoginViewResponse
    {
        return app(LoginViewResponse::class);
    }

    /**
     * Attempt to authenticate a new session.
     *
     * @param  \Laravel\Fortify\Http\Requests\LoginRequest  $request
     * @return mixed
     */
    public function store(LoginRequest $request)
    {
        return $this->loginPipeline($request)->then(function ($request) {
            return app(LoginResponse::class);
        });
    }

    /**
     * Get the authentication pipeline instance.
     *
     * @param  \Laravel\Fortify\Http\Requests\LoginRequest  $request
     * @return \Illuminate\Pipeline\Pipeline
     */
    protected function loginPipeline(LoginRequest $request)
    {
        if (Fortify::$authenticateThroughCallback) {
            return (new Pipeline(app()))->send($request)->through(array_filter(
                call_user_func(Fortify::$authenticateThroughCallback, $request)
            ));
        }

        if (is_array(config('fortify.pipelines.login'))) {
            return (new Pipeline(app()))->send($request)->through(array_filter(
                config('fortify.pipelines.login')
            ));
        }

        return (new Pipeline(app()))->send($request)->through(array_filter([
            config('fortify.limiters.login') ? null : EnsureLoginIsNotThrottled::class,
            RedirectIfTwoFactorAuthenticatable::class,
            AttemptToAuthenticate::class,
            PrepareAuthenticatedSession::class,
        ]));
    }

    /**
     * Destroy an authenticated session.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Laravel\Fortify\Contracts\LogoutResponse
     */
    public function destroy(Request $request): LogoutResponse
    {
        $this->guard->logout();

        $request->session()->invalidate();

        $request->session()->regenerateToken();

        return app(LogoutResponse::class);
    }
}

基本的にFortifyの各クラスを参考に実装していきます。それでは作成していきましょう。

AdminLoginResponseクラスの作成

  • multi-auth/app/Responsesディレクトリを作成し、その中にAdminLoginResponseを作成
    • AdminLoginResponseは/{your-workspace-root}/multi-auth/vendor/laravel/fortify/src/Http/Responses/LoginResponse.phpからコピペして一部修正します。
muti-auth/app/Responses/AdminLoginResponse.php
<?php

namespace App\Responses;

use Laravel\Fortify\Contracts\LoginResponse as LoginResponseContract;

class AdminLoginResponse implements LoginResponseContract
{
    /**
     * Create an HTTP response that represents the object.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function toResponse($request)
    {
        return $request->wantsJson()
            ? response()->json(['two_factor' => false])
            : redirect()->intended('admin/dashboard'); // ログイン後に遷移させたいリダイレクト先を指定
    }
}

login.blade.phpの修正

  • Userと同じBladeを使用しますがAdmin用に分けても構いません。今回は簡易にするために、同じBladeですがguard変数があるかどうかでactionのURLを分けています。
multi-auth/resources/views/auth/login.blade.php
<x-guest-layout>
    <x-jet-authentication-card>
        <x-slot name="logo">
            <x-jet-authentication-card-logo />
        </x-slot>

        <x-jet-validation-errors class="mb-4" />

        @if (session('status'))
        <div class="mb-4 font-medium text-sm text-green-600">
            {{ session('status') }}
        </div>
        @endif

        <!-- guard変数がセットされているかによってPOST先を変更 -->
        <form method="POST" action="{{ isset($guard) ? route('admin.login') : route('login') }}">
            @csrf

            <div>
                <x-jet-label for="email" value="{{ __('Email') }}" />
                <x-jet-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email')"
                    required autofocus />
            </div>

            <div class="mt-4">
                <x-jet-label for="password" value="{{ __('Password') }}" />
                <x-jet-input id="password" class="block mt-1 w-full" type="password" name="password" required
                    autocomplete="current-password" />
            </div>

            <div class="block mt-4">
                <label for="remember_me" class="flex items-center">
                    <input id="remember_me" type="checkbox" class="form-checkbox" name="remember">
                    <span class="ml-2 text-sm text-gray-600">{{ __('Remember me') }}</span>
                </label>
            </div>

            <div class="flex items-center justify-end mt-4">
                @if (Route::has('password.request'))
                <a class="underline text-sm text-gray-600 hover:text-gray-900" href="{{ route('password.request') }}">
                    {{ __('Forgot your password?') }}
                </a>
                @endif

                <x-jet-button class="ml-4">
                    {{ __('Login') }}
                </x-jet-button>
            </div>
        </form>
    </x-jet-authentication-card>
</x-guest-layout>

AttemptToAuthenticateクラスをapp/Actions/Admin配下に作成

認証処理を実施するクラスです。

  • {your-workspace-root}/multi-auth/vendor/laravel/fortify/src/Actions/AttemptToAuthenticate.phpからコピペして一部修正します。
multi-auth/app/Actions/Admin/AttemptToAuthenticate.php
<?php

namespace App\Actions\Admin;

use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Validation\ValidationException;
use Laravel\Fortify\Fortify;
use Laravel\Fortify\LoginRateLimiter;

class AttemptToAuthenticate
{
    /**
     * The guard implementation.
     *
     * @var \Illuminate\Contracts\Auth\StatefulGuard
     */
    protected $guard;

    /**
     * The login rate limiter instance.
     *
     * @var \Laravel\Fortify\LoginRateLimiter
     */
    protected $limiter;

    /**
     * Create a new controller instance.
     *
     * @param  \Illuminate\Contracts\Auth\StatefulGuard  $guard
     * @param  \Laravel\Fortify\LoginRateLimiter  $limiter
     * @return void
     */
    public function __construct(StatefulGuard $guard, LoginRateLimiter $limiter)
    {
        $this->guard = $guard;
        $this->limiter = $limiter;
    }

    /**
     * Handle the incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  callable  $next
     * @return mixed
     */
    public function handle($request, $next)
    {
        if (Fortify::$authenticateUsingCallback) {
            return $this->handleUsingCustomCallback($request, $next);
        }

        if ($this->guard->attempt(
            $request->only(Fortify::username(), 'password'),
            $request->filled('remember')
        )) {
            return $next($request);
        }

        $this->throwFailedAuthenticationException($request);
    }

    /**
     * Attempt to authenticate using a custom callback.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  callable  $next
     * @return mixed
     */
    protected function handleUsingCustomCallback($request, $next)
    {
        $user = call_user_func(Fortify::$authenticateUsingCallback, $request);

        if (!$user) {
            return $this->throwFailedAuthenticationException($request);
        }

        $this->guard->login($user, $request->filled('remember'));

        return $next($request);
    }

    /**
     * Throw a failed authentication validation exception.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return void
     *
     * @throws \Illuminate\Validation\ValidationException
     */
    protected function throwFailedAuthenticationException($request)
    {
        $this->limiter->increment($request);

        throw ValidationException::withMessages([
            Fortify::username() => [trans('auth.failed')],
        ]);
    }
}

AdminLoginServiceProviderクラスの作成

  • 先ほど作成したAttemptToAuthenticateクラスと後ほど作成するLoginControllerで使用するguardをDIするためにAdminLoginServiceProviderクラスを作成します。
$ php artisan make:provider AdminLoginServiceProvider
Provider created successfully.
multi-auth/app/Providers/AdminLoginServiceProvider.php
<?php

namespace App\Providers;

use App\Http\Controllers\Auth\LoginController;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Support\Facades\Auth;
use App\Actions\Admin\AttemptToAuthenticate;
use Illuminate\Support\ServiceProvider;

class AdminLoginServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        $this->app
            ->when([LoginController::class, AttemptToAuthenticate::class])
            ->needs(StatefulGuard::class)
            ->give(function () {
                return Auth::guard('admin');
            });
    }

    /**
     * Bootstrap services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }
}

app.phpを修正

作成したAdminLoginServiceProviderが反映されるようにapp.phpを修正します。

multi-auth/config/app.php
~~省略~~
        /*
         * Application Service Providers...
         */
        App\Providers\AppServiceProvider::class,
        App\Providers\AuthServiceProvider::class,
        // App\Providers\BroadcastServiceProvider::class,
        App\Providers\EventServiceProvider::class,
        App\Providers\RouteServiceProvider::class,
        App\Providers\FortifyServiceProvider::class,
        App\Providers\JetstreamServiceProvider::class,
        App\Providers\AdminLoginServiceProvider::class, // この行を追加

Admin認証用のLoginControllerを作成

  • ログイン画面表示や認証、ログアウトなどのアクションを受けるLoginControllerをapp/Http/Controllers/Auth配下に作成します。
  • {your-workspace-root}/multi-auth/vendor/laravel/fortify/src/Http/Controllers/AuthenticatedSessionController.phpを参考に、先ほどまでで作った各クラスを利用するように修正します。
$ php artisan make:controller Auth/LoginController
Controller created successfully.
multi-auth/app/Http/Controllers/Auth/LoginController.php
<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Http\Request;
use Illuminate\Routing\Pipeline;
use App\Actions\Admin\AttemptToAuthenticate;
use Laravel\Fortify\Actions\PrepareAuthenticatedSession;
use App\Responses\AdminLoginResponse;
use Laravel\Fortify\Contracts\LogoutResponse;
use Laravel\Fortify\Http\Requests\LoginRequest;

class LoginController extends Controller
{
    /**
     * The guard implementation.
     *
     * @var \Illuminate\Contracts\Auth\StatefulGuard
     */
    protected $guard;

    /**
     * Create a new controller instance.
     *
     * @param  \Illuminate\Contracts\Auth\StatefulGuard
     * @return void
     */
    public function __construct(StatefulGuard $guard)
    {
        $this->guard = $guard;
    }

    /**
     * Show the login view.
     *
     * @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
     */
    public function create()
    {
        return view('auth.login', ['guard' => 'admin']);
    }

    /**
     * Attempt to authenticate a new session.
     *
     * @param  \Laravel\Fortify\Http\Requests\LoginRequest  $request
     * @return mixed
     */
    public function store(LoginRequest $request)
    {
        return $this->loginPipeline($request)->then(function ($request) {
            return app(AdminLoginResponse::class);
        });
    }

    /**
     * Get the authentication pipeline instance.
     *
     * @param  \Laravel\Fortify\Http\Requests\LoginRequest  $request
     * @return \Illuminate\Pipeline\Pipeline
     */
    protected function loginPipeline(LoginRequest $request)
    {
        return (new Pipeline(app()))->send($request)->through(array_filter([
            AttemptToAuthenticate::class,
            PrepareAuthenticatedSession::class,
        ]));
    }

    /**
     * Destroy an authenticated session.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Laravel\Fortify\Contracts\LogoutResponse
     */
    public function destroy(Request $request): LogoutResponse
    {
        $this->guard->logout();

        $request->session()->invalidate();

        $request->session()->regenerateToken();

        return app(LogoutResponse::class);
    }
}

AdminDashboardControllerとadmin/dashboard.blade.phpの作成

  • 管理者ログイン後のダッシュボード表示用にAdminDashboardControllerとadmin/dashboard.blade.phpを作成します。
  • admin/dashboard.blade.phpはdashboard.blade.phpからコピペして一部編集します。
$ php artisan make:controller AdminDashboardController
Controller created successfully.
multi-auth/app/Http/Controllers/AdminDashboardController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class AdminDashboardController extends Controller
{
    public function index()
    {
        return view('admin.dashboard');
    }
}
/multi-auth/resources/views/admin/dashboard.blade.php
<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            <!-- わかりやすいようにここを修正-->
            管理者ダッシュボード
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-xl sm:rounded-lg">
                <x-jet-welcome />
            </div>
        </div>
    </div>
</x-app-layout>

未ログイン時の挙動を追記

  • User、Adminともに認証が必要なページ(今回はdashboard, admin/dashboard)にアクセスした場合、それぞれのログインページにリダイレクトさせるためにAuthenticate.phpに追記します。
multi-auth/app/Http/Middleware/Authenticate.php
<?php

namespace App\Http\Middleware;

use Illuminate\Auth\Middleware\Authenticate as Middleware;

class Authenticate extends Middleware
{
    /**
     * Get the path the user should be redirected to when they are not authenticated.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return string|null
     */
    protected function redirectTo($request)
    {
        if (!$request->expectsJson()) {
            if ($request->is('admin/*')) {
                return route('admin.login');
            }
            return route('login');
        }
    }
}

web.phpの修正

  • 管理者ログインとダッシュボード用のルーティングを追記します。
multi-auth/routes/web.php
<?php

use App\Http\Controllers\AdminDashboardController;
use App\Http\Controllers\Auth\LoginController;
use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

Route::prefix('admin')->group(function () {
    Route::get('login', [LoginController::class, 'create'])->name('admin.login');
    Route::post('login', [LoginController::class, 'store']);

    Route::middleware('auth:admin')->group(function () {
        Route::get('dashboard', [AdminDashboardController::class, 'index']);
    });
});

Route::middleware(['auth:web', 'verified'])->get('/dashboard', function () {
    return view('dashboard');
})->name('dashboard');

auth.phpの修正

  • 最後にauth.phpを修正し、Admin用の認証ガードであるadminを定義します。
  • ファイルを修正した後はphp artisan config:cacheで設定値を反映させましょう。
multi-auth/config/auth.php
~~省略~~
    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],

        'admin' => [
            'driver' => 'session',
            'provider' => 'admins',
        ],

        'api' => [
            'driver' => 'token',
            'provider' => 'users',
            'hash' => false,
        ],
    ],
~~省略~~
    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class,
        ],
        'admins' => [
            'driver' => 'eloquent',
            'model' => App\Models\Admin::class,
        ],

        // 'users' => [
        //     'driver' => 'database',
        //     'table' => 'users',
        // ],
    ],
~~省略~~
    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => 'password_resets',
            'expire' => 60,
            'throttle' => 60,
        ],

        'admins' => [
            'provider' => 'admins',
            'table' => 'password_resets',
            'expire' => 60,
            'throttle' => 60,
        ],
    ],

ビルトインサーバーを起動して(php artisan serve)管理者ログインができるか確認

スクリーンショット 2020-10-19 19.41.51.png
スクリーンショット 2020-10-19 19.42.00.png

無事に管理者ログインできました:tada:

最後に

Fortifyではかなり多くのクラスが登場し、なおかつDIがかなり使われているため最初はなかなかコードを追うのが大変でしたが、少しずつ読み解いていくことで新しい認証スキャフォールドについて理解を深めることができました。Laravelはいいぞ。

誤字・脱字、間違った実装などありましたらコメント等いただけますと幸いです?
それでは良いLaravelライフを?

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

【準備編】Laravel 8(+jetstream, fortify)でマルチログイン

はじめに

Laravel8から標準のユーザー認証機能がlaravel/uiからLaravel Jetstream + Laravel Fortifyに変更されました。新しい認証機能もlaravel/uiと同様に導入自体は非常に簡単で、かつ多機能(プロフィール管理、チーム管理、二段階認証などなど)、必要に応じてconfigファイルで利用する機能を選択することが可能です。その一方で認証処理の実装が変わっていることもあり、拡張するには少し手間がかかります(ました)。ドキュメントでは下記のように言及されていますが、

Laravel Jetstreamが用意する認証スカフォールドを使う必要はないことに留意してください。このスカフォールドを使用しない選択をした場合、Laravelの認証クラスを直接使用し、ユーザーの認証管理を行う必要があります。

せっかくならば新しい認証機能使いたいぞ!
ということで、本記事では一般ユーザーと管理者ユーザーでログイン画面、ログイン後の画面を分けるマルチログインをLaravel JetstreamとLaravel Fortifyを使って実装していきます。
なお、Laravel Fortifyの詳しい処理を深く追ったり説明したり、ということはこの記事ではしません。

動作環境

本記事では全てローカルで動作させます

  • PHP 7.4.11
  • MySQL 8.0.21
  • Laravel Framework 8.10.0

マルチログインの仕様

  • 一般ユーザー(User)、管理者(Admin)でログインユーザーとページを分ける
  • ルーティングは以下とする(本記事ではドメインはlocalhost:8000として扱います)
    • 一般ユーザーログインページ
      • localhost:8000/login
    • 一般ユーザートップページ
      • localhost:8000/dashboard
    • 管理者ログインページ
      • localhost:8000/admin/login
    • 管理者トップページ
      • localhost:8000/admin/dashboard
  • 両ユーザー共に未ログインの時にはトップページを表示することはできない(ログインページにリダイレクトさせる)

プロジェクトの準備

  • プロジェクトの生成(今回はmulti-authというプロジェクトにしています)
    • Blade + livewireを使用するのでWhich Jetstream stack do you prefer?は0を選択
    • teamsは使用しないのでWill your application use teams?はnoを選択
$ laravel new multi-auth


    |     |         |
    |,---.|--- ,---.|--- ,---.,---.,---.,-.-.
    ||---'|    `---.|    |    |---',---|| | |
`---'`---'`---'`---'`---'`    `---'`---^` ' '


Which Jetstream stack do you prefer?
  [0] livewire
  [1] inertia
 > 0

 Will your application use teams? (yes/no) [no]:
 > no

Installing laravel/laravel (v8.1.0)
  - Installing laravel/laravel (v8.1.0): Downloading (100%)         
Created project in /{your-workspace-root}/multi-auth
~~省略~~
Application ready! Build something amazing.
  • プロジェクトが生成されたらアセットのビルドを実行
$ cd multi-auth
$ npm install && npm run dev
  • .envのDB_DATABASEと同じ名前のDBを、ローカルのMySQLで作成してmigrationを実行
.env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=multi_auth ← これ
DB_USERNAME=root
DB_PASSWORD=
$ php artisan migrate

Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (42.86ms)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (19.50ms)
Migrating: 2014_10_12_200000_add_two_factor_columns_to_users_table
Migrated:  2014_10_12_200000_add_two_factor_columns_to_users_table (22.99ms)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_08_19_000000_create_failed_jobs_table (25.19ms)
Migrating: 2019_12_14_000001_create_personal_access_tokens_table
Migrated:  2019_12_14_000001_create_personal_access_tokens_table (39.09ms)
Migrating: 2020_10_14_073012_create_sessions_table
Migrated:  2020_10_14_073012_create_sessions_table (52.85ms)
  • Adminモデルの作成とmigrationを実行
    • create_users_table.phpからupメソッドの中身をコピペ
$ php artisan make:model Admin -m
Model created successfully.
Created Migration: 2020_10_14_075325_create_admins_table
create_admins_table.php
~~省略~~
    public function up()
    {
        Schema::create('admins', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->foreignId('current_team_id')->nullable();
            $table->text('profile_photo_path')->nullable();
            $table->timestamps();
        });
    }
$ php artisan migrate
Migrating: 2020_10_14_075325_create_admins_table
Migrated:  2020_10_14_075325_create_admins_table (31.63ms)
  • Adminモデルの内容を修正(User.phpからコピペ)
Admin.php
<?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Fortify\TwoFactorAuthenticatable;
use Laravel\Jetstream\HasProfilePhoto;
use Laravel\Sanctum\HasApiTokens;

class Admin extends Authenticatable
{
    use HasApiTokens;
    use HasFactory;
    use HasProfilePhoto;
    use Notifiable;
    use TwoFactorAuthenticatable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password',
        'remember_token',
        'two_factor_recovery_codes',
        'two_factor_secret',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    /**
     * The accessors to append to the model's array form.
     *
     * @var array
     */
    protected $appends = [
        'profile_photo_url',
    ];
}

  • 一般ユーザーと管理者をシーダーで生成できるようにする
$ php artisan make:seeder UserSeeder
Seeder created successfully.
$ php artisan make:seeder AdminSeeder
Seeder created successfully.
UserSeeder.php
<?php

namespace Database\Seeders;

use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;

class UserSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        User::create([
            'name' => '一般ユーザー',
            'email' => 'user@user.user',
            'password' => Hash::make('password'),
        ]);
    }
}
AdminSeeder.php
<?php

namespace Database\Seeders;

use App\Models\Admin;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;

class AdminSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        Admin::create([
            'name' => '管理者',
            'email' => 'admin@admin.admin',
            'password' => Hash::make('password')
        ]);
    }
}
DatabaseSeeder.php
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        $this->call(UserSeeder::class);
        $this->call(AdminSeeder::class);
    }
}
$ php artisan db:seed 
Seeding: Database\Seeders\UserSeeder
Seeded:  Database\Seeders\UserSeeder (250.64ms)
Seeding: Database\Seeders\AdminSeeder
Seeded:  Database\Seeders\AdminSeeder (106.95ms)
Database seeding completed successfully.
  • ビルトインサーバーを起動してブラウザでlocalhost:8000を表示
    • localhost:8000/loginにて一般ユーザーでログインできるか確認
$ php artisan serve

Starting Laravel development server: http://127.0.0.1:8000
[Mon Oct 1 00:00:00 0000] PHP 7.4.11 Development Server (http://127.0.0.1:8000) started

スクリーンショット 2020-10-19 14.54.04.png

スクリーンショット 2020-10-19 14.53.37.png

無事ログインできれば準備編は終了

実装編に続きます

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

Laravel7 でCSSが読み込めない

Laravel 7.25.0です。

CSSの場所:
Laravel/public/css/app.css
HTMLの場所
Laravel/resources/views/layouts.blade.php

問題点

何故かCSSが読み込めなかった。

解決

<link rel="stylesheet" href="/css/app.css">

<link rel="stylesheet" href="{{ asset('css/app.css') }}">

に変えたら読み込めました。

原因

不明です。(すみません)

参考

https://teratail.com/questions/126308

おしまい。

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

Laravel 【初心者】 PHPフレームワーク Laravel入門 第2版 の内容を一部図式化してみた件 ⑪フォームリクエスト

はじめに

この記事はプログラミングをはじめたばかりの素人が、メモするのに利用しています。
内容には誤りがあるかもしれません。

PHPフレームワーク Laravel入門 第2版 について

Laravelを独学で学ぶにあたり、評判がよかったのでAmazon Kindleで購入しました。
内容も色々な方法が書いてあるので、Laravelをはじめて勉強される方にはおすすめの本です。

Amazon リンク
https://www.amazon.co.jp/gp/product/B08625YD7H/ref=ppx_yo_dt_b_d_asin_image_o09?ie=UTF8&psc=1

図式化について

学習中に、文字だけだとイメージが掴みづらかったので、自分の為に作成したものです。
また方法が色々あり過ぎて混乱したので、整理する意味もあります。
ディレクトリを覚えるのにも使いました。

コードは必要なものだけ抜粋しているので、全てではありません。
これだけ見ても勉強にはならないので、PHPフレームワーク Laravel入門 第2版を是非購入してみて下さい。

※ちなみに著者の方とは何の関係もありません。

素人が作成した物なので、参考程度に使ってください。

フォームリクエスト

fr.jpg

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

Laravel 【初心者】 PHPフレームワーク Laravel入門 第2版 の内容を一部図式化してみた件 ⑩バリデーション

はじめに

この記事はプログラミングをはじめたばかりの素人が、メモするのに利用しています。
内容には誤りがあるかもしれません。

PHPフレームワーク Laravel入門 第2版 について

Laravelを独学で学ぶにあたり、評判がよかったのでAmazon Kindleで購入しました。
内容も色々な方法が書いてあるので、Laravelをはじめて勉強される方にはおすすめの本です。

Amazon リンク
https://www.amazon.co.jp/gp/product/B08625YD7H/ref=ppx_yo_dt_b_d_asin_image_o09?ie=UTF8&psc=1

図式化について

学習中に、文字だけだとイメージが掴みづらかったので、自分の為に作成したものです。
また方法が色々あり過ぎて混乱したので、整理する意味もあります。
ディレクトリを覚えるのにも使いました。

コードは必要なものだけ抜粋しているので、全てではありません。
これだけ見ても勉強にはならないので、PHPフレームワーク Laravel入門 第2版を是非購入してみて下さい。

※ちなみに著者の方とは何の関係もありません。

素人が作成した物なので、参考程度に使ってください。

バリデーション

v1.jpg

バリデーション(エラー表示)

v2.jpg

バリデーション(フィールド毎にエラー表示)

v3.jpg

バリデーション(ディレクティブを利用したエラー表示)

v4.jpg

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

Laravel 【初心者】 PHPフレームワーク Laravel入門 第2版 の内容を一部図式化してみた件 ⑨グローバルミドルウェア ミドルウェアグループ

はじめに

この記事はプログラミングをはじめたばかりの素人が、メモするのに利用しています。
内容には誤りがあるかもしれません。

PHPフレームワーク Laravel入門 第2版 について

Laravelを独学で学ぶにあたり、評判がよかったのでAmazon Kindleで購入しました。
内容も色々な方法が書いてあるので、Laravelをはじめて勉強される方にはおすすめの本です。

Amazon リンク
https://www.amazon.co.jp/gp/product/B08625YD7H/ref=ppx_yo_dt_b_d_asin_image_o09?ie=UTF8&psc=1

図式化について

学習中に、文字だけだとイメージが掴みづらかったので、自分の為に作成したものです。
また方法が色々あり過ぎて混乱したので、整理する意味もあります。
ディレクトリを覚えるのにも使いました。

コードは必要なものだけ抜粋しているので、全てではありません。
これだけ見ても勉強にはならないので、PHPフレームワーク Laravel入門 第2版を是非購入してみて下さい。

※ちなみに著者の方とは何の関係もありません。

素人が作成した物なので、参考程度に使ってください。

グローバルミドルウェア ミドルウェアグループ

gmiddle.jpg

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

Laravel 【初心者】 PHPフレームワーク Laravel入門 第2版 の内容を一部図式化してみた件 ⑧アクション後に実行するミドルウェア

はじめに

この記事はプログラミングをはじめたばかりの素人が、メモするのに利用しています。
内容には誤りがあるかもしれません。

PHPフレームワーク Laravel入門 第2版 について

Laravelを独学で学ぶにあたり、評判がよかったのでAmazon Kindleで購入しました。
内容も色々な方法が書いてあるので、Laravelをはじめて勉強される方にはおすすめの本です。

Amazon リンク
https://www.amazon.co.jp/gp/product/B08625YD7H/ref=ppx_yo_dt_b_d_asin_image_o09?ie=UTF8&psc=1

図式化について

学習中に、文字だけだとイメージが掴みづらかったので、自分の為に作成したものです。
また方法が色々あり過ぎて混乱したので、整理する意味もあります。
ディレクトリを覚えるのにも使いました。

コードは必要なものだけ抜粋しているので、全てではありません。
これだけ見ても勉強にはならないので、PHPフレームワーク Laravel入門 第2版を是非購入してみて下さい。

※ちなみに著者の方とは何の関係もありません。

素人が作成した物なので、参考程度に使ってください。

アクション後に実行するミドルウェア

amiddle.jpg

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

Laravel 【初心者】 PHPフレームワーク Laravel入門 第2版 の内容を一部図式化してみた件 ⑦アクション前に実行するミドルウェア

はじめに

この記事はプログラミングをはじめたばかりの素人が、メモするのに利用しています。
内容には誤りがあるかもしれません。

PHPフレームワーク Laravel入門 第2版 について

Laravelを独学で学ぶにあたり、評判がよかったのでAmazon Kindleで購入しました。
内容も色々な方法が書いてあるので、Laravelをはじめて勉強される方にはおすすめの本です。

Amazon リンク
https://www.amazon.co.jp/gp/product/B08625YD7H/ref=ppx_yo_dt_b_d_asin_image_o09?ie=UTF8&psc=1

図式化について

学習中に、文字だけだとイメージが掴みづらかったので、自分の為に作成したものです。
また方法が色々あり過ぎて混乱したので、整理する意味もあります。
ディレクトリを覚えるのにも使いました。

コードは必要なものだけ抜粋しているので、全てではありません。
これだけ見ても勉強にはならないので、PHPフレームワーク Laravel入門 第2版を是非購入してみて下さい。

※ちなみに著者の方とは何の関係もありません。

素人が作成した物なので、参考程度に使ってください。

アクション前に実行するミドルウェア

bmiddle.jpg

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

Laravel7マイグレートエラー(SQLSTATE[HY000]: General error: 1215 Cannot add foreign key constraint)

Laravelで、タグ付けのできる投稿サイトをチームで制作しています。
投稿テーブル(reviews)を先に作っていたのですが、今回タグテーブル(tags)を作成し、reviewsとはreview_tagという中間テーブルで多対多のリレーションを作成したいです。
しかし、マイグレートした時にエラーで少しだけ苦しんだのでメモしておきます。

問題点はここ

$ php artisan migrate

Migrating: 2020_09_19_035606_create_tags_table
Migrated:  2020_09_19_035606_create_tags_table (0.08 seconds)
Migrating: 2020_09_19_053140_create_review_tag_table

   Illuminate\Database\QueryException 

  SQLSTATE[HY000]: General error: 1215 Cannot add foreign key constraint (SQL: alter table `review_tag` add constraint `review_tag_tag_id_foreign` foreign key (`tag_id`) references `tags` (`id`) on delete cascade)

外部キーが設定できないぜと仰っているようです。いろいろ調べると、主キーidの設定でBIGINTとINTは両立できない模様

エラーになった時の3つの各テーブルのマイグレーションファイルは下のようになっていました。主キーを設定している箇所にご注目ください。

reviewsテーブルのマイグレーションファイル↓

XXXXXX_create_reviews_table.php
class CreateReviewsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('reviews', function (Blueprint $table) {
            $table->id();//BIGINTの主キー
            $table->date('live_date');
            $table->string('title', 255);
            $table->text('text');
            $table->timestamps(0);
            $table->foreignId('user_id')->constrained('users');
        });
    }

tagsテーブルのマイグレーションファイル↓

XXXXXX_create_tag_table.php
class CreateTagsTable extends Migration
{
     * @return void
     */
    public function up()
    {
        Schema::create('tags', function (Blueprint $table) {
            $table->increments('id');//BIGINTではないid主キー
            $table->string('tag_name', 30);
            $table->string('info');
            $table->string('url');
            $table->timestamps();
        });
    }

review_tag中間テーブルのマイグレーションファイル↓

XXXXXX_create_review_tag_table.php
public function up()
    {
        Schema::create('review_tag', function (Blueprint $table) {
            $table->increments('id');//BIGINTではないid主キー
            $table->timestamps();
            $table->foreignId('review_id')->constrained('reviews')->onDelete('cascade'); //foreignIdはunsignedBigIntegerのエイリアス
            $table->foreignId('tag_id')->constrained('tags')->onDelete('cascade'); //foreignIdはunsignedBigIntegerのエイリアス
        });
    }

こう解決しました

まず、MySQLにてdrop table テーブル名;でtagsとreview_tagを一旦削除。
次に、tagsとreview_tagのマイグレーションファイルにて、$table->increments('id');$table->id();に変更。
(正直、reviewsと中間テーブルを挟んで間接的にしか繋がってない(?)tagsテーブルの方のマイグレーションファイルのidも修正が必要だったのかは不明...)
こうして再度マイグレートしたら、エラー出ずに上手くいきました。

$ php artisan migrate
Migrating: 2020_09_19_035606_create_tags_table
Migrated:  2020_09_19_035606_create_tags_table (0.03 seconds)
Migrating: 2020_09_19_053140_create_review_tag_table
Migrated:  2020_09_19_053140_create_review_tag_table (0.09 seconds)

以上になります。ごもし指摘などあれば、ぜひよろしくお願いします。

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