動的プロパティは、EloquentだけでなくCollectionもHigher Order Message(より上級のメッセージ?)という名前で颯爽と登場します。これも最初に見たら、こんなことできるの、という感じです。混乱してはいけません。Eloquentと同様に複雑なことをしないときのコード表現に便利なメソッドの短縮形です。

以下の前回の話も参照してください。
混乱してはいけないLaravelの動的プロパティ – Eloquent編

Collectionメソッドを動的プロパティとして扱う

Laravelのマニュアルと似たような例を使います。

ここでは、スポーツ選手のプレイヤーをクラス(Player)とします。クラスにはプロパティとして、名前、人気の投票数、そしてVIPかどうかの3つの項目があります。例としてtinkerで表示されるようにプロパティは皆publicとしています。さらに、投票数が500以上ならVIPを正とする関数も定義されています。

namespace App\Models;

Class Player
{
    function __construct(
        public string $name, // 名前
        public int $votes,  // 投票数
        public bool $isVip = false) // VIP?
    {
        //
    }

    public function markAsVip(): void
    {
        if ($this->votes >= 500) { // 500数以上ならVIPに設定
            $this->isVip = true;
        }
    }
}

まず、2つインスタンスを含むCollectionを作成します。

> use App\Models\Player;

> $players = collect([
    new Player("LeBron James", 500), 
    new Player("Stephen Curry", 400)
]);

= Illuminate\Support\Collection {#5066
    all: [
      App\Models\Player {#5096
        +name: "LeBron James",
        +votes: 500,
        +isVip: false,
      },
      App\Models\Player {#5067
        +name: "Stephen Curry",
        +votes: 400,
        +isVip: false,
      },
    ],
  }

Collectionに含まれるプレイヤーのインスタンスにおいて、人気票に基づいてVIPかどうかを決めるとします。

通常なら、以下のようにCollectionのeach()のメソッドを使い、それぞれのインスタンスにおいてmarkAsVip()をコールバックします。

> $players->each(fn (Player $player) => $player->markAsVip());

= Illuminate\Support\Collection {#5101
    all: [
      App\Models\Player {#5089
        +name: "LeBron James",
        +votes: 500,
        +isVip: true,
      },
      App\Models\Player {#5102
        +name: "Stephen Curry",
        +votes: 400,
        +isVip: false,
      },
    ],
  }

しかし、なんとこれと同じことが、Collectionの動的プロパティとして以下のように短縮して実行も可能なのです。

> $players->each->markAsVip();

= Illuminate\Support\Collection {#5101
    all: [
      App\Models\Player {#5089
        +name: "LeBron James",
        +votes: 500,
        +isVip: true,
      },
      App\Models\Player {#5102
        +name: "Stephen Curry",
        +votes: 400,
        +isVip: false,
      },
    ],
  }

凄いですね。

Collectionの他のメソッドも使えます。VIPだけのプレイヤーを抽出したいとすると、filter()を動的プロパティとします。、

> $players->filter->isVip;
= Illuminate\Support\Collection {#5102
    all: [
      App\Models\Player {#5089
        +name: "LeBron James",
        +votes: 500,
        +isVip: true,
      },
    ],
  }

もう1つ例を見てみましょう。今度は、sum()を使ってプレーヤーの総投票数の計算もできてしまいます。

> $players->sum->votes;
= 900

ちなみに、このような短縮形が使えるのは、Collectionのすべてのメソッドとはいきませんが、以下のメソッドの使用が現在可能です。

average, avg, contains, each, every, filter, first, flatMap, groupBy, keyBy, map, max, min, partition, reject, skipUntil, skipWhile, some, sortBy, sortByDesc, sum, takeUntil, takeWhile, unique

参照

混乱してはいけないLaravelの動的プロパティ – Eloquent編
混乱してはいけないLaravelの動的プロパティ – 裏側編
混乱してはいけないLaravelの動的プロパティ – PHP8.2で廃止となった編

By khino