LoginSignup

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

Laravel教材

Last updated at Posted at 2022-05-11

フレームワークとは

本教材ではPHPのフレームワークであるLaravelについて学習しますが、Laravelを学習する前にフレームワークがどのようなものかについて説明します。

フレームワークを一言で説明すると、「アプリケーションやシステムを開発するために必要な機能が詰まっているパッケージ」となります。

例えば新しいサービスを開発する場合、いかに早く、適切なサービスを開発することができるかが重要になります。
なぜなら、リリース日が遅れてしまったりサービスに不備があった場合、競合他社に市場シェアを先取りされてしまったり、市場からの信用を失ってしまうことになりかねないからです。

そのため、多くの開発現場では素早く安全にアプリケーションを開発するためにフレームワークを使用して開発することが一般的です。

また、フレームワークにはたくさんの種類があり、使用するプログラミング言語やできることが異なります。
もちろんPHPのフレームワークもたくさん存在していて、LaravelやCakePHP、Symfony、CodeIgniter、Phalcon、FuelPHPなどがあります。

一方でフレームワークを使用するデメリットも存在します。
それは、プログラミング言語の知識以外にフレームワーク自体の理解も必要となることです。
しかし、多くのフレームワークはとても分かりやすく、使用していて楽しいものです。

これまでたくさんの課題を乗り越えてきた皆さんなら、ある程度集中して学習すればフレームワークを使用した開発にも慣れていただけると思います。
ぜひ安心してフレームワークの学習に取り組んでください。

★検索ワード
・Webフレームワーク
・フレームワーク メリット デメリット
・PHP フレームワーク

Laravelとは

Laravel(ララベル)は2011年にリリースされた比較的新しいフレームワークですが、PHPの中で1番人気のあるフレームワークです。

主にWebアプリケーションを作成する際に用いられるフレームワークですが、スマートフォン用アプリのバックエンド側やシステム開発など幅広いジャンルでの活用が可能となっています。

また、Laravelはとてもわかりやすい設計のため、学習コストが低く、世界的に人気であり、拡張性も高いことから、初めて学習するフレームワークとして最適です。

Laravelのバージョン

Laravelは比較的後発のフレームワークであり、2011年6月にバージョン1がリリースされました。
その後バージョン2、3と早いペースでリリースされた後に、バージョン4ではほぼゼロベースで再構築され、抜本的な変更が行われました。
その後、5、6、7、8、9と徐々に新しいバージョンがリリースされ、現在はバージョン10が最新となっています。

今後は毎年1回メジャーバージョンアップがされる予定になっているので、2024年にはバージョン11のリリースが予定されています。

このように毎年アップデートされているLaravelですが、基本的な部分はすでに確立されており、多少アップデートが入っても大きな変更はあまりありません。
そのため、今回は最新バージョンのLaravel10を使用して学習を進めていきますが、6以降のバージョンではそれほど変化はないので、まずはバージョンを気にせず、最新バージョンの10を学んでいきましょう!

Laravelのドキュメントについて

Laravelが世界中の開発者に選ばれている理由の1つとして、公式マニュアルがとても充実していることが挙げられます。
また、非公式ではありますが有志の方が作成している日本語訳のドキュメントもあります。

公式マニュアルは学習を開始して間もないころだと読みにくいものかもしれませんが、「こういうものがあるんだな。」程度に知っておくと後々の開発で役立ちますのでブックマークをしておきましょう。

★検索ワード
・Laravel できること
・Laravel document
・Laravel ドキュメント

Laravelで作成するもの

Laravelを効率よく学習していただくために、まずは簡単なブログシステムを開発していきます。

ネイティブPHPでもブログシステムを開発したと思いますが、Laravelで改めてブログシステムを作成することで、フレームワークを使うことのメリットを感じていただければと思います。

CRUD(クラッド)とは

実はブログシステムを開発することで、アプリケーションの基本となるCRUDを学ぶことができます。
CRUDとは、CreateReadUpdateDeleteの頭文字で、ほとんどのアプリケーションが持っている4つの基本機能のことです。

これら4つの基本機能は、下記の通りです。

  • Create:生成・登録機能→新しいデータの登録ができる機能
  • Read:読取・表示機能→登録されているデータの表示ができる機能
  • Update:更新機能→登録されているデータの更新ができる機能
  • Delete:削除機能→登録されているデータの削除ができる機能

例としてTwitterを考えてみましょう。
Twitterでは、「ツイートを投稿する」、「ツイートを表示する」、「ユーザー登録情報を更新する」、「ツイートを削除する」など様々なことができます。
このように、基本的なアプリケーションはCRUD操作をすることができます。

そのため、まずはブログシステムの開発を通してLaravelで記事の投稿・表示・更新・削除機能を作成することでCRUD操作について学習していきましょう。

★検索ワード
・CRUDとは

環境構築

PHPの学習前に環境構築としてMAMPXAMPPをインストールしたように、Laravelを学ぶ前にも環境構築が必要になります。

Laravelの開発環境を構築する方法はいくつかありますが、今回は公式ドキュメントでもおすすめされているLaravel Sailを用いた環境構築を行っていきます。
Laravel Sailを簡単に説明すると、Laravelの開発環境をDockerで簡単に構築できる機能となります。

ここでDockerという新しいサービスが登場しました。
Laravel Sailは、Dockerの経験がない人でも簡単にLaravelの環境構築ができる機能なので、Dockerについての知識は必要ありませんが、簡単に説明しておきます。

Dockerとは

Dockerとは開発に必要な環境をOS(Windows OSやmacOSなど)レベルでまとめてくれるサービスのことです。
これまで開発環境を構築するためには、各OSやパソコンごとに必要な手順が異なっていました。
しかし、Dockerが登場したことでOSに関係なくコマンドを複数回入力するだけで開発環境を構築することができるようになりました。

つまり、Dockerは環境構築を簡単にしてくれる便利機能だと思っていただければ大丈夫です!

Laravel Sailでの環境構築

それでは、さっそくLaravel Sailでの環境構築を行っていきましょう!
ちなみに、Laravel Sailでの環境構築方法はLaravelの日本語訳ドキュメントにも記載がありますので、1度目を通してみてもいいでしょう。
・Laravelの日本語訳ドキュメント:Laravelの環境構築方法

Laravel Sailでの開発環境構築方法はMacWindowsで違う部分がありますので、それぞれ分けて解説していきます。

Macでの環境構築

それではMacでの開発環境構築方法を説明していきます。
※Windowsの方はこちらのレクチャーを飛ばして下さい。

MacでLaravel Sailを使用するためには、MAMPのストップMac本体の設定変更Docker Desktopが必要になります。

MAMPのストップ

DockerとMAMPを同時に起動するとエラーが起こる可能性があるので、必ずMAMPはSTOPしておいてください。

設定変更

まずはMac本体の設定から変更していきます。
OSのバージョン差異によって設定方法が下記説明通り行かない場合がございます。
その場合はmac フルディスクアクセス ターミナルで検索してみてください。

画面左上のアップルマークをクリックして、システム環境設定をクリックしてください。
スクリーンショット 2022-06-01 21.47.36.png
システム環境設定が開くので、セキュリティとプライバシーをクリックしてください
スクリーンショット 2022-06-01 21.50.08.png
プライバシーを選択しフルディスクアクセスをクリック、左下の鍵マークをクリックするとMac本体のパスワード入力が求められるので、パスワードを入力してください。
そして、ターミナルのチェックボックスにチェックを入れてください。
202206092300.png
するとターミナルを再起動するか聞かれるので、終了して再度開くをクリックしてください。
※自動的に再起動がされない場合は、手動でターミナルを再起動してください。

ターミナルが再起動されればMacの設定変更は完了です。

Docker Desktopのインストール

次にDocker Desktopをインストールしましょう。
下記URLからDocker Desktopをインストールして下さい。
https://www.docker.com/products/docker-desktop/

インストール手順は頻繁に変更されるので、下記説明通りいかない場合がございます。
その場合はmac docker desktop インストールで検索をしてみてください。

URLにアクセスすると下記のような画面が出てきます。
2022年4月現在、MacにはIntel製のチップApple製のチップの2種類が存在しています。
ご自身のチップに合う方のボタン(赤枠で囲われている)をクリックして下さい。
スクリーンショット 2022-05-26 14.13.09.png

なお、ご自身のMacがどちらのチップかわからない場合は画面左上のアップルマークをクリックして、このMacについてをクリックして下さい。
スクリーンショット 2022-05-26 14.37.47.png
概要をクリックしてチップを確認して下さい。
下記画像のようにApple M1などとなっている場合はApple製のチップです。

下記画像のようにIntel Core i7などとなっている場合はIntel製のチップです。

どちらかのボタンをクリックすると、チップに合ったDocker.dmgファイルがダウンロードされるので、ダウンロードしたDocker.dmgをダブルクリックして下さい。

下記画像のようなウィンドウが出てくるので、そのウィンドウ内でDockerアイコンApplicationsフォルダにドラッグします。
スクリーンショット 2022-05-26 15.00.19.png
Docker.appを起動して下さい。
スクリーンショット 2022-05-26 15.03.58.png
警告が表示されますが、開くをクリックして下さい。
スクリーンショット 2022-05-26 15.04.51.png
アクセス権限の可否を聞かれた場合はOKボタンをクリックして、Mac本体のパスワードを入力して下さい。
スクリーンショット 2022-05-26 15.11.48.png

これでDocker Desktopのインストールは完了です。

Laravelプロジェクト作成

次にLaravel Sailを使用して、Laravelの開発環境を構築していきます。
今回はデスクトップにLaravelフォルダを作成し、その中にさらにfirst-appフォルダを作成してから、first-appフォルダ内にLaravelのファイル一式を格納します。

ターミナルを起動して、下記3つのコマンドを実行して下さい。

ターミナル
$ cd ~/Desktop
$ mkdir Laravel
$ cd Laravel

上記コマンドを入力すると、デスクトップにLaravelフォルダが作成され、ターミナルでLaravelフォルダが選択状態になります。

次に下記コマンドを入力して下さい。

ターミナル
$ curl -s "https://laravel.build/first-app" | bash

これでLaravelファイル一式のダウンロードが始まります。
コマンド内にfirst-appとありますが、ここがフォルダ名になります。
例えば、first-appmy-appなどに変更してコマンドを実行すると、Laravelフォルダ下にフォルダ名my-appが作成されます。

Docker is not running.などのエラーでダウンロードが始まらない場合は、Dockerのアプリケーションが開いているか確認しましょう。

なお、途中で下記画像のようにMacのパスワード入力が求められるので入力して下さい。
※文字を入力してもターミナルには何も反映されませんが、そのまま入力して下さい。
スクリーンショット 2022-05-26 15.35.59.png
下記画像のようになればLaravelファイル一式のダウンロードが完了し、デスクトップ上のLaravelフォルダ内にfirst-appというフォルダのLaravelプロジェクトが作成されています。
スクリーンショット 2022-06-01 22.25.51.png
下記コマンドを入力してLaravel Sailを起動します。

ターミナル
$ cd first-app
$ ./vendor/bin/sail up -d

終了まで時間がかかるので、気長に待ちましょう。
無事に実行が完了すると以下のような画面になります。
スクリーンショット 2022-06-01 22.50.33.png
次はhttp://localhost/へアクセスしてください。
下記画像のような画面が出てくればOKです。
※画像はMacの設定をダークモードにしているため、全体的に黒色のサイトになっています。
スクリーンショット 2022-05-30 3.17.42.png
次に、下記コマンドを実行してみましょう。

ターミナル
$ ./vendor/bin/sail stop

無事に実行が完了すると以下のような画面になります。
スクリーンショット 2022-06-01 23.11.27.png
このコマンドはシャットダウンと同じで開発環境をオフにすることができます。
もちろん開発環境をオフにしたので、http://localhost/にアクセスしてもLaravelの画面は表示できません。
Macをシャットダウンする時や開発を中断する時は、このコマンドを実行してください。

開発を再開する場合は、ターミナルで下記コマンドを~/Desktop/Laravel/first-app配下で実行しましょう。

ターミナル
$ ./vendor/bin/sail up -d

Windowsでの環境構築

次にWindowsでの環境構築方法を説明していきます。
※Macの方はこちらのレクチャーを飛ばして下さい。

WindowsでLaravel Sailを使用するには、XAMPPのストップWSL2Docker Desktopが必要になります。

XAMPPのストップ

DockerとXAMPPを同時に起動するとエラーが起こる可能性があるので、必ずXAMPPはSTOPしておいてください。

WSL2のインストール

WSLはWindows Subsystem for Linuxの略で、Windows上でLinux環境を実行することができます。
Macの場合、ターミナル(terminal)というアプリケーションでLinuxコマンドを実行することができますが、WindowsはデフォルトでLinuxコマンドを実行できないので、WSL2をインストールします。

画面左下の検索窓(Windowsマークの隣)でPowerShellと検索し、Windows PowerShellを選択して、管理者として実行するをクリックしてください。
20220530122500.png
管理者で実行できたら、下記画像の通りwsl --installと入力し、エンターを押してください。
ちなみに、テキストを入力してエンターを押すことをコマンドを実行するなどと言います。
20220529205604.png
下記画像のようになれば、WSL2のインストールは完了です。
20220530123300.png
WSL2を有効にするためには再起動が必要なので、パソコンを再起動してください。

万が一、下記画像のようなエラーが発生した場合はWindowsのアップデートが必要です。
20220529212020.png

Windowsのアップデート方法は、画面左下のWindowsマークをクリックして、設定をクリック、Windows Updateをクリックして、更新プログラムの確認をクリックすると最新のWindowsにアップデートすることができます。
参考URL:Windows の更新

アップデートが完了次第、PowerShellにて管理者としてwsl --installを実行しましょう。

WSL2の設定

再起動後、自動でUbuntuというアプリケーションが起動されます。
※自動でUbuntuが起動しない場合は、画面左下の検索窓(Windowsマークの隣)でUbuntuと検索し、開くをクリックしてください。

Ubuntuが起動したら、画面に従ってユーザー名パスワードを設定してください。
ここで設定したユーザー名とパスワードは忘れないようにしましょう。
※パスワード入力時は何も表示されませんが、気にせず入力してください。
スクリーンショット 2022-05-30 12.57.11.png
ユーザー名とパスワードが設定できると下記のような画面になります。
202205302200.png
これでWSL2の準備は完了です。

なお、以降Ubuntuアプリのことをターミナルと表記致します。
ターミナルでコマンドを実行してください。」という指示があった場合は、Ubuntuアプリでコマンドを実行してください。

また、万が一下記画像のようなエラーが出た場合は、BIOSの設定変更が必要になります。
20220530215700.png
BIOSの起動方法は、基本パソコン起動直後のメーカーロゴ表示中にDeleteキーかF2キーを押すことが一般的ですが、パソコンメーカーによって異なります。
ご自身のパソコンメーカー名でBIOSの起動方法を調べてください。

例えば、「Lenovo」の場合は、Lenovo BIOS 起動方法などで検索してください。
自作PCを使用している方は、マザーボードのメーカー名で検索してください。

BIOSを起動できた方はVirtualization TechnologyEnableにしてください。
Virtualization Technologyの場所もパソコンメーカーで異なるので、パソコンメーカー名 Virtualization Technology Enableなどで検索してください。

無事にVirtualization TechnologyEnableにできたら、保存してBIOSを終了してください。

その後、画面左下の検索窓(Windowsマークの隣)でUbuntuと検索し、開くをクリックしてください。
そして、画面に従ってユーザ名とパスワードを設定してください。
※パスワード入力時は何も表示されませんが、気にせず入力してください。

Docker Desktopのインストール

下記URLからDocker Desktopをインストールして下さい。
https://www.docker.com/products/docker-desktop/

インストール手順は頻繁に変更されるので、下記説明通りいかない場合がございます。
その場合はwindows docker desktop インストールで検索をしてみてください。
スクリーンショット 2022-05-30 22.03.21.png
Also available for Windows and Linuxと書いてあるので、Windowsをクリックしてください。

クリックすると、Docker Desktop Installer.exeファイルがダウンロードされるので、そのファイルを開いてください。

ファイルを開くとインストール画面が出てくるので、画面に従ってインストールしてください。
インストールが完了すると、利用規約などに同意するか聞かれる場合があるので、聞かれた場合は同意(accept)してください。

初回起動時はチュートリアルなどがあるかもしれませんが、全てスキップしてしまって大丈夫です。

下記のような画面が出てくればインストールは無事に完了しています。
20220530222233.png
ここまでくればWindowsでLaravel Sailが使えるようになりました。

Laravelプロジェクト作成

次にLaravel Sailを使用して、Laravelの開発環境を構築していきます。
今回はホームディレクトリにLaravelフォルダを作成して、その中にさらにfirst-appフォルダを作成してから、first-appフォルダ内にLaravelのファイル一式を格納します。

画面左下の検索窓(Windowsマークの隣)でUbuntuと検索し、開くをクリックしてUbuntuアプリ(ターミナル)を起動してください。
起動が完了したら、下記コマンドを入力してください。

ubuntu
$ cd ~
$ mkdir Laravel
$ cd Laravel

上記コマンドを実行すると、ホームディレクトリにLaravelフォルダが作成され、ターミナルでLaravelフォルダが選択状態になります。

次に下記コマンドを実行してください。

ubuntu
$ curl -s "https://laravel.build/first-app" | bash

途中で下記画像のようにUbuntuのパスワード入力が求められるので入力して下さい。
※パスワード入力時は何も表示されませんが、気にせず入力してください。
20220530224030.png
下記画像のようになれば、Laravelプロジェクトの作成が完了しました。
20220530224425.png
先ほど実行したコマンド内にfirst-appとありますが、ここがフォルダ名になります。
例えば、first-appmy-appなどに変更してコマンドを実行すると、フォルダ名がmy-appとなります。

下記コマンドを入力してLaravel Sailを起動します。

ubuntu
$ cd first-app
$ ./vendor/bin/sail up -d

終了まで時間がかかるので、気長に待ちましょう。
無事に実行が完了すると以下のような画面になります。
202206020000.png
次はhttp://localhost/へアクセスしてください。
下記画像のような画面が出てくればOKです。
202206020001.png
次に、下記コマンドを実行してみましょう。

ubuntu
$ ./vendor/bin/sail stop

無事に実行が完了すると以下のような画面になります。
202206021027.png
このコマンドはシャットダウンと同じで開発環境をオフにすることができます。
もちろん開発環境をオフにしたので、http://localhost/にアクセスしてもLaravelの画面は表示できません。
Windowsをシャットダウンする時や開発を中断する時は、このコマンドを実行してください。

開発を再開する場合は、ターミナルで下記コマンドを~/Laravel/first-app配下で実行しましょう。

ubuntu
$ ./vendor/bin/sail up -d

VSCodeでfirst-appディレクトリを開く

VSCodeでfirst-appディレクトリを開くためには、ターミナルで下記コマンドを実行してください。

ubuntu
$ cd ~/Laravel/first-app
$ code . 

万が一下記のようなエラーが出た場合は対応が必要です。
202206021050.png
ターミナルで下記コマンドを実行してください。

Ubuntu
$ cd ~
$ vim .bashrc

vim .bashrcを実行すると、.bashrcというファイルがvimというエディタを使用してターミナル上で開かれます。
vimはVSCodeのようなエディタなので、vim ファイル名とすることでUbuntu上でファイルを開いて中身を確認したり、変更することができます。

それでは、キーボードのIを押してください。
すると下記画像のように画面下に-- INSERT --という文字が出現します。
202206021112.png
これで.bashrcファイルを変更することができるようになりました。

さっそくエンターキーを押してみてください。
1番上の行が改行されるはずです。

改行してできた新しい行に下記の1行を追加してください。

ubuntu
export PATH=$PATH:'/mnt/c/Users/[ユーザー名]/AppData/Local/Programs/Microsoft VS Code/bin'

※[ユーザー名]となっている部分はご自身のUbuntuのユーザー名を入力してください。
追加できたらキーボードのESCキー(通常キーボードの左上)を押してください。
すると下記画像のように、先ほどまであった-- INSERT --がなくなります。
202206021121.png
この状態で:wqと入力してください。
すると下記画像のようになります。
202206021125.png
この状態でエンターキーを押すとvimを終了させることができます。
万が一間違えたキーを押してしまってもESCキーを押してから再度:WQからエンターキーvimから抜けることができます。

vimから抜けたらターミナルを閉じて、再度開いてから下記コマンドを実行してください。

ubuntu
$ cd ~/Laravel/first-app
$ code .

それでもVSCodeでfirst-appディレクトリが開かなければ、パソコンの再起動をしてみてください。

・参考URL
codeコマンドでエラー:https://snowsystem.net/other/windows/vscode-wsl-run-error/
vimの使い方:https://qiita.com/pe-ta/items/0510bee10bcfd88afeee

★検索ワード
・Mac Laravel sail 環境構築
・Windows Laravel sail 環境構築
・Mac Docker Desktop インストール方法
・Windows10 WSL2 インストール方法
・Windows11 WSL2 インストール方法
・Windows Docker Desktop インストール方法

phpMyAdminのインストール

次に、データベースを操作するためのphpMyAdminをインストールしていきましょう!

まず、Laravel Sailで作成したfirst-appフォルダをVSCodeで開いてみましょう。
その中にdocker-compose.ymlファイルがあるのでそちらを編集していきます。

ちなみに、このdocker-compose.ymlファイルはLaravel Sailで開発環境を構築した場合に作成されるファイルで、このファイルにDockerの設定が記述されています。

それでは、docker-compose.ymlファイルを下記のように編集してください。

docker-compose.yml
# For more information: https://laravel.com/docs/sail
version: '3'
services:
    # --- ここから追加 ---
    phpmyadmin:
        image: phpmyadmin/phpmyadmin
        links:
            - mysql:mysql
        ports:
            - 8888:80
        environment:
            PMA_HOST: mysql
        networks:
            - sail
    # --- ここまで追加 ---
    laravel.test:
    # --- 以下省略 ---

追加部分のみを抜粋すると下記の通りです。

追加部分
    phpmyadmin:
        image: phpmyadmin/phpmyadmin
        links:
            - mysql:mysql
        ports:
            - 8888:80
        environment:
            PMA_HOST: mysql
        networks:
            - sail

ポイントは、追加部分のコードをservices:の下に記述することです。
また、インデントをservices:より1つ下げてください。
docker-compose.ymlでは、インデントがしっかりしていないとエラーになるので気を付けてください。

無事にコードの追加が終わった方は、ターミナルで下記コマンドをfirst-appディレクトリ上で実行してください。

ターミナル
$ ./vendor/bin/sail up -d

実行が完了したら、http://localhost:8888/へアクセスしてください。
下記のような画面が出てくれば、phpMyAdminのインストールは完了です。
スクリーンショット 2022-05-30 3.02.07.png

★検索ワード
・Laravel sail phpMyAdmin 導入

各URL&コマンド一覧

これで全ての環境構築が完了しました。
開発を始める前に、各URLとコマンドを改めて確認しましょう。

Laravel画面URL

http://localhost/

phpMyAdmin画面URL

http://localhost:8888/

first-appディレクトリへ移動

sail upコマンドsail stopコマンドなど、これから出てくるsailコマンドは必ずfirst-app配下で行ってください。

なぜなら、sailコマンドを実行するためのファイルはfirst-appディレクトリに存在するためです。
他のディレクトリ配下でsailコマンドを実行しても、no such file or directory: ./vendor/bin/sailなどのエラーが出てしまいます。

そうならないためにもターミナルubuntuではfirst-appディレクトリに移動する必要があります。
そのためのコマンドは下記の通りです。

Macの場合

ターミナルを開いて下記コマンドの実行

ターミナル
$ cd ~/Desktop/Laravel/first-app

Windowsの場合

ubuntuを開いて下記コマンドの実行

ubuntu
$ cd ~/Laravel/first-app

Sail upコマンド

開発を始める場合は、ターミナルで下記コマンドを実行してください。

ターミナル
$ .vendor/bin/sail up -d

Sail stopコマンド

開発を終了する場合は、ターミナルで下記コマンドを実行してください。

ターミナル
$ .vendor/bin/sail stop

Laravelのディレクトリ構成

Laravelでの開発環境構築が無事に終了したので、さっそくLaravelについて学習していきましょう。
Laravelをダウンロードして最初に驚くことの1つとして、ダウンロードされたフォルダとファイルの多さです。
これほどまでに多くのフォルダとファイルがダウンロードされた理由は、それら全てがLaravelでアプリケーションを開発するために必要な機能を担っているからです。

それらのフォルダやファイルがどのような役割なのかをざっくり把握してもらうために、まずはLaravelのディレクトリ構成から説明してきます。

教材で説明するディレクトリ構成と実際にダウンロードしたfirst-appを見比べるために、first-appをVSCodeで表示しましょう。

Laravelをインストールすると下記のようなディレクトリ構成になっています。

firstapp
├── app
├── bootstrap
├── config
├── database
├── lang
├── public
├── resources
├── routes
├── storage
├── tests
├── vendor
└── その他ファイル(.envやwebpack.mix.jsなど)

それでは、各ディレクトリを簡単に説明していきます。

app

アプリケーションのコアとなるディレクトリです。
Laravelを使用した開発では、主にappディレクトリ内のファイルを操作することでアプリケーションを構築していきます。

bootstrap

フレームワークの初期処理を担っているディレクトリです。
基本的にこちらのbootstrapディレクトリ内のファイルを操作することはありません。

config

アプリケーションの設定ファイルを置くディレクトリです。
アプリケーションの設定を追加、変更、削除をする場合に操作します。

database

データベースの内容変更やテストデータを作成するような機能が格納されているディレクトリです。

lang

言語ごとの設定をすることができるディレクトリです。
現状はあまり操作することはありません。

public

CSSやJavaScript、画像ファイルなどが入ってるディレクトリです。

resources

CSSやJavaScript、BladeというHTMLのような特有のテンプレートファイルを格納するディレクトリです。

routes

アプリケーションのURL設定などを行うディレクトリです。

strage

キャッシュファイルやログファイルなどが格納されるディレクトリです。

tests

PHPUnitという自動テストファイルなどを格納するディレクトリです。

vendor

Composerの依存内容が格納されているディレクトリです。

以上がLaravelのディレクトリ構成となっています。
現段階でどのディレクトリがどのような働きをしているのかをわざわざ暗記する必要は全くありません。
これからLaravelの学習を行うにあたって、徐々に何がどこにあるかを把握していきましょう!

★検索ワード
・Laravel ディレクトリ構成

データベース接続

以前のレクチャーでphpMyAdminをインストールしているので、さっそくデータベースへ接続してみましょう。
http://localhost:8888/へアクセスすると、下記の通りphpMyAdminのログイン画面になります。
スクリーンショット 2022-05-30 3.02.07.png
Laravelではデータベースへの接続情報を.envというファイルで管理しています。
まずはfirst-app/.envファイルをVSCodeで開きましょう。

すると下記のようにたくさんの設定が記述されています。

.env
APP_NAME=Laravel
APP_ENV=local
APP_KEY=base64:IRagpojO8u1OxSEJJKG5LYdpOwUMwi1sHn+CukKEuS0=
APP_DEBUG=true
APP_URL=http://first-app.test

LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug

DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=first_app
DB_USERNAME=sail
DB_PASSWORD=password

BROADCAST_DRIVER=log
CACHE_DRIVER=file
FILESYSTEM_DISK=local
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120

MEMCACHED_HOST=memcached

REDIS_HOST=redis
REDIS_PASSWORD=null
REDIS_PORT=6379

MAIL_MAILER=smtp
MAIL_HOST=mailhog
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"

AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false

PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1

MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

SCOUT_DRIVER=meilisearch
MEILISEARCH_HOST=http://meilisearch:7700

この.envファイルには、データベースへの接続情報だけではなくLaravelの設定内容が色々記述されています。
その中でデータベースの情報は下記の部分です。

データベース情報
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=first_app
DB_USERNAME=sail
DB_PASSWORD=password

DB_******となっている部分がデータベースの設定情報ですが、DBはデータベースの略です。
とても覚えやすいですね!

上記コードの中にDB_USERNAMEsailDB_PASSWORDpasswordと記述されているので、phpMyAdminのユーザー名にsail、パスワードにpasswordとそれぞれ入力してログインをクリックしてください。
スクリーンショット 2022-05-30 11.53.32.png
また、DB_DATABASEfirst_appと記述されているので、今回使用するデータベース名はfirst_appということになります。

Laravel Sailで環境構築をした場合、すでにデータベースは下記画像のように作成されているので、データベースへの接続確認は以上で終了になります。
スクリーンショット 2022-05-30 11.56.29.png

★検索ワード
・Laravel phpMyAdmin 使い方

Laravelの初期設定

Laravelプロジェクトを作成した直後にやっておいたほうが良い初期設定を行っていきます。

初期設定で行うことは以下の2つです。

  • アプリケーション名の変更
  • タイムゾーンと言語設定

アプリケーション名の変更

はじめにアプリケーション名を任意の名前に変更しておきましょう。
Laravelのアプリケーション名は以前データベースの設定を確認したfirst-app/.envファイルで設定します。
.envファイルを下記のように変更してください。

.env
# APP_NAME=Laravelから下記に変更
APP_NAME="ブログシステム"
APP_ENV=local
# --- 以下省略 ---

Laravelをインストールした直後の名前はLaravelになっています。
それを、今回開発するブログシステムという名前に変更しましょう。

ちなみに、日本語にする場合や半角スペースを入れたい場合は"(ダブルクオーテーション)で囲みます。

タイムゾーンと言語設定

次に、日本時間の設定と言語設定を日本語に変更します。
これらの設定はfirst-app/config/app.phpで設定されているので、そちらのファイルを開いてください。

app.phpを下記の通り変更してください。

app.php
// 'timezone' => 'UTC'から下記に変更(72行目付近)
'timezone' => 'Asia/Tokyo',

// 'locale' => 'en'から下記に変更(85行目付近)
'locale' => 'ja',

// 'faker_locale' => 'en_US'から下記に変更(111行目付近)
'faker_locale' => 'ja_JP',

app.phpはかなりコード量が多いですが、各変更前のコードをVSCode上で文字列検索すると簡単に見つけることができます。

VScodeでの文字列検索方法は下記の通りです。
まずVScodeで対象ファイルを開いてください。
開いたらご自身のOSに合ったキーボードのキーを押してください。
・Mac:Command + f
・Windows:Ctrl + f

変更したコードはそのままの意味ですが、意味は下記の通りです。
'timezone' => 'Asia/Tokyo',としましたが、日本時間に設定しています。
'locale' => 'ja',としましたが、言語設定を英語から日本語に設定しています。
'faker_locale' => 'ja_JP'としましたが、テストデータを作る際に日本語で作成してくれる設定をしています。

これでファイルの変更は終わりです。
最後にターミナルで下記2つのコマンドをfirst-appディレクトリ上で実行してください。

ターミナル
$ ./vendor/bin/sail php artisan cache:clear
$ ./vendor/bin/sail php artisan config:clear

実行後は下記画像のようになります。
スクリーンショット 2022-05-30 23.34.04.png
これらのコマンドを実行することで、先ほど変更した内容がアプリケーションへ確実に反映されるようになります。
.envファイルやapp.phpなどを変更した場合は、上記コマンドを実行してください。
以上で初期設定は終わりです。

★検索ワード
・Laravel 初期設定
・Laravel アプリケーション名変更
・Laravel 日本時間設定
・Laravel 日本語設定

マイグレーション(migration)

Laravelには、データベースのテーブル作成や編集などを管理する方法としてマイグレーションという機能が用意されています。

今回はそのマイグレーション機能を使用して、テーブル作成やテーブル設定を行っていきます。
マイグレーションでテーブルを作成するための手順は、下記3段階に分けることができます。

  1. マイグレーションファイルの作成
  2. マイグレーションファイルにテーブル定義を記述
  3. マイグレーションの実行

マイグレーションファイルはどんなテーブルを作成するのかを定義する設計書のようなものです。
また、設計書を作成した後に設計書に基づいて作成作業をしなければテーブルは完成しません。
この作成作業に当たる部分がマイグレーションの実行というわけです。

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

まず、1つのテーブルを作成するのに必要なマイグレーションファイルは1つだということを覚えておいてください。

マイグレーションファイルはターミナルからコマンド1つで作成することができます。
コマンドは以下の通りです。

migrationファイル作成コマンド
$ php artisan make:migration create_テーブル名(名詞の複数形)_table

今回はLaravel Sailで開発環境を構築しているので、下記のようなコマンドになります。

migrationファイル作成
$ ./vendor/bin/sail php artisan make:migration create_テーブル名(名詞の複数形)_table

本来Laravelが用意しているコマンドはphp artisanから始まります。
しかし、Laravel Sailで開発環境を構築した場合は./vendor/bin/sail php artisanとなるので注意してください。
また、テーブル名のところは作成するテーブル名を複数形で入力してください。

今回は記事テーブルを作成するのでarticlesテーブルとして作成します。
それでは、ターミナルで下記コマンドをfirst-appディレクトリ上で実行してください。

ターミナル
$ ./vendor/bin/sail php artisan make:migration create_articles_table

Created Migration: 2022_06_03_182626_create_articles_tableなどと出力されればマイグレーションファイルの作成は完了です。
ちなみに、2022_06_03_182626の部分は作成日時になるので、皆さんが作成したファイルでは異なる時間となっているはずです。

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

作成したarticlesテーブルのマイグレーションファイルは、first-app/database/migrationsの中に存在し、先ほど作成した2022_06_03_182626_create_articles_table.phpfirstapp/database/migrationsの中に入っています。

また、Laravelのバージョンによっても異なりますが、デフォルトで下記のマイグレーションファイルが存在します。

  • 2014_10_12_000000_create_users_table.php
  • 2014_10_12_100000_create_password_resets_table.php
  • 2019_08_19_000000_create_failed_jobs_table.php
  • 2019_12_14_000001_create_personal_access_tokens_table.php

これらデフォルトで存在するマイグレーションファイルのほとんどは、ログイン機能を実装する場合などに使用します。
今回の学習では使用しないので4つとも削除してしまってもOKです。

それでは、実際に作成した2022_06_03_182626_create_articles_table.phpをVScodeで開いて中身を確認してみましょう。

2022_06_03_182626_create_articles_table.php
<?php

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

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

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

まず、マイグレーションファイルはMigrationクラスを継承したクラスです。
このMigrationクラスを継承していることで、Laravelの機能であるマイグレーションを使用することができます。

また、マイグレーションファイルの中にはupメソッドdownメソッドがあります。
upメソッドが実行(作成)するメソッドなので、このメソッドの中にテーブル定義を記述していきます。
一方でdownメソッドは基本使用しませんが、元に戻す(削除)処理を記述することができます。

また、public function up(){}public function down(){}となっていますが、これらは関数のことです。
Laravelでは関数のことをメソッドと呼ぶのでそちらも合わせて覚えておいてください。

upメソッドの中を見てみると、Schema::create()と記述しています。
このSchema::create()()内でテーブル名やカラムの定義をしていくことになります

まずSchema::create('articles')と記述されている部分ですが、この第一引数であるarticlesがテーブル名となります。
また、第二引数でfunction (Blueprint $table) {}と記述されていますが、LaravelではBlueprintという機能を使用してカラムを作成していきます。

{}内を確認すると、すでに$table->id();$table->timestamps();が記述されています。
これら2つのコードは、テーブルがほぼ必ず持っているといっても過言ではない3つのカラムを生成することができます。

$table->id();idカラム$table->timestamps();created_atカラムupdated_atカラムという作成日時と更新日時のカラム3つを生成することができます。

これらのデフォルトのコードに、作成するカラムの記述をプラスしてテーブル定義を定義していきます。

マイグレーションファイルの記述

マイグレーションファイルを使用してカラムを追加する場合、基本$table->カラムタイプ('カラム名')という形でカラム定義を記述します。

ちなみに使用できるカラムタイプはドキュメントに全て載っています。
どのようなカラムがあるのか気になる方は、こちらのドキュメント利用可能なカラムタイプを参照してください。
下記画像はドキュメントの利用可能なカラムタイプをスクショしたものですが、そちらからもわかるように先ほど確認したidtimestampsもありますね!
スクリーンショット 2022-05-30 23.54.19.png
ドキュメントに利用可能なカラムは全て載っているので、暗記せずに使いながら覚えていきましょう!
そもそもバージョンの違い等で利用可能なカラムは変わる場合もあるので、全てを暗記している人はいません!

では、実際にarticlesテーブルの定義を記述していきましょう。
2021_12_15_140034_create_articles_table.phpを下記の通り記述してください。

2022_05_30_144855_create_articles_table.php
<?php

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

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('articles', function (Blueprint $table) {
            $table->id();
            $table->string('title', 100); // ここを追加
            $table->text('content');      // ここを追加
            $table->timestamps();
        });
    }

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

追加したコードは下記の2行です。

追加したコード
$table->string('title', 100);
$table->text('content');

$table->string('title', 100);とすることでtitleというカラム名の100文字という文字数制限があるカラムを生成します。
$table->text('content');とすることでcontentというカラム名の文字数制限がないカラムを生成します。

これでブログシステムに必要な記事のタイトル(titleカラム)内容(contentカラム)を保存できるarticlesテーブルを作成することができます。

マイグレーションの実行

マイグレーションファイルの記述が終わったので、実際にコマンドでマイグレーションの実行をしていきましょう。

ターミナルで下記コマンドをfirst-appディレクトリ上で実行してください。

ターミナル
$ ./vendor/bin/sail php artisan migrate

下記のような実行結果が返ってくれば成功です。

実行結果
Migration table created successfully.
Migrating: 2019_12_14_000001_create_personal_access_tokens_table
Migrated:  2019_12_14_000001_create_personal_access_tokens_table (81.27ms)
Migrating: 2022_05_30_144855_create_articles_table
Migrated:  2022_05_30_144855_create_articles_table (42.16ms)

では、テーブルが作成されているのかをphpMyAdminにて確認してみましょう。

http://localhost:8888/へアクセスして、データベースfirst_appの中に、articlesテーブルmigrationsテーブルpersonal_access_tokensテーブルの3つが作成されていればOKです。
※デフォルトのマイグレーションファイルを削除していない方はusersテーブルpassword_resetsテーブルなども一緒に作成されますが、そのままで大丈夫です。

ちなみにmigrationsテーブルはマイグレーションをした履歴が保存されるテーブルです。
また、先ほどデフォルトのマイグレーションファイルは全て削除しましたが、personal_access_tokensテーブルは自動的に作成されてしまいます。
これらのテーブルは自動的に作成されるものなので、そのままで大丈夫です。

★検索ワード
・Laravel マイグレーション
・Laravel マイグレーションファイル作成

テストデータの作成

Laravelには、データベースにテストデータを作成するためのシーダー(seeder)という機能が用意されています。

今回はそのシーダーを使用してテストデータの準備をしていきます。
シーダーでテストデータを作成するための手順は、下記4段階に分けることができます。

  1. シーダーファイルの作成
  2. シーダーファイルにデータ内容を記述
  3. シーダーファイルの登録
  4. シーダーの実行

それでは上記手順に沿って、テストデータを作成してみましょう。

シーダーファイルの作成

シーダーファイルもマイグレーションファイル同様、1コマンドで作成することができます。
コマンドは下記の通りです。

シーダーファイル作成コマンド
$ ./vendor/bin/sail php artisan make:seeder テーブル名TableSeeder

それでは、ターミナルで下記コマンドをfirst-appディレクトリ上で実行してください。

ターミナル
$ ./vendor/bin/sail php artisan make:seeder ArticlesTableSeeder

今回はarticlesテーブルのテストデータを作成するので、ArticlesTableSeederとなります。
ArticlesTableSeederとテーブル名の1文字目がAと大文字になっていることに注意してください。

Seeder created successfully.と出力されればシーダーファイルの作成は完了です。
first-app/database/seeders配下にArticlesTableSeeder.phpが作成されていればOKです。

シーダーファイルの記述

それでは作成したArticlesTableSeeder.phpをVSCodeで開いて中身を確認してみましょう。

ArticlesTableSeeder.php
<?php

namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

class ArticlesTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        //
    }
}

まず、シーダーファイルはSeederクラスを継承したクラスです。
このSeederクラスを継承していることで、Laravelの機能であるシーダーを使用することができます。

また、シーダーファイルはデフォルトでrunメソッドを含んでいます。
このrunメソッド内に、データベースへ入れるデータの内容を記述することでテストデータを作成することができます。

それでは、さっそくテストデータ作成のための編集を行っていきましょう。
下記のようにfirst-app/database/seeders/ArticlesTableSeeder.phpを編集してください。

ArticlesTableSeeder.php
<?php

namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB; // ここを追加

class ArticlesTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        // --- ここから追加 ---
        DB::table('articles')->insert([
            'title' => '1番目の投稿',
            'content' => 'これは1番目の投稿のテストデータです',
            'created_at' => date('Y-m-d H:i:s'),
            'updated_at' => date('Y-m-d H:i:s'),
        ]);

        DB::table('articles')->insert([
            'title' => '2番目の投稿',
            'content' => 'これは2番目の投稿のテストデータです',
            'created_at' => date('Y-m-d H:i:s'),
            'updated_at' => date('Y-m-d H:i:s'),
        ]);

        DB::table('articles')->insert([
            'title' => '3番目の投稿',
            'content' => 'これは3番目の投稿のテストデータです',
            'created_at' => date('Y-m-d H:i:s'),
            'updated_at' => date('Y-m-d H:i:s'),
        ]);
        // --- ここまで追加 ---
    }
}

では、記述したコードの説明をしていきます。

まず、use Illuminate\Support\Facades\DB;を記述することで、DBファサードという機能を使えるようにしています。
ファサードという機能はとても難しい概念なので、まずはDBファサードを使用することでデータベースに関連した作業ができるというイメージだけを持っていただければ大丈夫です。

次に、DB::table('テーブル名')->insert([追加するデータ群]);と記述することで記述したテーブル名のテーブルへデータを追加することができるようになります。

ちなみに追加するデータは'カラム名' => '追加するデータ'と記述します。
articlesテーブルにはtitlecontentという2つのカラムが存在しているので、それぞれに追加するデータを入れ、created_atupdated_atdate('Y-m-d H:i:s')と記述することで、現在の日時を入れる処理をしています。
idカラムには作成した順に自動的に番号が入るので指定しなくてOKです。

これでシーダーファイルの編集は終わりです。

シーダーファイルの登録

作成したシーダーファイルは、実行するためにDatabaseSeeder.phpへ登録することが必要です。

登録の仕方は下記の通りです。
database/seeders/DatabaseSeeder.phpを下記の通り編集してください。

DatabaseSeeder.php
<?php

namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        $this->call(ArticlesTableSeeder::class); // ここを追加
        // \App\Models\User::factory(10)->create();

        // \App\Models\User::factory()->create([
        //     'name' => 'Test User',
        //     'email' => 'test@example.com',
        // ]);
    }
}

作成したシーダーファイルをDatabaseSeeder.phpへ登録するときは、$this->call(シーダークラス::class)と記述するだけです。

今回作成したシーダーファイルはArticlesTableSeeder.phpでクラス名はArticlesTableSeederクラスとなっているので、$this->call(ArticlesTableSeeder::class);となります。

シーダーファイルの実行

最後にシーダーファイルを実行していきましょう。
ターミナルで下記コマンドをfirst-appディレクトリ上で実行してください。

ターミナル
$ ./vendor/bin/sail php artisan db:seed

下記のような実行結果が返ってくれば成功です。

実行結果
Seeding: Database\Seeders\ArticlesTableSeeder
Seeded:  Database\Seeders\ArticlesTableSeeder (52.20ms)
Database seeding completed successfully.

では、実際にデータが作成されているのかをhttp://localhost:8888/へアクセスして、phpMyAdminにて確認してみましょう。
下記画像のように、データベースfirst_apparticlesテーブルに3つのレコードが登録されていればOKです。
スクリーンショット 2022-05-31 22.42.05.png
これでテストデータの作成は完了です。
システムの実装より先にテストデータをに作成しておくことで、実装時に動作確認などがしやすくなるので、シーダーでのテストデータ作成は覚えておきましょう。

★検索ワード
・Laravel シーダーファイル作成
・Laravel テストデータ作成

MVCモデル

これからの学習をよりわかりやすくするためにMVCモデルというアプリケーションを作成する際のデザインパターンについて説明していきます。

MVCモデルとはアプリケーション開発で採用されるコードの構成手法です。
ユーザーに対する入出力処理とシステムの内部処理を分離することを目的としており、システムの機能を3つの役割に分けて開発を行っていきます。

モデル(Model)

1つ目はモデルです。
主にアプリケーションのデータに対する操作を担います。
データベースへデータ登録する処理やデータ取得の処理を記述することが理想です。

ビュー(View)

2つ目はビューです。
主にユーザーに対する入出力処理を担います。
実際にユーザーが見る画面がこのViewです。

コントローラー(Controller)

そして最後はコントローラーです。
主にModelViewの橋渡しを担います。
ビューを表示させる処理やデータをモデルからビューへ渡す処理など幅広い処理を記述することができます。

3つの関連性

これら3つの頭文字をとりMVCモデルと呼ばれています。
LaravelではMVCモデルが採用されており「どのディレクトリにM・V・Cを格納するのか」がざっくり決まっています。
また、LaraveだけではなくRubyのフレームワークであるRuby on Railsなどでも採用されており、その他にもたくさんのフレームワークがMVCモデルを採用しています。

そんな汎用性の高いMVCモデルですが、それぞれ3つの関連性をわかりやすくするために図を使って説明していきます。
スクリーンショット 2022-05-31 22.54.34.png
上の図を見てください。
MVCモデルの場合、クライアントからアクセスが来るとアクセスの処理をControllerへ割り振ります。
データの取得や変更などを行う場合は、ControllerからModelに移動してデータの処理を行います。
Modelではデータベースとの接続ができるので、受け取ったデータを処理した後にControllerへデータを渡します。
その後ControllerからViewへデータを受け渡し、Viewでサイトの表示を行うという流れになっています。

流れを確認しただけですべてを理解することはできないので、それぞれのModelViewControllerについて学習しながら、少しずつ理解していきましょう!

今の段階では、「Laravelはサイトを表示するための役割を3つに分けて実装していくのだな」というイメージを持っておいていただければOKです。

★検索ワード
・フレームワーク MVCとは
・Laravel MVC

モデルについて

それではさっそくMVCの中のモデル(Model)について学習していきます。
先ほども説明した通り、モデルはデータベースへのアクセスやデータ操作などの役割を担います。

Laravelでは、モデルを簡単に実装するための仕組みが用意されているのでさっそく使ってみましょう。

モデルファイルの作成

モデルファイルもマイグレーションファイルやシーダーファイルのように、Artisanコマンドを使用して作成することができます。
Artisanコマンドとは、php artisan ~~~~~~のコマンドのことです。

コマンドは以下の通りです。

モデル作成
$ ./vendor/bin/sail php artisan make:model クラス名

基本クラス名はモデルを使用して操作したいテーブル名の単数形を使用します。
そのため、articlesテーブルのデータを取得したりするモデルファイルを作成する場合のモデル名はArticleとなります。

それではarticlesテーブルのモデルファイルを作成するので、ターミナルで下記コマンドをfirst-appディレクトリ上で実行してください。

ターミナル
$ ./vendor/bin/sail php artisan make:model Article

Model created successfully.と出力されればモデルファイルの作成は完了です。
first-app/app/Models配下にArticle.phpが作成されていればOKです。

Laravelのモデルは、Eloquentモデルとも言うことができます。
EloquentとはLaravelで提供されているデータ操作のための機能であり、モデルとデータベースを紐付ける役割をしています。

それでは作成したArticle.phpを確認してみましょう。

Article.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    use HasFactory;
}

ファイルの中にはModelクラスを継承したArticleクラスが作成されています。
ModelクラスはLaravelが初期機能として持っているクラスであり、このクラスを継承しているため、Articleクラスはモデルとしての機能を発揮することができます。

また、作成したArticleクラスとLaravelに接続しているデータベースのarticlesテーブルが、Laravelによって自動的に紐づけられます。
よって、Articleクラスを操作することで簡単にテーブルの操作を行うことができます。

Article.phpの中のArticleクラスと、データベースのarticlesテーブルで名前が単数形と複数形で異なることに注意してください。
Eloquentでは、モデルのクラス名を複数形にしてアッパーキャメルケースだったものをスネークケースにしたものが、紐づけ先のテーブルとして自動的に認識されます。
少し難しい言葉が並んでいますが、例は下記の通りです。
※アッパーキャメルケース例:OpenHoursのように先頭の文字と単語の区切り文字を大文字している名前
※スネークケース例:open_hoursのようにアンダースコア(アンダーバー)で区切られている名前

例えば「admin_statuses」というテーブルに紐づけたい場合のクラス名は、下記のようになります。
①単数形にする(admin_statuses->admin_status)
②アッパーキャメルケースにする(admin_status->AdminStatus)
つまり、php artisan make:model AdminStatusというコマンドになります。
今回は、articlesテーブルに紐づけたいので、Articleというファイル名にしてあります。
※モデルのクラス名に拡張子が付いたものがモデルのファイル名になります。

モデルの記述

モデルファイルはほとんど中身を変更せずに使用することができますが、追加しておいた方がいいこともあります。

まずは、下記の通りfirst-app/app/Models/Article.phpを編集してください。

Article.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    use HasFactory;

    // テーブル名
    protected $table = 'articles';

    // 可変項目
    protected $fillable = [
        'title',
        'content',
    ];
}

まず追加したのは下記のコードです。
protected $table = 'articles';
先ほど説明した通り、モデルファイルはテーブル名の単数形であり、アッパーキャメルケースであれば、自動的にテーブルとモデルが紐付けられますが、protected $tableを使用することで任意のテーブル名を指定することもできます。

もしモデルクラス名とテーブル名が全く違う場合などはこのように記述してください。
今回のコードであれば、記述しなくても自動的にモデルファイルとテーブルは結びついていますが、解説のためにこちらも記述しております。

次に追加したのが下記のコードです。

// 可変項目
protected $fillable = [
    'title',
    'content',
];

こちらのコードはコントローラーなどでデータベースに登録する際に使用するcreateメソッドを使うために記述します。
createメソッドを使用しない場合は必要ありませんが、将来的に使用するので初めに記述しておきましょう。

まずはprotected $fillable = []と記述し、[]の中にはテーブルの可変項目のカラム名を記述していきます。
今回はarticlesテーブルの2つのカラムtitlecontentは、各レコードによって入れたい値が可変になるので[]に記述しています。

基本$fillableにはidカラムcreated_atカラムupdated_atカラム以外のカラムを入れておけばOKです。
状況に応じてidカラムcreated_atカラムupdated_atカラムなども入れておきましょう。

これでモデルファイルの作成は終わりです。
モデルを学習しただけだと全体像が掴めないと思うので、次に進んでみましょう。

★検索ワード
・Laravel モデル作成
・Laravel Eloquent
・Laravel fillable guarded
・Laravel モデル テーブル名指定

ルート(Route)について

LaravelにはMVCの他にルート(Route)という機能が存在しています。
今回はそんなルートについて学習していきましょう。

ルートとは

ルートとはLaravelでルーティングを行う機能です。
ルーティングとはブラウザからアクセスがあった際に、どのコントローラーのどの処理を動かすのかを定義することを言います。
もう少しわかりやすく言うと、「あるURLへリクエストがあったとき、コントローラーのどのメソッドを実行するのかを命令する機能」です。

MVCの図にルートを追加してもう一度確認してみましょう。
スクリーンショット 2022-06-11 20.53.05.png
このようにブラウザからあるURLへアクセスがあると、始めにルートにたどり着き、どのコントローラーのどのメソッドに処理を振り分けるか決定します。
そこから振り分けられたコントローラーが指定されたメソッドを実行するというイメージです。

では、なぜルートという機能が必要なのでしょうか?
それは、コントローラーはアプリケーション内に複数あることが普通であり、URLごとにどのコントローラーのどのメソッドに振り分けるのかを決める必要があるからです。

つまり、ルートがないとURLにアクセスがあった時に、どのコントローラーのどのメソッドを実行すればいいかLaravelが認識できないので、ページを表示することができなくなってしまいます。

このように、ルートはページを表示させるためには無くてはならない機能なのです。

web.phpについて

Laravelではルーティングをfirst-app/routes/web.phpファイルで行います。
web.phpはすでに下記のような記述がされています。

web.php
<?php

use Illuminate\Support\Facades\Route;

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

※余計なコメントなどは消しています。

Route::get()というコードが記述されていますが、第一引数に「指定したいパス」、第二引数には「第一引数のパスへアクセスがあった場合に実行する処理」を記述します。
この場合、パスは/だけなのでhttp://localhostが開かれたら、return view(‘welcome’);を実行する、という意味になります。
このルーティングこそが最初に見た下記画面を表示しているルートです。
スクリーンショット 2022-05-30 3.17.42.png
なお通常、第二引数にはコントローラーとメソッド名を記述しますが、デフォルトの記述のように無名関数を使用してweb.phpにそのまま処理を記述することもできます。

return view('welcome')は、ヘルパーメソッドのViewを使用して、first-app/resources/views/welcome.blade.phpというBladeファイルを表示するという意味になります。
※ヘルパーメソッドは、Laravelの中に最初から組み込まれている関数のことです。

また、今回始めてBladeファイルというものが出てきました。
詳しくはビューのレクチャーで説明でしますが、このBladeファイルがLaravelで見た目を表示するためのファイルです。

少しwelcome.blade.phpを見てみましょう。
VSCodeでfirst-app/resources/views/welcome.blade.phpを開いてください。

first-app/resources/views/welcome.blade.php
<!-- ここから上は省略 -->

<div class="p-6">
    <div class="flex items-center">
        <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="w-8 h-8 text-gray-500"><path d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"></path></svg>
        <div class="ml-4 text-lg leading-7 font-semibold"><a href="https://laravel.com/docs" class="underline text-gray-900 dark:text-white">Documentation</a></div>
    </div>

    <div class="ml-12">
        <div class="mt-2 text-gray-600 dark:text-gray-400 text-sm">
            Laravel has wonderful, thorough documentation covering every aspect of the framework. Whether you are new to the framework or have previous experience with Laravel, we recommend reading all of the documentation from beginning to end.
        </div>
    </div>
</div>

<!-- ここから下は省略 -->

今回はwelcome.blade.phpのすべてを表示するととても長くなってしまうので、下記画像の赤枠部分のみを抜粋しています。
スクリーンショット 2022-06-07 0.19.51.png
ファイルの中身を見ると、DocumentationというテキストやLaravel has wonderful...という文章が記述されているのがわかると思います。

例えばDocumentationドキュメントと変更して、ファイルを保存すると下記画像のようになります。
スクリーンショット 2022-06-11 21.28.57.png
Bladeファイルについては後ほど学習するので、現時点で気にする必要はありません。

ルートの記述

では、このweb.phpへ新たに1つのルートを追加してみましょう。

web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ArticleController; // ここを追加

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

// 投稿一覧を表示
Route::get('/articles', [ArticleController::class, 'showArticles'])->name('showArticles'); // ここを追加

まず、後ほど作成するArticleコントローラーを使えるように下記の1文を追加しています。
use App\Http\Controllers\ArticleController;
ArticleControllerはまだ作成していません。

このuse文を使用することで、後ほど作成するコントローラーをweb.phpで使用することができるようになります。
web.phpで他のコントローラーを使用する場合はuse App\Http\Controllers\コントローラーのクラス名を記載しましょう。

初めてのルーティングとして、投稿一覧を表示するためのルートを記述したいので、Route::get('/articles', [ArticleController::class, 'showArticles'])->name('showArticles');というコードを追加しました。

ルートファイルでは、Routeファサードという便利機能を使用することができます。
Route::と記述したあとにHTTPリクエストを記述してください。
今回は記事一覧をただ表示するだけなので、HTTPリクエストはgetを指定していますが、他にもpostなどが使用できます。

第一引数では先ほど学んだ通り'/articles'というパスを設定しています。
そのため、http://localhost/articlesというURLにアクセスすると、ここへルーティングされるということです。

第二引数内に記述しているArticleCotroller::classは、後ほど作成するArticleコントローラーを指定しています。
また、showArticlesArticleコントローラー内に定義されているメソッド名です。

つまり[ArticleController::class, 'showArticles']と記述することで、http://localhost/articlesへアクセスがあった場合にArticleコントローラーshowArticlesメソッドが実行されるということです。

また、Laravelのヘルパーメソッドであるnameメソッドを使用することによって、ルートごとに名前を付けることができます。
今回の場合だと、記事一覧を表示させる画面のルートにshowArticlesと名前を付けたことになります。

この段階では何が便利かわかりにくいですが、ルートに名前を指定しておけばルートを指定する時にとても便利なので、なるべくわかりやす名前を付けておきましょう!

まとめると、Route::get('/articles', [ArticleController::class, 'showArticles'])->name('showArticles');と記述することで、http://localhost/articlesにアクセスがあった場合、ArticleコントローラーshowArticlesメソッドが実行されるということです。

今のArticleコントローラーを作成する前の状態でhttp://localhost/articlesにアクセスしても、下記画像のようなエラーが発生します。
スクリーンショット 2022-06-11 22.31.52.png
ArticleCotrollerがないよ!としっかりエラーが出ていますね!
Laravelはエラー画面がとてもわかりやすいのも特徴です!

★検索ワード
・Laravel ルーティング
・Laravel ルート 記述方法

コントローラー(Controller)について

ルートの説明でArticleコントローラーが登場したので、次はMVCのCであるコントローラーについて説明します。

コントローラーでは、ビューを表示させる処理やモデルからビューへデータを渡す処理など幅広い処理を記述することができます。
Laravelでは、コントローラーを簡単に実装するための仕組みが用意されているのでさっそく使ってみましょう。

コントローラーファイルの作成

コントローラーファイルもArtisanコマンドを使用して作成することができます。
コマンドは以下の通りです。

コントローラー作成
$ ./vendor/bin/sail php artisan make:controller クラス名

基本クラス名はテーブル名の単数形にControllerを連結させたものです。
そのため、articlesテーブルのコントローラーを作成する場合のクラス名はArticleControllerとなります。

それではコントローラーファイルを作成するので、ターミナルで下記コマンドをfirst-appディレクトリ上で実行してください。

ターミナル
$ ./vendor/bin/sail php artisan make:controller ArticleController

Controller created successfully.と出力されればコントローラーファイルの作成は完了です。
first-app/app/Http/Controllers配下にArticleController.phpが作成されていればOKです。

それでは作成したArticleController.phpを確認してみましょう。

ArticleController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class ArticleController extends Controller
{
    //
}

ファイルの中にはControllerクラスを継承したArticleControllerクラスが作成されています。
ControllerクラスはLaravelが初期機能として持っているクラスであり、このクラスを継承しているため、ArticleControllerクラスはコントローラーとしての機能を発揮することができます。

もう一度web.phpに追加したコードを確認してみましょう。

web.phpで追加したコード
Route::get('/articles', [ArticleController::class, 'showArticles'])->name('showArticles');

上記のコードをweb.phpへ追加しましたが、簡単に言うとhttp://localhost/articlesへアクセスがあった場合、ArticleコントローラーshowArticlesメソッドを実行するという意味でした。

コントローラーの記述

ここまででArticleコントローラーは作成できたので、showArticlesメソッドを作成していきましょう。
first-app/app/Http/Controllers/ArticleController.phpを下記のように編集してください。

ArticleController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class ArticleController extends Controller
{
    // --- ここから追加 ---
    /**
     * 記事一覧を表示する
     */
    public function showArticles()
    {
        return view('articles.index');
    }
    // --- ここまで追加 ---
}

まず、ルーティングで指定したshowArticlesメソッドを作成します。
web.phpで指定したメソッドをコントローラーファイル内で定義する場合は、public function メソッド名(){}と記述してください。
そして{}内にメソッドの処理を記述していきます。

今回はreturn view('articles.index')showArticlesメソッドの処理として記述されています。
このreturn view()は以前ルーティングのところでも説明しましたが、ヘルパーメソッドのViewメソッドを使用して、first-app/resources/views配下のBladeファイルを表示するという意味です。

また、()内に'articles.index'と記述されているので、first-app/resources/views/articles/配下のindex.blade.phpというBladeファイルを表示するという処理になっています。

さらに下記のコメントのようなものが、メソッドの上に追加されています。

PHPDoc
/**
 * 記事一覧を表示する
 */

これはPHPDocと呼ばれていて、クラスやメソッドの上に記述することでどのようなクラスやメソッドなのかを説明することができる機能です。

現段階でPHPDocについて意識する必要は全くないので、コメント程度に思っておいていただければOKです!

ビューを表示する

コントローラーの作成から記述までが終わったので、さっそくhttp://localhost/articlesへアクセスしてみましょう。

アクセスすると下記のようなエラーが出ると思います。
スクリーンショット 2022-06-12 1.57.16.png
エラーの内容はfirst-app/resources/views/articles配下にindex.blade.phpというファイルが存在しないという内容です。

showArticlesメソッドreturn view('articles.index')と指定しましたが、まだBladeファイルを作成していないので当然のエラーですね。

では、さっそくBladeファイルを作成してみましょう。
コマンドでも画面上でそのまま作成してもどちらでも大丈夫です。

まずはfirst-app/resources/views配下にarticlesディレクトリを作成してください。
コマンドで作成する場合は、ターミナルで下記コマンドをfirst-appディレクトリ上で実行してください。

ターミナル
$ mkdir resources/views/articles

次に作成したarticlesディレクトリにindex.blade.phpを作成してください。
コマンドで作成する場合は、ターミナルで下記コマンドをfirst-appディレクトリ上で実行してください。

ターミナル
$ touch resources/views/articles/index.blade.php

作成したindex.blade.phpを開いて、下記の通り編集してください。

index.blade.php
Laravelで初めての画面出力

編集して保存したらhttp://localhost/articlesへアクセスしてください。
ブラウザにLaravelで初めての画面出力と表示されていればOKです。

詳しいBladeファイルの記述方法はビューの解説で説明しますので、まずはコントローラーでBladeファイルを表示する順序だけわかっていただければOKです。

画面表示の流れの確認

一度Bladeファイルを表示する流れをおさらいしておきましょう。

まず、URL(http://localhost/articles)にアクセスがされると、ルーティングの処理(Route::get('/articles', [ArticleController::class, 'showArticles'])->name('showArticles');)が実行されます。

ルーティングでは、ArticleコントローラーshowArticlesメソッドを実行するように処理が記述されています。
そのため、first-app/app/Http/Controllers/ArticleController.phpの中のArticleControllerクラスshowArticlesメソッドを実行します。

showArticlesメソッドにはreturn view('articles.index')と記述されており、first-app/resources/views/articles/index.blade.phpを表示する処理になっています。

index.blade.phpを確認するとLaravelで初めての画面出力とテキストが記述されているので、URL(http://localhost/articles)にアクセスがされると、ブラウザにLaravelで初めての画面出力というテキストが表示されることになります。

今回はテキストのみの表示だけだったので、データベースへの接続がない分モデルは登場しませんでした。
なので、下記画像の②データ処理③DB接続は行っていません。
スクリーンショット 2022-06-11 20.53.05.png
後ほど記事一覧を表示させる時に②と③の方法については説明します。

★検索ワード
・Laravel コントローラー作成
・PHPDocとは

ビュー(View)について

コントローラーの説明で作成したfirst-app/resources/views/articles/index.blade.phpですが、このBladeファイルこそがLaravelのビュー(View)にあたります。

Bladeテンプレートとは

そもそもBladeファイルはBladeテンプレートというテンプレートエンジンで作成されたファイルのことです。

テンプレートエンジンとは、テンプレート部分とデータを合成してドキュメントを出力するソフトウェアのことを指します。
いまいちイメージが湧かないかもしれませんが、HTMLとPHPが直接記述できる便利機能だと思っていただければ大丈夫です。

また、Bladeテンプレートではテンプレートの継承データの受け渡しを簡単に行うことができるようになっており、Laravelを扱う上で必須の知識となるので、さっそく学習していきましょう。

ちなみにテンプレートエンジンはBladeの他にもSmartyTwigなどがありますが、LaravelではBladeを採用しています。

Bladeテンプレートの使い方

Bladeテンプレートには使用できる構文が存在しており、わかりやすいように主なものを表にまとめておきます。

構文 説明
@extends('Bladeファイル') ()内のBladeファイルを継承することができる
@yield('セクション名') ()内のセクション名が同じ@section~@endsectionまでを読み込むことができる
@section('セクション名') ~ @endsection @section@endsectionで囲まれた部分を@yield部分に追加する
@include('Bladeファイル') ()内のファイルを読み込むことができる

これらの構文を使用することでHTMLファイルやPHPファイルをそのまま使用するよりも遥かに管理しやすい状態で見た目(View)を作成することができるようになります。
使い方は見た目を実装しながら、順番に説明していきます。

Bladeの記述

先ほども少し触れた通り、BladeファイルにはHTMLをそのまま記述することができます。
なので、ざっくりとした見た目を最初に作成してしまいましょう。

それでは、以前作成したfirst-app/resources/views/articles/index.blade.phpを下記のように編集してください。

index.blade.php
<!DOCTYPE HTML>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <title>ブログ</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/css/bootstrap.min.css" integrity="sha384-r4NyP46KrjDleawBgD5tp8Y7UzmLA05oM1iAEQ17CSuDqnUK2+k9luXQOfXJCJ4I" crossorigin="anonymous">
</head>

<body>
    <!-- ヘッダー -->
    <header>
        <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
            <div class="container-fluid">
                <a class="navbar-brand" href="#">ブログ</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="collapse navbar-collapse justify-content-end" id="navbarNavAltMarkup">
                    <div class="navbar-nav">
                        <a class="nav-item nav-link active" href="#">記事一覧 <span class="sr-only"></span></a>
                        <a class="nav-item nav-link" href="#">記事投稿</a>
                    </div>
                </div>
            </div>
        </nav>
    </header>

    <!-- コンテンツ -->
    <div class="container mt-4">
        <div class="row">
            <h2>記事一覧</h2>
            <table class="table table-bordered table-hover ">
                <thead class="bg-info text-light">
                    <tr>
                        <th scope="col">#</th>
                        <th scope="col">タイトル</th>
                        <th scope="col">内容</th>
                        <th scope="col">日付</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td>1</td>
                        <td>テスト1</td>
                        <td>テスト1です。</td>
                        <td>2022/01/01</td>
                    </tr>
                    <tr>
                        <td>2</td>
                        <td>テスト2</td>
                        <td>テスト2です。</td>
                        <td>2022/01/02</td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>

    <!-- フッター -->
    <footer class="footer bg-primary  fixed-bottom">
        <div class="container text-center">
            <span class="text-light">©︎Laravel教材</span>
        </div>
    </footer>

    <!-- JavaScript -->
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/js/bootstrap.min.js" integrity="sha384-oesi62hOLfzrys4LxRF63OJCXdXDipiYWBnvTl9Y9/TRlw5xlKIEHpNyvvDShgf/" crossorigin="anonymous"></script>
</body>

</html>

編集が終わったら保存してhttp://localhost/articlesへアクセスしてみましょう。
下記画像のように画面が表示されればOKです。
スクリーンショット 2022-06-14 18.32.37.png
まず、記事一覧に表示されている記事自体はHTMLで直接記述しているものになります。
後ほどデータベースから記事を取得して表示させますが、まずはHTMLで直接記述することで記事の表示をしておきます。

また、今回は見た目部分を整えるためにBootstrapというフレームワークを使用しています。
Bootstrapは複雑なCSSやJavaScriptを記述しなくてもHTMLにclassidを指定することで、簡単に見た目を整えることができるので重宝されています。

そんな便利なBootstrapですが、CDNでお手軽に使用することができます。
Bootstrapを使用する際のCDNは下記のようになっています。

BootstrapのCDN
<!-- CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/css/bootstrap.min.css" integrity="sha384-r4NyP46KrjDleawBgD5tp8Y7UzmLA05oM1iAEQ17CSuDqnUK2+k9luXQOfXJCJ4I" crossorigin="anonymous">

<!-- JavaScript -->
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/js/bootstrap.min.js" integrity="sha384-oesi62hOLfzrys4LxRF63OJCXdXDipiYWBnvTl9Y9/TRlw5xlKIEHpNyvvDShgf/" crossorigin="anonymous"></script>

本教材はLaravelを学習するための教材なのでBootstrapについての解説は省きますが、興味のある方は公式ドキュメントがあるので見てみてください。
※Bootstrapの基本的な使い方は、ドキュメントに記述しているコードをコピペして使用していきます。

Bladeの共通化

それでは、index.blade.phpに全て記述していた見た目部分をBladeの力を使用してヘッダーやコンテンツ、フッターと共通化させていきます。

共通化の流れは下記の通りです。

  1. 共通のテンプレートであるlayout.blade.phpを作成する
  2. 共通のヘッダーを作成する
  3. 共通のフッターを作成する
  4. index.blade.phpに共通のテンプレートであるlayout.blade.phpを継承させる

共通化継承と言われてもなかなかイメージがしにくいと思うので、まずは実装していきましょう。

layout.blade.phpの作成

それでは共通のテンプレートにするためのlayout.blade.phpを作成しましょう。
first-app/resources/viewsディレクトリにlayout.blade.phpをコマンドかVSCode上で作成してください。
コマンドで作成する場合は、ターミナルで下記コマンドをfirst-appディレクトリ上で実行してください。

ターミナル
$ touch resources/views/layout.blade.php

作成したlayout.blade.phpindex.blade.phpの内容をそのまま貼り付けてください。

layout.blade.php
<!DOCTYPE HTML>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <title>ブログ</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/css/bootstrap.min.css" integrity="sha384-r4NyP46KrjDleawBgD5tp8Y7UzmLA05oM1iAEQ17CSuDqnUK2+k9luXQOfXJCJ4I" crossorigin="anonymous">
</head>

<body>
    <!-- ヘッダー -->
    <header>
        <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
            <div class="container-fluid">
                <a class="navbar-brand" href="#">ブログ</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="collapse navbar-collapse justify-content-end" id="navbarNavAltMarkup">
                    <div class="navbar-nav">
                        <a class="nav-item nav-link active" href="#">記事一覧 <span class="sr-only"></span></a>
                        <a class="nav-item nav-link" href="#">記事投稿</a>
                    </div>
                </div>
            </div>
        </nav>
    </header>

    <!-- コンテンツ -->
    <div class="container mt-4">
        <div class="row">
            <h2>記事一覧</h2>
            <table class="table table-bordered table-hover ">
                <thead class="bg-info text-light">
                    <tr>
                        <th scope="col">#</th>
                        <th scope="col">タイトル</th>
                        <th scope="col">内容</th>
                        <th scope="col">日付</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td>1</td>
                        <td>テスト1</td>
                        <td>テスト1です。</td>
                        <td>2022/01/01</td>
                    </tr>
                    <tr>
                        <td>2</td>
                        <td>テスト2</td>
                        <td>テスト2です。</td>
                        <td>2022/01/02</td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>

    <!-- フッター -->
    <footer class="footer bg-primary  fixed-bottom">
        <div class="container text-center">
            <span class="text-light">©︎Laravel教材</span>
        </div>
    </footer>

    <!-- JavaScript -->
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/js/bootstrap.min.js" integrity="sha384-oesi62hOLfzrys4LxRF63OJCXdXDipiYWBnvTl9Y9/TRlw5xlKIEHpNyvvDShgf/" crossorigin="anonymous"></script>
</body>

</html>

これで今回作成したfirst-app/resources/views/layout.blade.phpfirst-app/resources/views/articles/index.blade.phpのコードは全く同じ状態です。

共通ヘッダーの作成

次にどのページにも共通するヘッダーであるheader.blade.phpの作成を行っていきます。
first-app/resources/viewsディレクトリにheader.blade.phpをコマンドかVSCode上で作成してください。

コマンドで作成する場合は、ターミナルで下記コマンドをfirst-appディレクトリ上で実行してください。

ターミナル
$ touch resources/views/header.blade.php

作成したheader.blade.phplayout.blade.phpのヘッダー部分をコピーして、下記の通り貼り付けてください。

header.blade.php
<header>
    <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
        <div class="container-fluid">
            <a class="navbar-brand" href="#">ブログ</a>
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse justify-content-end" id="navbarNavAltMarkup">
                <div class="navbar-nav">
                    <a class="nav-item nav-link active" href="#">記事一覧 <span class="sr-only"></span></a>
                    <a class="nav-item nav-link" href="#">記事投稿</a>
                </div>
            </div>
        </div>
    </nav>
</header>

また、layout.blade.phpのヘッダー部分を下記の通り編集してください。

layout.blade.php
<!DOCTYPE HTML>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <title>ブログ</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/css/bootstrap.min.css" integrity="sha384-r4NyP46KrjDleawBgD5tp8Y7UzmLA05oM1iAEQ17CSuDqnUK2+k9luXQOfXJCJ4I" crossorigin="anonymous">
</head>

<body>
    <!-- ヘッダー -->
    @include('header')

    <!-- コンテンツ -->
    <div class="container mt-4">
        <div class="row">
            <h2>記事一覧</h2>
            <table class="table table-bordered table-hover ">
                <thead class="bg-info text-light">
                    <tr>
                        <th scope="col">#</th>
                        <th scope="col">タイトル</th>
                        <th scope="col">内容</th>
                        <th scope="col">日付</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td>1</td>
                        <td>テスト1</td>
                        <td>テスト1です。</td>
                        <td>2022/01/01</td>
                    </tr>
                    <tr>
                        <td>2</td>
                        <td>テスト2</td>
                        <td>テスト2です。</td>
                        <td>2022/01/02</td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>

    <!-- フッター -->
    <footer class="footer bg-primary  fixed-bottom">
        <div class="container text-center">
            <span class="text-light">©︎Laravel教材</span>
        </div>
    </footer>

    <!-- JavaScript -->
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/js/bootstrap.min.js" integrity="sha384-oesi62hOLfzrys4LxRF63OJCXdXDipiYWBnvTl9Y9/TRlw5xlKIEHpNyvvDShgf/" crossorigin="anonymous"></script>
</body>

</html>

このように@include('header')と記述することでfirst-app/resources/views/header.blade.phpを読み込むことができます。

つまり、本来であればヘッダー部分をlayout.blade.phpに直接記述していましたが、ヘッダー部分だけをheader.blade.phpで管理することによって、ヘッダーを編集したい場合はheader.blade.phpのみを編集すればいいことになります。

このようにヘッダー部分を別ファイルで管理することによってメンテナンス性の向上可読性に繋がります。

共通フッターの作成

それではヘッダーと同じように、どのページにも共通するフッターfooter.blade.phpの作成を行っていきます。
first-app/resources/viewsディレクトリにfooter.blade.phpをコマンドかVSCode上で作成してください。

コマンドで作成する場合は、ターミナルで下記コマンドをfirst-appディレクトリ上で実行してください。

ターミナル
$ touch resources/views/footer.blade.php

作成したfooter.blade.phplayout.blade.phpのフッター部分をコピーして、下記の通り貼り付けてください。

footer.blade.php
<footer class="footer bg-primary  fixed-bottom">
    <div class="container text-center">
        <span class="text-light">©︎Laravel教材</span>
    </div>
</footer>

また、layout.blade.phpもフッター部分を下記の通り編集してください。

layout.blade.php
<!DOCTYPE HTML>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <title>ブログ</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/css/bootstrap.min.css" integrity="sha384-r4NyP46KrjDleawBgD5tp8Y7UzmLA05oM1iAEQ17CSuDqnUK2+k9luXQOfXJCJ4I" crossorigin="anonymous">
</head>

<body>
    <!-- ヘッダー -->
    @include('header')

    <!-- コンテンツ -->
    <div class="container mt-4">
        <div class="row">
            <h2>記事一覧</h2>
            <table class="table table-bordered table-hover ">
                <thead class="bg-info text-light">
                    <tr>
                        <th scope="col">#</th>
                        <th scope="col">タイトル</th>
                        <th scope="col">内容</th>
                        <th scope="col">日付</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td>1</td>
                        <td>テスト1</td>
                        <td>テスト1です。</td>
                        <td>2022/01/01</td>
                    </tr>
                    <tr>
                        <td>2</td>
                        <td>テスト2</td>
                        <td>テスト2です。</td>
                        <td>2022/01/02</td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>

    <!-- フッター -->
    @include('footer')

    <!-- JavaScript -->
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/js/bootstrap.min.js" integrity="sha384-oesi62hOLfzrys4LxRF63OJCXdXDipiYWBnvTl9Y9/TRlw5xlKIEHpNyvvDShgf/" crossorigin="anonymous"></script>
</body>

</html>

ヘッダーと同じく@include('footer')と記述することでfirst-app/resources/views/footer.blade.phpを読み込むことができます。

これでフッター部分はfooter.blade.phpで管理することができるようになりました。

index.blade.phpにlayout.blade.phpを継承させる

それでは、残っているコンテンツ部分を@yield@section~@endsectionで読み込ませるようにしてから、index.blade.phplayout.blade.phpを継承させていきます。

コンテンツの表示

まずはコンテンツ部分を@yield@section~@endsectionを使用して読み込ませる記述をしていきます。

layout.blade.phpを下記の通り編集してください。

layout.blade.php
<!DOCTYPE HTML>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <title>ブログ</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/css/bootstrap.min.css" integrity="sha384-r4NyP46KrjDleawBgD5tp8Y7UzmLA05oM1iAEQ17CSuDqnUK2+k9luXQOfXJCJ4I" crossorigin="anonymous">
</head>

<body>
    <!-- ヘッダー -->
    @include('header')

    <!-- コンテンツ -->
    <div class="container mt-4">
        @yield('content')
    </div>

    <!-- フッター -->
    @include('footer')

    <!-- JavaScript -->
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/js/bootstrap.min.js" integrity="sha384-oesi62hOLfzrys4LxRF63OJCXdXDipiYWBnvTl9Y9/TRlw5xlKIEHpNyvvDShgf/" crossorigin="anonymous"></script>
</body>

</html>

まず共通のレイアウトとなるlayout.blade.phpのコンテンツ部分にある<div class="container mt-4"></div>以外のコードを全て削除してから、代わりに@yield('content')と記述します。

このように@yield('content')と記述することで、あるファイルでlayout.blade.phpが継承された時に@section('content')@endsectionで囲まれた部分を@yield('content')部分に読み込ませることができます。

それではindex.blade.phplayout.blade.phpを継承して、@section('content')@endsectionで囲んだ部分にコンテンツを記述してみましょう。

index.blade.phpを下記の通り編集してください。

index.blade.php
@extends('layout')

@section('content')
    <div class="row">
        <h2>記事一覧</h2>
        <table class="table table-bordered table-hover ">
            <thead class="bg-info text-light">
                <tr>
                    <th scope="col">#</th>
                    <th scope="col">タイトル</th>
                    <th scope="col">内容</th>
                    <th scope="col">日付</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td>1</td>
                    <td>テスト1</td>
                    <td>テスト1です。</td>
                    <td>2022/01/01</td>
                </tr>
                <tr>
                    <td>2</td>
                    <td>テスト2</td>
                    <td>テスト2です。</td>
                    <td>2022/01/02</td>
                </tr>
            </tbody>
        </table>
    </div>
@endsection

まず継承するためには@extends()を使用します。
@extends()を使用し()内に継承したいBladeファイルを記述することで、index.blade.phpに()内で指定したBladeファイルを継承することができます。

今回はindex.blade.phplayout.blade.phpを継承したいので、@extends('layout')となります。
無事にlayout.blade.phpを継承することができたので、layout.blade.phpの中に記述されている全てのコードがindex.blade.phpに記述されているのと同じ状況を作り出すことができました。

そして、最後にindex.balde.phpの先ほどlayout.blade.phpで削除したコンテンツ部分を@section('content')@endsectionで囲まれた部分に記述すればOKです。

さっそくhttp://localhost/articlesへアクセスしてみましょう。
下記画像のように画面が表示されればOKです。
スクリーンショット 2022-06-14 18.32.37.png
これで無事にコンテンツ部分を読み込ませることに成功しましたが、実はあと1つ@yield()@section()~@endsectionで記述した方が良い箇所があります。

それはlayout.blade.phptitle要素です。

タイトル部分はページ毎に変更したいので、ここも@yield()を使用してページ毎に変更可能にさせましょう。

それでは、layout.blade.phpを下記の通り編集してください。

layout.blade.php
<!DOCTYPE HTML>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <title>@yield('title', 'ブログ')</title> 
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/css/bootstrap.min.css" integrity="sha384-r4NyP46KrjDleawBgD5tp8Y7UzmLA05oM1iAEQ17CSuDqnUK2+k9luXQOfXJCJ4I" crossorigin="anonymous">
</head>

<body>
    <!-- ヘッダー -->
    @include('header')

    <!-- コンテンツ -->
    <div class="container mt-4">
        @yield('content')
    </div>

    <!-- フッター -->
    @include('footer')

    <!-- JavaScript -->
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/js/bootstrap.min.js" integrity="sha384-oesi62hOLfzrys4LxRF63OJCXdXDipiYWBnvTl9Y9/TRlw5xlKIEHpNyvvDShgf/" crossorigin="anonymous"></script>
</body>

</html>

変更した箇所は、title要素の中身です。
変更前はブログというテキストのみを記述していましたが、このままだと全てのページでtitle要素がブログになってしまいます。

代わりに@yield('title', 'ブログ')と記述することで、layout.blade.phpを継承したファイルで@section('title')~@endsectionを使用することで、ページ固有のタイトルを決めることができるようになります。

また、万が一継承したファイルに@section('title')~@endsectionがない場合も@yield()の第二引数であるブログがページのタイトルになるので、設定し忘れたとしてもしっかりとタイトルブログが表示されます。

では、次にindex.blade.phpを下記の通り編集してください。

index.blade.php
@extends('layout')

@section('title')
    記事一覧
@endsection

@section('content')
    <div class="row">
        <h2>記事一覧</h2>
        <table class="table table-bordered table-hover ">
            <thead class="bg-info text-light">
                <tr>
                    <th scope="col">#</th>
                    <th scope="col">タイトル</th>
                    <th scope="col">内容</th>
                    <th scope="col">日付</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td>1</td>
                    <td>テスト1</td>
                    <td>テスト1です。</td>
                    <td>2022/01/01</td>
                </tr>
                <tr>
                    <td>2</td>
                    <td>テスト2</td>
                    <td>テスト2です。</td>
                    <td>2022/01/02</td>
                </tr>
            </tbody>
        </table>
    </div>
@endsection

追加したコードは以下の通りです。

追加したコード
@section('title')
    記事一覧
@endsection

layout.blade.php@yield('title')と記述したので、index.blade.phpでは@section('title')~@endsectionと記述します。
また、index.blade.phpは記事一覧を表示させるためのページなので、記事一覧というページタイトルにしています。

それでは改めてhttp://localhost/articlesへアクセスしてみましょう。
下記画像のように画面が表示されていて、Chromeのタブ部分であるページタイトルが記事一覧になっていればOKです。
スクリーンショット 2022-06-14 18.32.37.png
このようにBladeで共通化してもユーザーが見る見た目はindex.blade.phpへ全てのHTMLを記述したときとまったく変わっていません。
その代わりindex.blade.phpの内容はlayout.blade.phpを継承したことで大きく変わっています。

これからBladeファイルを作成するときはlayout.blade.phpを継承し、titlecontent部分だけを記述すればページが完成します。

まだ記事一覧ページしか作成していないのでBladeの恩恵を感じにくいかもしれませんが、これから記事詳細画面や記事作成画面、記事編集画面を作成していく中でより恩恵を感じられると思います。

ひとまず記事一覧画面の見た目の作成はこれで完了です。

★検索ワード
・Laravel view 作成
・Laravel view 共通化
・Laravel @extends
・Laravel @yield
・Laravel @section @endsection
・Laravel @include
・Bootstrap 使い方

全ての記事を取得して一覧で表示

ここまでで記事一覧画面をHTMLのみで作成しましたが、これからは記事のデータをデータベースから取得して、取得したデータを表示するようにしていきます。

今回初めてLaravelを使用してデータベースからデータを取得するので、どのようにデータを取得して表示させるのかの流れを確認していきます。

流れは下記のようになっています。

  1. モデルを使用してデータベースから記事データを取得する
  2. コントローラーでモデルを呼び出してビューにデータを渡す
  3. ビュー(Bladeファイル)で記事データを全て表示する

それでは流れに沿って一つひとつ解説していきます。
なお、コードは後ほど書き換えるので下記の説明時には変更しなくて大丈夫です。

記事データを取得する方法

Laravelのモデルを使用してデータを取得する方法はいくつかありますが、全てのデータを取得したい場合にはallメソッドを使用することができます。

allメソッドの使い方は下記の通りです。

allメソッドの使い方
$変数名 = モデルクラス名::all();

データを格納するための変数を用意し、モデルクラス名に:all()と記述するだけで、モデルに紐付いたテーブルのデータが全て変数に格納することができます。

今回作成しているArticleモデルを使用する場合は下記のようになります。

Articleモデルの場合
$articles = Article::all();

データを格納するための変数名は、データが複数あることが考えられるのでarticlesと複数形にしています。
また、モデルクラス名はfirst-app/app/Models/Article.phpファイルのクラス名になるので、Articleになります。

それに::all()を記述するだけです。
とても簡単ですね!

コントローラーでモデル呼び出してデータを渡す方法

それでは、先ほど学んだデータの取得方法のallメソッドをコントローラーのメソッド内で実行していきます。

まずfirst-app/app/Http/Controllers/ArticleController.phpを確認してみましょう。
その中にshowArticlesというメソッドがあります。
このメソッドこそが記事一覧を表示するためのメソッドです。

そのため、このメソッド内でallメソッドを使用してデータを取得し、ビュー(Bladeファイル)へデータを渡すことになります。

ビューへデータを渡す方法はいくつかありますが、まずは配列で渡す場合を説明します。

配列で渡す場合
$articles = Article::all();

return view('articles.index', [
    'articles' => $articles,
]);

viewメソッドの第二引数として配列を記述し、その中に'Bladeで受け取る時の名前' => 渡したいデータの変数名とすることでBladeファイルへデータを渡すことができます。

今回の例だとindex.blade.phpのBladeファイルで変数articlesのデータを受け取る時は、$articlesと記述することで受け取れるようになります。

次に配列ではなくcompact関数を使用した場合も説明します。

compact関数で渡す場合
$articles = Article::all();

return view('articles.index', compact('articles'));

viewメソッドの使い方は同じですが、第二引数に配列ではなくcompact関数を使用しています。
compact('渡したいデータの変数名')を記述することでBladeファイルへデータを渡すことができます。
配列よりもコードがコンパクトになるので、こちらをおすすめします。

また、今回の例もindex.blade.phpのBladeファイルで変数articlesのデータを受け取る時は、$articlesと記述することで受け取れるようになります。

Bladeでデータを表示する方法

BladeファイルにはPHPを直接記述することができます。
その機能とBladeの構文を使用することで簡単にデータを表示することができます。

今回Bladeファイルで受け取るデータはarticlesテーブルの全データなので、複数あることが予想されます。
そのためforeachを使用したループ処理を使用して、全ての記事データを表示していきます。

Bladeでは@foreach~@endforeachというループ処理用の構文が用意されているので、そちらを使用してデータを表示させることができます。

今回の場合は下記のようになります。

@foreachでデータを表示する
@foreach($articles as $article)
    {{ $article->id }}
@endforeach

@foreach($articles as $article)とすることで、変数articlesの要素を変数articleとして取り出すことができます。

Bladeファイル内でPHPを記述する場合は{{}}で囲んでください。
{{}}で囲うことでブラウザに値を出力することができます。

$article->idについては実装時に説明します。

実装

実際に手を動かしながら実装していきましょう。

まずはfirst-app/app/Http/Controllers/ArticleController.phpを下記の通り編集してください。

ArticleController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Article; // ここを追加

class ArticleController extends Controller
{
    /**
     * 記事一覧を表示する
     */
    public function showArticles()
    {
        // 全ての記事データを取得
        $articles = Article::all();

        dd($articles);
    }
}

まず追加したのは下記コードです。

use App\Models\Article;

このようにuse文を使用することで、Articleモデルを他ファイルで使用することができるようになります。

use文の使い方はuseと記述してから、使用したいファイルのnamespace部分とクラス名を記述します。
例えば、今回の場合はfirst-app/app/Models/Article.phpnamespaceを確認します。
するとnamespace App\Models;と記述されており、クラス名はArticleとなっています。

つまり、Articleモデルを他ファイルで使用したい場合はuse App\Models\Articleと記述することになります。

これでArticleコントローラー内でArticleモデルを使用できるようになったので、Article::all()と記述することで、Articleモデルを通してarticlesテーブルから全てのデータを取得することができます。
取得したデータを変数articlesへ格納するために$articles = Article::all();と記述しています。
これでデータベースに保存されている全ての記事データを変数articlesに格納することができました。

実はLaravelには変数内にどのような値が入っているのかを確認するためのヘルパーメソッドが用意されています。
それがddメソッドです。
このddメソッドを使用すると、第一引数に記述した変数の内容をブラウザで視覚的に確認することができます。
また、ddメソッド以降の処理は実行されないので、ある時点での変数の中身を確認したい時に重宝します。

今回は全ての記事データが入っている変数articlesを確認するためにdd('articles')と記述してみました。

それではhttp://localhost/articlesへアクセスしてみましょう。
ddメソッドを使用すると、下記画像のような画面が出てきます。
スクリーンショット 2022-06-13 22.40.54.png
#items: array:3[▶]となっているをクリックしてみてください。
そうすると下記画像のように中身が展開されます。
スクリーンショット 2022-06-13 22.42.06.png
現在articlesテーブルには、テストデータとして作成した3つのデータが入っています。
その3つのデータが配列で入っていることがこれで確認できました。

次に0 => App\Mo…\Article{#319 ▶}をクリックしてみましょう。
※ #319という番号は人によって違う場合がございます。
スクリーンショット 2022-06-13 22.45.51.png
するとさらに展開され、その中に#attributes: array5 [▶]があると思います。
そのをクリックしてみてください。
スクリーンショット 2022-06-13 22.47.06.png
attributesが展開され、中に入っているデータを確認することができます。
スクリーンショット 2022-06-13 22.48.26.png
これはシーダーを使用して追加した1番目のテストデータですね。
このように、モデルで取得したデータの内容はattributesに格納されるので、覚えておきましょう。

2番目や3番目のデータも同じようにをクリックして展開していくことでデータ内容を確認することができます。

このようにddメソッドを使用すると、変数の内容をブラウザで簡単に確認することができます。
デバッグや実装中にどのような値が入っているか一旦確認したい場合に使用してみましょう。

次はコントローラーからBladeファイルに値を渡すための処理を記述しましょう。
first-app/app/Http/Controllers/ArticleController.phpを下記の通り編集してください。

ArticleController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Article;

class ArticleController extends Controller
{
    /**
     * 記事一覧を表示する
     */
    public function showArticles()
    {
        // 全ての記事データを取得
        $articles = Article::all();

        // compact関数で渡す場合
        return view('articles.index', compact('articles'));
    }
}

先ほど説明した通り、コントローラーからBladeファイルへ値を渡すためにcompact関数を使っています。

return view('articles.index', compact('articles'));と記述することでindex.blade.phpに全ての記事データが入っている変数articlesを渡すことができます。

それでは渡した変数をindex.blade.phpで表示するようにしましょう。

first-app/resources/views/articles/index.blade.phpを下記の通り編集してください。

index.blade.php
@extends('layout')

@section('title')
    記事一覧
@endsection

@section('content')
    <div class="row">
        <h2>記事一覧</h2>
        <table class="table table-bordered table-hover ">
            <thead class="bg-info text-light">
                <tr>
                    <th scope="col">#</th>
                    <th scope="col">タイトル</th>
                    <th scope="col">内容</th>
                    <th scope="col">日付</th>
                </tr>
            </thead>
            <tbody>
                @foreach ($articles as $article)
                    <tr>
                        <td>{{ $article->id }}</td>
                        <td>{{ $article->title }}</td>
                        <td>{{ $article->content }}</td>
                        <td>{{ $article->updated_at }}</td>
                    </tr>
                @endforeach
            </tbody>
        </table>
    </div>
@endsection

ArticleコントローラーのshowArticlesメソッドから全ての記事データ入った変数articlesが渡されてきたので、Bladeファイルではループ処理を使用して1つずつ表示させていきます。

Laravelでループ処理を実装したい場合は@foreach()~@endforeachを使用することで、簡単にループ処理が実装できます。

下記の通りddメソッドを使用して、変数articlesの一つひとつのデータを確認すると、テーブルのカラム名がそのままキーとして設定されていることがわかります。
スクリーンショット 2022-06-13 22.48.26.png
そのため、記事のIDを取得したい場合は$article->id、タイトルを取得したい場合は$article->title、内容を取得したい場合は$article->content、更新日を取得したい場合は$article->updated_atと記述すればいいことになります。

また、今回は使用していませんが、作成日を取得したい場合は$article->created_atとなります。

Bladeでは、それらを{{}}で囲むことで安全にブラウザに表示することができます。

それではhttp://localhost/articlesへアクセスしてみてください。

下記のようにデータベースから取得した3つのデータが表示できていればOKです!
スクリーンショット 2022-06-14 18.34.21.png

データ取得から表示までの流れの確認

最後にデータベースからデータを取得した後、データを表示させるまでの流れをMVCの画像を見ながら確認していきましょう。

まず、下記の画像がMVCにルートを追加した画像です。
スクリーンショット 2022-06-11 20.53.05.png
http://localhost/articlesへアクセスがあった時に最初に読まれるファイルはfirst-app/routes/web.phpです。
これが①のアクセスです。

その中のRoute::get('/articles', [ArticleController::class, 'showArticles'])->name('showArticles');が実行されることで、ArticleコントローラーshowArticlesメソッドが実行されます。

そのため次は、first-app/app/Http/Controllers/ArticleController.phpshowArticlesメソッドの中身の処理を確認しましょう。

showArticlesメソッドには$articles = Article::all();という処理が記述されています。
これはArticleモデルが継承しているModelクラスが持つallメソッドを実行しています。

allメソッドを実行するとモデルがデータベースへ接続しにいき、取得したarticlesテーブルの全データをコントローラーの変数articlesに格納します。
つまり②のデータ処理がここで行われており、③のDB接続も同時に行っています。

そして、return view('articles.index', compact('articles'));と記述することで、コントローラーからビューであるfirst-app/resources/views/articles/index.blade.phpというBladeファイルへ変数articlesを渡しています。
これが④のデータ譲渡です。

そして最後にfirst-app/resources/views/articles/index.blade.php@foreachを使用して全てのデータを一つひとつ表示しているという流れです。
これが⑤のサイト表示です。

Laravelでは、このように①→②→③→②→④→⑤のような流れでデータを取得し、表示することができます。
これがMVCの基本的な流れになるので、ぜひ覚えておきましょう!

★検索ワード
・Laravel 全てのデータを取得
・Laravel Eloquent all
・Laravel ddメソッド
・Laravel compact
・Laravel View データを渡す

記事詳細を取得して表示

ここまでで全ての記事を一覧で表示できるようになりましたが、今回は一つひとつの記事の詳細画面を作成していきます。

記事詳細画面を作成する流れは下記のようになります。

  1. 詳細を表示する記事のIDでリンクを作成する
  2. ルートでIDを受け取りコントローラーへ渡す
  3. 受け取ったIDのデータをモデルを使用して取り出す
  4. ビューで表示する

それでは流れに沿って一つひとつ実装していきましょう。
なお、コードは後ほど書き換えるので下記の説明時には変更しなくて大丈夫です。

詳細表示する記事のIDでリンクを作成する

特定の記事を取得するためにはIDを使用します。
IDを使用する理由は、IDとは記事一つひとつに与えられた固有の値であり、同一テーブル内でIDがかぶることがないからです。

例えばIDが1の記事を取得する場合は下記のようなURLにします。
http://localhost/article/1
こうすることでURL内の1とテーブル内のIDが1のデータが紐付けられるので、IDが1のデータを取得することができます。

ルートでIDを受け取りコントローラーへ渡す

では、http://localhost/article/1というURLへアクセスされると、ルート(web.php)へ処理が移動します。

なので、ルートでIDを受け取り、その後コントローラーにもIDを渡すことが必要になります。
そのためのルートの記述は下記のようになります。

ルートでIDを受け取ってコントローラーへ渡す
Route::get('/article/{id}', [ArticleController::class, 'showArticle']);

このような記述をすることで、http://localhost/article/1へアクセスされるとArticleコントローラーshowArticleメソッドが実行されるようになります。
ちなみに、showArticleメソッドは後ほど作成します。

また、'/article/{id}'{id}についてですが、ルートパラメーターと呼ばれていて、{}で囲むことで定義することができます。

今回の例だと$idという変数で値をコントローラーへ渡すことができます。
つまり、http://localhost/article/1へアクセスがあった場合、1を引数idとしてshowArticleメソッドに渡すことができるということです。

受け取ったIDのデータを取り出す

showArticleメソッドでIDを受け取った後は、そのIDを使用してarticlesテーブルからIDが1のデータを取得します。

1つのデータを取得するときはallメソッドではなく、findメソッドを使用します。
具体的な使い方は下記の通りです。

findメソッドの使い方
$article = Article::find(1);

このようにArticle::find(1)と記述することでIDが1の記事を取得することができます。
つまり、allメソッドでは全てのデータを取得していましたが、findメソッドを使用することで特定のデータを取得することができます。

また、別の方法としてSQLのWhere句のような記述方法もあります。

where
$article = Article::where('id' => '1')->first();

whereメソッドfirstメソッドを使用することで、記事IDが1のデータを取得することができます。
whereメソッドの第一引数には検索するカラム名を取得し、第二引数には検索するための値を指定します。
今回の記事IDが1のデータを取得するためには、where('id' => '1')となります。
また、whereメソッドで検索条件を決めた後にfirstメソッド->で繋げることで、検索条件に合うデータを1つ取得することができます。

ビューで表示する

あとはコントローラーで取得したデータをビューに渡して表示するだけです。
詳細表示用のBladeファイルも後ほどlayout.blade.phpを継承して作成していきます。

実装

それでは記事詳細画面を作成する流れを説明したので、実際に手を動かしながら実装していきましょう。

まずはfirst-app/routes/web.phpを下記の通り編集してください。

web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ArticleController;

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

// 記事一覧を表示
Route::get('/articles', [ArticleController::class, 'showArticles'])->name('showArticles');

// 記事詳細を表示
Route::get('/article/{id}', [ArticleController::class, 'showArticle'])->name('showArticle'); // ここを追加

これで記事詳細画面を表示するルートを記述することができました。
先ほど説明した通り、ルートパラメーターを使用して記事IDをルートで受け取り、コントローラーへ渡せるようにしています。

次にfirst-app/resources/views/articles/index.blade.phpを下記の通り編集してください。

index.blade.php
@extends('layout')

@section('title')
    記事一覧
@endsection

@section('content')
    <div class="row">
        <h2>記事一覧</h2>
        <table class="table table-bordered table-hover ">
            <thead class="bg-info text-light">
                <tr>
                    <th scope="col">#</th>
                    <th scope="col">タイトル</th>
                    <th scope="col">内容</th>
                    <th scope="col">日付</th>
                </tr>
            </thead>
            <tbody>
                @foreach ($articles as $article)
                    <tr>
                        <td>{{ $article->id }}</td>
                        <td>
                            <a href="{{ route('showArticle', $article->id) }}">
                                {{ $article->title }}
                            </a>
                        </td>
                        <td>{{ $article->content }}</td>
                        <td>{{ $article->updated_at }}</td>
                    </tr>
                @endforeach
            </tbody>
        </table>
    </div>
@endsection

変更したコードは下記の通りです。

リンクを作成する
<td>
    <a href="{{ route('showArticle', $article->id) }}">
        {{ $article->title }}
    </a>
</td>

上記のコードを見ていただくとわかるように、記事タイトルをaタグで囲ってリンクにしています。
今回は記事タイトルをリンクにし、クリックされた時に記事詳細画面を表示するようにしていきます。

先ほど、記事詳細画面を作成する流れでも説明した通り、まずは記事の詳細を表示するための記事IDに結びついたリンクを作成しています。

aタグのリンク先を決めるためにはhref属性を使用しますが、そのhref属性にLaravelのヘルパーメソッドであるrouteメソッドを使用します。

routeメソッドの第一引数を見てみるとshowArticleとなっています。
このshowArticleはルーティングでshowArticleメソッドを実行するルートのルート名です。
なんとなくルーティング時に指定していたルート名ですが、ここでようやく使用するときがやってきました。

それではweb.phpshowArticleメソッドを実行するためのルートを見てください。
Route::get('/article/{id}', [ArticleController::class, 'showArticle'])->name('showArticle');
ルート名を指定していたのはname('showArticle');です。

このルート名をrouteメソッドの第一引数に指定することで、指定したルートへのリンクが作成できるようになります

次に第二引数を見てみると$article->idとなっています。
これは記事IDを第二引数に指定しているということです。

このように第二引数に紐付けたい値を記述することでルートに記事ID渡すことができます。

では、実際にhttp://localhost/articlesへアクセスしてから1番目の投稿をクリックしてください。
下記のようなエラー画面が表示されますが、URLを確認してみましょう。
http://localhost/articles/1になっているはずです。
スクリーンショット 2022-06-21 0.31.00.png
今回は記事IDが1である記事タイトル1番目の投稿をクリックしたので、URLには1が表示されています。
ちなみに、エラー内容はArticleコントローラーshowArticleメソッドがありませんというエラーです。
なので、次はshowArticleメソッドを作成していきましょう!

受け取ったIDのデータを取り出す

まずはfirst-app/app/Http/Controllers/ArticleController.phpを下記の通り編集してください。

ArticleController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Article;

class ArticleController extends Controller
{
    /**
     * 記事一覧を表示する
     */
    public function showArticles()
    {
        // 全ての記事データを取得
        $articles = Article::all();

        // compact関数で渡す場合
        return view('articles.index', compact('articles'));
    }

    /**
     * 記事詳細を表示する
     */
    public function showArticle($id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        dd($article);
    }
}

追加したコードは下記の通りです。

追加したコード
/**
 * 記事詳細を表示する
 */
public function showArticle($id)
{
    // 渡されてきた記事IDのデータを取得
    $article = Article::find($id);

    dd($article);
}

まず、ルートから渡されてきた記事IDはshowArticleメソッドの第一引数として取得することができます。
そのため、public function showArticle($id){}としましょう。

次に、受け取った記事IDの記事データを取得したいので下記コードのようにfindメソッドを使用します。
Article::find($id)

そしてddメソッドを使用して、取得したデータを一度ブラウザで確認しておきます。
http://localhost/article/1へアクセスしてみてください。
スクリーンショット 2022-06-14 23.27.02.png
上記のようなデータが表示されるので、先ほどと同じようにattributesを展開するとデータの中身を確認することができます。

無事に記事IDが1のデータが取得できていますね!

データが確認できたので、改めてfirst-app/app/Http/Controllers/ArticleController.phpを下記の通り編集してビューに値を渡しましょう。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Article;

class ArticleController extends Controller
{
    /**
     * 記事一覧を表示する
     */
    public function showArticles()
    {
        // 全ての記事データを取得
        $articles = Article::all();

        // compact関数で渡す場合
        return view('articles.index', compact('articles'));
    }

    /**
     * 記事詳細を表示する
     */
    public function showArticle($id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        return view('articles.detail', compact('article'));
    }
}

ここは記事一覧を表示させた時と同じように、viewメソッドを使用してdetail.blade.phpへ値を渡しています。

もちろんhttp://localhost/article/1へアクセスしても、detail.blade.phpが存在しないので、View[articles.detail] not foundというdetail.blade.phpが見つからないよ!というエラーが発生します。

ビューで表示する

最後にコントローラーから渡されてきた記事データを表示するための画面を作成しましょう。

articlesディレクトリにdetail.blade.phpを作成してください。
コマンドで作成する場合は、ターミナルで下記コマンドをfirst-appディレクトリ上で実行してください。

ターミナル
$ touch resources/views/articles/detail.blade.php

作成したdetail.blade.phpを開いて、下記の通り編集してください。

detail.blade.php
@extends('layout')

@section('title')
    記事詳細
@endsection

@section('content')
    <h2>{{ $article->title }}</h2>
    <div class="d-flex">
        <span class="mr-2">作成日:{{ $article->created_at }}</span>
        <span>更新日:{{ $article->updated_at }}</span>
    </div>
    <p class="mt-4">{{ $article->content }}</p>
    <a href="{{ route('showArticles') }}" class="mt-3 btn btn-secondary">戻る</a>
@endsection

index.blade.phpと同じようにlayout.blade.phpを継承することで簡単にページを作成できました。
変えたのは@sectionの中身だけで、titleには記事一覧として、contentには記事のデータが表示できるようにしています。

コントローラーから記事IDが1の記事を変数articleで渡されているので$article->titleと記述すると記事のタイトルが取得できます。
その他の作成日や更新日、内容なども変数articleを使用すれば、簡単に表示させることができます。

また、今回は受け取った記事は記事IDが1の記事1つだけなのでループ処理はいりません。

そして、最後に戻るボタンを実装しています。

先ほどは記事IDに紐付いたリンクを生成しましたが、戻るボタンの実装では記事一覧画面へ飛べるリンクを生成するだけなので、特に第二引数は必要ありません。

記事一覧画面を表示させるルート名showArticlesrouteメソッドの第一引数に記述するだけで、記事一覧画面に遷移することができます。

動作確認

それでは念の為、正しく実装できているか動作確認をしてみましょう。
まずはhttp://localhost/articlesへアクセスして、1番目の投稿をクリックしてください。
そうすると下記のように記事IDが1の記事詳細画面が表示されます。
スクリーンショット 2022-06-15 0.08.38.png
次に戻るボタンをクリックしてみましょう。
そうすると記事一覧画面に戻ります。
最後に3番目の投稿をクリックしてみましょう。
下記のように記事IDが3の記事詳細画面が表示されればOKです。
スクリーンショット 2022-06-15 0.10.29.png

★検索ワード
・Laravel 詳細ページ
・Laravel Eloquent find
・Laravel Eloquent where
・Laravel Eloquent first

記事投稿

ここまで記事一覧画面と記事詳細画面を作ってきましたが、ここからは記事を投稿する機能の作成をしていきます。

記事投稿の流れは下記のようになっています。

  1. ビューのフォームからデータを飛ばす
  2. ルートで受け取りコントローラーへ渡す
  3. フォームリクエストを使用してバリデーションをする
  4. モデルでデータを登録する
  5. エラー処理を記述

ビューのフォームからデータを飛ばす

まずは記事投稿フォームに記事のタイトルと内容が入力され、登録ボタンがクリックされた時にデータを飛ばしていきます。

ルートで受け取りコントローラーへ渡す

ルートでリクエストを受け取り、データを登録するためのメソッドにデータを渡します。

フォームリクエストを使用してバリデーションをする

Laravelにはバリデーションを簡単に実装するためのフォームリクエストという機能が最初から組み込まれています。

フォームリクエストの作成方法を説明する前に、そもそもバリデーションとは何なのかということについても学習していきましょう。

バリデーションとは

入力されたデータが正しい値かどうかをチェックする機能をバリデーションと言います。

例えば、マイグレーションでarticlesテーブルを作成した時に、テーブル定義でタイトルと内容は必須で登録しなければいけないことにしています。
しかし、記事をフォームで登録する時にタイトルと内容の2つが入力されていなかった場合は、ない値をデータベースに登録することはできないので、エラーになってしまいます。

バリデーションではこのような事態を避けるために、必須チェックというものを実装することができます。
簡単に説明すると、フォームに値が入っているか入っていないかのチェックができるということです。
また、バリデーションは必須チェックだけではなく、データの型チェックや文字数チェックなど様々なチェックをすることができます。

つまり、開発者の意図しないデータがフォームに入力されることを防ぐことができるので、セキュリティ強化の面からバリデーションはほぼ必須となります。

フォームリクエストの作成方法

それではバリデーションがどのようなものか説明したので、フォームリクエストの作成方法について説明します。
フォームリクエストも下記コマンド1つで作成することができます。

フォームリクエストの作成
$ ./vendor/bin/sail php artisan make:request クラス名

このコマンドを使用すると、1度目の実行時にfirst-app/app/Http/ディレクトリにRequestsというディレクトリが生成され、その中にクラス名で指定した名前のPHPファイルが作成されます。

2回目以降はfirst-app/app/Http/Requestsディレクトリがすでに存在しているので、その中に新しいフォームリクエストファイルが作成されます。

使用方法などは実装する時に説明していきます。

モデルでデータを登録

すでにモデルの機能であるallメソッドfindメソッドについて学習しましたが、モデルでデータを登録する時にはcreateメソッドというものを使用します。

使い方は下記の通りです。

createメソッド
Article::create([
    'title' => 'タイトル',
    'content' => 'コンテンツ',
]);

このようにArticle::create()と記述し、第一引数に配列で登録するデータを入れるだけです。

また、Laravelにはもう1つデータを登録するためのfillメソッド + saveメソッドが提供されています。
使い方は下記の通りです。

saveメソッド
$article = new Article;

$article->fill([
    'title' => 'タイトル',
    'content' => 'コンテンツ',
]);

$article->save();

他にも登録方法はありますが、セキュリティの観点からそれらは非推奨となっているので、上記2つの方法のどちらかでデータ登録機能を実装しましょう。
おすすめは記述するコードが少ないcreateメソッドです。

また、これら2つの登録方法を使用した場合、変更できるカラムはfirst-app/app/Models/Article.phpprotected $fillableprotected $guardedで指定したものだけです。

そのため、first-app/app/Models/Article.phpprotected $fillabletitlecontentを指定しています。

エラー処理を記述

最後にデータベースへ登録できなかった場合のエラー処理をしておきます。

これをすることによって万が一エラーが発生した時に、ユーザーが視覚的に記事を登録できないことを把握することができます。

実装

それでは記事登録の流れを説明したので、さっそく実装していきましょう。

記事投稿用のルート設定

まずは記事を登録するフォームを表示するためのルートを設定していきましょう。
first-app/routes/web.phpを下記の通り編集してください。

web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ArticleController;

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

// 投稿一覧を表示
Route::get('/articles', [ArticleController::class, 'showArticles'])->name('showArticles');

// 記事投稿フォームを表示
Route::get('/article/create', [ArticleController::class, 'createArticle'])->name('createArticle'); // ここを追加

// 投稿詳細を表示
Route::get('/article/{id}', [ArticleController::class, 'showArticle'])->name('showArticle');

いつも通りRoute::getでパスとコントローラー、メソッド名を指定しています。
今回は記事を作成するという意味のcreateArticleメソッドとしました。

createArticleメソッドの作成

次に、Articleコントローラーにルートで指定したcreateArticleメソッドを記述していきましょう。

first-app/app/Http/Controllers/ArticleController.phpを下記の通り編集してください。

ArticleController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Article;

class ArticleController extends Controller
{
    /**
     * 記事一覧を表示する
     */
    public function showArticles()
    {
        // 全ての記事データを取得
        $articles = Article::all();

        // compact関数で渡す場合
        return view('articles.index', compact('articles'));
    }

    /**
     * 記事投稿フォームを表示
     */
    public function createArticle()
    {
        return view('articles.create');
    }

    /**
     * 記事詳細を表示する
     */
    public function showArticle($id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        return view('articles.detail', compact('article'));
    }
}

追加したコードは下記の通りです。

追加したコード
/**
 * 記事投稿フォームを表示
 */
public function createArticle()
{
    return view('articles.create');
}

これで記事投稿フォームを表示するためのcreateArticleメソッドを定義することができました。
投稿フォームに表示させるデータは何もないので、create.blade.phpへ渡す処理をviewメソッドを使用して記述するだけでOKです。

記事投稿用フォームの作成

次にfirst-app/resources/views/articles配下にcreate.blade.phpをコマンドかVSCodeで作成してください。

コマンドで作成する場合は、ターミナルにて下記コマンドをfirst-appディレクトリ上で実行してください。

ターミナル
$ touch resources/views/articles/create.blade.php

作成したfirst-app/resources/views/articles/create.blade.phpを下記の通り編集してください。

create.blade.php
@extends('layout')

@section('title')
    記事作成
@endsection

@section('content')
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <h2>記事投稿フォーム</h2>
            <form method="POST" action="">
                <div class="form-group">
                    <label for="title">
                        タイトル
                    </label>
                    <input id="title" name="title" class="form-control" value="{{ old('title') }}" type="text">
                    @if ($errors->has('title'))
                        <div class="text-danger">
                            {{ $errors->first('title') }}
                        </div>
                    @endif
                </div>
                <div class="form-group mt-3">
                    <label for="content">
                        内容
                    </label>
                    <textarea id="content" name="content" class="form-control" rows="4">{{ old('content') }}</textarea>
                    @if ($errors->has('content'))
                        <div class="text-danger">
                            {{ $errors->first('content') }}
                        </div>
                    @endif
                </div>
                <div class="mt-3">
                    <a class="btn btn-secondary mr-2" href="{{ route('showArticles') }}">
                        キャンセル
                    </a>
                    <button type="submit" class="btn btn-primary">
                        投稿
                    </button>
                </div>
            </form>
        </div>
    </div>
@endsection

それではhttp://localhost/article/createへアクセスしてください。
下記画像のような画面が出てくればOKです。
スクリーンショット 2022-06-17 16.38.07.png
フォーム部分のコードを説明していきます。

まず、form要素のmethod属性はPOSTにしています。
action属性は本来登録処理のルートを設定しなければなりませんが、今は何も指定していません。
というのも、存在しないルートをビューで指定してしまうと下記のようなエラーが出てしまうためです。
スクリーンショット 2022-06-15 16.27.37.png
上記のエラーは、まだ存在していないルート名storeArticlerouteメソッドで指定した時のエラーです。

次にタイトル入力欄について確認してみましょう。

タイトル入力
<input id="title" name="title" class="form-control" value="{{ old('title') }}" type="text">

まずタイトル入力欄にはinputタグを使用しています。
name属性にはtitleを設定しており、登録処理の際にname属性で入力された値を指定することでデータを受け取ることができます。

また、value属性old('title')と記述していますが、こちらはLaravelのヘルパーメソッドであるoldメソッドを使用しています。
()内にname属性と同じ値を指定することで、バリデーションのエラー時にフォームの値を保持することができます。
ここはバリデーション実装時に改めて確認しましょう。

また、下記のコードも追加しています。

バリデーションエラー時の表示
@if ($errors->has('title'))
    <div class="text-danger">
        {{ $errors->first('title') }}
    </div>
@endif

Bladeテンプレートでは条件分岐を@if(条件式)~@endifと記述することができます。
もちろん複数の条件分岐をしたい場合は@if(条件式A)~@elseif(条件式B)~@else~@endifのように記述することもできます。

今回のコードでは条件式に$error->has('title')と記述されています。
このコードの意味は、タイトルをバリデーションで検証した結果、エラーがあればtrue、なければfalseを出力するといったものです。

そして、エラーがあった場合はtrueとなり条件分岐内の処理が実装され、$errors->first('title')と記述されているので、1つ目のエラーメッセージが出力されます。

こちらもバリデーションを実装した時に動きを確認してみましょう。

次に内容入力欄について確認してみましょう。
ほとんどタイトル入力欄と同じですが、inputタグではなくtextareaタグを使用しています。
textarea要素にはvalue属性がないので、old('content')textareaの開始タグtextareaの終了タグで囲みましょう。

バリデーションエラーを出すためのコードはタイトル部分と全く同じです。

最後にボタン部分について確認してみましょう。
まず、キャンセルボタンに関してはaタグを使用して、クリックされた時にroute('showArticles')に遷移する処理を実装しています。
こうすることで、キャンセルボタンをクリックされた後に処理一覧画面に戻すことができます。

次に投稿ボタンに関してはbuttonタグを使用して、type属性submitを指定することで、ボタンがクリックされた時にフォームを送信するようにしています。

以上でフォーム部分の説明は終わりです。

記事投稿処理用のルート設定

次に投稿処理用のルートを設定していきましょう。
first-app/routes/web.phpを下記の通り編集してください。

web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ArticleController;

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

// 投稿一覧を表示
Route::get('/articles', [ArticleController::class, 'showArticles'])->name('showArticles');

// 記事投稿フォームを表示
Route::get('/article/create', [ArticleController::class, 'createArticle'])->name('createArticle');

// 記事登録処理
Route::post('/article/store', [ArticleController::class,'storeArticle'])->name('storeArticle'); // ここを追加

// 投稿詳細を表示
Route::get('/article/{id}', [ArticleController::class, 'showArticle'])->name('showArticle');

今回はフォームからPOSTでデータが送られてくるので、Route::getではなくRoute::postとしています。
あとはいつも通りパスとコントローラー、メソッド名を指定しています。
メソッド名は保存するという意味のstoreArticleメソッドとしました。

フォームにaction属性を追加

ルートを設定したので、create.blade.phpaction属性を追加します。

first-app/resources/views/articles/create.blade.phpform開始タグを下記のように変更してください。

form開始タグ
<form method="POST" action="{{ route('storeArticle') }}">

action属性に登録処理用のルート名であるstoreArticleを指定すると、投稿ボタンがクリックされるとArticleコントローラーstoreArticleメソッドが実行されます。

storeArticleメソッドの作成

では、コントローラーにstoreArticleメソッドを記述していきましょう。
first-app/app/Http/Controllers/ArticleController.phpを下記の通り編集してください。

ArticleController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Article;

class ArticleController extends Controller
{
    /**
     * 記事一覧を表示する
     */
    public function showArticles()
    {
        // 全ての記事データを取得
        $articles = Article::all();

        // compact関数で渡す場合
        return view('articles.index', compact('articles'));
    }

    /**
     * 記事投稿フォームを表示
     */
    public function createArticle()
    {
        return view('articles.create');
    }

    /**
     * 記事投稿処理
     */
    public function storeArticle(Request $request)
    {
        dd($request);
    }

    /**
     * 記事詳細を表示する
     */
    public function showArticle($id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        return view('articles.detail', compact('article'));
    }
}

追加したコードは下記の通りです。

追加したコード
/**
 * 記事投稿処理
 */
public function storeArticle(Request $request)
{
    dd($request);
}

フォームから送信された値はstoreArticle(Request $request)と記述することで、変数requestとして受け取ることができます。

これはArticleコントローラーの5行目あたりを見てもらうとuse Illuminate\Http\Request;と記述されています。
この機能を使用することで、Laravelではフォームから送信された値を変数requestとして受け取ることができるということです。

まずはフォームから何か値が送られてくるときはRequest $requestとなることを覚えておきましょう。

フォームから送られてきた値が本当に取得できているのかを確認するためにdd($request)と記述しておきます。

それでは、変更が完了したらhttp://localhost/article/createへアクセスし、タイトルと内容に適当なテキストを入力して投稿ボタンをクリックしてください。

すると419 PAGE EXPIREDエラー画面が表示されます。

CSRF保護

実は、Laravelでフォームを使用する場合はCSRF保護というものをしなければなりません。

そもそもCSRF(シーサーフ)とは、クロスサイトリクエストフォージェリの略で、Webアプリケーションに存在する脆弱性、もしくはその脆弱性を利用した攻撃方法のことです。

具体的には、アプリケーションに対して攻撃しようとしている攻撃者が作成した「不正なリンク」を攻撃対象者にクリックさせることで、あたかも攻撃対象者が操作したかのようにみせかけて攻撃者がアプリケーションに対して不正なリクエストを送信することです。

このような攻撃からアプリケーションを守るために、LaravelではCSRF保護というものが提供されています。
やり方は至って簡単で、layout.blade.php<meta name="csrf-token" content="{{ csrf_token() }}">を追加することとform開始タグ直後に@csrfというコードを追加するだけです。

こうすることでフォーム送信時にトークンが発行され、そのトークンを使用してリクエストがなりすましかどうかを判断することができます。

CSRFの概念は難しいので理解をする必要はありません。
どのようにCSRFの対策ができるのかを理解しておきましょう。

それではさっそくfirst-app/resources/views/layout.blade.phpを下記の通り編集してください。

layout.blade.php
<!DOCTYPE HTML>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <!-- titleは可変 -->
    <title>@yield('title', 'ブログ')</title> 
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/css/bootstrap.min.css" integrity="sha384-r4NyP46KrjDleawBgD5tp8Y7UzmLA05oM1iAEQ17CSuDqnUK2+k9luXQOfXJCJ4I" crossorigin="anonymous">
</head>

<body>
    <!-- ヘッダー -->
    @include('header')

    <!-- コンテンツ -->
    <div class="container mt-4">
        @yield('content')
    </div>

    <!-- フッター -->
    @include('footer')

    <!-- JavaScript -->
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/js/bootstrap.min.js" integrity="sha384-oesi62hOLfzrys4LxRF63OJCXdXDipiYWBnvTl9Y9/TRlw5xlKIEHpNyvvDShgf/" crossorigin="anonymous"></script>
</body>

</html>

head要素の中に<meta name="csrf-token" content="{{ csrf_token() }}">という1コードを追加しています。

次にfirst-app/resources/views/articles/create.blade.phpform開始タグの直後に下記コードを追加してください。

create.blade.php
<form method="POST" action="{{ route('storeArticle') }}">
    @csrf

再度http://localhost/article/createへアクセスして、タイトルと内容に適当なテキストを入れて投稿ボタンをクリックしてください。
下記画像のようにddメソッドを使用した変数requestの結果が表示されていればOKです。
スクリーンショット 2022-06-15 21.52.47.png
その中のrequestを展開してみましょう。
requestの中にはparametersがあり、それを展開すると_tokentitlecontentが入っています。
_tokenはCSRF保護で作成されたトークンのことであり、titlecontentは入力した値が入っていればフォームで入力された値が取得できていればOKです。

これで変数requestを使用すると、簡単にフォームから入力された値が取得できることが確認できました。
ddメソッドはとても便利ですね!

登録処理を実装

それでは、Articleコントローラーにフォームから受け取った値を登録する処理を記述していきましょう。
first-app/app/Http/Controllers/ArticleController.phpを下記の通り編集してください。

ArticleController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Article;

class ArticleController extends Controller
{
    /**
     * 記事一覧を表示する
     */
    public function showArticles()
    {
        // 全ての記事データを取得
        $articles = Article::all();

        // compact関数で渡す場合
        return view('articles.index', compact('articles'));
    }

    /**
     * 記事投稿フォームを表示
     */
    public function createArticle()
    {
        return view('articles.create');
    }

    /**
     * 記事投稿処理
     */
    public function storeArticle(Request $request)
    {
        // 記事登録処理
        Article::create([
            'title' => $request->title,
            'content' => $request->content,
        ]);

        return redirect()->route('showArticles');
    }

    /**
     * 記事詳細を表示する
     */
    public function showArticle($id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        return view('articles.detail', compact('article'));
    }
}

変更したコードは下記の通りです。

変更したコード
/**
 * 記事投稿処理
 */
public function storeArticle(Request $request)
{
    // 記事登録処理
    Article::create([
        'title' => $request->title,
        'content' => $request->content,
    ]);

    return redirect()->route('showArticles');
}

先ほど説明した通りcreateメソッドを使用しています。

また、return redirect()->route('showArticles');と記述しています。
以前作成した他のメソッドとは少し違いviewメソッドではなくredirectメソッドを使用して画面の遷移を実装しています。

viewメソッドredirectメソッドも画面を遷移させる処理という点は同じですが、ルートを記述した時にRoute::getとした場合はviewメソッドを使用して、反対にRoute::postとした場合はredirectメソッドを使用して画面遷移を実装してください。

わかりやすいように表にまとめておきます。

ルート メソッド
Route::get view()
Route::post redirect()

それではhttp://localhost/article/createへアクセスして、タイトルと内容に適当なテキストを入れて投稿ボタンをクリックしてください。

登録ボタンをクリックすると記事一覧画面へリダイレクトされ、入力した内容が4つ目の記事として表示されていれば記事投稿処理は無事に実装できています。

フォームリクエストを使用してバリデーションをする

投稿処理を実装することはできましたが、記事投稿画面でタイトルと内容が空の状態で投稿ボタンをクリックされると下記画像のようなエラーが発生してしまいます。
スクリーンショット 2022-06-16 21.30.05.png
これはテーブルの設定でtitlecontentがNULL(値がないこと)を許可していないからです。

なので、titlecontentには何かしら文字列型の値が入っている必要があります。
その制御であるバリデーションをLaravelではフォームリクエストという機能を使用して実装します。

それでは、先ほど説明した下記コマンドをターミナルにてfirst-app配下で実行してください。

ターミナル
$ ./vendor/bin/sail php artisan make:request ArticleStoreRequest

今回は記事登録のバリデーションを作成したいので、クラス名はArticleStoreRequestとします。
指定したクラス名+phpの拡張子のファイルであるArticleStoreRequest.phpfirst-app/app/Http/Requestsに作成されていればOKです。
first-app/app/Http/Requests/ArticleStoreRequest.phpを下記の通り編集してください。

ArticleStoreRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class ArticleStoreRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true; // falseからtrueへ変更
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, mixed>
     */
    public function rules()
    {
        return [
            'title' => 'required|string|max:100',
            'content' => 'required|string',
        ];
    }
}

まず、ArticleStoreRequestクラスではFormRequestクラスを継承していることから、フォームリクエストというLaravelの機能が使用できます。

次にauthorizeメソッドreturn falsereturn trueに変更してください。
こちらのauthorizeメソッドは、フォームリクエストの利用が許可されているかどうかを示すメソッドで、falseの場合はArticleStoreRequestクラスが使用できなくなるので、必ずtrueにしてください。

次にrulesメソッドreturn [];にバリデーションをしたいカラム名を入力して=> ''と記述し、''内に、どのようなバリデーションをするかを記述します。

''内に記述できるバリデーションは多岐に渡り、ドキュメント使用可能なバリデーションルールに全て記載されています。
スクリーンショット 2022-06-16 22.02.24.png
これらのバリデーションルールを暗記する必要は全くありません!
使用したいバリデーションルールを実装する時にドキュメントを見ながら設定できればOKです!

今回、タイトルと内容に適応させたバリデーションルールは下記の通りです。

  • title(タイトル):required(必須チェック)、string(文字列型チェック)、max:100(文字数制限100文字まで)
  • content(内容):required(必須チェック)、string(文字列型チェック)

タイトルのmax:100についてですが、テーブル設定の際に最大文字数を100文字に指定しているので、PHP側でも100文字までしかタイトルを入れられないようにしています。

これでArticleStoreRequest.phpの編集は終わりなので、Articleコントローラーへ反映させましょう。

独自に作成したフォームリクエストをコントローラーに反映させるためにはuse文を使用して、作成したフォームリクエストクラスを読み込まなければなりません。

なので、今回作成したArticleStoreRequestを読み込むためには、下記のコードがArticleコントローラーに必要となります。
use App\Http\Requests\ArticleStoreRequest;

次に記事投稿処理であるstoreArticleメソッドの引数にRequest $requestと記述していますが、Request部分をArticleStoreRequestとします。

こうすることでフォームから送信された値がArticleStoreRequest.phpで指定したバリデーションを通過した後に、Articleコントローラーの変数requestで受け取れるようになります。

それではfirst-app/app/Http/Controllers/ArticleController.phpを下記の通り編集してください。

ArticleController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Article;
use App\Http\Requests\ArticleStoreRequest;

class ArticleController extends Controller
{
    /**
     * 記事一覧を表示する
     */
    public function showArticles()
    {
        // 全ての記事データを取得
        $articles = Article::all();

        // compact関数で渡す場合
        return view('articles.index', compact('articles'));
    }

    /**
     * 記事投稿フォームを表示
     */
    public function createArticle()
    {
        return view('articles.create');
    }

    /**
     * 記事投稿処理
     */
    public function storeArticle(ArticleStoreRequest $request)
    {
        // 記事登録処理
        Article::create([
            'title' => $request->title,
            'content' => $request->content,
        ]);

        return redirect()->route('showArticles');
    }

    /**
     * 記事詳細を表示する
     */
    public function showArticle($id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        return view('articles.detail', compact('article'));
    }
}

これでバリデーションの実装は完了したので、さっそく動作確認をしてみましょう。

http://localhost/article/createへアクセスして、タイトルと内容に何も入力しないで投稿ボタンをクリックしてください。
下記画像の通り、エラーメッセージが表示されればOKです。
スクリーンショット 2022-06-17 16.42.58.png
エラーメッセージが英語でわかりにくいですが、titlecontentは入力必須です。というエラーが起こっています。

日本語化

初期設定で日本語の設定はしたはずですが、なぜかバリデーションエラーが英語のままです。
それは、初期設定の設定だけだと設定を変更しただけで日本語のファイルがLaravelにないために起こります。

Laravelでは日本語のファイルをダウンロードする方法がドキュメントに記載されているので、そちらのコマンドを実行していきましょう。

ドキュメントには./vendor/bin/sailが付いていませんが、今回はDocker環境なので付けて実行しましょう。
それでは、ターミナルで下記3つのコマンドをfirst-appディレクトリ上で実行してください。

ターミナル
$ ./vendor/bin/sail php -r "copy('https://readouble.com/laravel/8.x/ja/install-ja-lang-files.php', 'install-ja-lang.php');"
$ ./vendor/bin/sail php -f install-ja-lang.php
$ ./vendor/bin/sail php -r "unlink('install-ja-lang.php');"

この3つのコマンドを実行しても、特にコマンド実行成功などのテキストはターミナルには表示されませんがfirst-app/resources/lang/jaフォルダが追加されており、さらにその中には4つのファイルが追加されます。
これら4つのファイルが日本語用のファイルになります。

改めてhttp://localhost/article/createへアクセスして、タイトルと内容に何も入力しないで投稿ボタンをクリックしてください。
下記画像の通り、エラーメッセージが表示されればOKです。
スクリーンショット 2022-06-17 16.46.02.png
まだtitlecontentが英語なので、これも修正していきましょう。

これを修正するためにはArticleStoreRequest.phpを編集する必要があります。
first-app/app/Http/Requests/ArticleStoreRequest.phpを下記の通り編集してください。

ArticleStoreRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

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

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, mixed>
     */
    public function rules()
    {
        return [
            'title' => 'required|string|max:100',
            'content' => 'required|string',
        ];
    }

    public function attributes()
    {
        return [
            'title' => 'タイトル',
            'content' => '内容',
        ];
    }
}

このようにattributesメソッドを追加して、name属性値 => '翻訳内容'と記述して翻訳していきましょう。

それではhttp://localhost/article/createへアクセスして、タイトルと内容に何も入力しないで投稿ボタンをクリックしてください。
下記画像の通り、エラーメッセージが表示されればOKです。
スクリーンショット 2022-06-17 16.46.39.png
これでバリデーションのエラーメッセージの日本語化は完了です。

ちなみに、タイトルに100文字以上入力し、内容にこれは内容です。というテキストを入力してから投稿ボタンをクリックすると、バリデーションエラーが以下画像のようなメッセージに変わります。
スクリーンショット 2022-06-22 18.00.39.png
まず、タイトルに入力した内容がバリデーションエラーが出ても消えていないことに注目してください。

例えば、バリデーションエラーが起こった際に入力した内容が全て消えてしまっていたら、ユーザーはどのように入力してエラーになったのかがわかりません。
それだけではなく、入力した内容の一部分だけ修正したいと思っても入力内容が全てクリアされていたら、また1から書き直しということになってしまいます。

このような状態を改善するために、create.blade.phpではoldメソッドを使用しています。
oldメソッドold()()内にname属性値を指定することで、バリデーションエラー時にフォームの値を保持することができます。

今回タイトルに100文字以上の値を入力しているので、文字数オーバーのバリデーションエラーが出ていますが、oldメソッドをvalue属性で使用しているため、フォームの内容が保持されており、入力内容がクリアされることなくバリデーションエラー後も出力されています。

このようにLaravelでフォームを作成する際は基本oldメソッドを使用しましょう。

次に、内容部分に注目してください。
こちらはバリデーションエラーがないため、エラーメッセージは表示されません。
それは以前create.blade.phpで実装した下記の条件分岐の記述があったためです。

バリデーションエラー
@if ($errors->has('content'))
    <div class="text-danger">
        {{ $errors->first('content') }}
    </div>
@endif

$errors->has('content')falseとなるので、エラーが表示されないということです。

一方でタイトル部分は100文字以上入力しているので、$errors->has('title')trueとなり、エラーメッセージが表示されています。

エラー処理を記述

無事に記事登録処理を実装することができましたが、データベースにデータを登録する際や編集・削除する際にはエラー処理を記述することが一般的です。

というのも、もし処理がうまく行かずデータが登録できなかった場合やデータベースに繋げなかった場合などにエラーが発生してしまいます。

そのような予期せぬエラーが発生してしまった場合に備えて、エラー時にデータを登録できないようにする処理とログにエラー内容を出力させる処理、エラー画面へ遷移させるための処理の3つを実装しておきます。

まずはfirst-app/app/Http/Controllers/ArticleController.phpを下記の通り編集してください。

ArticleController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Article;
use App\Http\Requests\ArticleStoreRequest;
use Illuminate\Support\Facades\DB; // ここを追加
use Illuminate\Support\Facades\Log; // ここを追加

class ArticleController extends Controller
{
    /**
     * 記事一覧を表示する
     */
    public function showArticles()
    {
        // 全ての記事データを取得
        $articles = Article::all();

        // compact関数で渡す場合
        return view('articles.index', compact('articles'));
    }

    /**
     * 記事投稿フォームを表示
     */
    public function createArticle()
    {
        return view('articles.create');
    }

    /**
     * 記事投稿処理
     */
    public function storeArticle(ArticleStoreRequest $request)
    {
        // トランザクション開始
        DB::beginTransaction();
        try {
            // 記事登録処理
            Article::create([
                'title' => $request->title,
                'content' => $request->content,
            ]);

            // トランザクションコミット
            DB::commit();
        } catch(\Exception $e) {
            // トランザクションロールバック
            DB::rollBack();

            // ログ出力
            Log::critical($e);

            // エラー画面遷移
            abort(500);
        }

        return redirect()->route('showArticles');
    }

    /**
     * 記事詳細を表示する
     */
    public function showArticle($id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        return view('articles.detail', compact('article'));
    }
}

それでは追加したコードについて説明します。

まず、トランザクションという処理を実装するためにuse Illuminate\Support\Facades\DB;を追加し、ログを出力させるためにuse Illuminate\Support\Facades\Log;を追加しています。

これらのuse文を暗記する必要はありません。
例えば、Laravel トランザクションと検索するだけでトランザクションを実装するための記事がたくさん出てくるからです。
もちろん、その記事の中でuse文を追加することが書いてあります。

ログ出力の場合もそうです。
Laravel ログ出力などで検索すると、トランザクションの時と同じように実装方法を説明してくれている記事がたくさん出てきます。

それらの記事を見ながら実装できれば完璧です。

次に、新たに記事投稿処理のstoreArticleメソッドtry-catch文トランザクション処理ログ出力処理エラー画面への遷移処理の4つを追加しています。

追加した4つについて詳しく見ていきましょう。

try-catch文

try-catch文を使用するとtry{}で囲まれた処理の中でエラーが発生した場合に処理を止めて、catch(){}内の処理を実行することができます。

例えば、今回でいうと登録処理に失敗した場合は、catch文の中にデータベースにデータを登録させないようにトランザクション処理を記述していたり、エラー内容を受け取ってログを出力させる処理、エラー画面へ遷移させる処理の3つを記述しています。

このようにエラー処理をすることで、エラーが発生しているにも関わらず不正にデータが登録されることを防ぎ、開発者にどこでどのようなエラーが起こっているのかがわかるようにログを出力し、ユーザーには何かしらのエラーが起こったことを画面を通して伝えることができます。

これらの処理を実装するための記述がtry-catch文です。

基本の使い方は下記の通りです。

try-catch文の使い方
try {
    // 一連の処理
} catch(\Exception $e) {
    // エラー時に行う処理
}

上記のように記述するだけでtry-catch文を使用することができます。
また、catch(\Exception $e){}と記述していますが、引数eにはエラー内容が渡されているので、エラー内容をログに残す際などに使用することができます。

トランザクション処理

トランザクションという言葉か何回か出てきましたが、そもそもトランザクションとは一連の作業を1つの処理として管理するために用いられるものです。
このトランザクションを使用することで、一連の処理が全て成功したどこかで失敗したの2パターンで処理を分けることができます。

今回でいうと、登録処理が無事に成功すればデータベースに値を登録し、登録処理が失敗したらデータを登録しないという処理を実装することができます。

Laravelでのトランザクションの使い方は下記の通りです。

トランザクションの使い方
use Illuminate\Support\Facades\DB; // use文

DB::beginTransaction();
try {
    // データベースへの保存や編集、削除処理など
    DB::commit();
} catch() {
    DB::rollback();
    // エラー時の処理
}

トランザクションはtry-catch文とセットで使うことを覚えておきましょう。
成功したらcommitとして、失敗した場合にはrollbackとすることで、処理が成功した時のみデータベースへデータが登録されることになります。

ログ出力

JavaScriptではconsole.log()を使用して、コンソールにログを出力していましたが、Laravelでも簡単にログを出力することができます。

Laravelではddメソッドが使用できるので、基本的なデバッグ時にログを使用することは少ないですが、ログを出力することはエラー時にエラー内容を確認したり、後々ddメソッドが使えない場合などのデバッグをする際にもとても役立につので、ぜひ覚えておきましょう。

Laravelでのログ出力方法は下記の通りです。

ログ出力方法
use Illuminate\Support\Facades\Log;

Log::emergency('エラーメッセージ');
Log::alert('エラーメッセージ');
Log::critical('エラーメッセージ');
Log::error('エラーメッセージ');
Log::warning('エラーメッセージ');
Log::notice('エラーメッセージ');
Log::info('エラーメッセージ');
Log::debug('エラーメッセージ');

()内にはもちろん変数も指定できるので、今回のコードのようにLog::critical($e)とすることで、エラー内容をログに出力することができます。
※変数eはtry-catct文を使用した際にエラーが入っている変数のこと

また、ログにはログレベルというものがあり、どのログを使用してもOKですが、エラー内容や緊急度によってログレベルを設定することができます。
emergencyが緊急度MAXで、下に行くほど緊急度が下がりdebugまでになると、開発者がデバッグするために出力するためのログになります。

基本使用するのは今回使用したエラー用のcriticalとデバッグしたい時に使用するdebug、何かの情報をログでお知らせをしたい場合(〇〇メソッドが実行された)などに使用するinfoなどが有名です。

基本はその3つをおさえておけばOKです!

またログはデフォルトでfirst-app/storage/logs/laravel.logに出力されるので、後ほど確認してみましょう。

エラー画面への遷移

エラー画面はLaravelにもともと備わっており、エラーが発生すると表示されます。

PHPの処理が失敗した時に出力されるエラーはステータスコードが500500エラーなので、Laravelのヘルパーメソッドであるabortメソッドを使用して、abort(500)と記述してあげることで500エラーページへ遷移させるようにしています。

今回説明したtry-catch文やトランザクション、ログ出力、エラー画面への遷移の実装は必須ではありませんが、プログラマーとして仕事をするのであれば実装しておきましょう。

最後に登録時にわざとエラーを起こして、ログが出力されているかを確認してみましょう。
first-app/app/Http/Controllers/ArticleController.phpstoreArticleメソッドを下記の通り変更してください。

storeArticleメソッド
/**
 * 記事投稿処理
 */
public function storeArticle(ArticleStoreRequest $request)
{
    // トランザクション開始
    DB::beginTransaction();
    try {
        // 記事登録処理
        Article::create([
            'title' => $request->title,
            // 'content' => $request->content,
        ]);

        // トランザクションコミット
        DB::commit();
    } catch(\Exception $e) {
        // トランザクションロールバック
        DB::rollBack();

        // ログ出力
        Log::critical($e);

        // エラー画面遷移
        abort(500);
    }

    return redirect()->route('showArticles');
}

登録処理のcontent部分をコメントアウトしたことにより、必須項目がデータベースへ登録されないのでエラーが発生します。

実際にhttp://localhost/article/createへアクセスして、タイトルと内容に適当なテキストを入れて投稿ボタンをクリックしてください。
下記画像のように500 | SERVER ERRORの画面が表示されればOKです。
スクリーンショット 2022-06-17 1.37.31.png
もちろんこれがabortメソッド500を指定していたので、サーバーエラーの画面に遷移しました。
また、first-app/storage/logs/laravel.logを開いてください。
Laravelはデフォルトでここにログが残るようになっています。
今回Log::critical($e)としているので、下記のようなログが出力されます。

ログ
[2022-06-17 20:26:28] local.CRITICAL: PDOException: SQLSTATE[HY000]: General error: 1364 Field 'content' doesn't have a default value in /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Connection.php:527

エラーが起こった日時local.CRITICALとなっています。
また、エラー内容も出力されており、Field 'content' doesn't have a default valueとなっています。
つまり、「contentカラムはデフォルトの値がテーブル設定でされていないので何かしらの値が必要です」というエラーです。
ちなみに、Log::debug($e)と記述していた場合はlocal.DEBUGとなります。

このようにログを出力することで、ブラウザでは500 | SERVER ERRORとなっていてエラー内容が確認できませんが、ログを確認することでどのようなエラーが起こっているのかを確認できます。

ブラウザでエラーが確認できない場合は積極的にログを確認しましょう。

それでは、first-app/app/Http/Controllers/ArticleController.phpstoreArticleメソッドを下記の通り元に戻しておきましょう。

ArticleController.php
/**
 * 記事投稿処理
 */
public function storeArticle(ArticleStoreRequest $request)
{
    // トランザクション開始
    DB::beginTransaction();
    try {
        // 記事登録処理
        Article::create([
            'title' => $request->title,
            'content' => $request->content,
        ]);

        // トランザクションコミット
        DB::commit();
    } catch(\Exception $e) {
        // トランザクションロールバック
        DB::rollBack();

        // ログ出力
        Log::debug($e);

        // エラー画面遷移
        abort(500);
    }

    return redirect()->route('showArticles');
}

これで登録処理が全て完了しました。

ヘッダーの遷移先を編集

最後にヘッダーの記事投稿ボタンから記事投稿画面へ遷移できるようにheader.blade.phpを変更しましょう。
first-app/resources/views/header.blade.phpを下記の通り編集してください。

header.blade.php
<header>
    <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
        <div class="container-fluid">
            <a class="navbar-brand" href="{{ route('showArticles') }}">ブログ</a>
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse justify-content-end" id="navbarNavAltMarkup">
                <div class="navbar-nav">
                    <a class="nav-item nav-link active" href="{{ route('showArticles') }}">記事一覧 <span class="sr-only"></span></a>
                    <a class="nav-item nav-link" href="{{ route('createArticle') }}">記事投稿</a>
                </div>
            </div>
        </div>
    </nav>
</header>

変更した内容は下記の通りです。

まず、記事投稿ボタンhref属性に記事投稿画面を表示させるルート名routeメソッドを使用して指定しています。
これで記事投稿ボタンがクリックされると、投稿フォームを表示することができるようになりました。

また、ブログ記事一覧ボタンにも記事一覧画面を表示させるルート名routeメソッドを使用して指定しています。
これでブログと記事一覧ボタンがクリックされると、記事一覧画面を表示することができるようになりました。

いい感じにブログシステムが構築できてきましたね!
これで記事投稿機能の実装は完了です。

★検索ワード
・Laravel 登録機能実装
・Laravel create fill save
・Laravel バリデーション実装
・Laravel CSRF 実装
・Laravel リダイレクト
・Laravel フォームリクエスト
・Laravel 日本語化
・PHP try-catch 使い方
・Laravel トランザクション
・Laravel ログ出力
・Laravel abortメソッド

記事編集

記事詳細画面表示と記事投稿ができるようになったので、次は投稿した記事の編集をする機能の作成をしていきます。
記事編集機能は記事詳細画面表示と記事投稿を合わせたような機能になっているので、それらの実装方法を思い出しながら実装してみましょう!

記事編集の流れは下記のようになっています。

  1. 記事詳細からIDに紐付けたリンクを作成して編集フォームへ遷移
  2. 編集フォームからPOSTで編集内容を送る
  3. コントローラーとモデルを使用してデータを編集する
  4. フォームリクエストを使用してバリデーション
  5. エラー処理

データの更新メソッド

リンクの作成方法やバリデーション、エラー処理などについてはこれまでの機能実装時に説明したので、まだ説明していないデータ更新のメソッドについて説明します。

実は記事投稿用メソッドの際に2つの方法を説明したと思いますが、データの更新にも2つの方法があります。
それがfillメソッド + saveメソッドupdateメソッドです。

まずはfillメソッド + saveメソッドの使い方から説明します。
実は記事投稿用メソッドを紹介した時にfillメソッド + saveメソッドの使い方も紹介しています。
その時の内容とほとんど一緒ですが、今回は記事編集なので編集するための記事の取得が必要になります。

使い方は下記の通りです。

saveメソッド
$article = Article::find(1);

$article->fill([
    'title' => 'タイトル',
    'content' => 'コンテンツ',
]);

$article->save();

まず、編集したい記事の情報をfindメソッドを使用して取得します。
今回は例として記事IDが1の記事を取得しています。

次にfillメソッドを使用して、編集したカラム名と編集内容を=>で結びつけます。
今回は例としてタイトルコンテンツとテキストをそのまま入力していますが、実際はフォームで入力された値をこちらに設定します。

そして最後にsaveメソッドを使用して保存します。

updateメソッドの使い方はfillメソッド + saveメソッドとほとんど同じです。
使い方は下記の通りです。

updateメソッド
$article = Article::find(1);

$article->update([
    'title' => 'タイトル',
    'content' => 'コンテンツ'
]);

イメージとしては、fillメソッド + saveメソッドを一気に行えるのがupdateメソッドです。

fillメソッド + saveメソッドupdateメソッドの違いは、fillメソッド + saveメソッドが差分更新なのに対して、updateメソッドはメソッドが実行されると差分があるかないか関係なく毎回更新します。

データベースへ負荷をなるべくかけないためにも、データベースに登録されている値と登録しようとしている値に差分があったときのみ更新するfillメソッド + saveメソッドを使用するのが一般的です。

実装

fillメソッド + saveメソッド以外の内容は記事詳細画面表示機能と記事投稿機能とほぼ同じなので、さっそく実装してみましょう。

記事詳細からIDに紐付けたリンクを作成して編集フォームへ遷移

まずは編集画面表示と編集処理用のルートを作成していきます。

first-app/routes/web.phpを下記の通り編集してください。

web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ArticleController;

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

// 投稿一覧を表示
Route::get('/articles', [ArticleController::class, 'showArticles'])->name('showArticles');

// 記事投稿フォームを表示
Route::get('/article/create', [ArticleController::class, 'createArticle'])->name('createArticle');

// 記事登録処理
Route::post('/article/store', [ArticleController::class,'storeArticle'])->name('storeArticle');

// 記事編集フォームを表示
Route::get('/article/edit/{id}', [ArticleController::class, 'editArticle'])->name('editArticle'); // ここを追加

// 記事編集処理
Route::post('/article/update/{id}', [ArticleController::class,'updateArticle'])->name('updateArticle'); // ここを追加

// 投稿詳細を表示
Route::get('/article/{id}', [ArticleController::class, 'showArticle'])->name('showArticle');

追加したのは下記の2行です。

追加したコード
// 記事編集フォームを表示
Route::get('/article/edit/{id}', [ArticleController::class, 'editArticle'])->name('editArticle'); // ここを追加

// 記事編集処理
Route::post('/article/update/{id}', [ArticleController::class,'updateArticle'])->name('updateArticle'); // ここを追加

記事編集フォームを表示させるためには記事IDが必要なので、/article/edit/{id}としています。
また、記事編集処理ではフォームを使用してPOSTで内容を送信しているのでRoute::postとしています。

次にルートで作成した記事編集画面表示のためのeditArticleメソッドを作成します。

first-app/app/Http/Controllers/ArticleController.phpを下記の通り編集してください。

ArticleController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Article;
use App\Http\Requests\ArticleStoreRequest;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class ArticleController extends Controller
{
    /**
     * 記事一覧を表示する
     */
    public function showArticles()
    {
        // 全ての記事データを取得
        $articles = Article::all();

        // compact関数で渡す場合
        return view('articles.index', compact('articles'));
    }

    /**
     * 記事投稿フォームを表示
     */
    public function createArticle()
    {
        return view('articles.create');
    }

    /**
     * 記事投稿処理
     */
    public function storeArticle(ArticleStoreRequest $request)
    {
        // トランザクション開始
        DB::beginTransaction();
        try {
            // 記事登録処理
            Article::create([
                'title' => $request->title,
                'content' => $request->content,
            ]);

            // トランザクションコミット
            DB::commit();
        } catch(\Exception $e) {
            // トランザクションロールバック
            DB::rollBack();

            // ログ出力
            Log::debug($e);

            // エラー画面遷移
            abort(500);
        }

        return redirect()->route('showArticles');
    }

    /**
     * 記事編集画面を表示する
     */
    public function editArticle($id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        return view('articles.edit', compact('article'));
    }

    /**
     * 記事詳細を表示する
     */
    public function showArticle($id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        return view('articles.detail', compact('article'));
    }
}

追加したコードは下記の通りです。

/**
 * 記事編集画面を表示する
 */
public function editArticle($id)
{
    // 渡されてきた記事IDのデータを取得
    $article = Article::find($id);

    return view('articles.edit', compact('article'));
}

記事詳細画面を表示させる時とほぼ同じコードですね!
違うのは遷移させるのがedit.blade.phpになっているだけです。

次に、記事詳細画面から編集画面に遷移できるようにボタンを作成していきましょう。

first-app/resources/views/articles/detail.blade.phpを下記の通り編集してください。

detail.blade.php
@extends('layout')

@section('title')
    記事詳細
@endsection

@section('content')
    <h2>{{ $article->title }}</h2>
    <div class="d-flex">
        <span class="mr-2">作成日:{{ $article->created_at }}</span>
        <span>更新日:{{ $article->updated_at }}</span>
    </div>
    <p class="mt-4">{{ $article->content }}</p>
    <a href="{{ route('showArticles') }}" class="mt-3 btn btn-secondary">戻る</a>
    <a href="{{ route('editArticle', $article->id) }}" class="mt-3 btn btn-primary">編集</a>
@endsection

追加したのは下記コードです。

<a href="{{ route('editArticle', $article->id) }}" class="mt-3 btn btn-primary">編集</a>

これで編集ボタンをクリックすると記事編集画面へ遷移し、ArticleコントローラーeditArticleメソッドへ引数として記事IDを渡すことができます。

編集フォームからPOSTで編集内容を送る

次に編集フォームからPOSTで編集内容を送信するためにedit.blade.phpを作成していきます。
first-app/resources/views/articlesディレクトリにedit.blade.phpをコマンドかVSCodeで作成してください。
コマンドで作成する場合は、ターミナルで下記コマンドをfirst-appディレクトリ上で実行してください。

ターミナル
$ touch resources/views/articles/edit.blade.php

作成したedit.blade.phpを下記の通り編集してください。

edit.blade.php
@extends('layout')

@section('title')
    記事編集
@endsection

@section('content')
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <h2>記事編集フォーム</h2>
            <form method="POST" action="{{ route('updateArticle', $article->id) }}">
                @csrf
                <div class="form-group">
                    <label for="title">
                        タイトル
                    </label>
                    <input id="title" name="title" class="form-control" value="{{ old('title', $article->title) }}" type="text">
                    @if ($errors->has('title'))
                        <div class="text-danger">
                            {{ $errors->first('title') }}
                        </div>
                    @endif
                </div>
                <div class="form-group mt-3">
                    <label for="content">
                        内容
                    </label>
                    <textarea id="content" name="content" class="form-control" rows="4">{{ old('content', $article->content) }}</textarea>
                    @if ($errors->has('content'))
                        <div class="text-danger">
                            {{ $errors->first('content') }}
                        </div>
                    @endif
                </div>
                <div class="mt-3">
                    <a class="btn btn-secondary mr-2" href="{{ route('showArticle', $article->id) }}">
                        キャンセル
                    </a>
                    <button type="submit" class="btn btn-primary">
                        更新
                    </button>
                </div>
            </form>
        </div>
    </div>
@endsection

http://localhost/article/edit/1へアクセスして、下記画像のように表示されればOKです!
スクリーンショット 2022-06-17 17.02.06.png
今回は例として記事IDが1の記事の編集画面を表示しています。

編集フォームの内容はほとんど登録フォームと同じですが、変更した部分について解説します。

まず、ページタイトルを記事編集、ページの見出しを記事編集フォームとしています。

次に、フォームで送信する先をaction属性の属性値として、編集処理のメソッドであるupdateArticleメソッドに指定しました。
また、記事IDを渡すためにrouteメソッドの第二引数に$article->idと指定しています。

次にinput要素valuetextarea要素内容をそれぞれ編集しています。
編集した理由は、データベースに保存されている記事タイトルと記事内容のデータをそれぞれのフォームに反映させるためです。
oldメソッドの第二引数に値を設定することで、編集フォームが表示された直後はデフォルト値としてデータベースに保存されたデータをフォームに表示してくれます。

つまり、記事IDが1の編集画面を表示すると、下記画像のようにデータベースに保存されている記事ID1の記事タイトルと記事内容がフォームに表示されるということです。
スクリーンショット 2022-06-17 17.02.06.png
一方でバリデーションに引っかかった場合はデフォルトの値ではなく、バリデーションで引っかかった時に入力された内容がフォームに反映されて、さらにバリデーションのエラーメッセージがその下に赤文字で出力されるようになります。

次に、キャンセルボタンの遷移先を記事詳細画面にするためにhref属性の属性値を記事詳細画面を表示するルート名showArticleに変更しています。
もちろん記事詳細画面への遷移には、どの記事の詳細画面なのかを指定しなければならないので、routeメソッドの第二引数に記事IDを渡しています。

最後に投稿ボタンを編集用に更新ボタンにして記事編集フォームの実装は終わりです。

コントローラーとモデルを使用してデータを編集する

編集フォームからPOSTで送信されたデータをデータベースに保存するための処理を実装していきましょう。
すでにルートには編集処理用のルーティングをしているので、そのルーティングに従ってArticleコントローラーupdateArticleメソッドを作成していきましょう。

first-app/app/Http/Controllers/ArticleController.phpを下記の通り編集してください。

ArticleController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Article;
use App\Http\Requests\ArticleStoreRequest;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class ArticleController extends Controller
{
    /**
     * 記事一覧を表示する
     */
    public function showArticles()
    {
        // 全ての記事データを取得
        $articles = Article::all();

        // compact関数で渡す場合
        return view('articles.index', compact('articles'));
    }

    /**
     * 記事投稿フォームを表示
     */
    public function createArticle()
    {
        return view('articles.create');
    }

    /**
     * 記事投稿処理
     */
    public function storeArticle(ArticleStoreRequest $request)
    {
        // トランザクション開始
        DB::beginTransaction();
        try {
            // 記事登録処理
            Article::create([
                'title' => $request->title,
                'content' => $request->content,
            ]);

            // トランザクションコミット
            DB::commit();
        } catch(\Exception $e) {
            // トランザクションロールバック
            DB::rollBack();

            // ログ出力
            Log::debug($e);

            // エラー画面遷移
            abort(500);
        }

        return redirect()->route('showArticles');
    }

    /**
     * 記事編集画面を表示する
     */
    public function editArticle($id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        return view('articles.edit', compact('article'));
    }

    /**
     * 記事編集処理
     */
    public function updateArticle(Request $request, $id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        // 編集する内容をfillメソッドを使用して記述
        $article->fill([
            'title' => $request->title,
            'content' => $request->content,
        ]);

        // 保存処理
        $article->save();

        return redirect()->route('showArticle', $article->id);
    }

    /**
     * 記事詳細を表示する
     */
    public function showArticle($id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        return view('articles.detail', compact('article'));
    }
}

追加したコードは下記の通りです。

追加したコード
/**
 * 記事編集処理
 */
public function updateArticle(Request $request, $id)
{
    // 渡されてきた記事IDのデータを取得
    $article = Article::find($id);

    // 編集する内容をfillメソッドを使用して記述
    $article->fill([
        'title' => $request->title,
        'content' => $request->content,
    ]);

    // 保存処理
    $article->save();

    return redirect()->route('showArticle', $article->id);
}

記事登録時の時と同じ様に、POSTで送信された値はupdateArticleメソッドの引数でRequest $requestと記述することで受け取る事ができます。

また、今回は編集する記事IDもフォームから送られてきているので、$idと記述して引数を受け取ります。
これでPOSTで送信されてきた値と記事IDをupdateArticleメソッド内で使用できるようになりました。

次は渡されてきた記事IDを使用して記事データを取得していきます。
取得するにはいつも通りfindメソッドを使用するだけです。

先ほど説明したfillメソッド + saveメソッドを使用して編集処理をしています。
$request->titleと記述することでフォームで入力したタイトルを取得することができます。
また、$request->contentと記述することでフォームで入力した内容を取得することができます。
これも記事登録処理と同じですね。

最後にリダイレクト先を記事詳細画面に指定すれば終わりです。
記事詳細画面を表示するためには記事IDが必要になるので、routeメソッドの第二引数に$article->idとして渡しておきましょう。

これで投稿を編集することができました。
しっかり記事が編集できるかの動作確認は各自行ってください!

フォームリクエストを使用してバリデーション

次にフォームリクエストを使用してバリデーションを実装しましょう。

今回は記事編集用のバリデーションなのでArticleUpdateRequest.phpを作成しましょう。
それでは、ターミナルで下記コマンドをfirst-appディレクトリ上で実行してください。

ターミナル
$ ./vendor/bin/sail php artisan make:request ArticleUpdateRequest

first-app/app/Http/Requests配下にArticleUpdateRequest.php`が作成されていればOKです。

それではfirst-app/app/Http/Requests/ArticleUpdateRequest.phpを下記の通り編集してください。

ArticleUpdateRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

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

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, mixed>
     */
    public function rules()
    {
        return [
            'title' => 'required|string|max:100',
            'content' => 'required|string',
        ];
    }

    public function attributes()
    {
        return [
            'title' => 'タイトル',
            'content' => '内容',
        ];
    }
}

内容はArticleStoreRequest.phpと全く同じなので、説明は省略します。

次にArticleコントローラーupdateArticleメソッドArticleUpdateRequestのバリデーションを反映させましょう。

first-app/app/Http/Controllers/ArticleController.phpを下記の通り編集してください。

ArticleController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Article;
use App\Http\Requests\ArticleStoreRequest;
use App\Http\Requests\ArticleUpdateRequest; // ここを追加
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class ArticleController extends Controller
{
    /**
     * 記事一覧を表示する
     */
    public function showArticles()
    {
        // 全ての記事データを取得
        $articles = Article::all();

        // compact関数で渡す場合
        return view('articles.index', compact('articles'));
    }

    /**
     * 記事投稿フォームを表示
     */
    public function createArticle()
    {
        return view('articles.create');
    }

    /**
     * 記事投稿処理
     */
    public function storeArticle(ArticleStoreRequest $request)
    {
        // トランザクション開始
        DB::beginTransaction();
        try {
            // 記事登録処理
            Article::create([
                'title' => $request->title,
                'content' => $request->content,
            ]);

            // トランザクションコミット
            DB::commit();
        } catch(\Exception $e) {
            // トランザクションロールバック
            DB::rollBack();

            // ログ出力
            Log::debug($e);

            // エラー画面遷移
            abort(500);
        }

        return redirect()->route('showArticles');
    }

    /**
     * 記事編集画面を表示する
     */
    public function editArticle($id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        return view('articles.edit', compact('article'));
    }

    /**
     * 記事編集処理
     */
    public function updateArticle(ArticleUpdateRequest $request, $id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        // 編集する内容をfillメソッドを使用して記述
        $article->fill([
            'title' => $request->title,
            'content' => $request->content,
        ]);

        // 保存処理
        $article->save();

        return redirect()->route('showArticle', $article->id);
    }

    /**
     * 記事詳細を表示する
     */
    public function showArticle($id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        return view('articles.detail', compact('article'));
    }
}

まずはArticleUpdateRequestArticleコントローラー内で使用できるように、use文を使用して下記のように記述しましょう。
use App\Http\Requests\ArticleUpdateRequest;

次にupdateArticleメソッドの第一引数をRequest $requestからArticleUpdateRequest $requestに変更します。

これで記事編集時にArticleUpdateRequest.phpで設定したバリデーションが実行されるので、記事編集フォームで何も入力しないで更新ボタンをクリックしたり、タイトルを100文字以上にすると、エラーメッセージが出力されるようになりました。

エラー処理

最後にエラー処理を実装しておきましょう。

実装する内容は下記の通りです。

  1. try-catch文
  2. トランザクション
  3. ログ出力
  4. エラー画面への遷移

これも登録処理とほとんど変わりません。

first-app/app/Http/Controllers/ArticleController.phpを下記の通り編集してください。

ArticleController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Article;
use App\Http\Requests\ArticleStoreRequest;
use App\Http\Requests\ArticleUpdateRequest;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class ArticleController extends Controller
{
    /**
     * 記事一覧を表示する
     */
    public function showArticles()
    {
        // 全ての記事データを取得
        $articles = Article::all();

        // compact関数で渡す場合
        return view('articles.index', compact('articles'));
    }

    /**
     * 記事投稿フォームを表示
     */
    public function createArticle()
    {
        return view('articles.create');
    }

    /**
     * 記事投稿処理
     */
    public function storeArticle(ArticleStoreRequest $request)
    {
        // トランザクション開始
        DB::beginTransaction();
        try {
            // 記事登録処理
            Article::create([
                'title' => $request->title,
                'content' => $request->content,
            ]);

            // トランザクションコミット
            DB::commit();
        } catch(\Exception $e) {
            // トランザクションロールバック
            DB::rollBack();

            // ログ出力
            Log::debug($e);

            // エラー画面遷移
            abort(500);
        }

        return redirect()->route('showArticles');
    }

    /**
     * 記事編集画面を表示する
     */
    public function editArticle($id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        return view('articles.edit', compact('article'));
    }

    /**
     * 記事編集処理
     */
    public function updateArticle(ArticleUpdateRequest $request, $id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        // トランザクション開始
        DB::beginTransaction();
        try {
            // 編集する内容をfillメソッドを使用して記述
            $article->fill([
                'title' => $request->title,
                'content' => $request->content,
            ]);

            // 保存処理
            $article->save();

            // トランザクションコミット
            DB::commit();
        } catch(\Exception $e) {
            // トランザクションロールバック
            DB::rollBack();

            // ログ出力
            Log::debug($e);

            // エラー画面遷移
            abort(500);
        }

        return redirect()->route('showArticle', $article->id);
    }

    /**
     * 記事詳細を表示する
     */
    public function showArticle($id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        return view('articles.detail', compact('article'));
    }
}

変更したコードは下記の通りです。

変更したコード
/**
 * 記事編集処理
 */
public function updateArticle(ArticleUpdateRequest $request, $id)
{
    // トランザクション開始
    DB::beginTransaction();
    try {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        // 編集する内容をfillメソッドを使用して記述
        $article->fill([
            'title' => $request->title,
            'content' => $request->content,
        ]);

        // 保存処理
        $article->save();

        // トランザクションコミット
        DB::commit();
    } catch(\Exception $e) {
        // トランザクションロールバック
        DB::rollBack();

        // ログ出力
        Log::debug($e);

        // エラー画面遷移
        abort(500);
    }

    return redirect()->route('showArticle', $article->id);
}

実装内容自体は記事登録時と同じですね!
もしわからない箇所がある場合は、記事登録部分を読み直してください!

★検索ワード
・Laravel 編集機能実装
・Laravel fill save update

記事削除

ここまで記事投稿・編集ができるようになったので、最後に記事の削除をできるようにしましょう。
記事削除機能に関しても記事編集機能同様、以前に実装したことの応用なので過去の実装を思い出しながら実装していきましょう。

記事削除の流れは下記のようになっています。

  1. 記事詳細からIDに紐付けたリンクを作成してルートへ渡す
  2. コントローラーとモデルを使用してデータを削除する
  3. エラー処理

データの削除メソッド

まずはデータを削除するためのメソッドの解説です。
登録や編集と同じように、データの削除を実現させるためのメソッドも2つあります。

それはdeleteメソッドdestroyメソッドです。

まずはdeleteメソッドの使い方から説明します。
使い方は下記の通りです。

deleteメソッド
$article = Article::find(1);
$article->delete();

まず削除したい記事を取得して、その記事にdeleteメソッドを使用するというイメージです。
とても簡単ですね!

次にdestroyメソッドの使い方を説明します。
使い方は下記の通りです。

destroyメソッド
Article::destroy(1);

destroyメソッドは記事ID(プライマリーキー)を引数として指定することで、指定したIDのデータを削除することができます。

destroyメソッドはプライマリーキーしか指定できないので、取得したデータを削除できるdeleteメソッドを使用すればOKです。

本教材でもdeleteメソッドを利用します。

実装

deleteメソッド以外の内容は過去の実装とほぼ同じなので、さっそく実装してみましょう。

記事詳細からIDに紐付けたリンクを作成してルートへ渡す

まずは削除処理用のルートを作成していきます。

first-app/routes/web.phpを下記の通り編集してください。

web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ArticleController;

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

// 投稿一覧を表示
Route::get('/articles', [ArticleController::class, 'showArticles'])->name('showArticles');

// 記事投稿フォームを表示
Route::get('/article/create', [ArticleController::class, 'createArticle'])->name('createArticle');

// 記事登録処理
Route::post('/article/store', [ArticleController::class,'storeArticle'])->name('storeArticle');

// 記事編集フォームを表示
Route::get('/article/edit/{id}', [ArticleController::class, 'editArticle'])->name('editArticle');

// 記事編集処理
Route::post('/article/update/{id}', [ArticleController::class,'updateArticle'])->name('updateArticle');

// 投稿詳細を表示
Route::get('/article/{id}', [ArticleController::class, 'showArticle'])->name('showArticle');

// 記事削除処理
Route::post('/article/delete/{id}', [ArticleController::class, 'deleteArticle'])->name('deleteArticle'); // ここを追加

追加したのは下記の1行です。

追加したコード
Route::post('/article/delete/{id}', [ArticleController::class, 'deleteArticle'])->name('deleteArticle');

記事の削除処理には削除する記事のIDが必要なので、/article/delete/{id}としています。
また、 削除処理にはRoute::postを使用します。

次にルートで作成した記事削除処理のためのボタンを作成します。
記事詳細画面に削除ボタンを設置して、ボタンをクリックされた時に削除させたいので、first-app/resources/views/articles/detail.blade.phpを下記の通り編集してください。

detail.blade.php
@extends('layout')

@section('title')
    記事詳細
@endsection

@section('content')
    <h2>{{ $article->title }}</h2>
    <div class="d-flex">
        <span class="mr-2">作成日:{{ $article->created_at }}</span>
        <span>更新日:{{ $article->updated_at }}</span>
    </div>
    <p class="mt-4">{{ $article->content }}</p>
    <div class="d-flex">
        <a href="{{ route('showArticles') }}" class="mt-3 mr-2 btn btn-secondary">戻る</a>
        <a href="{{ route('editArticle', $article->id) }}" class="mt-3 mr-2 btn btn-primary">編集</a>
        <form method="POST" action="{{ route('deleteArticle', $article->id) }}">
            @csrf
            <button type="submit" class="mt-3 mr-2 btn btn-danger">削除</button>
        </form>
    </div>
@endsection

http://localhost/article/1にアクセスして、下記画像のように削除ボタンが追加されていればOKです。
スクリーンショット 2022-06-17 22.40.26.png
削除ボタンはフォームを使用して作成します。
そのためルートを記述した時にRoute::postとしました。
また、CSRF保護の@csrfも忘れないようにしてください。

もちろんaction属性にはdeleteArticleメソッドを指定し、第二引数には削除する記事のIDを$article-idとして指定しています。

コントローラーとモデルを使用してデータを削除する

それではArticleコントローラーdeleteArticleメソッドを実装していきましょう。
first-app/app/Http/Controllers/ArticleController.phpを下記の通り編集してください。

ArticleController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Article;
use App\Http\Requests\ArticleStoreRequest;
use App\Http\Requests\ArticleUpdateRequest;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class ArticleController extends Controller
{
    /**
     * 記事一覧を表示する
     */
    public function showArticles()
    {
        // 全ての記事データを取得
        $articles = Article::all();

        // compact関数で渡す場合
        return view('articles.index', compact('articles'));
    }

    /**
     * 記事投稿フォームを表示
     */
    public function createArticle()
    {
        return view('articles.create');
    }

    /**
     * 記事投稿処理
     */
    public function storeArticle(ArticleStoreRequest $request)
    {
        // トランザクション開始
        DB::beginTransaction();
        try {
            // 記事登録処理
            Article::create([
                'title' => $request->title,
                'content' => $request->content,
            ]);

            // トランザクションコミット
            DB::commit();
        } catch(\Exception $e) {
            // トランザクションロールバック
            DB::rollBack();

            // ログ出力
            Log::debug($e);

            // エラー画面遷移
            abort(500);
        }

        return redirect()->route('showArticles');
    }

    /**
     * 記事編集画面を表示する
     */
    public function editArticle($id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        return view('articles.edit', compact('article'));
    }

    /**
     * 記事編集処理
     */
    public function updateArticle(ArticleUpdateRequest $request, $id)
    {
        // トランザクション開始
        DB::beginTransaction();
        try {
            // 渡されてきた記事IDのデータを取得
            $article = Article::find($id);

            // 編集する内容をfillメソッドを使用して記述
            $article->fill([
                'title' => $request->title,
                'content' => $request->content,
            ]);

            // 保存処理
            $article->save();

            // トランザクションコミット
            DB::commit();
        } catch(\Exception $e) {
            // トランザクションロールバック
            DB::rollBack();

            // ログ出力
            Log::debug($e);

            // エラー画面遷移
            abort(500);
        }

        return redirect()->route('showArticle', $article->id);
    }

    /**
     * 記事詳細を表示する
     */
    public function showArticle($id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        return view('articles.detail', compact('article'));
    }

    /**
     * 記事削除処理
     */
    public function deleteArticle($id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        // 記事削除処理
        $article->delete();

        return redirect()->route('showArticles');
    }
}

追加したコードは下記の通りです。

追加したコード
/**
 * 記事削除処理
 */
public function deleteArticle($id)
{
    // 渡されてきた記事IDのデータを取得
    $article = Article::find($id);

    // 記事削除処理
    $article->delete();

    return redirect()->route('showArticles');
}

今回は削除する記事IDが送られてきているので、$idと記述し引数として受け取ります。
これで削除する記事IDをdeleteArticleメソッドで使用できるようになりました。

取得したIDを使用して、先ほど説明したdeleteメソッドで削除処理を実装しています。

最後にリダイレクト先をshowArticlesに指定し、記事一覧表示画面遷移させる処理を実装して終わりです。

これで記事詳細画面から削除ボタンをクリックすることで記事を削除できるようになりました。

エラー処理

最後にエラー処理を実装しておきましょう。

実装する内容はいつもと同じで下記の通りです。

  1. try-catch文
  2. トランザクション
  3. ログ出力
  4. エラー画面への遷移

first-app/app/Http/Controllers/ArticleController.phpを下記の通り編集してください。

ArticleController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Article;
use App\Http\Requests\ArticleStoreRequest;
use App\Http\Requests\ArticleUpdateRequest;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class ArticleController extends Controller
{
    /**
     * 記事一覧を表示する
     */
    public function showArticles()
    {
        // 全ての記事データを取得
        $articles = Article::all();

        // compact関数で渡す場合
        return view('articles.index', compact('articles'));
    }

    /**
     * 記事投稿フォームを表示
     */
    public function createArticle()
    {
        return view('articles.create');
    }

    /**
     * 記事投稿処理
     */
    public function storeArticle(ArticleStoreRequest $request)
    {
        // トランザクション開始
        DB::beginTransaction();
        try {
            // 記事登録処理
            Article::create([
                'title' => $request->title,
                'content' => $request->content,
            ]);

            // トランザクションコミット
            DB::commit();
        } catch(\Exception $e) {
            // トランザクションロールバック
            DB::rollBack();

            // ログ出力
            Log::debug($e);

            // エラー画面遷移
            abort(500);
        }

        return redirect()->route('showArticles');
    }

    /**
     * 記事編集画面を表示する
     */
    public function editArticle($id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        return view('articles.edit', compact('article'));
    }

    /**
     * 記事編集処理
     */
    public function updateArticle(ArticleUpdateRequest $request, $id)
    {
        // トランザクション開始
        DB::beginTransaction();
        try {
            // 渡されてきた記事IDのデータを取得
            $article = Article::find($id);

            // 編集する内容をfillメソッドを使用して記述
            $article->fill([
                'title' => $request->title,
                'content' => $request->content,
            ]);

            // 保存処理
            $article->save();

            // トランザクションコミット
            DB::commit();
        } catch(\Exception $e) {
            // トランザクションロールバック
            DB::rollBack();

            // ログ出力
            Log::debug($e);

            // エラー画面遷移
            abort(500);
        }

        return redirect()->route('showArticle', $article->id);
    }

    /**
     * 記事詳細を表示する
     */
    public function showArticle($id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        return view('articles.detail', compact('article'));
    }

    /**
     * 記事削除処理
     */
    public function deleteArticle($id)
    {
        // トランザクション開始
        DB::beginTransaction();
        try{
            // 渡されてきた記事IDのデータを取得
            $article = Article::find($id);

            // 記事削除処理
            $article->delete();

            // トランザクションコミット
            DB::commit();
        } catch(\Exception $e) {
            // トランザクションロールバック
            DB::rollBack();

            // ログ出力
            Log::debug($e);

            // エラー画面遷移
            abort(500);
        }

        return redirect()->route('showArticles');
    }
}

これも編集時と同じで実装内容自体は記事登録時と同じですね!
もしわからない箇所がある場合は、記事登録部分を読み直してください!

★検索ワード
・Laravel 削除機能実装
・Laravel delete destroy

おめでとうございます!

Laravelの学習お疲れさまでした。
教材を1度読んだだけではなかなかLaravelを理解することは難しいと思いますが、PHPで実装するよりもLaravelで実装した方が色々簡単になりそうなことはわかりましたか?

まずはここまで理解できていればLaravelの基礎は大丈夫です。
まだイメージできないなという方は、イメージができるまで何度かこちらの教材を読み直してみてください!

0

Register as a new user and use Qiita more conveniently

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