前回、VSCodeとClineで自動で開発してショップお買い物アプリを、手動で作成します。
1. データベースの準備(XAMPP)
XAMPP Control Panelを開き、Apache と MySQL の「Start」ボタンをそれぞれクリックして起動(ステータスが緑色に変化)させておく。
http://localhost/phpmyadmin/を開く。shop_laravel_dbという名前で空のデータベースを作成する(文字コード:utf8mb4_general_ci)。
2. Laravelプロジェクトの作成と環境設定
① プロジェクトの作成
コマンドプロンプトで C:\xampp\htdocs に移動し、プロジェクトを作成。
cd C:\xampp\htdocs
C:\xampp\htdocs> composer create-project laravel/laravel shop_app② VS Codeをコマンドプロンプトから起動
プロジェクトの作成が完了したら、生成された shop_app ディレクトリに移動し、コマンドプロンプトから直接VS Codeを起動する。
C:\xampp\htdocs> cd shop_app
C:\xampp\htdocs\shop_app> code .💡 補足
code .(コード・ドット)を実行すると、現在カレントディレクトリに指定しているshop_appフォルダが自動的にVS Codeに読み込まれた状態でエディタが立ち上がる。
③ .env ファイルの編集
VS Codeが開いたら、プロジェクトのルート直下にある .env ファイルを選択し、データベース接続情報をXAMPP環境に合わせて書き換える。
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=shop_laravel_db
DB_USERNAME=root
DB_PASSWORD=
D=3. マイグレーションとモデルの作成
① マイグレーションファイルの作成
VSCodeのターミナルで以下のコマンドを実行し、テーブル定義ファイルを作成する。
C:\xampp\htdocs\shop_app> php artisan make:migration create_shop_tablesdatabase/migrations/xxxx_xx_xx_xxxxxx_create_shop_tables.php を開き、以下のように編集する。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
// 1. 商品テーブル
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->integer('price');
$table->string('image_url')->default('https://via.placeholder.com/150');
$table->timestamps();
});
// 2. 注文メインテーブル
Schema::create('orders', function (Blueprint $table) {
$table->id();
$table->string('customer_name');
$table->text('address');
$table->string('tel');
$table->integer('total_price');
$table->timestamps();
});
// 3. 注文明細テーブル
Schema::create('order_details', function (Blueprint $table) {
$table->id();
$table->foreignId('order_id')->constrained()->onDelete('cascade');
$table->foreignId('product_id')->constrained();
$table->integer('price');
$table->integer('quantity');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('order_details');
Schema::dropIfExists('orders');
Schema::dropIfExists('products');
}
};② シーダーの作成(テストデータ挿入)
C:\xampp\htdocs\shop_app> php artisan make:seeder ProductSeederdatabase/seeders/ProductSeeder.php を編集。
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class ProductSeeder extends Seeder
{
public function run(): void
{
DB::table('products')->insert([
['name' => 'オーガニックコーヒー豆', 'price' => 1200, 'image_url' => 'https://via.placeholder.com/150?text=Coffee'],
['name' => '高級宇治抹茶セット', 'price' => 2500, 'image_url' => 'https://via.placeholder.com/150?text=Matcha'],
['name' => 'ハンドメイドマグカップ', 'price' => 1800, 'image_url' => 'https://via.placeholder.com/150?text=Cup'],
]);
}
}database/seeders/DatabaseSeeder.php を開き、作成したシーダーを呼ぶようにする。
public function run(): void
{
$this->call(ProductSeeder::class);
}③ マイグレーションとシードの実行
C:\xampp\htdocs\shop_app> php artisan migrate --seed④ モデルの作成
C:\xampp\htdocs\shop_app> php artisan make:model Product
C:\xampp\htdocs\shop_app> php artisan make:model Order
C:\xampp\htdocs\shop_app> php artisan make:model OrderDetail4. ルーティングの定義
routes/web.php を開き、画面遷移とアクションへのルートを定義する。
<?php
use App\Http\Controllers\ShopController;
use Illuminate\Support\Facades\Route;
Route::get('/', [ShopController::class, 'index'])->name('shop.index');
Route::post('/cart/add', [ShopController::class, 'addToCart'])->name('cart.add');
Route::get('/cart', [ShopController::class, 'cart'])->name('cart.index');
Route::post('/cart/update', [ShopController::class, 'updateCart'])->name('cart.update');
Route::post('/cart/remove', [ShopController::class, 'removeFromCart'])->name('cart.remove');
Route::get('/checkout', [ShopController::class, 'checkout'])->name('checkout');
Route::post('/success', [ShopController::class, 'success'])->name('success');5. コントローラーの作成(ロジック実装)
C:\xampp\htdocs\shop_app> php artisan make:controller ShopControllerapp/Http/Controllers/ShopController.php を以下のように編集。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Product;
use App\Models\Order;
use App\Models\OrderDetail;
use Illuminate\Support\Facades\DB;
class ShopController extends Controller
{
// 商品一覧
public function index()
{
$products = Product::all();
return view('shop.index', compact('products'));
}
// カート追加
public function addToCart(Request $request)
{
$productId = $request->input('product_id');
$quantity = $request->input('quantity', 1);
$cart = session()->get('cart', []);
if (isset($cart[$productId])) {
$cart[$productId] += $quantity;
} else {
$cart[$productId] = $quantity;
}
session()->put('cart', $cart);
return redirect()->route('cart.index');
}
// カート画面
public function cart()
{
$cart = session()->get('cart', []);
$cartItems = [];
$totalPrice = 0;
if (!empty($cart)) {
$products = Product::whereIn('id', array_keys($cart))->get();
foreach ($products as $p) {
$qty = $cart[$p->id];
$subtotal = $p->price * $qty;
$totalPrice += $subtotal;
$cartItems[] = [
'id' => $p->id,
'name' => $p->name,
'price' => $p->price,
'image_url' => $p->image_url,
'quantity' => $qty,
'subtotal' => $subtotal
];
}
}
return view('shop.cart', compact('cartItems', 'totalPrice'));
}
// カート数量更新
public function updateCart(Request $request)
{
$quantities = $request->input('quantities', []);
$cart = session()->get('cart', []);
foreach ($quantities as $id => $qty) {
if ($qty <= 0) {
unset($cart[$id]);
} else {
$cart[$id] = (int)$qty;
}
}
session()->put('cart', $cart);
return redirect()->route('cart.index');
}
// カートから削除
public function removeFromCart(Request $request)
{
$id = $request->input('product_id');
$cart = session()->get('cart', []);
if (isset($cart[$id])) {
unset($cart[$id]);
}
session()->put('cart', $cart);
return redirect()->route('cart.index');
}
// お届け先入力画面
public function checkout()
{
if (empty(session()->get('cart'))) {
return redirect()->route('shop.index');
}
return view('shop.checkout');
}
// 注文確定(トランザクション処理)
public function success(Request $request)
{
$cart = session()->get('cart', []);
if (empty($cart)) {
return redirect()->route('shop.index');
}
$request->validate([
'customer_name' => 'required|string|max:100',
'address' => 'required|string',
'tel' => 'required|string',
]);
DB::beginTransaction();
try {
$products = Product::whereIn('id', array_keys($cart))->get();
$totalPrice = 0;
$orderDetailsData = [];
foreach ($products as $p) {
$qty = $cart[$p->id];
$totalPrice += $p->price * $qty;
$orderDetailsData[] = [
'product_id' => $p->id,
'price' => $p->price,
'quantity' => $qty,
];
}
// 1. ordersテーブル保存
$order = new Order();
$order->customer_name = $request->input('customer_name');
$order->address = $request->input('address');
$order->tel = $request->input('tel');
$order->total_price = $totalPrice;
$order->save();
// 2. order_detailsテーブル保存
foreach ($orderDetailsData as $data) {
$detail = new OrderDetail();
$detail->order_id = $order->id;
$detail->product_id = $data['product_id'];
$detail->price = $data['price'];
$detail->quantity = $data['quantity'];
$detail->save();
}
DB::commit();
session()->forget('cart'); // カート空にする
return view('shop.success');
} catch (\Exception $e) {
DB::rollBack();
return back()->withErrors(['error' => '注文処理に失敗しました: ' . $e->getMessage()]);
}
}
}6. ビュー(Bladeテンプレート)の作成
resources/views/shop ディレクトリを作成し、その中に以下の4つのファイルを配置する。
① index.blade.php(商品一覧)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>商品一覧</title>
<style>
body { font-family: sans-serif; background: #f4f4f4; margin: 20px; }
.grid { display: flex; gap: 20px; }
.card { background: #fff; padding: 15px; border-radius: 5px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); width: 200px; text-align: center; }
.price { color: #bf0000; font-weight: bold; margin: 10px 0; }
.btn { background: #007bff; color: #fff; border: none; padding: 8px 15px; border-radius: 3px; cursor: pointer; }
</style>
</head>
<body>
<h1>商品一覧</h1>
<p><a href="{{ route('cart.index') }}">カートを見る</a></p>
<div class="grid">
@foreach ($products as $p)
<div class="card">
<img src="{{ $p->image_url }}" alt="" style="max-width:100%;">
<h3>{{ $p->name }}</h3>
<p class="price">¥{{ number_format($p->price) }}</p>
<form action="{{ route('cart.add') }}" method="POST">
@csrf
<input type="hidden" name="product_id" value="{{ $p->id }}">
<input type="number" name="quantity" value="1" min="1" style="width: 50px; margin-bottom: 10px;"><br>
<button type="submit" class="btn">カートに入れる</button>
</form>
</div>
@endforeach
</div>
</body>
</html>② cart.blade.php(カート画面)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>買い物カート</title>
<style>
body { font-family: sans-serif; background: #f4f4f4; margin: 20px; }
table { width: 100%; border-collapse: collapse; background: #fff; }
th, td { padding: 10px; border: 1px solid #ddd; text-align: center; }
th { background: #eee; }
.total { text-align: right; font-size: 1.2rem; font-weight: bold; margin: 20px 0; }
</style>
</head>
<body>
<h1>買い物カート</h1>
@if (empty($cartItems))
<p>カートは空です。<a href="{{ route('shop.index') }}">お買い物を続ける</a></p>
@else
<form action="{{ route('cart.update') }}" method="POST">
@csrf
<table>
<thead>
<tr>
<th>商品名</th><th>単価</th><th>数量</th><th>小計</th><th>操作</th>
</tr>
</thead>
<tbody>
@foreach ($cartItems as $item)
<tr>
<td>{{ $item['name'] }}</td>
<td>¥{{ number_format($item['price']) }}</td>
<td>
<input type="number" name="quantities[{{ $item['id'] }}]" value="{{ $item['quantity'] }}" min="1" style="width:60px;">
</td>
<td>¥{{ number_format($item['subtotal']) }}</td>
<td>
<button type="submit" formaction="{{ route('cart.remove') }}" name="product_id" value="{{ $item['id'] }}" style="color:red;">削除</button>
</td>
</tr>
@endforeach
</tbody>
</table>
<div class="total">合計金額: ¥{{ number_format($totalPrice) }}</div>
<button type="submit">数量を更新</button>
<a href="{{ route('shop.index') }}">お買い物を続ける</a> |
<a href="{{ route('checkout') }}" style="font-weight:bold; color:green;">購入手続きへ</a>
</form>
@endif
</body>
</html>③ checkout.blade.php(お届け先入力)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>お届け先入力</title>
<style>
body { font-family: sans-serif; background: #f4f4f4; margin: 20px; }
.form-group { margin-bottom: 15px; }
label { display: block; font-weight: bold; margin-bottom: 5px; }
input[type="text"] { width: 300px; padding: 8px; }
</style>
</head>
<body>
<h1>お届け先情報入力</h1>
<form action="{{ route('success') }}" method="POST">
@csrf
<div class="form-group">
<label>お名前</label>
<input type="text" name="customer_name" required>
</div>
<div class="form-group">
<label>ご住所</label>
<input type="text" name="address" required style="width:400px;">
</div>
<div class="form-group">
<label>電話番号</label>
<input type="text" name="tel" required>
</div>
<button type="submit" style="background: green; color: white; padding: 10px 20px; border: none; cursor: pointer;">注文を確定する</button>
</form>
<p><a href="{{ route('cart.index') }}">カートに戻る</a></p>
</body>
</html>④ success.blade.php(注文完了)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>注文完了</title>
<style>body { font-family: sans-serif; text-align: center; margin-top: 50px; }</style>
</head>
<body>
<h1>ご注文ありがとうございました!</h1>
<p>Laravelバックエンドでの注文処理(トランザクション)が正常に完了しました。</p>
<p><a href="{{ route('shop.index') }}">トップページへ戻る</a></p>
</body>
</html>7. 動作確認
① ブラウザからアクセス
ブラウザを開き、以下のURLへアクセスする。 👉 http://localhost/shop_app/public/
💡 注意ポイント Laravelの公開ディレクトリはプロジェクト直下の
publicフォルダであるため、URLの末尾に/public/が必要。
② 挙動の検証
- 商品一覧画面:
productsテーブルから取得したデータがBlade(index.blade.php)を介して正常に表示されるか確認。 - カート追加: 数量を指定して「カートに入れる」を押した際、セッションに保持され、
cart.blade.php(http://localhost/shop_app/public/cart)へ正しく遷移するか検証。 - 数量変更・削除: カート画面で数量の更新、および削除ボタンが正常に機能(ルート定義通りのPOST処理が実行)するか確認。
- 注文処理: お届け先入力から「注文を確定する」を実行し、DBのトランザクションが成功して完了画面(
success.blade.php)が表示されるか確認。 - DB確認: phpMyAdminを開き、
ordersおよびorder_detailsにレコードが正しく格納され、カートのセッションがクリアされているか最終確認して完了。



コメント