3
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[Laravel]CRUD基礎☆レストランメニュー作成アプリのチュートリアル

Last updated at Posted at 2020-11-20

(※ この記事は、Laravel8が出るより結構前に書いて、他のブログに載せていました。それを転載したものなので、Laravel7で解説しています。ご了承くださいませ。。(m´・ω・`)m ゴメン…)

環境:XAMPP

前回は、Laravelで作った初めてのアプリとして、簡単なタスク管理アプリのチュートリアル記事を投稿しました。
今回は、もうちょっと色々機能をつけて、レストランのメニュー作成アプリを作ってみようと思います!完成品は以下画像のような感じです。
管理画面で情報を入力して画像をアップロードすると、メニューページに反映されるというものです。今回のアプリで基本的なCRUD機能の作成はマスターできるのかな~と思います。

お客さん用画面はこちら。『おすすめメニュー』とか、『ランチメニュー』とかのカテゴリー別にメニューが表示されます。(カテゴリーも自由に設定できます。)

サイト管理者は管理画面のログインページからログインします。

ログインするとダッシュボードに飛びます。メニューを追加する場合は、右上ボタンから追加できます。

メニュー追加フォームはこちら。カテゴリーは、別ボタンを用意して別ページから追加できる仕様です。

ちなみに私、Webデザインは全然できませんので、フロント側はBootstrapで作ったデザイン性のないシンプル画面です。。。悪しからず。(;'∀') 汗

全体のソースコードはGitHubにアップしております。
必要な方は、以下のリンクからご確認ください。
https://github.com/Tomochan-taco/restaurant_menu

それでは早速スタート!

#1 : プロジェクトの作成

手順1: XAMPPのインストール後、C:\xampp\htdocs に移動して、composerでプロジェクトを作成します。プロジェクト名は restaurant_menu にします。(※ 7系で解説しているので、バージョン指定してください。)

composer create-project --prefer-dist laravel/laravel restaurant_menu "7.*"

手順2: プロジェクトを作成したら、cd コマンドでプロジェクト内『C:\xampp\htdocs\restaurant_menu』に移動します。因みに今回のLaravelのバージョンを確認したら下記のようになりました。

php artisan --version  //Laravelバージョン確認用コマンド
Laravel Framework 7.28.3

#2 : BootstrapとFont Awesomeのインストール

今回も前回同様、BootstrapとFont Awesomeでフロント側を作成しています。ですので、BootstrapとFont Awesomeのインストールを先にやっちゃいます。

手順1: Laravelで用意されているBootstrapのscaffoldingは、laravel/uiパッケージに含まれています。その為、まずはcomposerでlaravel/uiパッケージをインストールします。

composer require laravel/ui:^2.4

手順2: 今回は認証機能も使うので、--authオプションをつけてBootstrapのscaffoldingをインストールします。

php artisan ui bootstrap --auth

C:\xampp\htdocs\restaurant_menu\package.json(以後、package.json と略して表記します。) の "devDependencies"箇所に、以下のようにBootstrapが追加されます。

restaurant_menu\package.json
    "devDependencies": {
       "axios": "^0.19",
       "bootstrap": "^4.0.0", // Bootstrapが追加されています。
       "cross-env": "^7.0",
       "jquery": "^3.2",
       "laravel-mix": "^5.0.1",
       "lodash": "^4.17.19",
       "popper.js": "^1.12",
       "resolve-url-loader": "^3.1.0",
       "sass": "^1.15.2",
       "sass-loader": "^8.0.0"
   }

手順3: npmでインストールします。

npm install && npm run dev

これで、C:\xampp\htdocs\restaurant_menu\public\css\app.css にコンパイルされてBootstrapが使えるようになります!

手順4: 次はFont Awesomeをインストールします。まずはnpmでfree versionをインストールします。

npm install --save @fortawesome/fontawesome-free

package.json の "dependencies" の箇所に、以下のようにFont Awesomeが追加されます。

restaurant_menu\package.json
    "dependencies": {
       "@fortawesome/fontawesome-free": "^5.14.0"
   }

手順5: C:\xampp\htdocs\restaurant_menu\resources\sass\app.scss に下記を追記してインポートします。

restaurant_menu\resources\sass\app.scss
// Font Awesome
@import '~@fortawesome/fontawesome-free/scss/fontawesome';
@import '~@fortawesome/fontawesome-free/scss/regular';
@import '~@fortawesome/fontawesome-free/scss/solid';
@import '~@fortawesome/fontawesome-free/scss/brands';

手順6: npmで実行します。

npm run dev

これでBootstrapとFont Awesomeが使えるようになります!

手順7: ブラウザ確認します。artisanコマンドでサーバを起動後、http://127.0.0.1:8000/ にアクセスしてみてください。
すると、以下画像のように表示されるはずです。LOGINとREGISTERが右上に表示されます!

php artisan serve

image.png

#3 : ロケールの設定

C:\xampp\htdocs\restaurant_menu\config\app.php で、
'timezone' と 'locale' を以下のように変更します。

restaurant_menu\config\app.php
'timezone' => 'Asia/Tokyo',
'locale' => 'ja',

#4 : データベースの作成と、.envファイルの設定

手順1: XAMPPでデータベースを作成します。データベース名は、restaurant_menu にします。(XAMPPでのデータベースの作り方がわからない方は、ご自身で調べてみてください。)

手順2: データベースを作ったら、C:\xampp\htdocs\restaurant_menu\.env
で、 APP_NAME を Restaurant Menu に、DB_DATABASE を restaurant_menu に変更します。(* 空白を含む値を環境変数に定義する場合は、ダブル引用符で囲む必要があります。)
DB_USERNAME と DB_PASSWORD はそのままにします。XAMPP側でデータベースユーザーを設定した人は、こちらも変更してください。

restaurant_menu.env
APP_NAME="Restaurant Menu"

|
|途中省略

DB_DATABASE=restaurant_menu
DB_USERNAME=root
DB_PASSWORD=

#5 : モデルとマイグレーションファイル (テーブル) の作成

データベースを作成したら、お次はテーブルを作成していきます。
データベースに入力する必要があるデータは、ダッシュボードの以下黄色枠の部分です。

image.png

但し、カテゴリーは、例えば『グランドメニュー』の場合に、デミグラスハンバーグやさんま定食など、一つのカテゴリーに対して複数のメニューがあるので、別テーブルにしていきます。

手順1: artisanコマンドで、『Category』と『Product』の二つのモデルを作成します。-mオプションをつけて同時にマイグレーションファイルも作成します。

php artisan make:model Category -m
php artisan make:model Product -m

手順2:『Category』モデルのマイグレーションファイル(C:\xampp\htdocs\restaurant_menu\database\migrations\2020_09_19_235630_create_categories_table.php)(2020_09_19 の部分は、作成した日付ですので、作成日によって変わります。)へ移動します。カテゴリーテーブルに追加するカラムは、カテゴリー名だけですので、18行目辺りに1行だけ追記します。

restaurant_menu\database\migrations\2020_09_19_235630_create_categories_table.php
    public function up()
   {
       Schema::create('categories', function (Blueprint $table) {
           $table->id();
           $table->string('name');  // 追記
           $table->timestamps();
       });
   }

手順3:『Category』モデルの複数代入設定をします。複数代入設定は、意図しないデータを書き換えさせない為に、入力できるデータベーステーブルのカラムを指定するものです。今回は、ホワイトリスト方式である『$fillable方式』を使って、入力できるカラムを指定します。
Categoryモデルファイル(C:\xampp\htdocs\restaurant_menu\app\Category.php *←以後、Category.php と略して表記します。)へ移動します。今回はnameカラム一個だけです。

restaurant_menu\app\Category.php
class Category extends Model
{
   protected $fillable=['name'];
}

手順4: Productモデルのマイグレーションファイル(C:\xampp\htdocs\restaurant_menu\database\migrations\2020_09_19_235644_create_products_table.php)へ移動します。
18行目辺りから、以下のように追記します。category_id が、Categoryテーブルとの外部キーです。

restaurant_menu\database\migrations\2020_09_19_235644_create_products_table.php
    public function up()
   {
       Schema::create('products', function (Blueprint $table) {
           $table->id();
           $table->string('name');  // ここから追記
           $table->text('description');
           $table->integer('price');
           $table->string('image');
           $table->integer('category_id');  // ここまで追記
           $table->timestamps();
       });
   }

手順5: Productモデルの複数代入設定をします。モデルファイル
(C:\xampp\htdocs\restaurant_menu\app\Product.php *←以後、Product.phpと略して表記します。) へ移動して、下記の通りカラムを指定します。

restaurant_menu\app\Product.php
class Product extends Model
{
   protected $fillable=['name', 'description', 'price', 'image', 'category_id'];
}

手順6:artisanコマンドでマイグレーションします。

php artisan migrate

データベースに戻り、categories テーブルと、products テーブルが作成されていることをご確認ください。

image.png

#6 : コントローラーの作成

普通だったらコントローラーより先にルーティングを設定すると思うのですが、ちょっとルーティングの部分でご説明申し上げたい箇所がありまして、それを説明する為に、コントローラーを先に作成します。
artisanコマンドで CategoryController と ProductController を作成します。
-r オプションをつけると、基本的なCRUDルーティングのアクションが自動で入った状態でコントローラーが作成されます!

php artisan make:controller CategoryController -r
php artisan make:controller ProductController -r

作成されたコントローラーファイル(C:\xampp\htdocs\restaurant_menu\app\Http\Controllers\CategoryController.php *←以後、CategoryController.php と略して表記します。また、ProductController の方も同様に略して表記します。)の中身を見てみると、-r オプションをつけたので、以下のようにindex() やらcreate() やら、7つのコントローラーアクションが自動で入力されています!​

restaurant_menu\app\Http\Controllers\CategoryController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class CategoryController extends Controller
{
   /**
    * Display a listing of the resource.
    *
    * @return \Illuminate\Http\Response
    */
   public function index()
   {
       //
   }

   /**
    * Show the form for creating a new resource.
    *
    * @return \Illuminate\Http\Response
    */
   public function create()
   {
       //
   }
   
   以下省略
   
   

#7 : ルーティングの設定

今回のルーティングは、リソースルーティングを使います。
これは、先ほど-r オプションをつけて、7つのアクションが自動で入った状態のコントローラーを作りましたが、そのそれぞれのアクションに対して、典型的なCRUDルーティングをたった一行のコードで割り当てることができるものです。
ルーティングファイル(C:\xampp\htdocs\restaurant_menu\routes\web.php  *←以後、web.phpと略して表記します。)にて、以下のように、下二行を追記します。

restaurant_menu\routes\web.php
Route::get('/', function () {
   return view('welcome');
});

Auth::routes();

Route::get('/home', 'HomeController@index')->name('home');

Route::resource('category','CategoryController');  //追記
Route::resource('product','ProductController');  //追記

追記後、artisanコマンドでルーティングを確認してみてください。

php artisan route:list

以下のように表示されます。

C:\xampp\htdocs\restaurant_menu>php artisan route:list
+--------+-----------+--------------------------+------------------+------------------------------------------------------------------------+------------+
| Domain | Method    | URI                      | Name             | Action                                                                 | Middleware |
+--------+-----------+--------------------------+------------------+------------------------------------------------------------------------+------------+
|        | GET|HEAD  | /                        |                  | Closure                                                                | web        |
|        | GET|HEAD  | api/user                 |                  | Closure                                                                | api        |
|        |           |                          |                  |                                                                        | auth:api   |
|        | POST      | category                 | category.store   | App\Http\Controllers\CategoryController@store                          | web        |
|        | GET|HEAD  | category                 | category.index   | App\Http\Controllers\CategoryController@index                          | web        |
|        | GET|HEAD  | category/create          | category.create  | App\Http\Controllers\CategoryController@create                         | web        |
|        | DELETE    | category/{category}      | category.destroy | App\Http\Controllers\CategoryController@destroy                        | web        |
|        | PUT|PATCH | category/{category}      | category.update  | App\Http\Controllers\CategoryController@update                         | web        |
|        | GET|HEAD  | category/{category}      | category.show    | App\Http\Controllers\CategoryController@show                           | web        |
|        | GET|HEAD  | category/{category}/edit | category.edit    | App\Http\Controllers\CategoryController@edit                           | web        |
|        | GET|HEAD  | home                     | home             | App\Http\Controllers\HomeController@index                              | web        |

         以下省略
         |
         |

createメソッドやら、updateメソッドやらに自動でルーティングが設定されています!(これを説明する為に、コントローラーを先に作成したのです!)これで、
『 Route::get('/category/create', 'CategoryController@create'); 』
とか、
『 Route::put('/category/{category}', 'CategoryController@update'); 』
とかをいちいち書かずに済みます!便利!

#8:作成手順の説明

さて、土台となるものを一通り作成し終えたところで、これからの開発手順をざっくり説明します。そうじゃないと色々と混乱してくると思うので。。(;^_^A

その1: カテゴリーの追加フォーム及び一覧ページの作成
メニュー追加フォームで、予め作られたカテゴリーをプルダウンから選ぶ項目を作ります。ですので、何が何でもカテゴリーを一番先に作らなくてはいけません。

その2: メニュー追加フォーム及びダッシュボードの作成
カテゴリー関連のページを作り終えたら、メニュー追加フォームと、その追加されたメニューを一覧表示するダッシュボードを作ります。

その3: お客さん用画面の作成
ダッシュボードまで作り終えたら、最後にお客さん用画面に情報を反映させて完成です。

#9:Viewページの作成(カテゴリー)

それでは、カテゴリーの追加フォーム及び一覧ページを作っていきます。一覧では、各カテゴリーの編集と削除もできるようにします。

手順1: まずは、追加フォームのViewを作ります。C:\xampp\htdocs\restaurant_menu\resources\views にcategoryというフォルダを作って、create.blade.php という名前でファイルを作ります。コードは以下の通りです。

\xampp\htdocs\restaurant_menu\resources\views\category\create.blade.php

@extends('layouts.app')

@section('content')
<div class="container mt-3" style="max-width: 720px;">
  <div class="text-right">
  <a href="{{ url('/product/create') }}"> 戻る</a>
  </div>
  
  <form>
    <div class="form-group">
      <label for="categoryAdd" class="font-weight-bold">新規カテゴリー追加</label>
      <input type="text" class="form-control" id="categoryAdd" name="name" />
    </div>
    <button type="submit" class="btn btn-primary">追加</button>
  </form>
  
  <div class="my-4">
    <a href="{{ url('/category/') }}"> 一覧・編集ページへ</a>
  </div>
</div>
@endsection

@extends('layouts.app') 』の箇所で使われているlayouts.appのページは、特に編集せずにデフォルトのものをそのまま使っています。
@extends() や @section() の使い方等、laravelのbladeに関する基本的な説明は省きますので、ご存知でない方はご自身で調べてみてください。

手順2: ブラウザで表示確認する為に、コントローラーを設定します。
CategoryController.phpで、以下のようにcreate()メソッドを設定します。

restaurant_menu\app\Http\Controllers\CategoryController.php
    public function create()
   {
       return view('category.create');
   }

ブラウザで、『 http://127.0.0.1:8000/category/create 』へアクセスします。すると以下のように表示されるはずです。

image.png

手順3: お次は、一覧ページのViewを作ります。
categoryフォルダに index.blade.php という名前でファイルを作ります。
コードは以下の通りです。(*データベースからデータを引っ張ってきて表示させる箇所は、現段階ではサンプル値の手入力です。)

restaurant_menu\resources\views\category\index.blade.php

@extends('layouts.app')

@section('content')
<div class="container" style="max-width: 720px">

<div class="text-right">
  <a href="{{ url('/product/create') }}">< 戻る</a>
</div>

 <table class="table table-bordered mt-2">
   <thead class="table-dark">
     <tr>
       <th scope="col">
         #
       </th>
       <th scope="col">
         作成日
       </th>
       <th scope="col">
         カテゴリー
       </th>
       <th scope="col">
         編集
       </th>
       <th scope="col">
         削除
       </th>
     </tr>
   </thead>
   <tbody>
     <tr>
       <th scope="row">
         1
       </th>
       <td>
         2020/2/11
       </td>
       <td>
         グランドメニュー
       </td>
       <td>
         <button type="button" class="btn btn-outline-danger"><i class="far fa-edit"></i> 編集</button>
       </td>
       <td>
         <button type="button" class="btn btn-outline-primary"><i class="far fa-trash-alt"></i> 削除</button>
       </td>
     </tr>
   </tbody>
 </table>

 <div class="my-4">
   <a href="{{ url('/category/create') }}">> カテゴリー新規追加ページへ</a>
 </div>

</div>
@endsection

手順4: ブラウザで表示確認する為に、コントローラーを設定します。
CategoryController.phpで、以下のようにindex()メソッドを設定します。

restaurant_menu\app\Http\Controllers\CategoryController.php
    public function index()
   {
       return view('category.index');
   }

ブラウザで、『 http://127.0.0.1:8000/category/ 』へアクセスします。すると以下のように表示されるはずです。

image.png

#10: データベースへの挿入(カテゴリー)

カテゴリー情報をデータベースに挿入していきます。

手順1: 前回作ったcategoryのviewファイル(create.blade.php)を編集します。ソースコードは以下の通りです。

restaurant_menu\resources\views\category\create.blade.php
    
    <form action="{{ route('category.store') }}" method="POST">  //追記
     @csrf  //追記
     <div class="form-group">
       <label for="categoryAdd" class="font-weight-bold">新規カテゴリー追加</label>
       <input type="text" class="form-control" id="categoryAdd" name="name" />
     </div>
     <button type="submit" class="btn btn-primary">追加</button>
   </form>

9行目辺りの、<form>タグにaction属性とmethod属性を追記します。POSTデータを送信してデータベースに挿入するので、CategoryControllerのstoreメソッドをヒットするようにします。laravelに限らずなのかもしれませんが、フォーム入力されたデータを保存するにはstore()メソッドを使います。
リソースルーティングを設定しているので、category.storeという名前のルーティングが既についています。ですので、action属性には {{ route('category.store') }} と書くことができます。bladeのroute関数についてご存知でない方は、ご自身で調べてみてください。
また、Laravelでお約束ですが、formタグの下には『@csrf 』をつけることを忘れないようにします。CSRF対策の為です。これを書かないと、419|Page Expired エラー等が起きます。

手順2: 正常にデータが送信できるかを確認します。ここではひとまずdd()関数を利用してチェックしてみます。
CategoryController.phpで、store()メソッドを以下のように設定します。
※ フォームから飛んでくるデータはRequest型で渡ってきます。その為、コールバック関数の引数には、Implicit Bindingを利用して、(Request $request) とします。この辺りがよくわからない方は、『Laravel フォーム Request Implicit Binding』等で検索してみてください。

restaurant_menu\app\Http\Controllers\CategoryController.php

    public function store(Request $request)
   {
       return dd($request->all());
   }

手順3:『 http://127.0.0.1:8000/category/create 』にアクセスします。何でもいいのですが、とりあえず『グランドメニュー』と入力して、追加ボタンを押します。

image.png

すると、以下のように表示されて、CSRFのトークンと、カテゴリー情報(inputタグの値)が送られてきているのがわかります!

image.png

手順4: データベースに挿入していきます。dd()メソッドはコメントアウトして、Eloquentモデルのcreate()メソッドを使って挿入します。
その際、Categoryモデルを使うので、コントローラーに『 use App\Category; 』を追記しておくことを忘れないようにします。
コードは以下の通りです。

restaurant_menu\app\Http\Controllers\CategoryController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Category;  //追記

以下省略



    public function store(Request $request)
  {
      // return dd($request->all());
      
      Category::create([
          'name'=>request('name')
      ]);
  }

手順5: データがきちんと挿入されるか確認します。先程と同じようにブラウザで『 グランドメニュー』と入力して追加ボタンを押します。

image.png

categoriesテーブルを確認すると、以下の通りグランドメニューが追加されています!データベースへの挿入ができました!

image.png

#11:フラッシュメッセージの表示とリダイレクト(カテゴリー)

カテゴリーを追加した際に、直前のページ(要は、カテゴリーを追加した同じページのこと。)にリダイレクトして、フラッシュメッセージを出すようにします。

手順1: まずは、View側でフラッシュメッセージを出すコードを追記します。categoryのviewファイル(create.blade.php)へ移動して、9行目辺りから追記していきます。コードは以下の通りです。

restaurant_menu\resources\views\category\create.blade.php

@extends('layouts.app')

@section('content')
<div class="container mt-3" style="max-width: 720px;">
  <div class="text-right">
  <a href="{{ url('/product/create') }}">< 戻る</a>
  </div>

  @if (session('message'))  //ここから追記
   <div class="alert alert-success" role="alert">{{ session('message') }}</div>
  @endif  //ここまで追記

  <form action="{{ route('category.store') }}" method="POST">
    @csrf
    
    以下省略
     |
     |

グローバルなsession()ヘルパ関数を使います。引数を一つにすることで、キーに対する値を取得することができます。if文にすることで、'message'というキー名でsessionデータがあれば表示しないさい、と書いています。

手順2: 次に、コントローラーに設定していきます。
CategoryControllerのstore()メソッドに、『 return redirect() ~~ 』部分を追記します。

restaurant_menu\app\Http\Controllers\CategoryController.php

       public function store(Request $request)
   {
       // return dd($request->all());
       
       Category::create([
           'name'=>request('name')
       ]);
       
       return redirect()->back()->with('message', 'カテゴリーが追加されました。');  //追記
   }

直前のページにリダイレクトするには、back()メソッドを使います。フラッシュメッセージを出すには、セッションデータを返す必要があるので、with()メソッドを使います。with()メソッドは、リダイレクトする場合や、viewを返す時などにデータを渡せるメソッドです。view側に変数を渡す場合等によく使われます。今回は、『 with('キー', 'バリュー'); 』のように2つ引数を渡すことで、配列としてセッションデータをview側に渡します。

手順3: ブラウザで確認してみます。
再度『 http://127.0.0.1:8000/category/create 』にアクセスして、カテゴリーを入力後に追加ボタンを押すと、リダイレクトされて、以下のようにフラッシュメッセージが表示されます。

image.png

#12:一覧ページへ反映させる(カテゴリー)
カテゴリーを無事データベースに追加できたので、それを一覧ページに表示させます。

手順1: まずは、コントローラーに一覧情報を表示させるためのデータ受け渡しの設定を行います。
CategoryController.phpで、元々書いてあった『return view('category.index');』はコメントアウトして、index() メソッドに下二行を追記します。

restaurant_menu\app\Http\Controllers\CategoryController.php
    public function index()
   {
       // return view('category.index');
       
       $categories = Category::latest()->get();  //追記
       return view('category.index', ['categories' => $categories]);  //追記
   }

latest()は、新しいもの順でデータを取ってくる際に使えるように、Laravelが用意してくれている便利なメソッドです。クエリビルダで言う所の、orderBy('created_at', 'desc')と同じ意味になります。その為、『Category::latest()->get();』は、『Category::orderBy('created_at', 'desc')->get();』と同じ結果になります。

手順2: 先程作った一覧ページのView(index.blade.php)へ移動して、30行目辺りの

以下に、//追記 もしくは //変更 と書かれている部分を追記・変更してください。
restaurant_menu\resources\views\category\index.blade.php

@extends('layouts.app')

@section('content')
<div class="container" style="max-width: 720px">

<div class="text-right">
 <a href="{{ url('/product/create') }}">< 戻る</a>
</div>

<table class="table table-bordered mt-2">
  <thead class="table-dark">
    <tr>
      <th scope="col">
        #
      </th>
      <th scope="col">
        作成日
      </th>
      <th scope="col">
        カテゴリー
      </th>
      <th scope="col">
        編集
      </th>
      <th scope="col">
        削除
      </th>
    </tr>
  </thead>
  <tbody>
  @if (count($categories) > 0)  //追記
   @foreach ($categories as $key=>$category)  //追記
     <tr>
       <th scope="row">
       {{ $key+1 }}  //変更
       </th>
       <td>
       {{ $category->created_at->format('Y/m/d') }}  //変更
       </td>
       <td>
       {{ $category->name }}  //変更
       </td>
       <td>
         <button type="button" class="btn btn-outline-danger"><i class="far fa-edit"></i> 編集</button>
       </td>
       <td>
         <button type="button" class="btn btn-outline-primary"><i class="far fa-trash-alt"></i> 削除</button>
       </td>
     </tr>
   @endforeach  //追記
   
   @else  //追記
     <tr>  //追記
     <td colspan="5">追加されたカテゴリーはありません。</td>  //追記
     </tr>  //追記

  @endif  //追記
  </tbody>
</table>

<div class="my-4">
  <a href="{{ url('/category/create') }}">> カテゴリー新規追加ページへ</a>
</div>

</div>
@endsection

31行目辺りの @if (count($categories) > 0) の部分で、カテゴリーが1つ以上あればテーブルを表示して、そうでなければ、51行目辺りの、@else 部分で『追加されたカテゴリーはありません。』という文言を表示するようにします。

手順3: @else 部分が正常に表示されるかを確認する為に、データを全部消去します。artisanコマンドで以下の通りmigrate:freshを行います。

php artisan migrate:fresh

手順4: ブラウザで確認します。『 http://127.0.0.1:8000/category 』にアクセスします。すると、以下の通り表示されるはずです。

image.png

手順5:『> カテゴリー新規追加ページへ』 のリンクをクリックして、追加ページへ移動します。そこで、『ランチメニュー』『グランドメニュー』『おすすめメニュー』を追加してみます。

image.png

手順6:『> 一覧・編集ページへ のリンク』をクリックすると、以下のようにデータが表示されます!

image.png

*おまけで解説です。↓↓
index.blade.phpの、『 @foreach ($categories as $key=>$category) 』の部分ですが、これは、通し番号の部分( {{ $key+1 }} のとこ。)を表示させる為に、このように書いています。Laravelではモデルを使った場合に、データベースから返ってくる値は常にCollectionインスタンスになるので、$key変数に、連想配列の”キー”の部分を入れ込むことができます。
* LaravelのCollectionが分からない方は、結構豊富に記事が出ているので調べてみてください!また、PHPの連想配列を取り出す方法に関しては、この記事がわかりやすかったです。

【やさしいPHP】foreach文の基礎を知ってかんたんな応用を試してみる

実際に確認してみます。CategoryController.phpのindex()メソッドに、以下のようにdd() ヘルパ関数を追記してみてください。

restaurant_menu\app\Http\Controllers\CategoryController.php

    public function index()
   {
       // return view('category.index');
       $categories = Category::latest()->get();
       dd($categories);  //追記
       return view('category.index', ['categories' => $categories]);
   }

http://127.0.0.1:8000/category 』にアクセスすると、以下のように表示されます。Collectionインスタンスが返されて、”キー”の部分には、配列で言う所の配列番号が入っています。これを表示させることで、一覧表の通し番号を表示させています。

#13:編集機能をつける(カテゴリー)

次は、カテゴリー一覧ページの編集ボタンから、カテゴリーを編集できるようにします。

image.png

手順1: 一覧ページ(index.blade.php)へ移動して、編集ボタンの箇所を<a>タグで囲みます。

restaurant_menu\resources\views\category\index.blade.php
<a href="{{ route('category.edit', [ 'category' => \$category->id ]) }}">  //追記
  <button type="button" class="btn btn-outline-danger"><i class="far fa-edit"></i> 編集</button>
</a>  //追記

『 route('category.edit', [ 'category' => $category->id ]) 』の箇所で、該当するカテゴリーのidをつけたURLへ遷移します。ブラウザで実際に編集ボタンを押してみると、URLが『 http://127.0.0.1:8000/category/1/edit 』や『 http://127.0.0.1:8000/category/2/edit 』へ飛びます。
php artisan route:list を今一度確認してみてください。
『 category.edit 』 という名前のついたルーティングは、『 category/{category}/edit 』へ遷移するように設定されています。route関数の第二引数につけたパラメーターは、自動的にURLの正しい場所へ埋め込まれます。

image.png

手順2: お次は、編集ボタンを押した先にeditページを表示させるようにコントローラーに設定していきます。(editページ自体はこの後作ります。)
CategoryController.phpで、edit() メソッドを以下のように編集します。indexページの編集ボタンを押したときに渡ってきたパラメーターが($id)の部分に入ります。

restaurant_menu\app\Http\Controllers\CategoryController.php

    public function edit($id)
   {
       $category = Category::find($id);
       return view('category.edit', ['category' => $category]);
   }

手順3: editページを作成します。C:\xampp\htdocs\restaurant_menu\resources\views\category に『 edit.blade.php 』という名前でファイルを作ります。同じフォルダのcreate.blade.phpファイルとほぼ中身は一緒なので、まずはそれを全てコピペします。そのうえで、以下『//追記 』もしくは 『//変更』と書かれている部分を追記・変更します。

restaurant_menu\resources\views\category\edit.blade.php

@extends('layouts.app')

@section('content')
<div class="container mt-3" style="max-width: 720px;">
  <div class="text-right">
  <a href="{{ url('/product/create') }}">< 戻る</a>
  </div>

  <form action="{{ route('category.update', [ 'category' => $category->id ]) }}" method="POST">  // 変更
    @csrf
    @method('PUT')  //追記
    <div class="form-group">
      <label for="categoryAdd" class="font-weight-bold">カテゴリー編集</label>  // 変更
      <input type="text" class="form-control" id="categoryAdd" name="name" value="{{ $category->name }}" />  // 変更
    </div>
    <button type="submit" class="btn btn-primary">編集</button>  // 変更
  </form>

  <div class="my-4">
    <a href="{{ url('/category/') }}">> 一覧ページへ</a>  // 変更
  </div>
</div>
@endsection

何点か説明します。
『 <form action="{{ route('category.update', [ 'category' => $category->id ]) }}" method="POST"> 』の箇所は、アップデートしたデータをデータベースに反映させる為に、update() メソッドをヒットするように設定します。また、update() メソッドはPUTリクエストで送信するため、『 @method('PUT') 』という記述を書く必要があります。これは、現段階ではブラウザがGETかPOSTしか理解することができないため、これはPOSTじゃなくて、PUTだよ、ということをブラウザ側に伝える為です。これは、DELETEリクエストの時も同様です。
また、編集ページのため、編集する前のデータがフォームに残った状態で表示させます。その為、inputタグにvalue属性を追記します。

これで、一覧ページの編集ボタンをクリックしてみてください。以下のように該当するカテゴリーが入力された状態で編集フォームが表示されます。

image.png

手順4: 編集されたデータをデータベースに保存していきます。CategoryController.php へ移動して、update()メソッドを、以下のように設定します。

restaurant_menu\app\Http\Controllers\CategoryController.php
    public function update(Request $request, $id)
   {
       $category = Category::find($id);
       $category->name = request('name');
       $category->save();
       return redirect('/category')->with('message', 'カテゴリーが編集されました。');
   }

コードの中身を説明します。
① 『$category = Category::find($id);』で、フォームから渡ってきたデータをidで探します。
② 『$category->name = request('name');』で、該当するidのデータのnameカラムを、フォームから渡ってきたRequest型の値に変更します。
③ ②のデータを保存します。
④ 一覧ページにリダイレクトして、そこで編集完了のフラッシュメッセージを出します。

手順5: 一覧ページ(index.blade.php)にフラッシュメッセージを表示させるコードを書いていなかったので、追記しておきます。

restaurant_menu\resources\views\category\index.blade.php

@extends('layouts.app')

@section('content')
<div class="container" style="max-width: 720px">
<div class="text-right">
 <a href="{{ url('/product/create') }}">< 戻る</a>
</div>

@if (session('message'))  //ここから追記
 <div class="alert alert-success" role="alert">{{ session('message') }}</div>
@endif  //ここまで追記

<table class="table table-bordered mt-2">
  <thead class="table-dark">

手順6: 編集ページで、『おすすめメニュー』を『夏のおすすめメニュー』と変更して、編集ボタンを押してみます。そうすると、一覧ページにリダイレクトして、編集された旨のフラッシュメッセージが表示されます。

image.png

#14:削除機能をつける(カテゴリー)

一覧ページでカテゴリーを削除できるようにします。削除する機能は、delete()メソッド一発で実現できるので簡単です!

手順1: CategoryController.php のdestroy()メソッドを以下のように設定します。

restaurant_menu\app\Http\Controllers\CategoryController.php
    public function destroy($id)
   {
       $category = Category::find($id);
       $category->delete();
       return redirect('/category')->with('message', 'カテゴリーが削除されました。');
   }

コードの説明をします。
① 『 $category = Category::find($id); 』で、パラメーターから渡ってきたidを元にデータを探します。
② 『 $category->delete(); 』では、①のデータをdelete()メソッド一発でデータベースから削除しています。
③ 一覧ページにリダイレクトして、フラッシュメッセージ用のデータをwith()関数でbladeに送ります。

手順2: 次は、View側の設定をしていきます。削除する際に、モーダルを使って確認画面を出してみようと思います。一覧ページ(index.blade.php)で、元々削除ボタンがあった

タグ箇所に、Bootstrapのモーダルのコードをとりえあえずコピペします。コピペするのは、以下サイトの『Live demo』のコードです。

BootStrapドキュメント

手順3: コピペしたら、以下『//変更』もしくは 『//追記 』と書かれている部分を変更・追記します。コードは以下の通りです。

restaurant_menu\resources\views\product\index.blade.php

<td>
 <!-- <button type="button" class="btn btn-outline-primary"><i class="far fa-trash-alt"></i> 削除</button> -->  //コメントアウト
 <!-- Button trigger modal -->
 <button type="button" class="btn btn-outline-primary" data-toggle="modal" data-target="#exampleModal{{$category->id}}"><i class="far fa-trash-alt"></i> 削除</button>  //変更

 <!-- Modal -->
 <div class="modal fade" id="exampleModal{{$category->id}}" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">  //変更
   <div class="modal-dialog" role="document">
     <form action="{{ route('category.destroy', [ 'category' => $category->id ]) }}" method="POST">  //追加
       @csrf  //追加
       @method('DELETE')  //追加
       <div class="modal-content">
         <div class="modal-header">
           <h5 class="modal-title" id="exampleModalLabel">カテゴリー削除</h5>  //変更
           <button type="button" class="close" data-dismiss="modal" aria-label="Close">
             <span aria-hidden="true">&times;</span>
           </button>
         </div>
         <div class="modal-body">
           本当に削除しますか?  //変更
         </div>
         <div class="modal-footer">
           <button type="button" class="btn btn-secondary" data-dismiss="modal">キャンセル</button>  //変更
           <button type="submit" class="btn btn-primary">削除</button>  //変更
         </div>
       </div>
     </form>
   </div>
 </div>
</td>

53行目辺りに、元々あった削除ボタンのコード『 <button type="button" class="btn btn-outline-primary"><i class="far fa-trash-alt"></i> 削除</button> 』はコメントアウトします。
次に、<div class="modal-dialog" role="document"> の直下、60行目辺りにformタグを追加します。formタグでは、destroy()メソッドをヒットさせるようにします。@csrf@method('DELETE') をつけるのを忘れないようにしてください。また、55行目あたりの『data-target="#exampleModal{{$category->id}}">』と58行目辺りの『id="exampleModal{{$category->id}}"』の箇所には『{{$category->id}}』を追加しています。
続いて、『カテゴリー削除』『本当に削除しますか?』『キャンセル』『削除』の文言を変更します。『削除』文言の<button>タグは、type="submit" に変更してください。

手順4: ブラウザで確認してみます。削除ボタンを押すとモーダルで確認画面が出てきて、更に削除ボタンを押すとフラッシュメッセージと共に一覧から削除されます。また、データベースからも削除されます。

image.png

image.png

#15:バリデーションをつける(カテゴリー)

フォームにバリデーションを付けていなかったので、追加ページと一覧ページにつけていきます。

手順1: まずは追加ページの方からです。コントローラの設定を先に行います。CategoryController.phpで、store()メソッドに、request()->validate 箇所を追記します。

restaurant_menu\app\Http\Controllers\CategoryController.php

    public function store(Request $request)
   {
       // return dd($request->all());
       
        request()->validate(
           ['name' => 'required|unique:categories'],
           ['name.required' => 'カテゴリーを入力してください。',
           'name.unique' => 'そのカテゴリーは既に追加されています。']
       );
       
       Category::create([
           'name'=>request('name')
       ]);
       
       return redirect()->back()->with('message', 'カテゴリーが追加されました。');
   }

手順2: 追加ページ(create.blade.php)に移動して、以下『//変更』もしくは 『//追記 』と書かれている部分を変更・追記します。

restaurant_menu\resources\views\product\create.blade.php

  <form action="{{ route('category.store') }}" method="POST">
   @csrf
   <div class="form-group">
     <label for="categoryAdd" class="font-weight-bold">新規カテゴリー追加</label>
     <input type="text" class="form-control @error('name') is-invalid @enderror" id="categoryAdd" name="name" />  //変更
     @error('name')  //追記
       <p class="text-danger">{{ $message }}</p>  //追記
     @enderror  //追記
   </div>
   <button type="submit" class="btn btn-primary">追加</button>
 </form>

17行目辺りのinputタグのclass名には、『 @error('name') is-invalid @enderror 』を追記しています。これは、@errorディレクティブを利用をして、エラーメッセージがあったらis-invalidクラスを付けてね、ということを表しています。また、その後の18行目から20行目にかけても@errorディレクティブを使って、$message変数を表示させるようにしています。

手順3: カテゴリー追加ページに行って、何も入力せずに追加ボタンを押すと、以下のようにエラーメッセージが表示されます。

image.png

重複して追加しようとすると、既に登録されている旨のエラーが出ます。

image.png

手順4: 編集ページも同様にバリデーションをかけていきます。
CategoryController.phpのupdate()メソッドにrequest()->validate()部分を追記します。

restaurant_menu\app\Http\Controllers\CategoryController.php

    public function update(Request $request, $id)
   {
        request()->validate(
           ['name' => 'required|unique:categories'],
           ['name.required' => 'カテゴリーを入力してください。',
           'name.unique' => 'そのカテゴリーは既に追加されています。']
       );
       
       $category = Category::find($id);
       $category->name = request('name');
       $category->save();
       return redirect('/category')->with('message', 'カテゴリーが編集されました。');
   }

手順5: View側でも設定していきます。追加ページと同じように追記していきます。編集ページ(edit.blade.php)にて、inputタグのclass名に、『@error('name') is-invalid @enderror』を追記します。その後の18行目から20行目にかけても@errorディレクティブを使って、$message変数を表示させます。

restaurant_menu\resources\views\product\edit.blade.php

   <form action="{{ route('category.update', [ 'category' => $category->id ]) }}" method="POST">
    @csrf
    @method('PUT')
    <div class="form-group">
      <label for="categoryAdd" class="font-weight-bold">カテゴリー編集</label>
      <input type="text" class="form-control @error('name') is-invalid @enderror" id="categoryAdd" name="name" value="{{ $category->name }}" />  //変更
      @error('name')  //追加
       <p class="text-danger">{{ $message }}</p>  //追加
      @enderror  //追加
    </div>
    <button type="submit" class="btn btn-primary">編集</button>
  </form>

これで、フォームを空にして編集ボタンを押すと、『カテゴリーを入力してください。』というエラーが出て、何も変更せずに編集ボタンを押すと『そのカテゴリーは既に追加されています。』というエラーが出ます。

#16:Viewページの作成(メニュー)

カテゴリー関連の機能は完成したので、お次はメニュー追加フォームとダッシュボードの作成を行います!

手順1:まずはメニュー追加フォームのViewを作ります。
C:\xampp\htdocs\restaurant_menu\resources\views にproductというフォルダを作って、create.blade.php という名前でファイルを作ります。コードは以下の通りです。
フラッシュメッセージと@errorディレクティブを使ったバリデーションの部分は、既にコード内に記載済みです。カテゴリーの時と同様なので、これに関する説明は省きます。

restaurant_menu\resources\views\product\create.blade.php

@extends('layouts.app')

@section('content')
<div class="container mt-3" style="max-width: 720px;">
 <div class="text-right">
   <a href="{{ url('/product/') }}">< 戻る</a>
 </div>
 
 @if ( session('message') )
 <div class="alert alert-success" role="alert">{{ session('message') }}</div>
 @endif
 
 <form action="{{ route('product.store') }}" method="POST" enctype="multipart/form-data">
   @csrf
   <div class="form-group" style="margin-top: 30px; margin-bottom: 30px">
     <label for="name" class="font-weight-bold">商品名</label>
     <input type="text" class="form-control @error('name') is-invalid @enderror" id="name" name="name" />
     @error('name')
     <p class="text-danger">{{ $message }}</p>
     @enderror
   </div>
   <div class="form-group" style="margin-bottom: 30px">
     <label for="textarea" class="font-weight-bold">詳細</label>
     <textarea class="form-control @error('description') is-invalid @enderror" id="textarea" rows="5" name="description"></textarea>
     @error('description')
     <p class="text-danger">{{ $message }}</p>
     @enderror
   </div>
   <div class="form-group" style="margin-bottom: 30px">
     <label for="price" class="font-weight-bold">値段</label>
     <input type="text" class="form-control @error('price') is-invalid @enderror" id="price" name="price" />
     <small class="form-text text-muted">半角数字で入力してください。</small>
     @error('price')
     <p class="text-danger">{{ $message }}</p>
     @enderror
   </div>
   <div class="form-group" style="margin-bottom: 30px">
     <label for="category" class="font-weight-bold">カテゴリー</label>
     <select class="form-control @error('category') is-invalid @enderror" id="category" name="category">
       <option value="" disabled selected style="display: none;">カテゴリーを選択してください。</option>
       @foreach(App\Category::all() as $category)
       <option value="{{ $category->id }}">{{ $category->name }}</option>
       @endforeach
     </select>
     @error('category')
     <p class="text-danger">{{ $message }}</p>
     @enderror
     <div class="text-right mt-2">
       <a type="button" href="{{ url('/category/create/') }}" class="btn btn-outline-secondary py-1" role="button">新規追加</a>
       <a type="button" href="{{ url('/category/') }}" class="btn btn-outline-secondary py-1" role="button">編集</a>
     </div>
   </div>
   <div class="form-group" style="margin-bottom: 30px">
     <label for="image" class="font-weight-bold">画像アップロード</label>
     <input type="file" class="form-control-file @error('image') is-invalid @enderror" id="image" name="image" />
     @error('image')
     <p class="text-danger">{{ $message }}</p>
     @enderror
   </div>
   
   <button type="submit" class="btn btn-primary my-3">送信</button>
   
 </form>
</div>
@endsection

40行目辺りの<option>タグ『 <option value="" disabled selected style="display: none;">』の箇所について少し説明します。
これは、『カテゴリーを選択してください。』の文章を、下記画像のようにデフォルトで表示させる為に書いています。

image.png

属性の説明は下記4点です。
① value値を空にします。( value="")
② 選択できないようにします。(disabled)
③ デフォルトで選択表示されているようにします。(selected)
④ プルダウンの中ではなく、フォームの中に表示させるようにします。(style="display: none;)
また、『@foreach(App\Category::all() as $category)』の部分は、コントローラーから変数が渡って来ている訳ではないので、ここでCategoryモデルにアクセスしています。

手順2: ブラウザで表示確認する為に、コントローラーの設定をします。(*おさらいですが、ルーティングは、リソースルーティングで既に設定しています。)
ProductController.phpで、以下のようにcreate()メソッドを設定します。

restaurant_menu\app\Http\Controllers\ProductController.php

    public function create()
   {
       return view('product.create');
   }

手順3: ブラウザで、『 http://127.0.0.1:8000/product/create 』へアクセスします。すると以下のように表示されるはずです。

image.png

手順4: 次に、ダッシュボードのViewを作ります。
先程作ったproductフォルダに、index.blade.phpと言う名前でファイルを作ります。コードは以下の通りです。(*データベースからデータを引っ張ってきて表示させる箇所は、現段階ではサンプル値の手入力です。)

restaurant_menu\resources\views\product\index.blade.php

@extends('layouts.app')

@section('content')
<div class="container-fluid my-2">
  <div class="row m-2">
    <div class="col">
      <h3 class="font-weight-bold">ダッシュボード</h3>
    </div>
    <div class="col text-right">
      <a type="button" href="{{ url('/product/create/') }}" class="btn btn-primary text-right" role="button"><i class="fas fa-plus"></i> 新規追加</a>
    </div>
  </div>
  
  <table class="table table-bordered">
    <thead class="table-dark">
      <tr>
        <th scope="col">
          id
        </th>
        <th scope="col">
          画像
        </th>
        <th scope="col">
          商品名
        </th>
        <th scope="col">
          詳細
        </th>
        <th scope="col">
          値段
        </th>
        <th scope="col">
          カテゴリー
        </th>
        <th scope="col">
          編集
        </th>
        <th scope="col">
          削除
        </th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <th scope="row">
          1
        </th>
        <td style="max-width: 200px;">
          <img src="#" class="img-fluid" />
        </td>
        <td>
          デミグラスハンバーグ
        </td>
        <td style="max-width: 300px;">
          お肉の旨味をギュッと閉じ込めたジューシーなハンバーグに、濃厚なデミグラスソースをたっぷりとかけてお楽しみください。
        </td>
        <td>
          880
        </td>
        <td>
          グランドメニュー
        </td>
        <td>
          <button type="button" class="btn btn-outline-danger"><i class="far fa-edit"></i> 編集</button>
        </td>
        <td>
          <button type="button" class="btn btn-outline-primary"><i class="far fa-trash-alt"></i> 削除</button>
        </td>
      </tr>
    </tbody>
  </table>
  
</div>
@endsection

手順5: ブラウザで表示確認する為に、コントローラーの設定をします。
ProductController.phpで、以下のようにindex()メソッドを設定します。

restaurant_menu\app\Http\Controllers\ProductController.php

  public function index()
   {
       return view('product.index');
   }

手順6: ブラウザで、『 http://127.0.0.1:8000/product/ 』へアクセスします。すると以下画像のように表示されるはずです。画像はまだアップしていないので、リンク切れになっています。

image.png

#17:データベースへの挿入(メニュー)

手順1: フォームからの送信データをデータベースに挿入していきます。ProductController.phpで、以下のようにstore()メソッドを設定します。コードは以下の通りです。バリデーションの部分は、既に含んでいます。

restaurant_menu\app\Http\Controllers\ProductController.php

    public function store(Request $request)
   {
       request()->validate(
           ['name' => 'required',
            'description' => 'required',
            'price' => 'required|integer',
            'category' => 'required',
            'image' => 'required|mimes:jpeg,png,jpg,gif,svg'],
           ['name.required' => '商品名を入力してください。',
            'description.required' => '詳細を入力してください。',
            'price.required' => '値段を入力してください。',
            'category.required' => 'カテゴリーを入力してください。',
            'image.required' => '画像を選択してください。']
       );
       
       $image = $request->file('image');
       $name = time().'.'.$image->getClientOriginalExtension();
       $destinationPath = public_path('/images');
       $image->move($destinationPath,$name);
     
       Product::create([
           'name'=>request('name'),
           'description'=>request('description'),
           'price'=>request('price'),
           'category_id'=>request('category'),
           'image'=>$name
       ]);
       
       return redirect()->back()->with('message', '商品情報が追加されました。');
   }

上記のコードの部分で、画像アップロードに関する部分を説明します。
① まずはフォームからアップされた画像を変数に代入して、($image = $request->file('image');)
② アップロードした日時と画像の拡張子をつけてユニークな画像名にします。($name = time().'.'.$image->getClientOriginalExtension();)
③ C:\xampp\htdocs\restaurant_menu\public にimagesという名前でフォルダを作っておきます。そして、そこに画像を保存するようにします。
($destinationPath = public_path('/images'); $image->move($destinationPath,$name);)

手順2: データの挿入にはProductモデルのcreate()メソッドを使っているので、ProductController.php上部に『use App\Product;』を追記することを忘れないようにします。

restaurant_menu\app\Http\Controllers\ProductController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Product;  //追記

以下省略


手順3: ブラウザに戻って、メニュー追加ページで適当に情報を入力して、送信ボタンを押してみます。正常に送信された場合は、以下画像のようにフラッシュメッセージが表示されます。

image.png

何も入力せずに送信ボタンを押すと、以下画像のようにエラーが表示されるはずです。

image.png

#18:ダッシュボードへ反映させる

ダッシュボードに追加したメニュー情報を反映させます。

手順1: まずは、コントローラーに情報を表示させるためのデータ受け渡しの設定を行います。
ProductController.phpで、元々書いてあった『return view('product.index');』はコメントアウトして、index() メソッドに下二行を追記します。

restaurant_menu\app\Http\Controllers\ProductController.php

    public function index()
   {
       // return view('product.index');

       $products = Product::latest()->paginate(5);
       return view('product.index', ['products' => $products]);
   }

特筆すべきは、paginate(5)のページネーションの箇所です。5項目追加したら次のページに行くように設定しています。

手順2: ダッシュボードのView(index.blade.php)へ移動して、43行目辺りの<tbody>以下に、//追記 もしくは //変更 と書かれている部分を追記・変更してください。@if() 文や、@foreach@else 文の箇所は、Categoryの時と同様なので説明は省きます。
また、115行目辺りの$products->links()の箇所は、コントローラーで設定したpaginate(5)の部分を表示させています。

restaurant_menu\resources\views\product\index.blade.php

@extends('layouts.app')

@section('content')
<div class="container-fluid my-2">
 <div class="row m-2">
   <div class="col">
     <h3 class="font-weight-bold">ダッシュボード</h3>
   </div>
   <div class="col text-right">
     <a type="button" href="{{ url('/product/create/') }}" class="btn btn-primary text-right" role="button"><i class="fas fa-plus"></i> 新規追加</a>
   </div>
 </div>
 
 <table class="table table-bordered">
   <thead class="table-dark">
     <tr>
       <th scope="col">
         #
       </th>
       <th scope="col">
         画像
       </th>
       <th scope="col">
         商品名
       </th>
       <th scope="col">
         詳細
       </th>
       <th scope="col">
         値段
       </th>
       <th scope="col">
         カテゴリー
       </th>
       <th scope="col">
         編集
       </th>
       <th scope="col">
         削除
       </th>
     </tr>
   </thead>
   <tbody>
     @if(count($products) > 0 )  //追記
     @foreach($products as $key=>$product)  //追記
     <tr>
       <th scope="row">
         {{ $key+1 }}  //変更
       </th>
       <td style="max-width: 200px;">
         <img src="{{asset('images')}}/{{$product->image}}" class="img-fluid" />  //変更
       </td>
       <td>
         {{$product->name}}  //変更
       </td>
       <td style="max-width: 300px;">
         {{$product->description}}  //変更
       </td>
       <td>
         {{$product->price}} 円  //変更
       </td>
       <td>
         {{$product->category->name}}  //変更
       </td>
       <td>
         <button type="button" class="btn btn-outline-danger"><i class="far fa-edit"></i> 編集</button>
       </td>
       <td>
         <button type="button" class="btn btn-outline-primary"><i class="far fa-trash-alt"></i> 削除</button>
       </td>
     </tr>
     @endforeach  //追記
     @else  //追記
     <tr>  //追記
       <td colspan="8">追加された商品情報はありません。</td>  //追記
     </tr>  //追記
     @endif  //追記
   </tbody>
 </table>
 
 <div class="d-flex">  //追記
   <div class="mx-auto">  //追記
     {{$products->links("pagination::bootstrap-4")}}  //追記
   </div>  //追記
 </div>  //追記
 
</div>
@endsection

手順3: 63 行目辺りの『{{$product->category->name}}』の箇所ですが、これはProductモデルからCategoryモデルにアクセスしようとしている為、(厳密に言うと、Categoryモデルのインスタンスのnameプロパティの値にアクセス)モデルのリレーション設定が必要です。
ProductモデルのモデルファイルProduct.phpで、以下のようにcategory()メソッドを追記します。一つのメニューにつき、一つのカテゴリーがあるので、関係性はhasOneにします。
Laravelのモデルのリレーションについてご存知でない方は、ご自身で調べてみてください。

restaurant_menu\app\Product.php

class Product extends Model
{
   protected $fillable=['name', 'description', 'price', 'image', 'category_id'];
   
   public function category(){
   	return $this->hasOne('App\Category','id','category_id');
   }
}

手順4: メニュー追加ページで、適当に情報を入力した後に、『< 戻る』ボタンを押してダッシュボードを表示させてみます。すると、以下画像のように表示されるはずです。

image.png

手順5: 画面上部の『Restaurant Menu』の部分ですが、ここをクリックするとダッシュボードに飛ぶように設定しようと思います。C:\xampp\htdocs\restaurant_menu\resources\views\layouts\app.blade.phpで、26行目辺りの以下コード部分(href属性のとこ。)を、『url('/product')』に変更します。

<a class="navbar-brand" href="{{ url('/') }}">
↓変更
<a class="navbar-brand" href="{{ url('/product') }}">

#19:編集/削除機能をつける(メニュー)

ダッシュボードの編集/削除機能を完成させていきます。

手順1: まずはダッシュボードのview画面を作成します。コードは以下の通りです。編集ボタンと削除ボタンの箇所に、カテゴリーでやった時と同様にコードを追記しています。その為、ここでのコードに関する説明は省きます。

restaurant_menu\resources\views\product\index.blade.php

@extends('layouts.app')

@section('content')
<div class="container-fluid my-2">
 <div class="row m-2">
   <div class="col">
     <h3 class="font-weight-bold">ダッシュボード</h3>
   </div>
   <div class="col text-right">
     <a type="button" href="{{ url('/product/create/') }}" class="btn btn-primary text-right" role="button"><i class="fas fa-plus"></i> 新規追加</a>
   </div>
 </div>
 
 @if (session('message'))  //追記
 <div class="alert alert-success" role="alert">{{ session('message') }}</div>  //追記
 @endif  //追記
 
 <table class="table table-bordered">
   <thead class="table-dark">
     <tr>
       <th scope="col">
         #
       </th>
       <th scope="col">
         画像
       </th>
       <th scope="col">
         商品名
       </th>
       <th scope="col">
         詳細
       </th>
       <th scope="col">
         値段
       </th>
       <th scope="col">
         カテゴリー
       </th>
       <th scope="col">
         編集
       </th>
       <th scope="col">
         削除
       </th>
     </tr>
   </thead>
   <tbody>
     @if(count($products) > 0 )
     @foreach($products as $key=>$product)
     <tr>
       <th scope="row">
         {{ $key+1 }}
       </th>
       <td style="max-width: 200px;">
         <img src="{{asset('images')}}/{{$product->image}}" class="img-fluid" />
       </td>
       <td>
         {{$product->name}}
       </td>
       <td style="max-width: 300px;">
         {{$product->description}}
       </td>
       <td>
         {{$product->price}} 円
       </td>
       <td>
         {{$product->category->name}}
       </td>
       <td>
         <a href="{{route('product.edit',[$product->id])}}">  //追記
           <button type="button" class="btn btn-outline-danger"><i class="far fa-edit"></i> 編集</button>
         </a>  //追記
       </td>
       <td>
         <!-- <button type="button" class="btn btn-outline-primary"><i class="far fa-trash-alt"></i> 削除</button> -->  //コメントアウト
           //ここから追記
         <button type="button" class="btn btn-outline-primary" data-toggle="modal" data-target="#exampleModal{{$product->id}}"><i class="far fa-trash-alt"></i> 削除</button>
         
         <!-- Modal -->
         <div class="modal fade" id="exampleModal{{$product->id}}" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
           <div class="modal-dialog" role="document">
             <form action="{{ route('product.destroy', [ 'product' => $product->id ]) }}" method="POST">
               @csrf
               @method('DELETE')
               <div class="modal-content">
                 <div class="modal-header">
                   <h5 class="modal-title" id="exampleModalLabel">カテゴリー削除</h5>
                   <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                     <span aria-hidden="true">&times;</span>
                   </button>
                 </div>
                 <div class="modal-body">
                   本当に削除しますか?
                 </div>
                 <div class="modal-footer">
                   <button type="button" class="btn btn-secondary" data-dismiss="modal">キャンセル</button>
                   <button type="submit" class="btn btn-primary">削除</button>
                 </div>
               </div>
             </form>
           </div>
         </div>
         //ここまで追記
       </td>
     </tr>
     @endforeach
     @else
     <tr>
       <td colspan="8">追加された商品情報はありません。</td>
     </tr>
     @endif
       
   </tbody>
 </table>
 
  <div class="d-flex">
   <div class="mx-auto">
    {{$products->links("pagination::bootstrap-4")}}
   </div>
  </div>
 
</div>
@endsection

手順2:次に、編集ボタンを押したときの編集画面のViewを作成していきます。productフォルダにedit.blade.phpという名前でファイルを作ります。こちらもカテゴリーの時と同様、追加フォーム画面とほぼ一緒です。コードは以下の通りです。

restaurant_menu\resources\views\product\edit.blade.php

@extends('layouts.app')

@section('content')
<div class="container mt-3" style="max-width: 720px;">
<div class="text-right">
  <a href="{{ url('/product/') }}">< 戻る</a>
</div>

<form action="{{ route('product.update', [$product->id]) }}" method="POST" enctype="multipart/form-data">
  @csrf
  @method('PUT')
  <div class="form-group" style="margin-top: 30px; margin-bottom: 30px">
    <label for="name" class="font-weight-bold">商品名</label>
    <input type="text" class="form-control @error('name') is-invalid @enderror" id="name" name="name" value="{{ $product->name }}"/>
    @error('name')
    <p class="text-danger">{{ $message }}</p>
    @enderror
  </div>
  <div class="form-group" style="margin-bottom: 30px">
    <label for="textarea" class="font-weight-bold">詳細</label>
    <textarea class="form-control @error('description') is-invalid @enderror" id="textarea" rows="5" name="description">{{ $product->description }}</textarea>
    @error('description')
    <p class="text-danger">{{ $message }}</p>
    @enderror
  </div>
  <div class="form-group" style="margin-bottom: 30px">
    <label for="price" class="font-weight-bold">値段</label>
    <input type="text" class="form-control @error('price') is-invalid @enderror" id="price" name="price" value="{{ $product->price }}"/>
    <small class="form-text text-muted">半角数字で入力してください。</small>
    @error('price')
    <p class="text-danger">{{ $message }}</p>
    @enderror
  </div>
  <div class="form-group" style="margin-bottom: 30px">
    <label for="category" class="font-weight-bold">カテゴリー</label>
    <select class="form-control @error('category') is-invalid @enderror" id="category" name="category">
      <option value="" disabled selected style="display: none;">カテゴリーを選択してください。</option>
      @foreach(App\Category::all() as $category)
      <option value="{{ $category->id }}" @if($category->id == $product->category_id) selected @endif>{{ $category->name }}</option>
      @endforeach
    </select>
    @error('category')
    <p class="text-danger">{{ $message }}</p>
    @enderror
    <div class="text-right mt-2">
      <a type="button" href="{{ url('/category/create/') }}" class="btn btn-outline-secondary py-1" role="button">新規追加</a>
      <a type="button" href="{{ url('/category/') }}" class="btn btn-outline-secondary py-1" role="button">編集</a>
    </div>
  </div>
  <div class="form-group" style="margin-bottom: 30px">
    <label for="image" class="font-weight-bold">画像アップロード</label>
    <input type="file" class="form-control-file @error('image') is-invalid @enderror" id="image" name="image" />
    @error('image')
    <p class="text-danger">{{ $message }}</p>
    @enderror
  </div>
  
  <button type="submit" class="btn btn-primary my-3">送信</button>
  
</form>
</div>
@endsection

上記のコードで特筆すべきことは、商品名等の各項目のinputタグ等にvalue値を書いていますが、textareaタグにはそもそもvalue属性はないので、タグの中にそのまま書いています。

手順3: コントローラーの設定を行います。ProductController.phpの edit()メソッド、update()メソッド、そしてdestroy()メソッドを以下のように設定します。カテゴリーでやった時とほぼ同様なので、コードの説明は省きます。

restaurant_menu\app\Http\Controllers\ProductController.php

    public function edit($id)
   {
       $product = Product::find($id);
       return view('product.edit', ['product' => $product]);
   }
    public function update(Request $request, $id)
   {
       request()->validate(
           ['name' => 'required',
            'description' => 'required',
            'price' => 'required|integer',
            'category' => 'required',
            'image' => 'mimes:jpeg,png,jpg,gif,svg'],
           ['name.required' => '商品名を入力してください。',
            'description.required' => '詳細を入力してください。',
            'price.required' => '値段を入力してください。',
            'category.required' => 'カテゴリーを入力してください。']
       );
       $product = Product::find($id);
       $name = $product->image;
       if( $request->hasFile('image')) {
           $image = $request->file('image');
           $name = time().'.'.$image->getClientOriginalExtension();
           $destinationPath = public_path('/images');
           $image->move($destinationPath,$name);
       }
       $product->update([
           'name'=>request('name'),
           'description'=>request('description'),
           'price'=>request('price'),
           'category_id'=>request('category'),
           'image'=>$name
       ]);
       return redirect()->route('product.index')->with('message','商品情報が更新されました。');
   }
update() メソッドに関してですが編集ページなので画像はアップロードしないで元の画像を使うときもあるかと思いますなのでバリデーションのrequiredは消します

    public function destroy($id)
   {
       $product = Product::find($id);
       $product->delete();
       return redirect('/product')->with('message', '商品情報が削除されました。');
   }

手順4: ブラウザで確認してみます。まずは、『 http://127.0.0.1:8000/product/ 』にアクセスして、登録したメニューのどれでもいいのですが、編集ボタンを押します。すると、以下画像のように編集前のデータが入った状態でページが表示されます。

image.png

編集して送信ボタンを押すと、ダッシュボードにリダイレクトされてフラッシュメッセージが表示されます。

image.png

削除ボタンを押すと、以下画像のようにモーダル表示されて、データを削除することができます。

image.png

#20:お客さん用画面を作成する

最後にお客さん用の画面を作成します。

手順1: まずはルートの設定をします。これは、リソースルーティングでは設定されていないページになるので、手動で設定する必要があります。C:\xampp\htdocs\restaurant_menu\routes\web.phpで、以下のように追記します。

restaurant_menu\routes\web.php
Route::get('/', 'ProductController@productTop');

手順2: 次に、コントローラーに設定します。ProductController.phpに、productTop()というメソッドを以下のように追記します。

restaurant_menu\app\Http\Controllers\ProductController.php
    public function productTop() {
       $categories = Category::latest()->get();
       return view('product.top', ['categories' => $categories]);
   }

手順3: Categoryモデルを使うので、ProductController.phpの上部に『use App\Category;』の追記を忘れないようにします。

restaurant_menu\app\Http\Controllers\ProductController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Product;
use App\Category;  //追記

以下省略


手順4: 次にViewの設定をします。C:\xampp\htdocs\restaurant_menu\resources\views\productに、top.blade.phpという名前でファイルを作ります。コードは以下の通りです。

restaurant_menu\resources\views\product\top.blade.php

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="{{ asset('js/app.js') }}" defer></script>
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
@foreach($categories as $category)
<div class="container mt-5">

  <h3 class="text-danger font-weight-bold">{{$category->name}}</h3>
  <hr class="bg-danger" />
  <div class="row row-cols-3 mb-5">
  @foreach(App\Product::where('category_id',$category->id)->get() as $product)
    <div class="card">
      <img style="width: 100%; height: 15vw; object-fit: cover;" src="{{ asset('images') }}/{{ $product->image }}" class="card-img-top" />
      <div class="card-body">
        <h5 class="card-title font-weight-bold" style="display:inline;">{{ $product->name }}</h5><span class="card-title pr-1" style="float: right">{{ $product->price }} 円 + 税</span>
        <hr />
        <p class="card-text">{{ $product->description }}</p>
      </div>
    </div>
    @endforeach
  </div>
  
</div>
@endforeach
</body>
</html>

ここで特筆すべきは、『 @foreach(App\Product::where('category_id',$category->id)->get() as $product) 』の部分です。
まず、コントローラーからproductモデルの値が渡ってきている訳ではないので、ここでProductモデルにアクセスしています。また、カテゴリー別に分けてメニューを表示する為に、クエリビルダのwhere節を使っています。カテゴリーをチェックして、同じカテゴリーのもののみ、その後の項目を表示させる為です。
Laravelのクエリビルダについて、わかりやすくまとめてらっしゃるサイトがあったので、ご紹介します。↓↓こちらのサイトです。

Laravel クエリビルダ記法まとめ

手順5: メニュー追加ページで情報入力した後に、『 http://127.0.0.1:8000/ 』にアクセスすると、以下のような感じで表示されるはずです。

#21:認証機能をかける

現段階では、ダッシュボード等の管理画面に、ログインしていなくてもアクセスできてしまうので、認証機能をかけます。
Bootstrapをインストールした時に、一緒にLaravelのauthentication機能もインストールしているので、今回やるべきことは簡単です!
ProductController.phpで、categoryとproductのリソースルーティング部分に、以下のように『->middleware('auth')』を追記するだけです!

restaurant_menu\app\Http\Controllers\ProductController.php
Route::resource('category','CategoryController')->middleware('auth');
Route::resource('product','ProductController')->middleware('auth');

これで『 http://127.0.0.1:8000/product/create 』や、『 http://127.0.0.1:8000/category 』にアクセスしようとすると、ログインページにリダイレクトされます!

以上で完成です。お疲れさまでした!

3
13
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
3
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?