検索や登録などのDB操作をモデルに切り出したいです。
解決したいこと
自動販売機の売上管理システムをつくっています。
検索や登録などの操作をコントローラーに全て書いていたのですが、
そういったDB操作をモデルに切り出そうと考えているのですが、
いまいち書き方が分からず行き詰まっています。
また、新規登録・更新・削除処理にはDBトランザクションを使用したいのですが、
書き方的にこれでいいのかも見ていただけたら幸いです。
なにかアドバイスや解決策があればご教示ください。
該当するソースコード
<?php
namespace App\Http\Controllers;
use App\Models\Product;
use App\Models\Company;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class ProductController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
{
$query = Product::query();
if($search = $request->search) {
$query->where('product_name', 'LIKE', "%{$search}%");
}
if($company_id = $request->company_id) {
$query->where('company_name', 'LIKE', "%{$company_name}%");
}
$products = $query->paginate(10);
return view('products.index', compact('products'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
$companies = Company::all();
return view('products.create', compact('companies'));
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(ProductStoreRequest $request)
{
$product = new Product([
'product_name' => $request->get('product_name'),
'company_id' => $request->get('company_id'),
'price' => $request->get('price'),
'stock' => $request->get('stock'),
'comment' => $request->get('comment'),
]);
if($request->hasFile('img_path')){
$filename = $request->img_path->getClientOriginalName();
$filePath = $request->img_path->storeAs('products', $filename, 'public');
$product->img_path = '/storage/' . $filePath;
}
DB::transaction(function () use($request) {
Product::store([
'product_name' => $request->product_name,
'company_id' => $request->company_id,
'price' => $request->price,
'stock' => $request->stock,
'comment' => $request->comment,
'img_path' => $request->img_path,
]);
});
$product->save();
return redirect('products');
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show(Product $product)
{
$products = Product::all();
return view('products.show', compact('product'));
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit(Product $product)
{
$companies = Company::all();
return view('products.edit', compact('product', 'companies'));
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(ProductUpdateRequest $request, Product $product)
{
// $product->product_name = $request->product_name;
// $product->price = $request->price;
// $product->stock = $request->stock;
// $product->save();
DB::transaction(function () use($request) {
Product::update([
'product_name' => $request->product_name,
'price' => $request->price,
'stock' => $request->stock,
]);
});
return redirect()->route('products.index')
->with('success', 'Product updated successfully');
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy(Product $product)
{
$product->delete();
DB::transaction(function () use($request) {
Product::delete([
'product_name' => $request->product_name,
'company_id' => $request->company_id,
'price' => $request->price,
'stock' => $request->stock,
'comment' => $request->comment,
'img_path' => $request->img_path,
]);
});
return redirect('/products');
}
}
ルーティング
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ProductController;
// use Illuminate\Support\Fecades\Auth;
Route::get('/', function () {
if(Auth::check()) {
return redirect()->route('products.index');
} else {
return redirect()->route('login');
}
});
Auth::routes();
Route::group(['middleware' => 'auth'], function() {
Route::resource('products', ProductController::class);
});
モデル
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
use HasFactory;
protected $table = 'products';
protected $fillable = [
'product_name',
'price',
'stock',
'company_id',
'comment',
'img_path',
];
public function sales() {
return $this->hasMany(Sale::class);
}
public function company() {
return $this->belongsTo(Company::class);
}
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Sale extends Model
{
use HasFactory;
protected $table = 'sales';
public function product() {
return $this->belongsTo(Product::class);
}
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Company extends Model
{
use HasFactory;
protected $table = 'companies';
}
Views
@extends('layouts.app')
@section('content')
<div class="container">
<h1 class="mb-4">商品一覧画面</h1>
<div class="search mt-5">
<form action="{{ route('products.index') }}" method="GET" class="row g-3">
<div class="col-sm-12 col-md-3">
<input type="text" class="form-control" placeholder="検索キーワード" name="search" value="@if (isset($search)) {{ $search }} @endif">
</div>
<div class="col-sm-12 col-md-3">
<input type="text" class="form-control" placeholder="メーカー名" name="company_name" value="@if (isset($company_name)) {{ $company_name }} @endif">
</div>
<div class="col-sm-12 col-md-1">
<button class="btn btn-outline-secondary" type="submit">検索</button>
</div>
</form>
</div>
<div class="products mt-5">
<table class="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>商品画像</th>
<th>商品名</th>
<th>価格</th>
<th>在庫数</th>
<th>メーカー名</th>
<th><a href="{{ route('products.create') }}" class="btn btn-warning">新規登録</a></th>
</tr>
</thead>
<tbody>
@foreach ($products as $product)
<tr>
<td>{{ $product->id }}</td>
<td><img src="{{ asset($product->img_path) }}" alt="商品画像" width="100"></td>
<td>{{ $product->product_name }}</td>
<td>{{ $product->price }}</td>
<td>{{ $product->stock }}</td>
<td>{{ $product->company->company_name }}</td>
<td>
<a href="{{ route('products.show', $product) }}" class="btn btn-info btn-sm mx-1">詳細</a>
<form method="POST" action="{{ route('products.destroy', $product) }}" class="d-inline">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger btn-sm mx-1">削除</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</form>
{{ $products->appends(request()->query())->links() }}
</div>
@endsection
@extends('layouts.app')
@section('content')
<div class="container">
<h1 class="mb-4">商品新規登録画面</h1>
@if($errors->any())
<div class="alert alert-danger mt-3">
<ul>
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form method="POST" action="{{ route('products.store') }}" enctype="multipart/form-data">
@csrf
<div class="mb-3">
<label for="product_name" class="form-label">商品名<span class="text-danger">*</span></label>
<input id="product_name" type="text" name="product_name" class="form-control" required>
</div>
<div class="mb-3">
<label for="company_id" class="form-label">メーカー<span class="text-danger">*</span></label>
<select class="form-select" id="company_id" name="company_id">
@foreach($companies as $company)
<option value="{{ $company->id }}">{{ $company->company_name }}</option>
@endforeach
</select>
</div>
<div class="mb-3">
<label for="price" class="form-label">価格<span class="text-danger">*</span></label>
<input id="price" type="text" name="price" class="form-control" required>
</div>
<div class="mb-3">
<label for="stock" class="form-label">在庫数<span class="text-danger">*</span></label>
<input id="stock" type="text" name="stock" class="form-control" required>
</div>
<div class="mb-3">
<label for="comment" class="form-label">コメント</label>
<textarea id="comment" name="comment" class="form-control" rows="3" required></textarea>
</div>
<div class="mb-3">
<label for="img_path" class="form-label">商品画像</label>
<input id="img_path" type="file" name="img_path" class="form-control" required>
</div>
<div class="container mt-4">
<div>
<button type="submit" class="btn btn-warning">新規登録</button>
<button type="button" class="border border-0"><a href="{{ route('products.index') }}" class="btn btn-primary">戻る</a></button>
</div>
</div>
</form>
</div>
@endsection
@extends('layouts.app')
@section('content')
<div class="container">
<h1 class="mb-4">商品情報詳細画面</h1>
<dl class="row mt-3" >
<dt class="col-sm-3">ID</dt>
<dd class="col-sm-9">{{ $product->id }}</dd>
<dt class="col-sm-3">商品画像</dt>
<dd class="col-sm-9">{{ $product->product_name }}</dd>
<dt class="col-sm-3">メーカー名</dt>
<dd class="col-sm-9">{{ $product->company->name }}</dd>
<dt class="col-sm-3">価格</dt>
<dd class="col-sm-9">{{ $product->price }}</dd>
<dt class="col-sm-3">在庫数</dt>
<dd class="col-sm-9">{{ $product->stock }}</dd>
<dt class="col-sm-3">コメント</dt>
<dd class="col-sm-9">{{ $product->comment }}</dd>
<dt class="col-sm-3">商品画像</dt>
<dd class="col-sm-9"><img src="{{ asset($product->img_path) }}" width="300"></dd>
</dl>
<div class="container mt-4">
<div>
<button type="button" class="border border-0"><a href="{{ route('products.edit', $product) }}" class="btn btn-warning">編集</a></button>
<button type="button" class="border border-0"><a href="{{ route('products.index') }}" class="btn btn-primary ml-5">戻る</a></button>
</div>
</div>
</div>
@endsection
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header"><h2>商品情報編集画面</h2></div>
@if($errors->any())
<div class="alert alert-danger mt-3">
<ul>
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="card-body">
<form method="POST" action="{{ route('products.update', $product) }}" enctype="multipart/form-data">
@csrf
@method('PUT')
<div class="mb-3">
<label for="id" class="form-label">ID.</label>
<p>{{ $product->id }}</p>
</div>
<div class="mb-3">
<label for="product_name" class="form-label">商品名<span class="text-danger">*</span></label>
<input type="text" class="form-control" id="product_name" name="product_name" value="{{ $product->product_name }}" required>
</div>
<div class="mb-3">
<label for="company_id" class="form-label">メーカー名<span class="text-danger">*</span></label>
<select class="form-select" id="company_id" name="company_id">
@foreach($companies as $company)
<option value="{{ $company->id }}" {{ $product->company_id == $company->id ? 'selected' : '' }}>{{ $company->company_name }}</option>
@endforeach
</select>
</div>
<div class="mb-3">
<label for="price" class="form-label">価格<span class="text-danger">*</span></label>
<input type="number" class="form-control" id="price" name="price" value="{{ $product->price }}" required>
</div>
<div class="mb-3">
<label for="stock" class="form-label">在庫数<span class="text-danger">*</span></label>
<input type="number" class="form-control" id="stock" name="stock" value="{{ $product->stock }}" required>
</div>
<div class="mb-3">
<label for="comment" class="form-label">コメント</label>
<textarea id="comment" name="comment" class="form-control" rows="3">{{ $product->comment }}</textarea>
</div>
<div class="mb-3">
<label for="img_path" class="form-label">商品画像:</label>
<input id="img_path" type="file" name="img_path" class="form-control">
<img src="{{ asset($product->img_path) }}" alt="商品画像" class="product-image">
</div>
<div class="container mt-4">
<button type="submit" class="btn btn-warning">更新</button>
<button type="button" class="border border-0"><a href="{{ route('products.index') }}" class="btn btn-primary">戻る</a></button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
作りたいものが下記記事のものと酷似していたので、
このコードは本記事を基に作成しました。
本記事とテーブルとカラムは全く同じです。