【Laravel】プロジェクトの作成からブラウザ経由のDB操作までを実際にやってみる（アプリ作成の手順詳細）

Laravel学習のため、プロジェクト作成しブラウザからのDB操作（新規追加、編集、削除）を自分でやってみる。


##目次
1. [composerのインストール](#1-composerのインストール)
2. [laravelのインストール](#2-laravelのインストール)
3. [laravelサーバーの起動](#3-laravelサーバーの起動)
4. [DBにsqliteを設定](#dbにsqliteを設定)
5. [モデルとマイグレーションの作成](#モデルとマイグレーションの作成)
6. [マイグレーションでDBのテーブルにカラムを追加](#マイグレーションでdbのテーブルにカラムを追加する)
7. [コントローラの作成](#コントローラの作成)
8. [ルーティングの設定](#ルーティングの設定)
9. [seederを使ってテストデータをDBに設定](#seederを使ってテストデータをdbに設定する)
10. [一覧画面の作成（ブラウザにDBの全データを表示）](#一覧画面の作成ブラウザにdbの全データを表示)
11. [詳細ページの作成](#詳細ページの作成)
12. [編集機能の作成](#編集機能の作成)
13. [削除機能の作成](#削除機能の作成)




<br>
##1. composerのインストール
[composerのインストール方法](https://qiita.com/yuta-> 38/items/ca900b5c84e04ff71832)を参照。


<br>
##2. laravelのインストール

```
$ composer create-project laravel/laravel [プロジェクト名] --prefer-dist
```

指定したプロジェクト名でフォルダが作成される。

・`--prefer-dist`オプション
高速ダウンロード用。

このオプションをつけると圧縮されていないファイルを優先してダウンロードする。また、すべてのVSC（バージョン管理ソフト）を取得しないので軽量となる。



<br>
##3. laravelサーバーの起動

`php artisan serve`

```
$ php artisan serve
Starting Laravel development server: http://127.0.0.1:8000
```

表示されたURLにアクセス
`http://127.0.0.1:8000`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/a6d13939-e12e-228f-b649-2536efe33f78.png)

上記画面が表示されればOK。
サーバーの停止は`ctrl + c`。


<br>
##DBにsqliteを設定
使用が簡単かつmacにデフォルトでインストールされているsqliteをDBに使用する。

laravelインストール時のデフォルトのDBはMySQLのため、環境ファイルの設定を一部変更する。

###.envファイルの変更
ルートディレクトリ直下にある.envファイルを開き、以下コードを変更する。

```:.env（変更前）
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=
```

↓　以下に変更

```:.env（変更後）
DB_CONNECTION=sqlite
```

###DB用のファイルを作成する

database/database.sqliteを作成する。

プロジェクトのルートディレクトリで以下を実行。

```
$ touch database/database.sqlite
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/d5acb45e-c7b7-649e-0b6b-9a23774cf9a1.png)


##モデルとマイグレーションの作成
DBのテーブルに対応するモデルと、テーブルにカラムを追加したり型指定するマイグレーションファイルを作成する。

（参考）[モデルとは？マイグレーションとの違い](https://qiita.com/yuta-38/items/f965a845b4a829ff301d)

```
$ php artisan make:model [モデル名] -m
```

- artisanコマンドの`make:model`でモデルを作成する。
- `-m`オプションでマイグレーションファイルも同時生成。（`--migrate`の省略形）
- モデル名は冒頭大文字の単数形。単語をつなげる場合はキャメルケースにする（例：MakerCode）

<br>
**▼モデルの命名規則**
DBのテーブル名は複数形とし、Model名はその単数形とする。

・DBのテーブル名: 複数形のスネークケース
・対応するModel名： 冒頭大文字のキャメルケース

＜例１＞
・DBのテーブル名: articles
・対応するModel名： Article

＜例２＞
・DBのテーブル名: maker_codes
・対応するModel名： MakerCode



<br>
**▼（例）Productモデルを作成**

```:
$ php artisan make:model Product -m

Model created successfully.
Created Migration: 2021_01_15_022833_create_products_table
```

２つのファイルが生成される。

・モデル
app > Models > モデル名.php
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/6a991d73-a0e2-f057-9c65-da5ad0a747e1.png)

・マイグレーション
database > migrations > タイムスタンプ_create_テーブル名_table.php
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/184c9bc0-085c-403c-71c2-f08a4613f1e1.png)


###(補足)モデルとマイグレーションファイルを別々に作る場合
マイグレーションファイルは作成用のartisanコードがあるので、それを使って生成できる。

```:
$ php artisan make:model Product
$ php artisan make:migration create_products_table
```



<br>
##マイグレーションでDBのテーブルにカラムを追加する
###マイグレーションファイルの編集
作成したいカラムを追記する。デフォルトでidとtimestampsのカラムがある。

up関数の中に追記する。

・`$table->型('カラム名')`

```php:2021_01_15_022833_create_products_table.php
    public function up()
    {
        Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->string('product_name')->unique(); //追加
            $table->integer('price'); //追加
            $table->boolean('is_stocked')->nullable(); //追加
            $table->timestamps();
        });
    }
```

必要に応じて、`unique()`や`nullable()`などの修飾子をつけることができる。

<br>
###カラムタイプと修飾子
他にもたくさんのカラムタイプが用意されている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/104ead98-9054-2d4c-a9c7-4314d713144b.png)
続きは以下リンク参照。

[Laravel公式 使用できるカラムタイプ一覧](https://readouble.com/laravel/7.x/ja/migrations.html)

**▼インデックス修飾子**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/c0bbd4d2-adba-9bc7-9cda-93276df42408.png)


**▼カラム修飾子**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/6811f6a7-7f1f-56e9-95a8-bf292296e696.png)


<br>
###マイグレーションの実行
作成したマイグレーションファイルの内容をテーブルに反映する。

```
php artisan migrate
```

<br>
**▼migrationの実行例**

```
$ php artisan migrate

Migration table created successfully.
Migrating: 2021_01_15_022833_create_products_table
Migrated:  2021_01_15_022833_create_products_table (0.90ms)
```

テーブルの作成に成功。


>エラー発生時
 Illuminate\Database\QueryException 
 Database (/laravel/test-pj/database/database.sqlite) does not exist. (SQL: PRAGMA foreign_keys = ON;)

>対策：$ touch database/database.sqlite　を実行


<br>
##コントローラの作成
DBにデータを追加・読み込み・編集・削除(CRUD操作)するためにコントローラを作成する。

作成時にオプションで`-r`をつけると、CRUD操作のためのアクションが記載されたコントローラを作成することができる。

```
$ php artisan make:controller [コントローラ名] -r
```

- `-r`は`--resource`の省略形
- コントローラ名は冒頭大文字で、後ろに`Controller`をつける（例：ProductController）
- app > Http > Controllerの中にファイルが生成される

--resourceオプションで作成したコントローラを特別にリソースコントローラと呼ぶ。

<br>
**▼ProcuctControllerの作成例**
create, show, editなどCRUD操作に必要なアクションの外枠を既に記述してくれている。
あとは実際の処理を各アクションの中に記述するだけの状態。

```:
$ php artisan make:controller ProductController -r
Controller created successfully.
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/0c8369df-d9d1-3b3f-1960-bb652205badf.png)


**▼ファイルの中身**


```php:ProductController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class ProductController 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()
    {
        //
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        //
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        //
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit($id)
    {
        //
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        //
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        //
    }
}
```


<br>
##ルーティングの設定
先ほど作成したコントローラの各アクションをルートに登録する。

ルーティングは、app > routes > web.php　に記述する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/6fed1a55-e92f-1320-57c4-37c808cda0c8.png)

リソースコントローラのルート登録は、Routeファサードのresourceメソッドで簡単にできる。

```:web.php
Route::resource('$URI', 'コントローラ名');
```

- $URIは複数形のパスを指定する。

<br>
**▼実例**

```php:web.php
Route::resource('products', 'ProductController');
```

**▼ルートの登録状況を確認**

・`$ php artisan route:list`

```
$ php artisan route:list
+--------+-----------+-------------------------+------------------+------------------------------------------------+------------+
| Domain | Method    | URI                     | Name             | Action                                         | Middleware |
+--------+-----------+-------------------------+------------------+------------------------------------------------+------------+
|        | GET|HEAD  | api/user                |                  | Closure                                        | api        |
|        | GET|HEAD  | products                | products.index   | App\Http\Controllers\ProductController@index   | web        |
|        | POST      | products                | products.store   | App\Http\Controllers\ProductController@store   | web        |
|        | GET|HEAD  | products/create         | products.create  | App\Http\Controllers\ProductController@create  | web        |
|        | GET|HEAD  | products/{product}      | products.show    | App\Http\Controllers\ProductController@show    | web        |
|        | PUT|PATCH | products/{product}      | products.update  | App\Http\Controllers\ProductController@update  | web        |
|        | DELETE    | products/{product}      | products.destroy | App\Http\Controllers\ProductController@destroy | web        |
|        | GET|HEAD  | products/{product}/edit | products.edit    | App\Http\Controllers\ProductController@edit    | web        |
+--------+-----------+-------------------------+------------------+------------------------------------------------+------------+
```

リソースコントローラの各アクションがルート登録されている。

Laravel8.xでエラーが出た場合は、[RouteServiceProvider.phpのコメントアウトを解除](https://qiita.com/yuta-38/items/2ddb0748cdca1867440c#%E3%82%B3%E3%83%B3%E3%83%88%E3%83%AD%E3%83%BC%E3%83%A9%E3%83%BC%E6%8C%87%E5%AE%9A%E6%99%82%E3%81%AE%E6%B3%A8%E6%84%8F%E7%82%B9)する


###（補足）sqliteにテーブルが作成されているか確認する
sqliteにアクセスするためには、`sqlite3`コマンドで、database.sqliteを開く。

ルートディレクトリで以下を実行。
・`$ sqlite3 database/database.sqlite`


```
$ sqlite3 database/database.sqlite
SQLite version 3.28.0 2019-04-15 14:49:49
Enter ".help" for usage hints.

#テーブル一覧を表示
sqlite> .table
failed_jobs      password_resets  users          
migrations       products  

#productsテーブルの構造（schema）を表示
sqlite> .schema products
CREATE TABLE IF NOT EXISTS "products" (
  "id" integer not null primary key autoincrement,
  "product_name" varchar not null,
  "price" integer not null,
  "is_stocked" tinyint(1) not null,
  "created_at" datetime null,
  "updated_at" datetime null
);
```

productsテーブルがあり、指定したカラムを持っていることがわかる。

**▼sqlite3の主なコマンド**

| コマンド | 内容 |
|:--|:--|
| .tables | テーブル一覧を表示  |
| .schema [テーブル名] | 指定したテーブルのスキーマ定義を表示  |
| .exit  | sqliteを終了  |
| .help  | ヘルプを表示  |



<br>
##seederを使ってテストデータをDBに設定する
seeder(シーダ)とは、DBを手軽にテストするために、テストデータをDBに設定する機能。

```
$ php artisan make:seeder シーダ名
```

- シーダ名は、[テーブル名]TableSeeder とするのが一般的
- 保存先： app > database > seeders 

なかなか便利な機能で、deleteやedit機能などでデータをあれこれ変更した後に、seederをDBに送ればまた初期のデータを復活させることができる。


<br>
**▼実例**

```
$ php artisan make:seeder ProductTableSeeder
Seeder created successfully.
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/0a8cbfb4-caa1-b224-a732-1963a8f2a49f.png)

```php:ProductTableSeeder.php
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

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

run関数を持ったクラスが生成される。


###seederの編集
seederファイルのrun関数の中に処理を記述する。
今回はクエリビルダで記述する。冒頭でDBファサードのuse宣言をが必要。


```php:ProductTableSeeder.php
<?php

namespace Database\Seeders;

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

class ProductTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        //以下追加
        DB::table('products')->insert([
            [
                'product_name' => 'iPhone6s',
                'price' => 62000,
                'is_stocked' => false
            ],
            [
                'product_name' => 'iPhone8',
                'price' => 84000, 
                'is_stocked' => true        
            ],
            [
                'product_name' => 'iPhone12',
                'price' => 120000,
                'is_stocked' => true               
            ]           
        ]);
    }
}

```

今回はクエリビルダで記述しているが、Eroquentでも問題ない。

(参考）[Eroquetとクエリビルダとは？](https://qiita.com/yuta-38/items/f965a845b4a829ff301d#eloquent%E3%81%A8%E3%81%AF)


<br>
###seederをDBに反映する
seederをDBに反映する際は、基本的に`DatabaseSeeder.php`を使う。
そのため、作成したクラスを`DatabaseSeeder.php`の中で呼び出す。


```php:DatabaseSeeder.php
public function run()
    {
        $this->call(ProductTableSeeder::class);
    }
```

DatabaseSeeder.phpをメインとして使うとseederファイルを複数に分割している場合も容易にDBに反映できる。


```
#Composerのオートローダを再生成する
$ composer dump-autoload

#DatabaseSeeder.phpを実行する
$ php artisan db:seed
```


<br>
**▼実際の処理例**

```
$ composer dump-autoload
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover --ansi
Discovered Package: facade/ignition
Discovered Package: fideloper/proxy
Discovered Package: fruitcake/laravel-cors
Discovered Package: laravel/sail
Discovered Package: laravel/tinker
Discovered Package: nesbot/carbon
Discovered Package: nunomaduro/collision
Package manifest generated successfully.
Generated optimized autoload files containing 4618 classes

$ php artisan db:seed
Seeding: Database\Seeders\ProductTableSeeder
Seeded:  Database\Seeders\ProductTableSeeder (9.16ms)
Database seeding completed successfully.
```

以上でseederで作成したデータをDBに反映完了。


<br>
**▼（補足）別のseederファイルを直接DBに反映する**
DatabaseSeeder.phpではなく、別途作成したseederファイルのみ読み込ませたい場合は、`--class`オプションでseederを指定する。

```
php artisan db:seed --class=ProductTableSeeder
```


<br>
###DBのデータを確認する
####・DBのデータを抽出してみる
artisanコマンドのtinkerを使って、Laravelの対話モードに入り、Eloquentのallメソッドで指定したモデルのすべてのデータを取得する。

```
$ php artisan tinker
>>>[モデルの完全な名前空間]::all();
```

対話モードは`exit`で抜ける。

>tinkerの元の意味は小さな改善をすること。phpの対話モードでコマンドを一つづつ実施することが由来している？


<br>
**▼実例**

```php:ターミナル
$ php artisan tinker
Psy Shell v0.10.5 (PHP 7.3.11 — cli) by Justin Hileman
>>>
>>> App\Models\Product::all();
=> Illuminate\Database\Eloquent\Collection {#4309
     all: [
       App\Models\Product {#4308
         id: "1",
         product_name: "iPhone6s",
         price: "62000",
         is_stocked: "0",
         created_at: null,
         updated_at: null,
       },
       App\Models\Product {#4307
         id: "2",
         product_name: "iPhone8",
         price: "84000",
         is_stocked: "1",
         created_at: null,
         updated_at: null,
       },
       App\Models\Product {#4298
         id: "3",
         product_name: "iPhone12",
         price: "120000",
         is_stocked: "1",
         created_at: null,
         updated_at: null,
       },
     ],
   }
>>>
>>> exit
Exit:  Goodbye
```

指定したデータが取得できている。


<br>
####・sqliteで確認する

ルートディレクトリで以下を実行。
・`$ sqlite3 database/database.sqlite`

テーブルの中のデータをすべて表示（SQL）
・`select * form [テーブル名]`

```
$ sqlite3 database/database.sqlite

sqlite> select * from products;
1|iPhone6s|62000|0||
2|iPhone8|84000|1||
3|iPhone12|120000|1||

sqlite> .exit
```


<br>
##一覧画面の作成（ブラウザにDBの全データを表示）
取得した全データを表示する一覧画面を作成する。

###コントローラのindexアクションを編集する
以前に作成したコントローラのindexアクションの戻り値にDBのすべてのデータを指定する。

Eloquentを使うため冒頭に使用するモデルのクラスをuse宣言する。

```php:ProductController.php
<?php
namespace App\Http\Controllers;

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

class ProductController extends Controller
{
    public function index()
    {
        $productsInfo = Product::all(); //追記
        return $productsInfo; //追記
    }

//以下略
```

(参考）[Eroquetとは？](https://qiita.com/yuta-38/items/f965a845b4a829ff301d#eloquent%E3%81%A8%E3%81%AF)


###サーバーを起動する
`$ php artisan serve`を実行する。

```
$ php artisan serve

Starting Laravel development server: http://127.0.0.1:8000
```

<br>
###ブラウザで表示する
URIにindexに対応するルートを入力する。（ここでは /products）

>ルートは、`$php artisan route:list`コマンドで確認できる。

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/812e539d-2150-a9e6-f6ba-5b2cb54ca5d7.png)|
|:--|

取得したデータの表示に成功。取得データをそのまま返しているのでデータはJSON形式で表示される。


<br>
###ビューの作成
取得データを見やすく表示するためにビューを作成する。
ビューは resources > views 配下に、拡張子`.blade.php`
で保存する。

今回は、productsディレクトリを作成し、外側となるbase.blade.phpとメイン部分となるindex.phpを用意する。

**▼ディレクトリ構造**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/60d39438-7e97-50ae-05db-454fa0adb951.png)

####レイアウトの作成
layoutsディレクトリの中に`base.blade.php`を作成する。

```php:base.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>@yield('title')</title>
</head>
<body>
  <h1>@yield('title')</h1>
  
  @yield('content')
</body>
</html>
```


###メインビューの作成
index.blade.phpを作成する。

```php:index.blade.php
@extends('.products.layouts.base')

@section('title', '製品一覧')

@section('newLink')
  <a href="/products/create">新規作成</a>
  <hr>
@stop

@section('content')
  @foreach ($products as $product)
    <div>
      <h2>{{$product->product_name}}</h2>
      <p>・製品価格：{{number_format($product->price)}}円</p>
      <p>・在庫：
        @if ($product->is_stocked) あり 
        @else なし
        @endif
      </p>
      <a href="/products/{{$product->id}}/edit">編集する</a> | 
      <a href="/products/{{$product->id}}">商品詳細</a>
      <hr>
    </div>
  @endforeach
@stop
```

後々作成する、新規作成、編集、商品詳細ページへのリンクも設置してある。

（参考）[section, yield, extendsについて](https://qiita.com/yuta-38/items/3435e92c76ef0e823a8d)


<br>
###コントローラでビューを指定
ProductControllerのindexアクションで、作成したビュー(index.blade.php)を開くように処理を変更する。

```php:ProductController.php
<?php
namespace App\Http\Controllers;

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

class ProductController extends Controller
{
    public function index()
    {
        $productsInfo = Product::all();
        return view('products.index', ['products' => $productsInfo]);   //変更
    }

//以下略
```

- `view`をviewヘルパーと呼び、指定したビューを開く。その際、第２引数でデータを渡せる。
- resource > products > index.blade.phpは、products.indexとして指定。
- 第２引数の書き方：　['変数名' => 渡すデータ]


<br>
**▼ブラウザの表示**
保存して、サーバー起動`php artisan serve`
し、URIを入力する。

http://127.0.0.1:8000/products

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/ddd48084-c53f-4b57-f2f7-be3cb02f2496.png)|
|:--|

DBから取得したデータが成形して表示されている。



<br>
##詳細ページの作成
商品詳細ページを作成する。

###コントローラの編集
ProductControllerのshowアクションを編集する。

```php:ProductController.php
    public function show($id)
    {
        //受け取ったidの製品情報を格納
        $product = Product::find($id);
        return view('products.show', ['product' => $product]);
    }
```

URLのproductsの後ろのデータが`$id`として渡される。
findメソッドを使って、指定したidのデータを変数$productに格納する。

<br>
###ビューの編集（show.blade.php）
resources > products 配下にshow.blade.phpを作成する。

```php:show.blade.php
@extends('.products.layouts.base')

@section('title', '製品詳細')

@section('content')
  <div>
    <h2>{{$product->product_name}}</h2>
    <p>・製品価格：{{number_format($product->price)}}円</p>
    <p>・在庫：
      @if ($product->is_stocked) あり 
      @else なし
      @endif
    </p>
    <a href="/products/{{$product->id}}/edit">編集する</a> |  
    <a href="/products">製品一覧に戻る</a> |
    @component('components.delete-txt', ['product'=>$product])
    @endcomponent
    <hr>
  </div>
@stop
```

あとは、http://127.0.0.1:8000/products/1 のように、末尾でidを指定すると製品詳細ページが開く。

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/c93eea3f-3dae-6c05-19e6-65979074fd99.png)|
|:--|

編集と削除リンク先は後ほど作成。


<br>
**▼（補足）削除機能のボタン**
削除機能はcomponentとして読み込んでいる。詳細の機能は最後の削除機能で作成する。

```php:
    @component('components.delete-txt', ['product'=>$product])
    @endcomponent
```



<br>
##新規追加機能の作成
###ルーティングの確認
DBに保存するには、createアクションを実行し、storeアクションを行う。

ルートを確認すると、/products/createでcreateアクションを実行、/productsにPOSTでアクセスするとstoreアクション実行となる。


```
$ php artisan route:list
+--------+-----------+-------------------------+------------------+------------------------------------------------+------------+
| Domain | Method    | URI                     | Name             | Action                                         | Middleware |
+--------+-----------+-------------------------+------------------+------------------------------------------------+------------+
|        | POST      | products                | products.store   | App\Http\Controllers\ProductController@store   | web        |
|        | GET|HEAD  | products/create         | products.create  | App\Http\Controllers\ProductController@create  | web        |
+--------+-----------+-------------------------+------------------+------------------------------------------------+------------+
```

<br>
###createビューの作成
新規作成ページとして、create.blade.phpを作成する。

```php:create.blade.php
@extends('.products.layouts.base')

@section('title', '製品の新規追加')

@section('content')
  <form action="/products" method="post">
    @csrf
    <div>
      <label for="product_name">製品名：</label>
      <input type="text" name="product_name" required autoforcous>
    </div>
    <div>
      <label for="price">製品価格：
      </label>
      <input type="text" name="price" required>
    </div>
    <div>
      <label for="stock">在庫有無：</label>
      <select name="stock">
        <option value="1">あり</option>
        <option value="0">なし</option>
      </select>
    </div>
    <div>
    <div>
      <input type="submit" value="送信">
    </div>
  </form>
@stop
```


・`@csrf`
クロスサイトリクエストフォージェリ対策として**必須**。

（参考）[Laravel公式 CSRF保護](https://readouble.com/laravel/7.x/ja/csrf.html)

`@csrf`の記述がないと、submit後に419のエラーページに飛ばされる。（419はLaravel独自のエラーページ）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/dcfffceb-a31b-1a8e-4c76-66cefe1ca0af.png)


<br>
###コントローラの編集
新規追加のためには、createアクションとstoreアクションの２つに処理を追加する。

####createアクション
createアクションでは、上記で作成したビューを表示させるのみ。


```php:ProductController.php
    public function create()
    {
        return view('products.create');
    }
```


###storeアクション
storeアクションでは、submitで送られてきたデータを受け取り、DBに保存する処理を記述する。


```php:ProductController.php
    public function store(Request $request)
    {
        $product = new Product;
        
        $product->product_name = $request->product_name;
        $product->price = $request->price;
        $product->is_stocked = $request->stock;

        $product->save();

        return redirect('products/'.$product->id);
    }
```

・`$product = new Product;`
Productモデルからインスタンスを作成し、変数に代入。
この変数に各データを保存していく。

・`$request->[name属性]`
submitで送られてきたデータは`$request`に保存されている。
name属性を指定してデータを取得する。

・`$product->save();`
すべてのデータを格納したら、saveメソッドでDBに保存する。

・`return redirect('products/'.$product->id);`
新たに作成した製品詳細ページにリダイレクトする。


<br>
####ブラウザで開く
ブラウザでcreateページを開く。
http://127.0.0.1:8000/products/create

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/92bca9c1-568f-23de-abe3-cb54b666e700.png)|
|:--|

↓ 送信

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/7033b10b-ca1b-5611-8d55-3edb19985b8a.png)|
|:--|

新規保存し、新しい製品詳細ページの表示に成功。



<br>
##編集機能の作成
###ルーティングの確認
既存商品の情報を編集するには、editアクションとupdateアクションを使う。

```
$ php artisan route:list
+--------+-----------+-------------------------+------------------+------------------------------------------------+------------+
| Domain | Method    | URI                     | Name             | Action                                         | Middleware |
+--------+-----------+-------------------------+------------------+------------------------------------------------+------------+
|        | PUT|PATCH | products/{product}      | products.update  | App\Http\Controllers\ProductController@update  | web        |
|        | GET|HEAD  | products/{product}/edit | products.edit    | App\Http\Controllers\ProductController@edit    | web        |
+--------+-----------+-------------------------+------------------+------------------------------------------------+------------+
```

`products/{product}/edit`にアクセスして、商品の編集を行い、`products/{product}`にPUT|PATCHでアクセスすればDBの情報を更新できる。


<br>
###編集ページの作成
編集用のビュー、edit.blade.phpを作成する。 
基本的にはcreate.blade.phpと同じ。PUTでアクセスするために擬似メソッドを設置する。

**▼create.blade.phpとの違い**

- formタグのactionでパスが変わる。
- 擬似フォームメソッドの`@method('PUT')`を設置。
- inputタグにvalue属性で既存の設定データを表示。
- 在庫の有無に合わせてselectタグの初期値を設定。
- submitボタンのvalueを更新に変更。

```php:edit.blade.php
@extends('.products.layouts.base')

@section('title', '製品の編集')

@section('content')
<form action="/products/{{$product->id}}" method="post">
    @csrf
    @method('PUT')
    <div>
      <label for="product_name">製品名：</label>
      <input type="text" name="product_name" value="{{$product->product_name}}" required autoforcous>
    </div>
    <div>
      <label for="price">製品価格：
      </label>
      <input type="text" name="price" value="{{$product->price}}" required>
    </div>
    <div>
      <label for="stock">在庫有無：</label>
      <select name="stock">
        @php 
          $selected = "selected";
          if ($product->is_stocked)
            $selected = ""
        @endphp
        <option value="1">あり</option>
        <option value="0" {{$selected}}>なし</option>
      </select>
    </div>
    <div>
      <input type="submit" value="更新">
    </div>
  </form>
@stop
```

<br>
・擬似フォームメソッド
HTMLフォームは`PUT`, `PATCH`, `DELETE`リクエストを作成できないため（対応していない）、`_method`を偽装する必要がある。

・`<input type="hidden" name="_method" value="リクエストメソッド名">`

type="hidden"をつけると画面上に表示していないデータを送信することができる。`_method`というキー名で、指定したリクエストメソッドを値として送信している。


**▼putを使う方法**
送信ボタンの上に以下タグを設置する。

```php:edit.blade.php
<input type="hidden" name="_method" value="put">
<input type="submit" value="更新">
```

これを、laravelの擬似フォームメソッドを使うととても簡単にかける。

```php:
@method('PUT')
```

・（参考）[Laravel公式 擬似フォームメソッド](htps://readouble.com/laravel/5.6/ja/controllers.html)


<br>
>（補足）PUTとPATCHの違い
PUTはすべてのデータをまるごと変更する場合に使い、PATCHは一部のデータを変更する場合に使うらしい。
ここでは、PUTとPATCHどちらを使っても同じ結果になる。




<br>
###コントローラの編集
editアクションとupdateアクションを編集する。

####editアクション
Productモデルの指定したidのデータを取得してビューに渡す。

```php:ProductController.php
    public function edit($id)
    {
        $product = Product::find($id);
        return view('products.edit', ['product'=>$product]);
    }
```


####updateアクション
記述はstoreアクションとほぼ同じ。
最初にProductモデルの新規インスタンスではなく、指定したidのデータを取得する。

受け取ったデータ`$request`を代入し、`save`でDBに保存する。

```php:ProductController.php
    public function update(Request $request, $id)
    {
        $product = Product::find($id);

        $product->product_name = $request->product_name;
        $product->price = $request->price;
        $product->is_stocked = $request->stock;

        $product->save();

        return redirect('products/'.$product->id); 
    }
```



<br>
##削除機能の作成
###ルーティングの確認

```
$ php artisan route:list
+--------+-----------+-------------------------+------------------+------------------------------------------------+------------+
| Domain | Method    | URI                     | Name             | Action                                         | Middleware |
+--------+-----------+-------------------------+------------------+------------------------------------------------+------------+
|        | DELETE    | products/{product}      | products.destroy | App\Http\Controllers\ProductController@destroy | web        |
+--------+-----------+-------------------------+------------------+------------------------------------------------+------------+
```

`products/{product}`にDELETEでアクセスすればデータを削除できる。


###削除コンポーネントを作成する
削除ボタンは編集と詳細ページに設置する。laravelの機能であるコンポーネント化し簡単に呼び出しを行う。

コンポーネントはボタンとテキストの２種類を用意する。

**▼編集**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/ab658873-be1f-afdf-686d-5103469d966a.png)

**▼詳細**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/5f177dcf-970f-b977-9ea3-fb86ec722f3d.png)


####ファイルの作成
resources > views > componentsの配下に、ファイルを２つ作成する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/11ae926d-fb85-345e-f368-3b5f33206d86.png)

**▼削除ボタンコンポーネント**

```php:delete-btn.blade.php
<form action="/products/{{$product->id}}" method="post" id="delete-btn">
      @csrf
      @method('DELETE')
      <input type="submit" name="" value="削除">
</form>
```

・acrtion属性の値に`"/products/{{$product->id}}"`を設定。

formはDELETEメソッドをサポートしていないため、ここではpostを指定。

・`@csrf`
クロスサイトリクエストフォージェリ防止機能を追加（必須！）

・`@method('DELETE')`
擬似フォームメソッドでDELETEを指定。これでdeleteメソッドで通信できるようになる。


**▼削除テキストコンポーネント**

```php:delete-txt.blade.php
<form action='/products/{{$product->id}}' method='POST' id='dlt-txt'>
  @csrf
  @method('DELETE')
  <a onclick='document.getElementById("dlt-txt").submit()'>削除する</a>
</form>
```

inputタグをテキストに変更するため、JavaScriptの`submit`メソッドを使用。

formタグに`id="dlt-btn"`を指定し、sbumitの対象を`document.getElementById(dlt-btn)`とする。

以上でコンポーネントが完成。


<br>
####componentの仕組み
前述のコードで既にcomponentの呼び出し処理は記述してあるが、componentの仕組みについて。

Laravelでは`blade.php`ファイルをコンポーネントとして呼び出すことができる。

`extends`, `yield`, `section`の簡易的な機能のようなもの。

コンポーネントの呼び出しはとても簡単で、呼び出したいところに以下のコードを記述する。

```:
  @component('ビューのパス', ['変数名'=>渡したいデータ])
  @endcomponent
```

ビューのパスは相対ではなく、viewsフォルダからの絶対パスとなる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/3d4a5c43-7f12-b517-7ea5-9d819eb0b4e8.png)

このため、products > edit.blade.php から components >
delete-text.blade.phpを呼び出したい場合は、`components.delete-txt`とする。

第２引数はデータを渡したいときに使う。読み込むのみでデータを渡す必要がない場合は不要。


<br>
###ブラウザの表示（削除機能の確認）

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/f2a29e87-1a7b-35e1-4213-b6a9a50bed5d.png)|
|:--|

↓ 商品詳細（iPhone8）

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/2c765bd3-dee9-3c07-0b65-0b074e7c9bb8.png)|
|:--|

↓ 削除

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/30ace42a-c700-9111-87bf-04ee0106f1d7.png)|
|:--|


削除成功。


<br>
バリデーション、CSSの調整はないが、LaravelのCRUD操作の基本はおさえられた。