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を作成しない分だけ効率かもしれません。

By khino