未経験者がLaravel共同開発での詰まりポイントの「メモ」をまとめてみた【総まとめ集編】

#1.はじめに
タイトルの通り、共同開発講座を受講中に起きたトラブルや詰まりポイントをまとめたメモ（約50記事）をジャンル別に全て公開します。
ジャンルは以下の通りなので、気になるものがあればぜひご覧ください。
###【ジャンル一覧】
・[GitHub関連](https://qiita.com/kei_Q/items/1e421eef9b3bdbcdd9e9)
・[Laravel関連](https://qiita.com/kei_Q/items/79ef7679b28bb3c9738e)
・[データベース（migration/seeding）関連](https://qiita.com/kei_Q/items/cf13f486628bb5662089)
・[環境関連](https://qiita.com/kei_Q/items/412a9695f26aad25a99c)
・[総まとめ集](https://qiita.com/kei_Q/items/e197268fc29fdd664b64)

下記の点を、ご了承ください。
・ジャンルで統一しているため、内容に関しては統一性はありません。
・メモの難易度もバラバラです。
・初学者向けの内容となっております。
・自分用のメモを転用しておりますので、表現が稚拙な部分があるかと思います。
**・あくまで僕のメモです！！**

#2.マイグレーションとシーディング

####2.1.マイグレーションとは
ザックリ言うとテーブルを作成するための作業
マイグレーションファイルをコマンドで作成し、作りたいテーブルのカラムを記載していく。
記載したファイルをコマンドでマイグレーションすると、データベースにテーブルが作成される。
また、マイグレーションファイルにはカラムに対してデータ型や修飾子、FKを記載しておくとマイグレーション実行時に反映される。
ただ、FKの関係でマイグレーションする順番が重要になってくるので、マイグレーションファイルを作成する順番に気を付ける。（FKで指定されているテーブルがないと、エラーが起こるので、先に親テーブルからマイグレーションされるように作成順番を考える。→子テーブルを後に作成する）
今回はphpmyadminをしようしているので、ブラウザ上で視覚的に確認することができる。
Phpmyadmin上で、使用するデータベースを作成しておかないと、テーブルをその中に作ることができないので注意

####2.2.マイグレーションファイルの作成（カラム、FK、その他修飾子の作成）

```
php artisan make:migration ファイル名
```

でファイル作成。

ファイルの中身はこんな感じ

```php:
　public function up()
    {
        Schema::create('m_products', function (Blueprint $table) {
            $table->increments('id');
            $table->string('product_name', 64);
            $table->integer('category_id')->unsigned();
            $table->integer('price')->unsigned();
            $table->string('description', 256);
            $table->integer('sale_status_id')->unsigned();
            $table->integer('product_status_id')->unsigned(); 
            $table->timestamp('resist_date');
            $table->integer('user_id')->unsigned();
            $table->char('delete_flag', 1);

            $table->foreign('sale_status_id')->references('id')->on('m_sales_statuses')->onDelete('cascade');
            $table->foreign('product_status_id')->references('id')->on('m_products_statuses')->onDelete('cascade');
        });
    }
```

####2.3.データ型について
上記のファイル内の記載のうち、string integer timestamp などがデータ型にあたる。
そのデータはどういう形式のデータなのか？文字なのか？数字なのか？その時の時間なのか？
を指定することができる。詳しくはGoogle先生に。(他の記事にも記載あります)

####2.4.php artisan migration の実行
このコマンドを実行すると、作成したマイグレーションファイルがテーブルとして作成される。
エラーになる場合は、他のエラーに関するメモを参照。

####2.5.migrationエラーについて
多かったエラーは
①データ型があっていない場合
②migrationする順番が違い、外部キー制約がつかないために起こるエラー

####2.6.シーディングとは
ザックリい言うと、テストデータを一気に作成することができる機能で、ファイルを作成してシーディングを行うことテーブル内にデータが挿入される。

####2.7.テストデータの決定
シーディングを行うテーブルに適したデータを考える。
例えば、商品（肉とか魚）のテストデータを考える時は、それぞれのカラムに適応した具体的なデータ（商品名や値段）を考える。
何個でも一気に登録できるが、まぁ、テストなので5~10個程度あれば十分。

####2.8.シーディングファイルの作成

```
php artisan make:seeder ファイル名
```

上記コマンドでシーディングファイルが作成される。
中身には前項で決定したテストデータを記載していく。

実際の内容としてはこんな感じ

```php:
public function run()
    {
        DB::table('m_products')->insert([
           'id' => 1,
           'product_name' => '黒毛和牛サーロイン',
           'category_id' => 1,
           'price' => 8000,
           'description' => 'なめらかでとろける食感と、甘くコクがある上品な香りが特徴です' ,
           'sale_status_id' => 1,
           'product_status_id' => 1,
           'resist_date' => date('Y-m-d H:i:s'),
           'user_id' => 1,
           'delete_flag' => ''
        ]);
        DB::table('m_products')->insert([
            'id' => 2,
            'product_name' => 'A5ランク松坂牛',
            'category_id' => 1,
            'price' => 12000,
            'description' => '松坂牛は濃厚で上品な甘みが絶品！' ,
            'sale_status_id' => 1,
            'product_status_id' => 1,
            'resist_date' => date('Y-m-d H:i:s'),
            'user_id' => 1,
            'delete_flag' => ''
         ]);
```

例として２つだけここに書いたが、実際は何個でも良い。


####2.9.php artisan db seed  の実行
このコマンドを実行することで、作成したシーディングファイル（テストデータ込み）がシーディングされて、当該テーブルにデータが挿入される。
このコマンドの実行時にエラーとしてよく起こるのは
①子テーブルと親テーブルの関係
②外部キー関連
が多いが、詳しくは別のメモにまとめてあるものを参照。

#3.viewの実装手順（ここでは新規ユーザ登録画面）

####3.1.実装の流れ
①作成していたモックアップ画面のhtml要素をコピーして、lara-d(プロジェクト)の当該ファイルに貼り付ける
②laravelcollectiveを導入する
③formタグ部分をlaravelcollectiveで書き換える。この時のformタグ内でのデータ型はデータベースのものとは違うので気を付ける（例：stringやintegerは使えない。ルーティングエラーになる。form内はtext型を使う）
④バリデーションをcontrollerファイル内で定義する。この時の指定はintegerやstringを使って行う。
⑤rink_to_routeの実装を行なって、挙動がしっかりしていることを確認する

####3.2.ブラウザ上でviewファイル（ルーティング）を確認する方法
URLに何を打ち込めばviewファイルが表示されるのか？？
↓
URLに打ち込むのは…

**192.168.33.11/signup**

192.168.33.11は自分で決めたローカルホストのIPアドレスのこと。
と打ち込めばローカルホストに繋がり、「signup」というルーティングから行き着く先のviewファイルを表示することができる。この「signup」はweb.phpに記述したルーティングの内容のこと。

これを打ち込めば「signup」というルーティング名で設定した先のviewが表示される


#4.新規ユーザ登録機能の実装

###4.1.実装手順

####4.1.1.画面（新規ユーザ登録画面フォーム）を作る

・LaravelCollectiveを導入
・フォームに情報を持たせてLaravelCollectiveで実装していく
→Form open/close 送信するルーティング名を指定
→カラム名、old()関数、クラス(bootstrap)
→**password確認のフォーム実装は’confirmation’が必要**
→型は基本的にtextで必要に応じてemail型やpassword型を記述する
・今回はcssが崩れていたのでcssも編集

####4.1.2.ルーティングの実装

・「ユーザ新規登録画面を表示させる」ルーティングを書く（signup）
・「ユーザ新規登録させる」ルーティングを書く（signup.post）

```php:
Route::get('signup', 'Auth\RegisterController@showRegistrationForm')->name('signup');
Route::post('signup', 'Auth\RegisterController@register')->name('signup.post');
```

**RegisterControllerは元々存在し、Registerアクションも元々存在する！（トレイトに入っている）**
→なので、コントローラを新しく生成する必要はない。

####4.1.3.Userモデルを作成

・Userモデル自体は元々存在する
・$fillableに作成したいユーザの情報要素を書いていく
・どのテーブルにそのモデルが作成されるのかを指定する

```php:
protected $table = 'm_users';

protected $fillable = [
        'last_name',
        'first_name',
        'zipcode',
        'prefecture',
        'municipality',
        'address',
        'apartments',
        'email',
        'phone_number',
        'password',
        'company_name',
    ];
```

####4.1.4.バリデーションの実装
・RegisterControllerにバリデーション記載していく
・Userモデルで定義したカラム（フォームから飛んでくる項目）に対して記載する
・integerよりもnumericを使用する

```php:
return Validator::make($data, [
        'last_name' => ['required', 'string', 'max:10'],
        'first_name' => ['required', 'string', 'max:10'],
        'zipcode' => ['required', 'numeric', 'digits:7'],
        'prefecture' => ['required', 'string', 'max:5'],
        'municipality' => ['required', 'string', 'max:10'],
        'address' => ['required', 'string', 'max:191'],
        'apartments' => ['required', 'string', 'max:191'],
        'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
        'phone_number' => ['required', 'numeric', 'max:15'],
        'password' => ['required', 'string', 'min:6', 'max:15', 'confirmed'],
    ]);
```


####4.1.5.createアクションの中身の記載
・RegisterControllerのcreateアクションに作成したいデータを変数に入れていく
・Userモデルで定義したカラム（フォームから飛んでくる項目）に対して記載する

```php:
return User::create([
            'last_name' => $data['last_name'],
            'first_name' => $data['first_name'],
            'zipcode' => $data['zipcode'],
            'prefecture' => $data['prefecture'],
            'municipality' => $data['municipality'],
            'address' => $data['address'],
            'apartments' => $data['apartments'],
            'email' => $data['email'],
            'phone_number' => $data['phone_number'],
            'password' => bcrypt($data['password']),
        ]);
```


####4.1.6.リダイレクト先の変更
・RegisterControllerの上の方にある$redirectTo=の部分を”/”に変える（新規登録後に飛ぶページにリンクするように）

```
protected $redirectTo = '/';
```

####4.1.7.リンク先の指定（ページ内のボタン及びヘッダーからの画面遷移）
・ページ内のボタンのリンクをページ遷移に従って変更
・ヘッダーからの画面遷移に従ってリンク先を変更


####4.1.8.その他（注意点）
**user_classification_idに先にテストデータを流し込んでおくこと**
ブラウザからフォームを送信してデータベースに登録する際にSQL1364エラーが起こることがある
→このエラーが一番引っかかった。。
これは今回で言うと、user_classification_idにデータが入っていないことで起こる。また、マイグレーションファイルにデフォルト値が指定されていないことが原因である。詳しくは別のエラーメモを参照（SQL1364エラー）


**->nullable()と->default()について**
なるべく  ->nullable()  よりも  ->default()  を用いるほうがいい。
また、default値を指定するときについて
今回の場合は、登録されるユーザはどんなタイプのユーザなのか？（user_classification_id）を意識してデフォルト値を設定すること。


#5.商品詳細画面の実装（セッションの保存まで）
###5.1.ルーティング

```php:web.php
Route::resource('cartitem', 'CartController', ['only' => ['index']]);
Route::group(["prefix" => 'iteminfo'], function() {
    Route::get('/{id}', 'CartController@show');
    Route::post('/add', 'CartController@addCart')->name('addcart');
});
```
###5.2.コントローラ(showアクション)
```php:CartController.php　(showアクション)
public function show($id)
    {
        //変数の初期化
        $ProductInfo = array();
        $ProductCategory = array();
        $UserId = '';
        //urlパラメータから飛んできたユーザidを元にモデルからそれぞれ商品、カテゴリーを特定
        $ProductInfo = Product::findOrFail($id);
        $ProductCategory = Category::findOrFail($ProductInfo -> category_id);
        $UserId = Auth::user()->id;
        
        return view('iteminfo', 
        [
            'ProductInfo' => $ProductInfo,
            'ProductCategory' => $ProductCategory,
            'UserId' => $UserId,
        ]);
    }
```

###5.3.iteminfo（商品詳細）画面の表示

```html


{!! Form::open(['route' => 'addcart']) !!}
    {{ Form::hidden('user_id', $UserId) }}
                            
    {{ Form::hidden('ProductId', $ProductInfo->id) }}
                            
        <div class="form-group row justify-content-center">
            <label class="col-form-label">購入個数</label>
           <div class="">
                                    
           <input type="number" class="inputNumber form-control" value="0" name="Quantity" >
           </div>
            <lavel class="col-form-label">個</lavel>
                <div class="col-sm-auto">
                 {!! Form::submit('カートへ', ['class' => 'btn btn-primary']) !!}
          </div>
        </div>
{!! Form::close() !!}
```


ここで

{{ Form::hidden('user_id', $UserId) }}

{{ Form::hidden('ProductId', $ProductInfo->id) }}

<input type="number" class="inputNumber form-control" value="0" name="Quantity" >

この３つのname属性で送信する
↓
web.phpを介して
↓
CartController（addCartアクション）へ


###5.4.コントローラ(addCartアクション)
```php:CartController（addCartアクション）
public function addCart(Request $request)
    {
        //セッションに保存したい変数を定義する（ここでは商品idと注文個数）
        //飛んできた$requestの中のname属性をそれぞれ指定
        $SessionProductId = $request->ProductId;
        $SessionProductQuantity = $request->Quantity;
        //配列の入れ物を作る（初期化）
        $SessionData = array();
        
        //作った配列に、compact関数を用いてidと個数の変数をまとめる（”” を使っているが変数の意味）
        $SessionData = compact("SessionProductId", "SessionProductQuantity");
        
        //session_dataというキーで、$SessionDataをセッションに登録
        $request->session()->push('session_data', $SessionData);
        
        return redirect('cartitem');
        
    }
```


このアクションでセッションにIDと個数を保存する。

```
$SessionProductId = $request->ProductId;
$SessionProductQuantity = $request->Quantity;
```

この二つの記述でIDと個数のそれぞれを変数と置く。
↓
データをひとまとめにするために

```
$SessionData = array();
```

として、空の配列を定義する。
↓
配列に二つの変数を統合する

```
$SessionData = compact("SessionProductId", "SessionProductQuantity");
```

この時ダブルクォーテーションがついているが"SessionProductId"は変数を意味している
↓
そしてセッションにpushして配列に次々と値を加えていく形をとる

```
$request->session()->push('session_data', $SessionData);
```

↓
web.phpのcartitemにリダイレクトする
↓
CartControllerのindexアクションに繋がる

###5.5.コントローラ(indexアクション)
```php:CartController(indexアクション)
public function index(Request $request)
    {
        //セッションに保存していた値を取得し、変数として定義
        $SessionData = $request->session()->get('session_data');
        //セッションデータのなかのそれぞれのデータを抽出
        $SessionProductId = array_column($SessionData, 'SessionProductId');
        $SessionProductQuantity = array_column($SessionData, 'SessionProductQuantity');
        dd($SessionData);


ここから下はまだ不明
```


セッションに保存していた値を取得し、変数として定義

```
$SessionData = $request->session()->get('session_data');
```


array_column関数を用いてIDと個数のデータをそれぞれ抽出

```
$SessionProductId = array_column($SessionData, 'SessionProductId');
```

あとはこのデータを用いてカート内商品一覧画面に表示できればそれで完了のはず。。


#6.バック商品修正画面の実装

**こちらECサイトの中で、管理者（出品者）側の画面で、出品する商品を修正する画面の実装です**

####6.1.実装する内容
①画面遷移時の商品情報のpre表示
②商品修正機能
③商品情報の削除機能


###6.2.画面遷移時の商品情報のpre表示

```php:BackProductController
public function edit($id)
    {
        $product = MProduct::with(['category', 'saleStatus', 'productStatus'])->find($id);
        
        //category関連の定義
        $categories = MCategory::getLists();
        $categoryName = MCategory::find($product->category_id)->category_name;
        $categoryId = $product->category_id;
        
        //sale_status関連の定義
        $saleStatuses = MSalesStatus::getLists();
        $saleStatusName = MSalesStatus::find($product->sale_status_id)->sale_status_name;
        $saleStatusId = $product->sale_status_id;

        //product_status関連の定義
        $productStatuses = MProductStatus::getLists();
        $productStatusName = MProductStatus::find($product->product_status_id)->product_status_name;
        $productStatusId = $product->product_status_id;

        return view('seller.back_product_edit',[
                'product' => $product,
                'categories' => $categories,
                'categoryName' => $categoryName,
                'categoryId' => $categoryId,
                'saleStatuses' => $saleStatuses,
                'saleStatusName' => $saleStatusName,
                'saleStatusId' => $saleStatusId,
                'productStatuses' => $productStatuses,
                'productStatusName' => $productStatusName,
                'productStatusId' => $productStatusId,
            ]
        );
    }
```


editアクションでその$idを持つ商品の修正画面に飛ばす。
MProductモデルから$idを持つ商品を探すときに、リレーション先のデータまで取得できるようにしなければならない。
MProductモデルに記載されているリレーションを貼った関数名をwith関数の引数に入力する。
その後、viewに返す。

例) 下記のような内容の関数名

```php:MProduct.php(モデル)
public function saleStatus()
    {
        return $this->belongsTo(MSaleStatus::class);
    }
```


```html

@extends('layouts.seller_app')
@section('content')
<main>
        
    <div class="page-header mt-5 text-center">
        <h4>商品情報修正</h4>
    </div>
    
        
    <div class="row mt-5 mb-5">
        <div class="col-sm-5 mx-auto">
    
            {!! Form::open(['route' => ['back_product_update', $product->id]]) !!}
            {{ method_field('PUT') }}
            
                
                <div class="form-group-sm">
                    {!! Form::label('productName', '商品名', ['class' => 'mt-2 mb-0']) !!}
                    <div class="pl-3">
                        {!! Form::text('productName', $product->product_name, ['class' => 'form-control d-inline w-100']) !!}
                    </div>
                    <div class="mt-1 text-right text-danger">
                        @if($errors->has('productName'))
                            {{ $errors->first('productName') }}
                        @endif
                    </div>
                </div>
            
                <div class="form-group-sm">
                    {!! Form::label('categoryName', '商品カテゴリ', ['class' => 'mt-2 mb-0']) !!}
                    <div class="pl-3">
                        <select class="form-control d-inline w-100" name="categoryId">
                        <option value={{ $categoryId }}>{{ $categoryName }}</option>
                            @foreach($categories as $id => $category_Name)
                                <option value={{ $id }}>
                                    {{ $category_Name }}
                                </option>  
                            @endforeach, 
                        </select>
                    </div>
                    <div class="mt-1 text-right text-danger">
                        @if($errors->has('categoryId'))
                            {{ $errors->first('categoryId') }}
                        @endif
                    </div>
                </div>
                
                <div class="form-group-sm">
                    {!! Form::label('price', '販売単価', ['class' => 'd-block mt-2 mb-0']) !!}
                    {!! Form::text('price', $product->price, ['class' => 'ml-3 mr-2 form-control col-sm-8 d-inline'])."円" !!}
                    <div class="mt-1 text-right text-danger">
                        @if($errors->has('price'))
                            {{ $errors->first('price') }}
                        @endif
                    </div>
                </div>
            
                <div class="form-group-sm">
                    {!! Form::label('saleStatusName', '販売状態', ['class' => 'd-block mt-2 mb-0']) !!}
                    <select class="ml-3 form-control col-sm-8" name="saleStatusId">
                        <option value="{{ $saleStatusId }}">{{ $saleStatusName }}</option>
                            @foreach($saleStatuses as $id => $sale_StatusName)
                                <option value="{{ $id }}">
                                    {{ $sale_StatusName }}
                                </option>  
                            @endforeach, 
                    </select>
                    <div class="mt-1 text-right text-danger">
                        @if($errors->has('saleStatusId'))
                            {{ $errors->first('saleStatusId') }}
                        @endif
                    </div>
                </div>

                <div class="form-group-sm">
                    {!! Form::label('productStatusName', '商品状態', ['class' => 'd-block mt-2 mb-0']) !!}
                    <select class="ml-3 form-control col-sm-8" name="productStatusId">
                        <option value="{{ $productStatusId }}">{{$productStatusName}}</option>
                            @foreach($productStatuses as $id => $product_StatusName)
                                <option value="{{ $id }}">
                                    {{ $product_StatusName }}
                                </option>  
                            @endforeach, 
                    </select>
                    <div class="mt-1 text-right text-danger">
                        @if($errors->has('productStatusId'))
                            {{ $errors->first('productStatusId') }}
                        @endif
                    </div>
                </div>

                <div class="form-group-sm">
                    {!! Form::label('description', '商品説明', ['class' => 'mt-2 mb-0']) !!}
                    <div class="pl-3">
                        {!! Form::textarea('description', $product->description, ['class' => 'form-control d-inline w-100']) !!}
                    </div>
                    <div class="mt-1 text-right text-danger">
                        @if($errors->has('description'))
                            {{ $errors->first('description') }}
                        @endif
                    </div>
                </div>

                <div>
                    <div class="w-50 float-left">
                        <div class="text-center mt-5"> 
                            {!! Form::submit('修正', ['class' => 'button btn btn-primary mt-2']) !!}
                        </div>
                    </div>
                    {!! Form::close() !!}

                    {!! Form::open(['route' => ['back_product_delete', $product->id]]) !!}
                    {{ method_field('DELETE') }}
                    <div class="w-50 float-right">
                        <div class="text-center mt-5">
                            {!! Form::submit('削除', ['class' => 'button btn btn-danger mt-2']) !!}
                        </div>
                    </div>
                </div> 
            {!! Form::close() !!}
        </div>
    </div>
</main>
@endsection
```

<font color="red">
**------------申し訳ありません-----------------
メモ作成後にコードを編集したので以下、少し変わります。
ですが、知識としては使えますので参考までにお願いいたします。**
</font>


**--このView画面で$idの商品情報をpre表示させる--**
例えばこれ

```
{!! Form::text('product_name', $product->product_name, ['class' => 'form-control d-inline w-100']) !!}
```

(重要ポイント！！)
text型のformで
**●第一引数：HTMLフォームの name属性**
	ここにはデータベースのカラム名を入れておくと良い
**●第二引数：HTMLフォームの value属性**
	ここには予め表示したい内容を入れておく
**●第三引数：追加属性（連想配列形式）**
	ここにはクラス（CSS要素）を記入


**この第二引数が重要！！**

```
$product->product_name
```

これは特定した $product のproduct_nameというカラムに格納されているデータを呼び出しており、入力欄にはそれが表示される。

ではこれはどうか

```
 {!! Form::text('category_name', $product->category->category_name, ['class' => 'form-control d-inline w-100']) !!}
```

第二引数は

```
$product->category->category_name
```

となっているが
これは
特定した$productからリレーションを貼っている関数名categoryを呼び出し、そのリレーション先にあるcategory_nameを呼び出している
という形。
こう書くことで、リレーション先のデータを取得することができる。

**上記を全てのフォームに記載することで商品情報のpre表示が完成する。**

<font color="red">
**-------ここまで、viewと比べてコードが違いますm(__)m---------**
</font>


###6.3.商品修正機能

```php:BackProductController
public function update(CreateProductRequest $request, $id)
    {
        $product = MProduct::with(['category', 'saleStatus', 'productStatus'])->find($id);
        
        $product->product_name = $request->productName;
        $product->category_id = $request->categoryId;
        $product->price = $request->price;
        $product->sale_status_id = $request->saleStatusId;
        $product->product_status_id = $request->productStatusId;
        $product->description = $request->description;
        $product->save();

        return redirect('seller/items');
    }
```

updateアクションで商品情報を修正する

####6.3.1.大まかな流れ
商品修正画面（back_product_edit.blade.php）のフォームから書き換えられた内容が送信された後、このアクションにくる。
↓
飛んできた$requestを頼りに、それぞれのカラムデータに格納していく。
↓
格納した情報を保存する
という流れ。


####6.3.2.実装の解説

まず

```
$product = MProduct::with(['category', 'saleStatus' ,'productStatus'])->find($id);
```

修正する商品である$productを特定する。このときリクエストから飛んできたidを元にモデルから探し出す。さらにリレーション先のテーブルも取得できるようにwith関数を使用。
特定ができたら

```
$product->product_name = $request->productName;
```

その商品が持つproduct_nameを新しく$request->productNameと定義し直す。
また
リレーション先のデータを書き換えたい場合は

```
$product->category_id = $request->categoryId;
```

のようにリレーション先に指定している関数名（MProductモデルに記載）をアロー関数でつなぐことで、リレーション先のテーブルにあるcategory_nameカラムを指定することができる。


###6.4.削除機能

割愛！！！！！！