Laravelは、シンプルかつ柔軟なWebアプリケーションフレームワークとして多くの開発現場で利用されています。本記事では、販売管理システムを題材に、Laravelを使った基本的なCRUDの実装から関連付けまで一連の流れをサンプルコード付きで解説します。初めてLaravelでの開発を行う方にもわかりやすいように、各ステップを丁寧に紹介しているので、ぜひ最後までご覧ください。
今回のサンプルでは、以下のような簡易的な販売管理システムを構築します。
これらの機能を通して、LaravelにおけるCRUD操作、モデル間のリレーション、Bladeによる画面作成の流れを学びます。
Laravelプロジェクトの新規作成は以下のコマンドで行えます。
composer create-project laravel/laravel sales-management
プロジェクト作成後、.env
ファイルでデータベース接続設定を行います。
envコピーする編集するDB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=sales_management
DB_USERNAME=root
DB_PASSWORD=secret
本記事で利用するテーブル構成は以下のとおりです。
products
テーブル customers
テーブル orders
テーブル order_details
テーブル 以下のようにMigrationを用意します(既にプロジェクトに含まれる database/migrations
フォルダに作成)。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->integer('price');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('products');
}
};
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::create('customers', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('customers');
}
};
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::create('orders', function (Blueprint $table) {
$table->id();
$table->foreignId('customer_id')->constrained('customers')->onDelete('cascade');
$table->date('order_date');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('orders');
}
};
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::create('order_details', function (Blueprint $table) {
$table->id();
$table->foreignId('order_id')->constrained('orders')->onDelete('cascade');
$table->foreignId('product_id')->constrained('products')->onDelete('cascade');
$table->integer('quantity');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('order_details');
}
};
作成したら、以下のコマンドでテーブルを作成します。
php artisan migrate
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
use HasFactory;
protected $fillable = [
'name',
'price'
];
public function orderDetails()
{
return $this->hasMany(OrderDetail::class);
}
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Customer extends Model
{
use HasFactory;
protected $fillable = [
'name',
'email'
];
public function orders()
{
return $this->hasMany(Order::class);
}
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Order extends Model
{
use HasFactory;
protected $fillable = [
'customer_id',
'order_date'
];
public function customer()
{
return $this->belongsTo(Customer::class);
}
public function orderDetails()
{
return $this->hasMany(OrderDetail::class);
}
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class OrderDetail extends Model
{
use HasFactory;
protected $fillable = [
'order_id',
'product_id',
'quantity'
];
public function order()
{
return $this->belongsTo(Order::class);
}
public function product()
{
return $this->belongsTo(Product::class);
}
}
<?php
namespace App\Http\Controllers;
use App\Models\Product;
use Illuminate\Http\Request;
class ProductController extends Controller
{
public function index()
{
$products = Product::all();
return view('products.index', compact('products'));
}
public function create()
{
return view('products.create');
}
public function store(Request $request)
{
$request->validate([
'name' => 'required',
'price' => 'required|integer|min:0'
]);
Product::create($request->all());
return redirect()->route('products.index')->with('success', '商品を登録しました');
}
public function show($id)
{
$product = Product::findOrFail($id);
return view('products.show', compact('product'));
}
public function edit($id)
{
$product = Product::findOrFail($id);
return view('products.edit', compact('product'));
}
public function update(Request $request, $id)
{
$request->validate([
'name' => 'required',
'price' => 'required|integer|min:0'
]);
$product = Product::findOrFail($id);
$product->update($request->all());
return redirect()->route('products.index')->with('success', '商品情報を更新しました');
}
public function destroy($id)
{
$product = Product::findOrFail($id);
$product->delete();
return redirect()->route('products.index')->with('success', '商品を削除しました');
}
}
<?php
namespace App\Http\Controllers;
use App\Models\Customer;
use Illuminate\Http\Request;
class CustomerController extends Controller
{
public function index()
{
$customers = Customer::all();
return view('customers.index', compact('customers'));
}
public function create()
{
return view('customers.create');
}
public function store(Request $request)
{
$request->validate([
'name' => 'required',
'email' => 'required|email|unique:customers'
]);
Customer::create($request->all());
return redirect()->route('customers.index')->with('success', '顧客を登録しました');
}
public function show($id)
{
$customer = Customer::findOrFail($id);
return view('customers.show', compact('customer'));
}
public function edit($id)
{
$customer = Customer::findOrFail($id);
return view('customers.edit', compact('customer'));
}
public function update(Request $request, $id)
{
$request->validate([
'name' => 'required',
'email' => 'required|email|unique:customers,email,' . $id
]);
$customer = Customer::findOrFail($id);
$customer->update($request->all());
return redirect()->route('customers.index')->with('success', '顧客情報を更新しました');
}
public function destroy($id)
{
$customer = Customer::findOrFail($id);
$customer->delete();
return redirect()->route('customers.index')->with('success', '顧客を削除しました');
}
}
<?php
namespace App\Http\Controllers;
use App\Models\Order;
use App\Models\Customer;
use App\Models\Product;
use App\Models\OrderDetail;
use Illuminate\Http\Request;
class OrderController extends Controller
{
public function index()
{
$orders = Order::with('customer')->get();
return view('orders.index', compact('orders'));
}
public function create()
{
$customers = Customer::all();
$products = Product::all();
return view('orders.create', compact('customers', 'products'));
}
public function store(Request $request)
{
$request->validate([
'customer_id' => 'required|exists:customers,id',
'order_date' => 'required|date',
'products.*.product_id' => 'required|exists:products,id',
'products.*.quantity' => 'required|integer|min:1'
]);
// Order作成
$order = Order::create([
'customer_id' => $request->customer_id,
'order_date' => $request->order_date,
]);
// OrderDetail作成
foreach ($request->products as $p) {
OrderDetail::create([
'order_id' => $order->id,
'product_id' => $p['product_id'],
'quantity' => $p['quantity']
]);
}
return redirect()->route('orders.index')->with('success', '受注を登録しました');
}
public function show($id)
{
$order = Order::with(['customer', 'orderDetails.product'])->findOrFail($id);
return view('orders.show', compact('order'));
}
public function edit($id)
{
$order = Order::with('orderDetails')->findOrFail($id);
$customers = Customer::all();
$products = Product::all();
return view('orders.edit', compact('order', 'customers', 'products'));
}
public function update(Request $request, $id)
{
$request->validate([
'customer_id' => 'required|exists:customers,id',
'order_date' => 'required|date',
'products.*.product_id' => 'required|exists:products,id',
'products.*.quantity' => 'required|integer|min:1'
]);
// Order更新
$order = Order::findOrFail($id);
$order->update([
'customer_id' => $request->customer_id,
'order_date' => $request->order_date,
]);
// 既存のOrderDetailを削除して再度作成(簡易実装)
$order->orderDetails()->delete();
foreach ($request->products as $p) {
OrderDetail::create([
'order_id' => $order->id,
'product_id' => $p['product_id'],
'quantity' => $p['quantity']
]);
}
return redirect()->route('orders.index')->with('success', '受注情報を更新しました');
}
public function destroy($id)
{
$order = Order::findOrFail($id);
$order->delete();
return redirect()->route('orders.index')->with('success', '受注を削除しました');
}
}
routes/web.php
にルートを追加します。
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ProductController;
use App\Http\Controllers\CustomerController;
use App\Http\Controllers\OrderController;
Route::get('/', function () {
return view('welcome');
});
// Resourceful Routes
Route::resource('products', ProductController::class);
Route::resource('customers', CustomerController::class);
Route::resource('orders', OrderController::class);
以下では簡易的に代表的なテンプレート例を示します。フォルダ構成は resources/views/
配下に layouts
, products
, customers
, orders
フォルダを作成してそこに配置します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>販売管理システム</title>
<link rel="stylesheet" href="{{ asset('css/app.css') }}">
</head>
<body>
<nav>
<ul>
<li><a href="{{ route('products.index') }}">商品管理</a></li>
<li><a href="{{ route('customers.index') }}">顧客管理</a></li>
<li><a href="{{ route('orders.index') }}">受注管理</a></li>
</ul>
</nav>
<div class="container">
@if(session('success'))
<div style="color: green;">{{ session('success') }}</div>
@endif
@yield('content')
</div>
</body>
</html>
@extends('layouts.app')
@section('content')
<h1>商品一覧</h1>
<a href="{{ route('products.create') }}">新規商品登録</a>
<table border="1">
<tr>
<th>ID</th>
<th>商品名</th>
<th>価格</th>
<th>操作</th>
</tr>
@foreach($products as $product)
<tr>
<td>{{ $product->id }}</td>
<td><a href="{{ route('products.show', $product->id) }}">{{ $product->name }}</a></td>
<td>{{ $product->price }}</td>
<td>
<a href="{{ route('products.edit', $product->id) }}">編集</a>
<form action="{{ route('products.destroy', $product->id) }}" method="POST" style="display:inline;">
@csrf
@method('DELETE')
<button type="submit" > </form>
</td>
</tr>
@endforeach
</table>
@endsection
@extends('layouts.app')
@section('content')
<h1>商品登録</h1>
<form action="{{ route('products.store') }}" method="POST">
@csrf
<div>
<label>商品名</label>
<input type="text" name="name" value="{{ old('name') }}">
@error('name')
<div style="color:red;">{{ $message }}</div>
@enderror
</div>
<div>
<label>価格</label>
<input type="number" name="price" value="{{ old('price') }}">
@error('price')
<div style="color:red;">{{ $message }}</div>
@enderror
</div>
<button type="submit">登録</button>
</form>
@endsection
@extends('layouts.app')
@section('content')
<h1>商品編集</h1>
<form action="{{ route('products.update', $product->id) }}" method="POST">
@csrf
@method('PUT')
<div>
<label>商品名</label>
<input type="text" name="name" value="{{ old('name', $product->name) }}">
@error('name')
<div style="color:red;">{{ $message }}</div>
@enderror
</div>
<div>
<label>価格</label>
<input type="number" name="price" value="{{ old('price', $product->price) }}">
@error('price')
<div style="color:red;">{{ $message }}</div>
@enderror
</div>
<button type="submit">更新</button>
</form>
@endsection
@extends('layouts.app')
@section('content')
<h1>顧客一覧</h1>
<a href="{{ route('customers.create') }}">新規顧客登録</a>
<table border="1">
<tr>
<th>ID</th>
<th>顧客名</th>
<th>メールアドレス</th>
<th>操作</th>
</tr>
@foreach($customers as $customer)
<tr>
<td>{{ $customer->id }}</td>
<td><a href="{{ route('customers.show', $customer->id) }}">{{ $customer->name }}</a></td>
<td>{{ $customer->email }}</td>
<td>
<a href="{{ route('customers.edit', $customer->id) }}">編集</a>
<form action="{{ route('customers.destroy', $customer->id) }}" method="POST" style="display:inline;">
@csrf
@method('DELETE')
<button type="submit" > </form>
</td>
</tr>
@endforeach
</table>
@endsection
@extends('layouts.app')
@section('content')
<h1>顧客登録</h1>
<form action="{{ route('customers.store') }}" method="POST">
@csrf
<div>
<label>顧客名</label>
<input type="text" name="name" value="{{ old('name') }}">
@error('name')
<div style="color:red;">{{ $message }}</div>
@enderror
</div>
<div>
<label>メールアドレス</label>
<input type="email" name="email" value="{{ old('email') }}">
@error('email')
<div style="color:red;">{{ $message }}</div>
@enderror
</div>
<button type="submit">登録</button>
</form>
@endsection
@extends('layouts.app')
@section('content')
<h1>顧客編集</h1>
<form action="{{ route('customers.update', $customer->id) }}" method="POST">
@csrf
@method('PUT')
<div>
<label>顧客名</label>
<input type="text" name="name" value="{{ old('name', $customer->name) }}">
@error('name')
<div style="color:red;">{{ $message }}</div>
@enderror
</div>
<div>
<label>メールアドレス</label>
<input type="email" name="email" value="{{ old('email', $customer->email) }}">
@error('email')
<div style="color:red;">{{ $message }}</div>
@enderror
</div>
<button type="submit">更新</button>
</form>
@endsection
@extends('layouts.app')
@section('content')
<h1>受注一覧</h1>
<a href="{{ route('orders.create') }}">新規受注登録</a>
<table border="1">
<tr>
<th>ID</th>
<th>顧客名</th>
<th>受注日</th>
<th>操作</th>
</tr>
@foreach($orders as $order)
<tr>
<td>{{ $order->id }}</td>
<td>{{ $order->customer->name }}</td>
<td>{{ $order->order_date }}</td>
<td>
<a href="{{ route('orders.show', $order->id) }}">詳細</a>
<a href="{{ route('orders.edit', $order->id) }}">編集</a>
<form action="{{ route('orders.destroy', $order->id) }}" method="POST" style="display:inline;">
@csrf
@method('DELETE')
<button type="submit" > </form>
</td>
</tr>
@endforeach
</table>
@endsection
@extends('layouts.app')
@section('content')
<h1>新規受注登録</h1>
<form action="{{ route('orders.store') }}" method="POST">
@csrf
<div>
<label>顧客</label>
<select name="customer_id">
<option value="">選択してください</option>
@foreach($customers as $customer)
<option value="{{ $customer->id }}" {{ old('customer_id') == $customer->id ? 'selected' : '' }}>
{{ $customer->name }}
</option>
@endforeach
</select>
@error('customer_id')
<div style="color:red;">{{ $message }}</div>
@enderror
</div>
<div>
<label>受注日</label>
<input type="date" name="order_date" value="{{ old('order_date') }}">
@error('order_date')
<div style="color:red;">{{ $message }}</div>
@enderror
</div>
<h3>商品情報</h3>
<div id="product-list">
<div class="product-row">
<select name="products[0][product_id]">
<option value="">商品を選択</option>
@foreach($products as $product)
<option value="{{ $product->id }}">{{ $product->name }} (¥{{ $product->price }})</option>
@endforeach
</select>
<input type="number" name="products[0][quantity]" placeholder="数量" min="1" value="1">
</div>
</div>
<button type="button" > <button type="submit">登録</button>
</form>
<script>
let productIndex = 1;
function addProductRow() {
const productList = document.getElementById('product-list');
const newRow = document.createElement('div');
newRow.className = 'product-row';
newRow.innerHTML = `
<select name="products[${productIndex}][product_id]">
<option value="">商品を選択</option>
@foreach($products as $product)
<option value="{{ $product->id }}">{{ $product->name }} (¥{{ $product->price }})</option>
@endforeach
</select>
<input type="number" name="products[${productIndex}][quantity]" placeholder="数量" min="1" value="1">
`;
productList.appendChild(newRow);
productIndex++;
}
</script>
@endsection
@extends('layouts.app')
@section('content')
<h1>受注編集</h1>
<form action="{{ route('orders.update', $order->id) }}" method="POST">
@csrf
@method('PUT')
<div>
<label>顧客</label>
<select name="customer_id">
<option value="">選択してください</option>
@foreach($customers as $customer)
<option value="{{ $customer->id }}"
{{ old('customer_id', $order->customer_id) == $customer->id ? 'selected' : '' }}>
{{ $customer->name }}
</option>
@endforeach
</select>
@error('customer_id')
<div style="color:red;">{{ $message }}</div>
@enderror
</div>
<div>
<label>受注日</label>
<input type="date" name="order_date" value="{{ old('order_date', $order->order_date) }}">
@error('order_date')
<div style="color:red;">{{ $message }}</div>
@enderror
</div>
<h3>商品情報</h3>
<div id="product-list">
@php $i = 0; @endphp
@foreach($order->orderDetails as $detail)
<div class="product-row">
<select name="products[{{ $i }}][product_id]">
<option value="">商品を選択</option>
@foreach($products as $product)
<option value="{{ $product->id }}"
{{ $detail->product_id == $product->id ? 'selected' : '' }}>
{{ $product->name }} (¥{{ $product->price }})
</option>
@endforeach
</select>
<input type="number" name="products[{{ $i }}][quantity]" placeholder="数量" min="1" value="{{ $detail->quantity }}">
</div>
@php $i++; @endphp
@endforeach
</div>
<button type="button" > <button type="submit">更新</button>
</form>
<script>
let productIndex = {{ $i }};
function addProductRow() {
const productList = document.getElementById('product-list');
const newRow = document.createElement('div');
newRow.className = 'product-row';
newRow.innerHTML = `
<select name="products[${productIndex}][product_id]">
<option value="">商品を選択</option>
@foreach($products as $product)
<option value="{{ $product->id }}">{{ $product->name }} (¥{{ $product->price }})</option>
@endforeach
</select>
<input type="number" name="products[${productIndex}][quantity]" placeholder="数量" min="1" value="1">
`;
productList.appendChild(newRow);
productIndex++;
}
</script>
@endsection
@extends('layouts.app')
@section('content')
<h1>受注詳細</h1>
<p><strong>受注番号:</strong> {{ $order->id }}</p>
<p><strong>顧客名:</strong> {{ $order->customer->name }}</p>
<p><strong>受注日:</strong> {{ $order->order_date }}</p>
<h3>商品一覧</h3>
<table border="1">
<tr>
<th>商品名</th>
<th>価格</th>
<th>数量</th>
</tr>
@foreach($order->orderDetails as $detail)
<tr>
<td>{{ $detail->product->name }}</td>
<td>{{ $detail->product->price }}</td>
<td>{{ $detail->quantity }}</td>
</tr>
@endforeach
</table>
<p><a href="{{ route('orders.index') }}">一覧に戻る</a></p>
@endsection
php artisan serve
http://127.0.0.1:8000/products
を開く本記事では、Laravelを使ってシンプルな販売管理システムを構築する流れを紹介しました。Migrationでテーブルを設計し、Modelでリレーションを定義、Controllerでビジネスロジックを実装し、最後にBladeテンプレートで画面を作成するというLaravelの基本的な流れを踏んでいます。
ここでは簡易的な実装例を示していますが、実際の業務ではバリデーションをより厳密に行ったり、在庫管理や決済機能などを追加したりと、さらに多くの機能を検討する必要があります。Laravelは拡張性が高いフレームワークなので、本記事を参考に自分の要件に合わせたカスタマイズを行ってみてください。
以上で、Laravelを用いた販売管理システムの基本実装について解説を終わります。ぜひ学習や業務の参考にしていただければ幸いです。