Laravel5.7, MySQL5.7 で見積書、および請求書を生成、管理するシステムを作成しています。
リレーション周辺の知見をいただきたく質問しました。
Laravel 職人のご意見をお聞かせください。
具体的には、見積モデルの「見積書発行日」時点での消費税モデルをどうリレーションするのか?自分が採用したアプローチの他にもっと良い方法はないのか?
という疑問です。
なぜ悩んでいるかを列挙すると、以下の通りです。
- リレーションはできるだけすっきりと書きたい。(可読性のため)
- トリッキーなことはしたくない。(Laravelの作法には従おう)
- 発行日が変更されたとき、正しい消費税率を関連づけたい。できれば発行日が変更されても「よしなに」税率も変更される。
- ビジネスロジックがブラックボックスになりがちだから、SQLのトリガーは使いたくない。
Model, migration
問題になる部分の関連 Modelは Estimate, Tax で、今のところ、以下のような構造です。
php
1<?php 2 3use Illuminate\Database\Migrations\Migration; 4use Illuminate\Database\Schema\Blueprint; 5use Illuminate\Support\Facades\Schema; 6 7class CreateEstimatesTable extends Migration 8{ 9 /** 10 * Run the migrations. 11 * 12 * @return void 13 */ 14 public function up() 15 { 16 Schema::create('estimates', function (Blueprint $table) { 17 $table->bigIncrements('id')->comment('ID'); 18 $table->unsignedBigInteger('company_id')->comment('会社ID'); 19 $table->unsignedBigInteger('client_id')->comment('顧客ID'); 20 $table->unsignedBigInteger('tax_id')->comment('税率ID'); 21 $table->string('estimate_number')->nullable()->comment('見積番号'); 22 $table->string('title')->comment('件名'); 23 $table->date('publish_dt')->comment('発行年月日'); 24 $table->date('expiration_dt')->nullable()->comment('見積有効期限'); 25 $table->string('condition')->nullable()->comment('支払条件'); 26 $table->string('extra', 1000)->nullable()->comment('備考'); 27 $table->timestamps(); 28 $table->softDeletes()->comment('削除日時'); 29 30 $table->foreign('company_id')->references('id')->on('companies'); 31 $table->foreign('client_id')->references('id')->on('clients'); 32 $table->foreign('tax_id')->references('id')->on('taxes'); 33 }); 34 } 35}
php
1<?php 2 3use Illuminate\Database\Migrations\Migration; 4use Illuminate\Database\Schema\Blueprint; 5use Illuminate\Support\Facades\Schema; 6 7class CreateTaxesTable extends Migration 8{ 9 /** 10 * Run the migrations. 11 * 12 * @return void 13 */ 14 public function up() 15 { 16 Schema::create('taxes', function (Blueprint $table) { 17 $table->bigIncrements('id')->comment('ID'); 18 $table->unsignedTinyInteger('rate')->comment('税率'); 19 $table->date('start_dt')->comment('開始年月日'); 20 $table->date('end_dt')->nullable()->comment('終了年月日'); 21 $table->timestamps(); 22 $table->softDeletes()->comment('削除日時'); 23 }); 24 } 25}
ここで、Estimate と Tax において、素のSQLであれば、estimates.tax_id を使わずとも、
sql
1SELECT * FROM estimates 2INNER JOIN taxes 3ON (estimates.publish_dt BETWEEN taxes.start_dt AND taxes.end_dt) 4 OR (estimates.publish_dt >= taxes.start_dt AND taxes.end_dt IS NULL)
と、リレーションを取ることが可能です。
コントローラーから見積書一覧を取得するときに、with
を使ってアクセスしたいので、
php
1<?php 2 3namespace App\Http\Controllers; 4 5use App\Models\Estimate; 6 7class EstimateController extends Controller 8{ 9 public function index() 10 { 11 $estimates = Estimate::with(['tax']) 12 ->orderByDesc('updated_at') 13 ->orderByDesc('id') 14 ->paginate(); 15 return view('estimates.index', compact('estimates')); 16 } 17}
まず最初に、このSQLを表現するリレーションを表現できないのか?
と調べましたが、もちろん以下の形では表現できません。
php
1<?php 2 3use Illuminate\Database\Eloquent\Model; 4 5class Estimate extends Model 6{ 7 public function tax(): BelongsTo 8 { 9 return $this->belongsTo(Tax::class, 'publish_dt'); // NG 10 } 11}
また、以下のように表現しても、できなくはないですが、
一覧表示するときにループの中で毎回SQLを発行してしまうので、良いアプローチではないと思います。
php
1<?php 2 3use Illuminate\Database\Eloquent\Model; 4 5class Estimate extends Model 6{ 7 public function tax() 8 { 9 return Tax::whereDate('start_dt', '<=', $this->publish_dt) 10 ->where(function($query){ 11 $query->whereDate('end_dt', '>=', $this->publish_dt) 12 ->orWhereNull('end_dt') 13 })->first(); 14 } 15}
暫定的に採用したアプローチ
estimates
にtax_id
を追加し、素直にbelongsTo でリレーションし、
EstimateObserver
クラスで creating
, updating
を定義しています。
php
1<?php 2 3namespace App\Observers; 4 5use App\Models\Estimate; 6use App\Models\Tax; 7 8class EstimateObserver 9{ 10 public function creating(Estimate $estimate) 11 { 12 if (is_null($estimate->publish_dt)) { 13 return; 14 } 15 $publish_dt = $estimate->publish_dt; 16 $estimate->tax_id = Tax::whereDate('start_dt', '<=', $publish_dt) 17 ->where(function ($query) use ($publish_dt) { 18 $query->whereNull('end_dt') 19 ->orWhereDate('end_dt', '>', $publish_dt); 20 })->first()->id; 21 } 22 23 public function updating(Estimate $estimate) 24 { 25 if (is_null($estimate->publish_dt)) { 26 return; 27 } 28 $publish_dt = $estimate->publish_dt; 29 $estimate->tax_id = Tax::whereDate('start_dt', '<=', $publish_dt) 30 ->where(function ($query) use ($publish_dt) { 31 $query->whereNull('end_dt') 32 ->orWhereDate('end_dt', '>', $publish_dt); 33 })->first()->id; 34 } 35}
回答3件
あなたの回答
tips
プレビュー