Laravel ECサイト step02

CRUD

Create

  • 商品モデルを作成
カラム名 説明 Laravel関数 MySQLのデータ型
name 商品名 string varchar(255)
description 商品の説明文 text text
price 価格 integer int(11)

sail artisan make:model Product -m

database\migrations\XXXX_XX_XX_XXXXXX_create_products_table.php

<?php

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

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('products', function (Blueprint $table) {
            $table->id();
+            $table->string('name');
+            $table->text('description');
+            $table->integer('price')->unsigned();
   /**
     *  マイナスの値が保存できないようにしている。
     */
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('products');
    }
};
  • 商品のコントローラーを作成
    CRUDをまとめて作成するので「--resource」オプション
    sail artisan make:controller ProductController --resource --model=Product

  • カテゴリと商品とを紐づけ

app\Models\Product.php

<?php

namespace App\Models;

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

class Product extends Model
{
    use HasFactory;
+
+    public function category()
+    {
+        return $this->belongsTo(Category::class);
+    }
}

app\Models\Category.php

<?php

namespace App\Models;

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

class Category extends Model
{
    use HasFactory;
+
+    public function products()
+    {
+        return $this->hasMany(Product::class);
+    }
}

マイグレーションファイルdatabase\migrations\XXXX_XX_XX_XXXXXX_create_products_table.phpに1行追加
新しいカラムとして、カテゴリのIDを追加

カラム名 説明 Laravel関数 MySQLのデータ型
category_id カテゴリID integer int(11)
<?php

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

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->text('description');
            $table->integer('price')->unsigned();
+            $table->integer('category_id')->unsigned();
            $table->timestamps();
        });
    }

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

Eloquentは外部キーカラムを自動的に決定します。規約により、親モデルの小文字クラス名に「_id」という接尾辞を付けます。
この場合では、Categoryモデルに対する外部キーカラムはcategory_idとなり、商品とカテゴリが紐づきます。

カテゴリーIDにはマイナスの値はあり得ないので、unsignedを指定しています。

<?php

use Illuminate\Support\Facades\Route;
+use App\Http\Controllers\ProductController;

/*
|--------------------------------------------------------------------------
| 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::resource('products', ProductController::class);
  • コントローラー
    app\Http\Controllers\ProductController.php
    • indexアクション
   public function index()
    {
-        //
+        $products = Product::all();
+
+        return view('products.index', compact('products'));
    }

indexで呼び出すviewファイル resources\views\products\index.blade.php

+<a href="{{ route('products.create') }}"> Create New Product</a>
+
+<table>
+    <tr>
+        <th>Name</th>
+        <th>Description</th>
+        <th>Price</th>
+        <th>Category ID</th>
+        <th >Action</th>
+    </tr>
+    @foreach ($products as $product)
+    <tr>
+        <td>{{ $product->name }}</td>
+        <td>{{ $product->description }}</td>
+        <td>{{ $product->price }}</td>
+        <td>{{ $product->category_id }}</td>
+        <td>
+            <a href="{{ route('products.show',$product->id) }}">Show</a>
+            <a href="{{ route('products.edit',$product->id) }}">Edit</a>
+        </td>
+    </tr>
+    @endforeach
+</table>
  • createアクション
   public function create()
    {
-        //
+        return view('products.create');
    }

データ入力フォーム
resources\views\products\create.blade.php

+<div>
+    <h2>Add New Product</h2>
+</div>
+<div>
+    <a href="{{ route('products.index') }}"> Back</a>
+</div>
+
+<form action="{{ route('products.store') }}" method="POST">
+    @csrf
+
+    <div>
+        <strong>Name:</strong>
+        <input type="text" name="name" placeholder="Name">
+    </div>
+    <div>
+        <strong>Description:</strong>
+        <textarea style="height:150px" name="description" placeholder="Description"></textarea>
+    </div>
+    <div>
+        <strong>Price:</strong>
+        <input type="number" name="price" placeholder="Price">
+    </div>
+    <div>
+        <button type="submit">Submit</button>
+    </div>
+
+</form>
  • storeアクション
    app\Http\Controllers\ProductController.php
   public function store(Request $request)
    {
-        //
+        $product = new Product();
+        $product->name = $request->input('name');
+        $product->description = $request->input('description');
+        $product->price = $request->input('price');
+        $product->save();
+
+        return to_route('products.index');
    }
  • showアクション app\Http\Controllers\ProductController.php
   public function show(Product $product)
    {
-        //
+        return view('products.show', compact('product'));
    }

viewファイル作成
resources\views\products\show.blade.php

+<div>
+    <h2> Show Product</h2>
+</div>
+<div>
+    <a href="{{ route('products.index') }}"> Back</a>
+</div>
+
+<div>
+    <strong>Name:</strong>
+    {{$product->name}}
+</div>
+
+<div>
+    <strong>Description:</strong>
+    {{$product->description}}
+</div>
+
+<div>
+    <strong>Price:</strong>
+    {{$product->price}} 
+</div>
  • editアクション
   public function edit(Product $product)
    {
-        //
+        return view('products.edit', compact('product'));
    }

resources\views\products\edit.blade.php

+<div>
+    <h2>Edit Product</h2>
+</div>
+<div>
+    <a href="{{ route('products.index') }}"> Back</a>
+</div>
+
+<form action="{{ route('products.update',$product->id) }}" method="POST">
+    @csrf
+    @method('PUT')
+
+    <div>
+        <strong>Name:</strong>
+        <input type="text" name="name" value="{{ $product->name }}" placeholder="Name">
+    </div>
+    <div>
+        <strong>Description:</strong>
+        <textarea style="height:150px" name="description" placeholder="description">{{ $product->description }}</textarea>
+    </div>
+    <div>
+        <strong>Price:</strong>
+        <input type="number" name="price"  value="{{ $product->price }}">
+    </div>
+    <div>
+        <button type="submit">Submit</button>
+    </div>
+
+</form>
  • updateアクション
   public function update(Request $request, Product $product)
    {
-        //
+        $product->name = $request->input('name');
+        $product->description = $request->input('description');
+        $product->price = $request->input('price');
+        $product->update();
+
+        return to_route('products.index');
    }
  • destroyアクション
   public function destroy(Product $product)
    {
-        //
+        $product->delete();
+ 
+        return to_route('products.index');
    }

resources\views\products\index.blade.phpを変更

<a href="{{ route('products.create') }}"> Create New Product</a>

<table>
    <tr>
        <th>Name</th>
        <th>Description</th>
        <th>Price</th>
        <th>Category ID</th>
        <th >Action</th>
    </tr>
    @foreach ($products as $product)
    <tr>
        <td>{{ $product->name }}</td>
        <td>{{ $product->description }}</td>
        <td>{{ $product->price }}</td>
        <td>{{ $product->category_id }}</td>
        <td>
+            <form action="{{ route('products.destroy',$product->id) }}" method="POST">
                <a href="{{ route('products.show',$product->id) }}">Show</a>
                <a href="{{ route('products.edit',$product->id) }}">Edit</a>
+                @csrf
+                @method('DELETE')
+                <button type="submit">Delete</button>
+            </form>
        </td>
    </tr>
    @endforeach
</table>
  • 商品登録時にカテゴリを選択できるようにしよう

    1. app\Http\Controllers\ProductController.phpにモデルを追加
<?php

namespace App\Http\Controllers;
use App\Models\Product;
+use App\Models\Category;
use Illuminate\Http\Request;

class ProductController extends Controller
/* === 後略 === */
  1. app\Http\Controllers\ProductController.phpのcreateアクションを修正
    public function create()
    {
-        return view('products.create');
+        $categories = Category::all();
+ 
+        return view('products.create', compact('categories'));
    }
  1. resources\views\products\create.blade.phpを修正
<div>
    <h2>Add New Product</h2>
</div>
<div>
    <a href="{{ route('products.index') }}"> Back</a>
</div>

<form action="{{ route('products.store') }}" method="POST">
    @csrf
  
    <div>
        <strong>Name:</strong>
        <input type="text" name="name" placeholder="Name">
    </div>
    <div>
        <strong>Description:</strong>
        <textarea style="height:150px" name="description" placeholder="Description"></textarea>
    </div>
    <div>
        <strong>Price:</strong>
        <input type="number" name="price" placeholder="Price">
    </div>
+    <div>
+        <strong>Category:</strong>
+        <select name="category_id">
+        @foreach ($categories as $category)
+        <option value="{{ $category->id }}">{{ $category->name }}</option>
+        @endforeach
+        </select>
+    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
   
</form>
  1. app\Http\Controllers\ProductController.phpのstoreアクションでcategory_idをデータベースに保存できるように修正
   public function store(Request $request)
    {
        $product = new Product();
        $product->name = $request->input('name');
        $product->description = $request->input('description');
        $product->price = $request->input('price');
+        $product->category_id = $request->input('category_id');
        $product->save();

        return to_route('products.index');
    }

app\Http\Controllers\ProductController.phpのedhitアクションを変更

    public function edit(Product $product)
    {
-        return view('products.edit', compact('product'));
+        $categories = Category::all();
+ 
+        return view('products.edit', compact('product', 'categories'));
    }
  1. resources\views\products\edit.blade.phpを修正
<div>
    <h2>Edit Product</h2>
</div>
<div>
    <a href="{{ route('products.index') }}"> Back</a>
</div>

<form action="{{ route('products.update',$product->id) }}" method="POST">
    @csrf
    @method('PUT')

    <div>
        <strong>Name:</strong>
        <input type="text" name="name" value="{{ $product->name }}" placeholder="Name">
    </div>
    <div>
        <strong>Description:</strong>
        <textarea style="height:150px" name="description" placeholder="description">{{ $product->description }}</textarea>
    </div>
    <div>
        <strong>Price:</strong>
        <input type="number" name="price"  value="{{ $product->price }}">
    </div>
+    <div>
+        <strong>Category:</strong>
+        <select name="category_id">
+        @foreach ($categories as $category)
+            @if ($category->id == $product->category_id)
+                <option value="{{ $category->id }}" selected>{{ $category->name }}</option>
+            @else
+                <option value="{{ $category->id }}">{{ $category->name }}</option>
+            @endif
+        @endforeach
+        </select>
+    </div>
    <div>
        <button type="submit">Submit</button>
    </div>

</form>
  1. app\Http\Controllers\ProductController.phpのupdadeアクションを修正
   public function update(Request $request, Product $product)
    {
        $product->name = $request->input('name');
        $product->description = $request->input('description');
        $product->price = $request->input('price');
+        $product->category_id = $request->input('category_id');
        $product->update();

        return to_route('products.index');
    }

ダミーデータの追加

Fakerのランダム値の日本語化。設定ファイルを編集
config/app.php

'faker_locale' => 'ja_JP',
  • ファクトリ
    コマンド sail artisan make:factory ProductFactory

database/factories/ProductFactory.php

<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends
\Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Product>
*/
class ProductFactory extends Factory

{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition()
{
return [
'name' => fake()->name(),
'description' => fake()->realText(50, 5),
'price' => fake()->numberBetween(100, 200000),
'category_id' => 1,
];
}
}
  • シーダ
    コマンド sail artisan make:seeder ProductSeeder database/seeders/ProductSeecer.php
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\Product;
class ProductSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
// ProductFactoryクラスで定義した内容にもとづいてダミーデータを20件生成
し、productsテーブルに追加する
Product::factory()->count(20)->create();
}
}
  • ファクトリとシーダークラスの作成後にコマンド実行でダミーデータを追加
    コマンド sail artisan db:seed --class=ProductSeeder