【Laravel】商品追加機能の実装について



<h1>はじめに</h1>
はじめまして！
webエンジニア転職を目指して三ヶ月が経ちました。
スクールでは共同開発が始まり、カート機能を実装しております。
そこで、終盤において商品追加機能を担当し、
復習&アウトプットも兼ねてご覧の皆さんに共有したいと思い、記事投稿に至ります！
至らぬ点が多々あるとは思いますが、何かの実装の参考になれば幸いです。😆

<h1>目次</h1>
1,　完成品
2,　環境
3,　使用するソースコード
　　3-①　Route
　　3-②　Controller
　　3-③　View
　　3-④　Migration
　　3-⑤　Model
4,　各実装の説明 ①〜⑤
参考にしたサイト
まとめ

<h1>1,　完成品</h1>
① 完成画面
[![Image from Gyazo](https://i.gyazo.com/8684883ea26c0c50768a4702856f27bf.gif)](https://gyazo.com/8684883ea26c0c50768a4702856f27bf)
② DB確認（Tinker）
![スクリーンショット 2021-04-28 17.13.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1178769/1dea4916-613d-150a-1280-f3f8c2999bf0.png)


<h1>2,　環境</h1>

macOS Big Sur 11.2.2　
Apache 2.4.46　
MySQL 5.7　
PHP 7.2.34
Laravel 5.8
 　
実は今回、共同開発において
VirtualBoxを用いたVagrantによる仮想開発環境で統一したかったのですが、
投稿主のmacが最近出たばかりのM1チップが搭載されております。
そして、悲しいことに当時(2月中旬)はVirtualBoxなどバージョンが追いついておらず、
仕方なくAWS(Could9)での環境設定を余儀なくされました。。



<h1>3,　使用するソースコード</h1>
<h3>3 - ① Route</h3>

```php:web.php
<?php

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

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

//商品追加
Route::get('newAdd', 'NewAddController@index')->name('newAdd.index');
Route::post('newProduct', 'NewAddController@store')->name('newProduct.post');

```
<h3>3 - ② Controller</h3>

```php:NewAddController
<?php

namespace App\Http\Controllers;

use App\User;
use App\Models\Product;
use App\Models\Category;
use App\Models\ProductStatus;
use App\Models\Sale;
use Illuminate\Http\Request;
use App\Http\Requests\ProductRequests;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Controller;

class NewAddController extends Controller
{
    public function index(Request $request)
    {
        return view('products.newAdd', [
            'user' => Auth::user(),
            'categories' => Category::all(),
            'products_status'  => ProductStatus::all(),
            'sales' => Sale::all(),
        ]);
    }

    public function store(ProductRequests $request)
    {
        $newProduct = new Product;
        $newProduct->product_name = $request->product_name;
        $newProduct->description = $request->description;
        $newProduct->price = $request->price;
        $newProduct->category_id = $request->category_id;
        $newProduct->product_status_id = $request->product_status_id;
        $newProduct->sale_status_id = $request->sale_status_id;
        $newProduct->user_id = Auth::user()->id;
        $newProduct->save();
        
        return view('welcome');
    }
}
```
<h3>3 - ③ View</h3>

```php:newAdd.blade.php
@extends('layouts.app')

@section('content')
    <main>

        <div class="page-header mt-5 text-center">
            <h2>商品追加</h2>
        </div>
        
        <div class="row mt-5 mb-5">
            <div class="col-sm-5 mx-auto">
                <form method="POST" action="{{ route('newProduct.post') }}">
                    @csrf
                    
                    <div class="form-group-sm clearfix">
                        <label for="formGroupExampleInput2" class="mt-3 mb-0">商品名</label>
                        <div class="product-info width-control">
                            <input type="text" name="product_name" class="content-half-width form-control-sm d-inline">
                            <!-- @if ($errors->has('product_name'))
                                <div class="row justify-content-center">
                                    <div class="cal-xs-4">
                                        <span style="color:red">{{ $errors->first('product_name') }}</span>
                                    </div>
                                </div>
                            @endif -->
                        </div>
                    </div>
        
                    <div class="form-group-sm clearfix">
                        <label for="formGroupExampleInput2" class="mt-3 mb-0">商品説明</label>
                        <div class="product-info width-control">
                            <textarea name="description" class="content-width form-control-sm"></textarea>
                            <!-- @if ($errors->has('description'))
                                <div class="row justify-content-center">
                                    <div class="cal-xs-4">
                                        <span style="color:red">{{ $errors->first('description') }}</span>
                                    </div>
                                </div>
                            @endif -->
                        </div>
                    </div>
                    
                    <div class="form-group-sm clearfix">
                        <label for="formGroupExampleInput2" class="mt-3 mb-0">価格</label>
                        <div class="product-info width-control">
                            <input type="text" name="price" class="content-half-width form-control-sm d-inline">
                            <!-- @if ($errors->has('price'))
                                <div class="row justify-content-center">
                                    <div class="cal-xs-4">
                                        <span style="color:red">{{ $errors->first('price') }}</span>
                                    </div>
                                </div>
                            @endif -->
                        </div>
                    </div>
                    
                    <div class="form-group-sm clearfix">
                        <label for="formGroupExampleInput2" class="mt-3 mb-0">商品カテゴリー</label>
                        
                        <div class="product-info width-control">
                            <select class="content-half-width form-control-sm d-inline" id="changeSelect" name="category_id" onchange="entryChange2();">
                                
                                <option value="">未選択</option>
                                @foreach ($categories as $category)
                                    <option value="{{ $category->id }}">{{ $category->category_name }}</option>
                                @endforeach
                                <!-- @if ($errors->has('category_id'))
                                <div class="row justify-content-center">
                                    <div class="cal-xs-4">
                                        <span style="color:red">{{ $errors->first('category_id') }}</span>
                                    </div>
                                </div>
                                @endif -->
                                
                            </select>
                        </dov>
                    </div>
                    
                    <div class="form-group-sm clearfix">
                        <label for="formGroupExampleInput2" class="mt-3 mb-0">商品状態</label>
                        
                        <div class="product-info width-control">
                            <select class="content-half-width form-control-sm d-inline" id="changeSelect" name="product_status_id" onchange="entryChange2();">
                                
                                <option value="">未選択</option>
                                @foreach ($products_status as $product_status)
                                    <option value="{{ $product_status->id }}">{{ $product_status->product_status_name }}</option>
                                @endforeach
                                <!-- @if ($errors->has('product_status_id'))
                                <div class="row justify-content-center">
                                    <div class="cal-xs-4">
                                        <span style="color:red">{{ $errors->first('product_status_id') }}</span>
                                    </div>
                                </div>
                                @endif -->
                                
                            </select>
                        </dov>
                    </div>
                    
                    <div class="form-group-sm clearfix">
                        <label for="formGroupExampleInput2" class="mt-3 mb-0">販売状態</label>
                        
                        <div class="product-info width-control">
                            <select class="content-half-width form-control-sm d-inline" id="changeSelect" name="sale_status_id" onchange="entryChange2();">
                                
                                <option value="">未選択</option>
                                @foreach ($sales as $sale)
                                    <option value="{{ $sale->id }}">{{ $sale->sale_status_name }}</option>
                                @endforeach
                                <!-- @if ($errors->has('sale_status_id'))
                                <div class="row justify-content-center">
                                    <div class="cal-xs-4">
                                        <span style="color:red">{{ $errors->first('sale_status_id') }}</span>
                                    </div>
                                </div>
                                @endif -->
                                
                            </select>
                        </dov>
                    </div>
                    
                    <div class="text-center mt-5">
                        <button type="submit" class="btn btn-primary w-50">追加</button>
                        <p class="mt-5">
                            <a href="#" class="text-primary d-inline">商品一覧</a>
                        </p>
                    </div>
                </form>
            </div>
        </div>

    </main>
@endsection

```
<h3>3 - ④ Migration</h3>

```php:××××_××_××_××××××_m_products_table.php
<?php

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

class CreateMProductsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('m_products', function (Blueprint $table) {
            $table->increments('id');
            $table->string('product_name', 64);
            $table->unsignedInteger('category_id');
            $table->integer('price');
            $table->string('description', 256);
            $table->unsignedInteger('sale_status_id');
            $table->unsignedInteger('product_status_id');
            $table->timestamp('regist_date');
            $table->unsignedInteger('user_id');
            $table->char('delete_flag', 1)->default(0);

            $table->foreign('user_id')->references('id')->on('m_users')->onDelete('cascade');
            $table->foreign('category_id')->references('id')->on('m_categories')->onDelete('cascade');
            $table->foreign('sale_status_id')->references('id')->on('m_sale_statuses')->onDelete('cascade');
            $table->foreign('product_status_id')->references('id')->on('m_product_statuses')->onDelete('cascade');
        });

    }

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

```
<h3>3 - ⑤ Model</h3>

```php:Product.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    protected $table = 'm_products';
    
    public $timestamps = false;
    
    //商品追加のホワイトリスト
    protected $fillable = [
        'product_name',
        'description',
        'price',
    ];

    // Userモデルを親に持つことを明記
    public function user()
    {
        return $this->belongsTo('App\User');
    }

    // Categoryモデルを親に持つことを明記
    public function category()
    {
        return $this->belongsTo('App\Models\Category');
    }

    // Saleモデルを親に持つことを明記
    public function sale()
    {
        return $this->belongsTo('App\Models\Sale');
    }

    // ProductStatusモデルを親に持つことを明記
    public function productstatus()
    {
        return $this->belongsTo('App\Models\ProductStatus');
    }
}

```
他のモデルについては同様の処理（リレーション）のため、今回は割愛させて頂きます。🙇‍♂️
当時の私は、下記サイトを参考に実装していました。
参考 : [リレーションについて](https://qiita.com/vivid_colors/items/766c541038b53a4095c8)

<h1>4,　各実装の説明 ①〜⑤</h1>
<h3>4 - ① Route</h3>

```php:web.php
//商品追加
Route::get('newAdd', 'NewAddController@index')->name('newAdd.index');
Route::post('newProduct', 'NewAddController@store')->name('newProduct.post');
```
こちらでは
Route::getでViewを呼び出しています。（indexメソッド）
Route::postでDBへの登録をしています。(storeメソッド)
それぞれメソッド、命名など異なりますので、読み間違え等に気をつけましょう。

<h3>4 - ② Controller</h3>

```php:NewAddController.php
    public function index(Request $request)
    {
        return view('products.newAdd', [
            'user' => Auth::user(),
            'categories' => Category::all(),
            'products_status'  => ProductStatus::all(),
            'sales' => Sale::all(),
        ]);
    }
```
完成品を確認して頂くと、<b>[カテゴリー] [商品状態] [販売状態]</b> が選択になっています。
なので、indexメソッドでは各Modelを通して
登録する <b>[ユーザー情報]</b> や選択肢で表示させるための <b>[３つの情報]</b> を取得し、Viewに渡します。

また、今回はそこまで複雑な処理ではないため上のように記述しましたが、
以下のようにcompact関数を使っても問題なく渡すことができます！

```php:NewAddController
    public function index(Request $request)
    {
        $user = Auth::user();
        $categories = Category::all();
        $products_status = ProductStatus::all();
        $sales = Sale::all();

        return view('products.newAdd', compact('categories', 'user', 'sales', 'products_status'));
    }

```

次にstoreメソッドですが、

```php:NewAddController
    public function store(ProductRequests $request)
    {
        $newProduct = new Product;
        $newProduct->product_name = $request->product_name;
        $newProduct->description = $request->description;
        $newProduct->price = $request->price;
        $newProduct->category_id = $request->category_id;
        $newProduct->product_status_id = $request->product_status_id;
        $newProduct->sale_status_id = $request->sale_status_id;
        $newProduct->user_id = Auth::user()->id;
        $newProduct->save();

        return view('welcome');
    }
 ```

```php
public function store(ProductRequests $request)
```

こちらの <b>ProductRequests</b> はフォームリクエストによるバリデーションをかけています。
今回の実装ではバリデーションについての解説は入れてませんので、
<b>Request</b> に書き換えてもらってOKです！


```php
$newProduct = new Product; 
```

ここの一文は「これからProductモデルに<b>新しい値を追加</b>していくよー」っていう感じです。笑
あとは <b>$newProduct</b> という変数に画面で入力（選択）した値が格納されていくようになっています。

よくエラーが起きてしまう原因...😂↓↓↓

```php
<?php

namespace App\Http\Controllers;

use App\User;
use App\Models\Product;
use App\Models\Category;
use App\Models\ProductStatus;
use App\Models\Sale;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Controller;
```

Controller上部ではモデルやリクエストインスタンスを持ってきているので、
ここが一つでも抜けていると <b>does not exit エラー</b>が起きてしまいます...。
気が抜けないですね。。

<h3>5 - ③ View</h3>

Viewについては
<b>「入力値」</b>と<b>「選択値」</b>の２種類があるので、分けて説明していきます。
と、その前に...

```php:
<form method="POST" action="{{ route('newProduct.post') }}">
```
フォームタグでPOST送信の設定を忘れずに。
action="{{ route(<b> ' 命名したルート名 ' </b>) }}" でルーティングしてあげます！

<br>
<b>↓↓ 入力値 ↓↓</b>

```php:newAdd.blade.php「入力値」
    <div class="form-group-sm clearfix">
        <label for="formGroupExampleInput2" class="mt-3 mb-0">商品名</label>
        <div class="product-info width-control">
            <input type="text" name="product_name" class="content-half-width form-control-sm d-inline">
            <!-- @if ($errors->has('product_name'))
                <div class="row justify-content-center">
                    <div class="cal-xs-4">
                        <span style="color:red">{{ $errors->first('product_name') }}</span>
                    </div>
                </div>
            @endif -->
        </div>
    </div>
```
ここはControllerで指定したnameと一致させるため<div>の中にそのまま同じnameを入れてあげます。
フォームタグで囲ってあるので、あとは上記のようにHTMLで入力欄を作ればOKです！

if文以降はエラー時のバリデーションを設定してます。
不要でしたら削除しても構いません。
<br>
<b>↓↓ 選択値 ↓↓</b>

```php:nweAdd.blade.php「選択値」
<div class="form-group-sm clearfix">
                        <label for="formGroupExampleInput2" class="mt-3 mb-0">商品カテゴリー</label>

                        <div class="product-info width-control">
                            <select class="content-half-width form-control-sm d-inline" id="changeSelect" name="category_id" onchange="entryChange2();">

                                <option value="">未選択</option>
                                @foreach ($categories as $category)
                                    <option value="{{ $category->id }}">{{ $category->category_name }}</option>
                                @endforeach
                                <!-- @if ($errors->has('category_id'))
                                <div class="row justify-content-center">
                                    <div class="cal-xs-4">
                                        <span style="color:red">{{ $errors->first('category_id') }}</span>
                                    </div>
                                </div>
                                @endif -->

                            </select>
                        </dov>
                    </div>
```


<h3>5 - ④ Migration</h3>
使用したソースコード参照

<h3>5 - ⑤ Model</h3>

```php:Product.php
    //商品追加のホワイトリスト
    protected $fillable = [
        'product_name',
        'description',
        'price',
    ];
```

$fillableで書き換えれるように設定しましょう。



ここでは使いませんが、$guardedはその逆です。




```php:Product.php
    // Userモデルを親に持つことを明記
    public function user()
    {
        return $this->belongsTo('App\User');
    }

    // Categoryモデルを親に持つことを明記
    public function category()
    {
        return $this->belongsTo('App\Models\Category');
    }

    // Saleモデルを親に持つことを明記
    public function sale()
    {
        return $this->belongsTo('App\Models\Sale');
    }

    // ProductStatusモデルを親に持つことを明記
    public function productstatus()
    {
        return $this->belongsTo('App\Models\ProductStatus');
    }
```
各リレーションを行っています。
詳しくは下記リンクを参考に。

個人的にすごく分かりやすかったのでオススメします！
参考 : [リレーションについて](https://qiita.com/vivid_colors/items/766c541038b53a4095c8)

<h1>参考にしたサイト</h1>
•　 [DBの操作について（挿入）](https://laraweb.net/tutorial/2633/)
<h1>まとめ</h1>
以上で商品追加の機能を実装できるかと思います！
DBへ新しいデータを登録する作業は初めてで、最初は色々記事などを漁りましたが、
ほとんど共同開発とスクール課題等で得た短い経験と知識ですんなり実装することができました！
もっと記述を簡単にできたり、ここの書き方間違ってる等たくさんあるかとは思いますが、何かの参考になれば幸いです🙇‍♂️