Laravel关联:联动创建Order与OrderItem工厂并计算订单总价
如何同时创建并填充互相依赖的Order和OrderItem表?
创建OrderItem时需要先获取Order的ID,但Order的total_price字段又依赖OrderItem的数据,这两张表需要同时创建并填充数据,该怎么实现?
我的现有代码
Order Factory
<?php namespace Database\Factories; use Illuminate\Database\Eloquent\Factories\Factory; use App\Models\User; use App\Models\OrderItem; /** * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Order> */ class OrderFactory extends Factory { /** * Define the model's default state. * * @return array<string, mixed> */ public function definition() { return [ 'id' => fake()->numerify('######'), 'user_id' => User::inRandomOrder()->first()->id, 'status' => 'paid', 'total_price' => OrderItem::where('user_id', 'user_id')->sum('unit_price'), ]; } }
OrderItem Factory
<?php namespace Database\Factories; use Illuminate\Database\Eloquent\Factories\Factory; use App\Models\Order; use App\Models\Product; use App\Models\User; /** * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\OrderItem> */ class OrderItemFactory extends Factory { /** * Define the model's default state. * * @return array<string, mixed> */ public function definition() { $product = Product::inRandomOrder()->first(); return [ 'order_id' => Order::inRandomOrder()->first()->id, 'user_id' => User::inRandomOrder()->first()->id, 'product_id' => $product->id, 'quantity' => mt_rand(1, 10), 'unit_price' => $product->price, ]; } }
我目前的可行方案(寻求更优解)
OrderSeeder
public function run() { for($i = 0; $i < 15; $i++) { $order = Order::factory()->create(); $orderItems = OrderItem::factory()->count(3)->for($order)->create(); $orderTotal = 0; foreach ($orderItems as $orderItem) { $orderTotal = $orderTotal + ($orderItem->quantity*$orderItem->unit_price); } $order->total_price = $orderTotal; $order->save(); } }
优化方案
方案1:利用Factory的回调逻辑简化流程
修改OrderFactory,通过afterCreating回调自动生成关联的OrderItem并计算总价,同时调整OrderItemFactory保证数据一致性。
修改后的OrderFactory
<?php namespace Database\Factories; use Illuminate\Database\Eloquent\Factories\Factory; use App\Models\User; /** * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Order> */ class OrderFactory extends Factory { public function definition() { return [ 'id' => fake()->numerify('######'), 'user_id' => User::inRandomOrder()->first()->id, 'status' => 'paid', 'total_price' => 0, // 先初始化0,后续自动计算 ]; } // 添加创建后的回调逻辑 public function configure() { return $this->afterCreating(function ($order) { // 生成3个关联当前订单的OrderItem,确保user_id和订单一致 $orderItems = OrderItem::factory() ->count(3) ->for($order) ->create(['user_id' => $order->user_id]); // 计算并更新订单总价 $totalPrice = $orderItems->sum(fn($item) => $item->quantity * $item->unit_price); $order->update(['total_price' => $totalPrice]); }); } }
修改后的OrderItemFactory
<?php namespace Database\Factories; use Illuminate\Database\Eloquent\Factories\Factory; use App\Models\Order; use App\Models\Product; use App\Models\User; /** * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\OrderItem> */ class OrderItemFactory extends Factory { public function definition() { $product = Product::inRandomOrder()->first(); return [ // 不再硬编码order_id,由for()方法自动关联 'user_id' => function (array $attributes) { // 若关联了订单,直接使用订单的user_id,避免数据不一致 if (isset($attributes['order_id'])) { return Order::find($attributes['order_id'])->user_id; } // 兜底逻辑:随机取用户 return User::inRandomOrder()->first()->id; }, 'product_id' => $product->id, 'quantity' => mt_rand(1, 10), 'unit_price' => $product->price, ]; } }
简化后的OrderSeeder
public function run() { // 直接创建15个订单,每个订单会自动生成3个OrderItem并计算总价 Order::factory()->count(15)->create(); }
方案2:使用模型观察者实现自动更新
如果希望业务逻辑和Factory解耦,可以给OrderItem添加观察者,当OrderItem创建/修改/删除时自动更新关联Order的总价。
创建OrderItem观察者
<?php namespace App\Observers; use App\Models\OrderItem; class OrderItemObserver { public function created(OrderItem $orderItem) { $this->updateOrderTotal($orderItem); } public function updated(OrderItem $orderItem) { $this->updateOrderTotal($orderItem); } public function deleted(OrderItem $orderItem) { $this->updateOrderTotal($orderItem); } // 封装总价计算逻辑 protected function updateOrderTotal(OrderItem $orderItem) { $order = $orderItem->order; $totalPrice = $order->orderItems->sum(fn($item) => $item->quantity * $item->unit_price); $order->update(['total_price' => $totalPrice]); } }
注册观察者(在AppServiceProvider中)
<?php namespace App\Providers; use App\Models\OrderItem; use App\Observers\OrderItemObserver; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { public function register(): void { // } public function boot(): void { OrderItem::observe(OrderItemObserver::class); } }
之后只需要按方案1修改Factory的基础定义,Seeder同样可以简化为直接创建指定数量的订单,观察者会自动维护总价的一致性。
内容的提问来源于stack exchange,提问作者Tilda Mendozae
相关产品推荐
相关产品推荐

