Collectionを使っての合計の計算は簡単です。例えば、注文の総個数の計算は$collection->sum('quantity')です。金額と個数の掛け算で総合計金額の計算はどうでしょう。直感的に$collection->sum('price*quantity')では、と思いますがそれはうまくいきません。さて、どう計算するのでしょう?
データの準備
準備として、まず以下migrationファイルを作成して、DBテーブルを作成します。
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateOrderItemsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('order_items', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('order_id')->index();
            $table->string('name');
            $table->decimal('price', 8, 0);
            $table->unsignedInteger('quantity');
            $table->timestamps();
        });
    }
    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('order_items');
    }
}
今度は、factoryの作成です。
/** @var \Illuminate\Database\Eloquent\Factory $factory */
use App\OrderItem;
use Faker\Generator as Faker;
use Illuminate\Support\Str;
$factory->define(OrderItem::class, function (Faker $faker) {
    return [
        'order_id' => $faker->numberBetween(1, 3),
        'name' => Str::upper($faker->word()),
        'price' => $faker->numberBetween(100, 1000),
        'quantity' => $faker->numberBetween(1, 10),
    ];
});
さて、次にtinkerでサンプルデータを作成します。
>>> factory(App\OrderItem::class, 9)->create();
=> Illuminate\Database\Eloquent\Collection {#4246
     all: [
       App\OrderItem {#4247
         id: 1,
         order_id: 1,
         name: "BEATAE",
         price: "924",
         quantity: 10,
         created_at: "2021-06-26 19:58:26",
         updated_at: "2021-06-26 19:58:26",
       },
       App\OrderItem {#4248
         id: 2,
         order_id: 1,
...
作成されたデータをmysqlで閲覧するとこんな感じです。
mysql> select id, order_id, price, quantity from order_items where order_id = 1; +----+----------+-------+----------+ | id | order_id | price | quantity | +----+----------+-------+----------+ | 1 | 1 | 924 | 10 | | 2 | 1 | 157 | 8 | | 3 | 1 | 294 | 7 | | 4 | 1 | 839 | 2 | | 6 | 1 | 450 | 6 | | 8 | 1 | 310 | 9 | +----+----------+-------+----------+
ちなみに、sqlでは先の合計金額の計算は、
mysql> select sum(price * quantity) from order_items where order_id = 1; +-----------------------+ | sum(price * quantity) | +-----------------------+ | 19722 | +-----------------------+
と簡単に計算できます。
合計金額の計算
データが揃ったところでCollectionを利用して計算です。
最初に、Eloquentで注文番号1のアイテムのCollectionを作成します。
>>> use App\OrdreItem;
>>> $items = OrderItem::where('order_id', '=', 1)->get();
=> Illuminate\Database\Eloquent\Collection {#3317
     all: [
       App\OrderItem {#4249
         id: 1,
         order_id: 1,
         name: "BEATAE",
         price: "924",
         quantity: 10,
         created_at: "2021-06-26 19:58:26",
         updated_at: "2021-06-26 19:58:26",
       },
       App\OrderItem {#4103
         id: 2,
         order_id: 1,
...
試しに、先の間違った合計金額の計算を実行してみましょうか。さて、どうなるか。
>>> >>> $items->sum('price*quantity');
=> 0
もちろん、sum()の引数は項目名を指定しなければならないので、’price*quantity’ような項目名はありもしないので合計はゼロです。
正しい方法としては、map()とsum()の2つの関数を利用します。それぞれのアイテムの合計金額のCollectionを作成してその和を計算です。
>>> $items->map(function($item) { return $item->price * $item->quantity; })->sum();
=> 19722
ちょっと長くなったけれどわかりやすい計算です。
この他には、reduce()を使用した計算もあります。
>>> $items->reduce(function($carry, $item) { return $carry + $item->price * $item->quantity;}, 0);
=> 19722
reduce()で使われる$carryは第2の引数として与えらた値(この場合ゼロ)を初期値としてループの中で計算(金額x個数)した値を前回の$carryに足していきます。こちらの方法も悪くはないですね。前のmapに比べては余計なCollectionを作成しない分だけ効率かもしれません。
