コレクション
配列を便利に扱う入れ物「コレクション」の作り方と、全メソッドの一覧(取り出す・絞る・変える・並べる・集計する)、大量データ向けの遅延コレクションを説明します。
コレクションは、配列(値の並び)を便利に扱うための入れ物です。Illuminate\Support\Collection というクラスで、「並べかえる」「絞りこむ」「合計を出す」といった作業を、メソッド(クラスの中の関数)として用意してくれます。メソッドは続けて書けます。たとえば「名前を大文字にして、からっぽのものを取りのぞく」は、次のように1本の流れで書けます。
$collection = collect(['Taylor', 'Abigail', null])->map(function (?string $name) {
return strtoupper($name);
})->reject(function (string $name) {
return empty($name);
});
ふつうのコレクションは「元のものを書きかえない」のが基本です。メソッドを呼ぶと、新しいコレクションが返ってきます(元のコレクションはそのまま残ります)。ただし、例外のメソッドがいくつかあります(下の「元のコレクションを書きかえるメソッド」の表)。
コレクションを作る#
collect ヘルパー関数(どこからでも呼べる便利な関数)に配列を渡すと、コレクションができます。
$collection = collect([1, 2, 3]);
Collection::make や Collection::fromJson(JSON の文字列から作る)でも作れます。
補足
Eloquent(モデルを扱うしくみ)で取り出した結果は、いつも Collection として返ってきます。
メソッドを足す(マクロ)#
コレクションは「マクロ」に対応していて、自分で作ったメソッドを実行中に足せます。macro メソッドに、呼ばれたときに動かす関数(クロージャ)を渡します。関数の中では $this で、コレクションのほかのメソッドを呼べます。
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
Collection::macro('toUpper', function () {
return $this->map(function (string $value) {
return Str::upper($value);
});
});
$collection = collect(['first', 'second']);
$upper = $collection->toUpper();
// ['FIRST', 'SECOND']
マクロは、ふつうサービスプロバイダ(アプリの起動のときに、道具箱へ道具を登録する場所)の boot メソッドの中で登録します。
引数を受け取るマクロも作れます。
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Lang;
Collection::macro('toLocale', function (string $locale) {
return $this->map(function (string $value) use ($locale) {
return Lang::get($value, [], $locale);
});
});
$collection = collect(['first', 'second']);
$translated = $collection->toLocale('es');
// ['primero', 'segundo'];
メソッド一覧を読む前に#
次の言葉は、一覧の中で何度も出てきます。
- キーと値:連想配列(名前と値の組の並び)の「名前」がキー、中身が値です。
['name' => 'Desk']なら、nameがキー、Deskが値です - ゆるい比較・きびしい比較:ゆるい比較では、
"4"(文字列)と4(数字)を同じものとみなします。きびしい比較(Strict)では、型もそろっていないと同じとみなしません。名前にStrictが付くメソッドが、きびしい比較の版です - 真偽テスト:値を受け取って
trueかfalseを返す関数のことです。「この条件に合うか」を決めるために渡します - エイリアス:同じ働きの別名です
- 静的メソッド:
Collection::make()のように、クラス名と::で直接呼ぶメソッドです - 例外を投げる:「これ以上は進めない」というエラーの知らせを出して、処理を止めることです。
ItemNotFoundExceptionのような名前が、そのエラーの種類です - コンストラクタ:クラスからオブジェクトを作るときに、最初に動くメソッドです
メソッド一覧#
すべてのメソッドを、目的ごとの表にまとめます。あとの節に、よく使うものの例を載せます。
作る・変換して取り出す#
| 名前 | 説明 |
|---|---|
make |
新しいコレクションを作る(静的メソッド。Collection::make([1, 2, 3])) |
fromJson |
JSON の文字列を json_decode して、新しいコレクションを作る(静的メソッド) |
wrap |
値がコレクションや配列でなければ、包んでコレクションにする(静的メソッド) |
unwrap |
コレクションなら、中の配列を取り出す。そうでなければそのまま返す(静的メソッド) |
times |
関数を指定の回数だけ呼び、その結果でコレクションを作る(静的メソッド) |
range |
指定の範囲の整数が入ったコレクションを作る |
collect |
同じ中身の、新しい Collection を返す。遅延コレクションをふつうのコレクションに変えるときに使う |
lazy |
同じ中身の遅延コレクション(LazyCollection)を返す |
macro |
コレクションに自分のメソッドを足す(静的メソッド。上の「メソッドを足す」を参照) |
all |
コレクションの中身の配列を、そのまま返す |
toArray |
ふつうの配列に変える。中の Eloquent モデルなども配列に変わる |
toJson |
JSON の文字列に変える |
toPrettyJson |
読みやすく整形した JSON の文字列に変える(JSON_PRETTY_PRINT) |
数える・調べる#
| 名前 | 説明 |
|---|---|
count |
要素の数を返す |
countBy |
値の出てきた回数を数える。関数を渡すと、その結果ごとに数える |
isEmpty |
からっぽなら true |
isNotEmpty |
からっぽでなければ true |
hasMany |
要素が2つ以上あれば true(条件の関数も渡せる) |
hasSole |
要素がちょうど1つなら true(条件の関数も渡せる) |
contains |
指定の値(または条件に合うもの)が入っているか調べる。ゆるい比較 |
containsStrict |
contains のきびしい比較の版 |
some |
contains の別名 |
doesntContain |
指定の値(または条件に合うもの)が入っていないか調べる。ゆるい比較 |
doesntContainStrict |
doesntContain のきびしい比較の版 |
every |
すべての要素が条件に合うか調べる(からっぽなら true) |
has |
指定のキーがあるか調べる(配列で複数も渡せる) |
hasAny |
指定のキーのどれか1つでもあるか調べる |
search |
値を探して、そのキーを返す。見つからなければ false |
ensure |
すべての要素が指定の型か確かめる。ちがえば UnexpectedValueException を投げる |
1つ・いくつかを取り出す#
| 名前 | 説明 |
|---|---|
first |
最初の要素(または条件に合う最初の要素)。無ければ null |
firstOrFail |
first と同じ。見つからなければ ItemNotFoundException を投げる |
firstWhere |
指定のキーと値の組を持つ、最初の要素 |
last |
最後の要素(または条件に合う最後の要素)。無ければ null |
get |
指定のキーの値。無ければ null(2番目の引数で初期値も指定できる) |
value |
最初の要素から、指定のキーの値を取り出す |
sole |
条件に合う要素が、ちょうど1つのときだけ、それを返す |
random |
ランダムに1つ(または指定の数だけ)取り出す |
after |
指定の要素の、次の要素 |
before |
指定の要素の、前の要素 |
nth |
n 番目ごとの要素を集めた、新しいコレクション |
pluck |
指定のキーの値だけを集める |
keys |
キーだけを集める |
values |
キーを 0 からの連番に振り直す |
絞りこむ#
| 名前 | 説明 |
|---|---|
filter |
条件に合う要素だけを残す。関数なしなら、false と同じ扱いの値を取りのぞく |
reject |
条件に合う要素を取りのぞく(filter の反対) |
where |
指定のキーの値が合う要素だけを残す。ゆるい比較で、比較の記号も使える |
whereStrict |
where のきびしい比較の版 |
whereBetween |
指定のキーの値が範囲の中にある要素だけを残す |
whereNotBetween |
指定のキーの値が範囲の外にある要素だけを残す |
whereIn |
指定のキーの値が、渡した配列の中にある要素だけを残す。ゆるい比較 |
whereInStrict |
whereIn のきびしい比較の版 |
whereNotIn |
指定のキーの値が、渡した配列の中にない要素だけを残す。ゆるい比較 |
whereNotInStrict |
whereNotIn のきびしい比較の版 |
whereInstanceOf |
指定のクラスの要素だけを残す |
whereNull |
指定のキーの値が null の要素だけを残す |
whereNotNull |
指定のキーの値が null でない要素だけを残す |
only |
指定のキーの要素だけを残す |
except |
指定のキーの要素を取りのぞく(only の反対) |
select |
指定のキーだけを選ぶ(SQL の SELECT のようなもの) |
unique |
重複をなくす。ゆるい比較 |
uniqueStrict |
unique のきびしい比較の版 |
duplicates |
重複している値だけを取り出す。ゆるい比較 |
duplicatesStrict |
duplicates のきびしい比較の版 |
take |
先頭から指定の数だけ取る(マイナスなら末尾から) |
takeUntil |
条件が true になる手前まで取る |
takeWhile |
条件が false になる手前まで取る |
skip |
先頭から指定の数だけ飛ばす |
skipUntil |
条件が true になるまで飛ばし、そこから残りを返す |
skipWhile |
条件が false になるまで飛ばし、そこから残りを返す |
slice |
指定の位置から切り出す(長さも指定できる) |
forPage |
「何ページ目・1ページに何件」で、そのページの要素を返す |
集計する#
| 名前 | 説明 |
|---|---|
sum |
合計 |
avg |
平均 |
average |
avg の別名 |
max |
最大値 |
min |
最小値 |
median |
中央値(並べたときの真ん中の値) |
mode |
最頻値(いちばん多く出てくる値)。配列で返る |
percentage |
条件に合う要素の割合(パーセント) |
形を変える(元のコレクションは変わらない)#
| 名前 | 説明 |
|---|---|
map |
要素ひとつずつを関数に通し、結果で新しいコレクションを作る |
mapInto |
要素をコンストラクタに渡して、指定のクラスのオブジェクトにする |
mapSpread |
入れ子の要素を、ばらして関数の引数に渡して変える |
mapToGroups |
関数が返した「キー => 値」で、グループに分ける |
mapWithKeys |
関数が返した「キー => 値」で、新しいキーと値の組を作る |
flatMap |
map したあと、1段だけ平らにする |
flatten |
入れ子を平らにする(深さも指定できる) |
flip |
キーと値を入れかえる |
collapse |
配列(コレクション)の入れ物を、1つの平らなコレクションにする |
collapseWithKeys |
collapse と同じだが、元のキーを残す |
dot |
入れ子を「ドット記法」のキーで、1段にする |
undot |
ドット記法のキーを、入れ子に戻す |
zip |
渡した配列の値を、同じ位置どうしで組にする |
pad |
指定の大きさになるまで、値で埋める |
multiply |
全要素のコピーを、指定の回数ぶん作る |
reverse |
順番を逆にする(キーは残る) |
replace |
merge に似ているが、数字のキーも上書きする |
replaceRecursive |
入れ子の中まで replace する |
keyBy |
指定のキーの値を、新しいキーにする |
groupBy |
指定のキー(または関数の結果)でグループに分ける |
partition |
条件に合うものと合わないものの、2つに分ける |
chunk |
指定の大きさごとに分ける |
chunkBy |
隣どうしで同じ値のものを、まとめて分ける |
chunkWhile |
関数の結果にもとづいて、分ける |
split |
指定の数のグループに分ける |
splitIn |
指定の数のグループに、前から順に埋めて分ける |
sliding |
「すべり窓」のように、少しずつずらしながら分ける |
implode |
要素をつないで、1つの文字列にする |
join |
要素を文字列でつなぐ(最後だけつなぎ方を変えられる) |
合わせる・比べる#
| 名前 | 説明 |
|---|---|
combine |
要素をキーにして、別の配列の値と組にする |
concat |
渡した配列の値を、末尾に足す |
merge |
渡した配列を合わせる。同じ文字列のキーは、渡したほうで上書き |
mergeRecursive |
入れ子の中まで merge する。同じキーの値は配列にまとめる |
union |
渡した配列を足す。同じキーは、元のほうを優先 |
crossJoin |
すべての組み合わせ(直積)を作る |
diff |
元にあって、渡したものに無い「値」だけ残す |
diffAssoc |
元にあって、渡したものに無い「キーと値の組」だけ残す |
diffAssocUsing |
diffAssoc で、キーの比べ方を関数で決める |
diffKeys |
元にあって、渡したものに無い「キー」の要素だけ残す |
intersect |
渡したものにも入っている「値」だけ残す |
intersectUsing |
intersect で、値の比べ方を関数で決める |
intersectAssoc |
渡したものにも入っている「キーと値の組」だけ残す |
intersectAssocUsing |
intersectAssoc で、キーの比べ方を関数で決める |
intersectByKeys |
渡したものにもある「キー」の要素だけ残す |
並べかえる#
| 名前 | 説明 |
|---|---|
sort |
値で並べかえる(キーは残る) |
sortDesc |
値で逆順に並べかえる(関数は渡せない) |
sortBy |
指定のキー(または関数の結果)で並べかえる |
sortByDesc |
sortBy の逆順 |
sortKeys |
キーで並べかえる |
sortKeysDesc |
キーで逆順に並べかえる |
sortKeysUsing |
キーを、関数を使って並べかえる |
shuffle |
ランダムに並べかえる |
くり返す・値を1つにまとめる・つなぐ#
| 名前 | 説明 |
|---|---|
each |
要素ひとつずつに関数を実行する(false を返すと止まる) |
eachSpread |
入れ子の要素をばらして関数に渡し、実行する |
reduce |
要素を1つの値にまとめる。前の結果を次に渡していく |
reduceInto |
渡した初期値を書きかえながら、1つにまとめる |
reduceSpread |
複数の初期値を持ちながら、配列にまとめる |
pipe |
コレクションを関数に渡して、その結果を返す |
pipeInto |
コレクションを、指定のクラスのコンストラクタに渡す |
pipeThrough |
コレクションを、関数の配列に順に通す |
tap |
途中でコレクションを関数に渡す。コレクション自体は変わらない |
条件で動かす#
| 名前 | 説明 |
|---|---|
when |
最初の引数が true のとき、関数を実行する |
unless |
最初の引数が false のとき、関数を実行する |
whenEmpty |
からっぽのとき、関数を実行する |
whenNotEmpty |
からっぽでないとき、関数を実行する |
unlessEmpty |
whenNotEmpty の別名 |
unlessNotEmpty |
whenEmpty の別名 |
元のコレクションを書きかえるメソッド#
| 名前 | 説明 |
|---|---|
forget |
指定のキーの要素を消す(新しいコレクションは作らず、元のコレクションを書きかえて、それを返す) |
pop |
最後の要素を取り出して消す |
shift |
最初の要素を取り出して消す |
prepend |
先頭に要素を足す |
push |
末尾に要素を足す |
put |
指定のキーに値を入れる |
pull |
指定のキーの値を取り出して消す |
splice |
指定の位置から切り取って返す(かわりの要素も入れられる) |
transform |
すべての要素を、関数の結果で置きかえる |
注意
ほとんどのメソッドは新しいコレクションを返しますが、この表のメソッドは元のコレクションそのものを変えます。map ではなく transform を使うと、元が変わります。
中身を見る(デバッグ)#
| 名前 | 説明 |
|---|---|
dump |
中身を画面に出す。処理はつづく |
dd |
中身を画面に出して、そこで処理を止める |
よく使うメソッドの例#
map:1つずつ変える#
$collection = collect([1, 2, 3, 4, 5]);
$multiplied = $collection->map(function (int $item, int $key) {
return $item * 2;
});
$multiplied->all();
// [2, 4, 6, 8, 10]
map は元のコレクションを変えません。元を変えたいときは transform を使います。
filter と reject:絞りこむ#
filter は、関数が true を返した要素だけを残します。
$collection = collect([1, 2, 3, 4]);
$filtered = $collection->filter(function (int $value, int $key) {
return $value > 2;
});
$filtered->all();
// [3, 4]
関数を渡さないと、null・false・空文字・0・空の配列のように、false と同じ扱いのものが取りのぞかれます。
$collection = collect([1, 2, 3, null, false, '', 0, []]);
$collection->filter()->all();
// [1, 2, 3]
reject は逆に、true を返した要素を取りのぞきます。
$filtered = collect([1, 2, 3, 4])->reject(function (int $value, int $key) {
return $value > 2;
});
$filtered->all();
// [1, 2]
where:キーの値で絞る#
$collection = collect([
['product' => 'Desk', 'price' => 200],
['product' => 'Chair', 'price' => 100],
['product' => 'Bookcase', 'price' => 150],
['product' => 'Door', 'price' => 100],
]);
$filtered = $collection->where('price', 100);
$filtered->all();
/*
[
['product' => 'Chair', 'price' => 100],
['product' => 'Door', 'price' => 100],
]
*/
2番目の引数に、比較の記号を入れられます。使える記号は ===・!==・!=・==・=・<>・>・<・>=・<= です。
$collection = collect([
['name' => 'Jim', 'platform' => 'Mac'],
['name' => 'Sally', 'platform' => 'Mac'],
['name' => 'Sue', 'platform' => 'Linux'],
]);
$filtered = $collection->where('platform', '!=', 'Linux');
$filtered->all();
/*
[
['name' => 'Jim', 'platform' => 'Mac'],
['name' => 'Sally', 'platform' => 'Mac'],
]
*/
範囲・含む・null で絞る where の仲間は、次のように使います。
$collection->whereBetween('price', [100, 200]); // 100 以上 200 以下
$collection->whereNotBetween('price', [100, 200]); // 範囲の外
$collection->whereIn('price', [150, 200]); // 150 か 200 のもの
$collection->whereNotIn('price', [150, 200]); // 150 でも 200 でもないもの
$collection->whereNull('name'); // name が null のもの
$collection->whereNotNull('name'); // name が null でないもの
$collection->whereInstanceOf(User::class); // User クラスのもの
補足
whereNotNull('name') は、null だけを取りのぞきます。0 や空文字は残ります。
pluck:キーの値を集める#
$collection = collect([
['product_id' => 'prod-100', 'name' => 'Desk'],
['product_id' => 'prod-200', 'name' => 'Chair'],
]);
$plucked = $collection->pluck('name');
$plucked->all();
// ['Desk', 'Chair']
2番目の引数で、結果のキーを決められます。
$plucked = $collection->pluck('name', 'product_id');
$plucked->all();
// ['prod-100' => 'Desk', 'prod-200' => 'Chair']
入れ子の値も、ドット記法(speakers.first_day のように . でつなぐ書き方)で取り出せます。
$collection = collect([
[
'name' => 'Laracon',
'speakers' => [
'first_day' => ['Rosa', 'Judith'],
],
],
[
'name' => 'VueConf',
'speakers' => [
'first_day' => ['Abigail', 'Joey'],
],
],
]);
$plucked = $collection->pluck('speakers.first_day');
$plucked->all();
// [['Rosa', 'Judith'], ['Abigail', 'Joey']]
キーが重なったときは、最後のものが残ります。
$collection = collect([
['brand' => 'Tesla', 'color' => 'red'],
['brand' => 'Pagani', 'color' => 'white'],
['brand' => 'Tesla', 'color' => 'black'],
['brand' => 'Pagani', 'color' => 'orange'],
]);
$plucked = $collection->pluck('color', 'brand');
$plucked->all();
// ['Tesla' => 'black', 'Pagani' => 'orange']
sum・avg・max・min・median・mode:集計#
collect([1, 2, 3, 4, 5])->sum();
// 15
collect([1, 1, 2, 4])->avg();
// 2
collect([1, 2, 3, 4, 5])->max();
// 5
collect([1, 2, 3, 4, 5])->min();
// 1
collect([1, 1, 2, 4])->median();
// 1.5
collect([1, 1, 2, 2])->mode();
// [1, 2]
配列の入った要素なら、キーを指定します。sum には関数も渡せます。
$collection = collect([
['name' => 'JavaScript: The Good Parts', 'pages' => 176],
['name' => 'JavaScript: The Definitive Guide', 'pages' => 1096],
]);
$collection->sum('pages');
// 1272
$collection = collect([
['name' => 'Chair', 'colors' => ['Black']],
['name' => 'Desk', 'colors' => ['Black', 'Mahogany']],
['name' => 'Bookcase', 'colors' => ['Red', 'Beige', 'Brown']],
]);
$collection->sum(function (array $product) {
return count($product['colors']);
});
// 6
percentage は、条件に合う要素の割合を出します。小数点以下は、はじめは2桁までです。precision で変えられます。
$collection = collect([1, 1, 2, 2, 2, 3]);
$percentage = $collection->percentage(fn (int $value) => $value === 1);
// 33.33
$percentage = $collection->percentage(fn (int $value) => $value === 1, precision: 3);
// 33.333
first・last・get:取り出す#
collect([1, 2, 3, 4])->first(function (int $value, int $key) {
return $value > 2;
});
// 3
collect([1, 2, 3, 4])->first();
// 1
collect([1, 2, 3, 4])->last();
// 4
get は、キーを指定して値を取り出します。無いときの値(関数も可)を2番目に渡せます。
$collection = collect(['name' => 'Taylor', 'framework' => 'Laravel']);
$collection->get('name');
// Taylor
$collection->get('age', 34);
// 34
$collection->get('email', function () {
return 'taylor@example.com';
});
// taylor@example.com
firstWhere は、キーと値の組で探します。
$collection = collect([
['name' => 'Regena', 'age' => null],
['name' => 'Linda', 'age' => 14],
['name' => 'Diego', 'age' => 23],
['name' => 'Linda', 'age' => 84],
]);
$collection->firstWhere('name', 'Linda');
// ['name' => 'Linda', 'age' => 14]
$collection->firstWhere('age', '>=', 18);
// ['name' => 'Diego', 'age' => 23]
$collection->firstWhere('age');
// ['name' => 'Linda', 'age' => 14](age が真と同じ扱いになる最初のもの)
sole は、条件に合うものが「ちょうど1つ」のときだけ返します。1つも無ければ ItemNotFoundException、2つ以上あれば MultipleItemsFoundException が投げられます。
collect([1, 2, 3, 4])->sole(function (int $value, int $key) {
return $value === 2;
});
// 2
$collection = collect([
['product' => 'Desk', 'price' => 200],
['product' => 'Chair', 'price' => 100],
]);
$collection->sole('product', 'Chair');
// ['product' => 'Chair', 'price' => 100]
contains・every・search・has:調べる#
$collection = collect([1, 2, 3, 4, 5]);
$collection->contains(function (int $value, int $key) {
return $value > 5;
});
// false
$collection = collect(['name' => 'Desk', 'price' => 100]);
$collection->contains('Desk');
// true
$collection = collect([
['product' => 'Desk', 'price' => 200],
['product' => 'Chair', 'price' => 100],
]);
$collection->contains('product', 'Bookcase');
// false
collect([1, 2, 3, 4])->every(function (int $value, int $key) {
return $value > 2;
});
// false
collect([2, 4, 6, 8])->search(4);
// 1
collect([2, 4, 6, 8])->search('4', strict: true);
// false
collect([2, 4, 6, 8])->search(function (int $item, int $key) {
return $item > 5;
});
// 2
$collection = collect(['account_id' => 1, 'product' => 'Desk', 'amount' => 5]);
$collection->has('product');
// true
$collection->has(['product', 'amount']);
// true
$collection->has(['amount', 'price']);
// false
$collection->hasAny(['product', 'price']);
// true
after と before も、strict: true や関数を渡せます。
$collection = collect([1, 2, 3, 4, 5]);
$collection->after(3);
// 4
$collection->after(5);
// null
$collection->before(3);
// 2
collect([2, 4, 6, 8])->after('4', strict: true);
// null
collect([2, 4, 6, 8])->after(function (int $item, int $key) {
return $item > 5;
});
// 8
ensure は、型がそろっているかの確認に使います。クラス名や、string・int・float・bool・array のような基本の型も指定できます。
return $collection->ensure(User::class);
return $collection->ensure([User::class, Customer::class]);
return $collection->ensure('int');
注意
ensure は、あとから別の型の要素が足されないことまでは守ってくれません。
groupBy・keyBy・partition:分ける#
$collection = collect([
['account_id' => 'account-x10', 'product' => 'Chair'],
['account_id' => 'account-x10', 'product' => 'Bookcase'],
['account_id' => 'account-x11', 'product' => 'Desk'],
]);
$grouped = $collection->groupBy('account_id');
$grouped->all();
/*
[
'account-x10' => [
['account_id' => 'account-x10', 'product' => 'Chair'],
['account_id' => 'account-x10', 'product' => 'Bookcase'],
],
'account-x11' => [
['account_id' => 'account-x11', 'product' => 'Desk'],
],
]
*/
キーの代わりに、関数を渡すこともできます。
$grouped = $collection->groupBy(function (array $item, int $key) {
return substr($item['account_id'], -3);
});
$grouped->all();
/*
[
'x10' => [
['account_id' => 'account-x10', 'product' => 'Chair'],
['account_id' => 'account-x10', 'product' => 'Bookcase'],
],
'x11' => [
['account_id' => 'account-x11', 'product' => 'Desk'],
],
]
*/
配列で複数の条件を渡すと、段ごとに入れ子で分けます。preserveKeys: true で、元のキーを残せます。
$data = new Collection([
10 => ['user' => 1, 'skill' => 1, 'roles' => ['Role_1', 'Role_3']],
20 => ['user' => 2, 'skill' => 1, 'roles' => ['Role_1', 'Role_2']],
30 => ['user' => 3, 'skill' => 2, 'roles' => ['Role_1']],
40 => ['user' => 4, 'skill' => 2, 'roles' => ['Role_2']],
]);
$result = $data->groupBy(['skill', function (array $item) {
return $item['roles'];
}], preserveKeys: true);
/*
[
1 => [
'Role_1' => [
10 => ['user' => 1, 'skill' => 1, 'roles' => ['Role_1', 'Role_3']],
20 => ['user' => 2, 'skill' => 1, 'roles' => ['Role_1', 'Role_2']],
],
'Role_3' => [
10 => ['user' => 1, 'skill' => 1, 'roles' => ['Role_1', 'Role_3']],
],
'Role_2' => [
20 => ['user' => 2, 'skill' => 1, 'roles' => ['Role_1', 'Role_2']],
],
],
2 => [
'Role_1' => [
30 => ['user' => 3, 'skill' => 2, 'roles' => ['Role_1']],
],
'Role_2' => [
40 => ['user' => 4, 'skill' => 2, 'roles' => ['Role_2']],
],
],
];
*/
keyBy は、指定のキーの値を、新しいキーにします。同じキーが複数あるときは、最後のものだけが残ります。
$collection = collect([
['product_id' => 'prod-100', 'name' => 'Desk'],
['product_id' => 'prod-200', 'name' => 'Chair'],
]);
$keyed = $collection->keyBy('product_id');
$keyed->all();
/*
[
'prod-100' => ['product_id' => 'prod-100', 'name' => 'Desk'],
'prod-200' => ['product_id' => 'prod-200', 'name' => 'Chair'],
]
*/
$keyed = $collection->keyBy(function (array $item, int $key) {
return strtoupper($item['product_id']);
});
/*
[
'PROD-100' => ['product_id' => 'prod-100', 'name' => 'Desk'],
'PROD-200' => ['product_id' => 'prod-200', 'name' => 'Chair'],
]
*/
partition は、PHP の配列の分解代入([$a, $b] = ... の書き方)と合わせて使います。
$collection = collect([1, 2, 3, 4, 5, 6]);
[$underThree, $equalOrAboveThree] = $collection->partition(function (int $i) {
return $i < 3;
});
$underThree->all();
// [1, 2]
$equalOrAboveThree->all();
// [3, 4, 5, 6]
mapToGroups と mapWithKeys は、関数が「キー => 値」を1組ずつ返します。
$collection = collect([
['name' => 'John Doe', 'department' => 'Sales'],
['name' => 'Jane Doe', 'department' => 'Sales'],
['name' => 'Johnny Doe', 'department' => 'Marketing'],
]);
$grouped = $collection->mapToGroups(function (array $item, int $key) {
return [$item['department'] => $item['name']];
});
$grouped->all();
/*
[
'Sales' => ['John Doe', 'Jane Doe'],
'Marketing' => ['Johnny Doe'],
]
*/
$collection = collect([
['name' => 'John', 'department' => 'Sales', 'email' => 'john@example.com'],
['name' => 'Jane', 'department' => 'Marketing', 'email' => 'jane@example.com'],
]);
$keyed = $collection->mapWithKeys(function (array $item, int $key) {
return [$item['email'] => $item['name']];
});
$keyed->all();
/*
[
'john@example.com' => 'John',
'jane@example.com' => 'Jane',
]
*/
chunk・split・sliding:小分けにする#
$collection = collect([1, 2, 3, 4, 5, 6, 7]);
$chunks = $collection->chunk(4);
$chunks->all();
// [[1, 2, 3, 4], [5, 6, 7]]
画面(ビュー)で、商品を3つずつ横に並べたいときなどに便利です。
@foreach ($products->chunk(3) as $chunk)
<div class="row">
@foreach ($chunk as $product)
<div class="col-xs-4">{{ $product->name }}</div>
@endforeach
</div>
@endforeach
chunkBy は、隣どうしで同じ値のものをまとめます。groupBy とちがい、同じ値でも離れていれば別の塊になります。
$chunks = $products->chunkBy('parent');
$collection = collect([1, 1, 2, 2, 1]);
$chunks = $collection->chunkBy(fn (int $value) => $value);
$chunks->all();
// [[1, 1], [2, 2], [1]]
chunkWhile は、関数の結果で分けます。関数には、いまの塊($chunk)も渡されるので、1つ前の要素と比べられます。
$collection = collect(str_split('AABBCCCD'));
$chunks = $collection->chunkWhile(function (string $value, int $key, Collection $chunk) {
return $value === $chunk->last();
});
$chunks->all();
// [['A', 'A'], ['B', 'B'], ['C', 'C', 'C'], ['D']]
split と splitIn は、グループの数を指定して分けます。
$collection = collect([1, 2, 3, 4, 5]);
$groups = $collection->split(3);
$groups->all();
// [[1, 2], [3, 4], [5]]
$collection = collect([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
$groups = $collection->splitIn(3);
$groups->all();
// [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10]]
sliding は、決まった数ずつ、少しずつずらしながら切り出します。となりの塊と重なる部分ができます。2番目の step で、ずらす幅を変えられます。
$collection = collect([1, 2, 3, 4, 5]);
$chunks = $collection->sliding(2);
$chunks->toArray();
// [[1, 2], [2, 3], [3, 4], [4, 5]]
$chunks = $collection->sliding(3, step: 2);
$chunks->toArray();
// [[1, 2, 3], [3, 4, 5]]
eachSpread と組み合わせると、「ひとつ前」と比べる処理が書けます。
$transactions->sliding(2)->eachSpread(function ($previous, $current) {
$current->total = $previous->total + $current->amount;
});
take・skip・slice・forPage:一部を切り出す#
$collection = collect([0, 1, 2, 3, 4, 5]);
$collection->take(3)->all();
// [0, 1, 2]
$collection->take(-2)->all();
// [4, 5]
$collection = collect([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
$collection->skip(4)->all();
// [5, 6, 7, 8, 9, 10]
$collection->slice(4)->all();
// [5, 6, 7, 8, 9, 10]
$collection->slice(4, 2)->all();
// [5, 6]
collect([1, 2, 3, 4, 5, 6, 7, 8, 9])->forPage(2, 3)->all();
// [4, 5, 6]
slice はキーを残します。連番に振り直したいときは values を使います。
takeUntil・takeWhile・skipUntil・skipWhile は、関数(takeUntil と skipUntil は単純な値も可)で境目を決めます。
$collection = collect([1, 2, 3, 4]);
$collection->takeUntil(function (int $item) {
return $item >= 3;
})->all();
// [1, 2]
$collection->takeUntil(3)->all();
// [1, 2]
$collection->takeWhile(function (int $item) {
return $item < 3;
})->all();
// [1, 2]
$collection->skipUntil(function (int $item) {
return $item >= 3;
})->all();
// [3, 4]
$collection->skipUntil(3)->all();
// [3, 4]
$collection->skipWhile(function (int $item) {
return $item <= 3;
})->all();
// [4]
注意
値が見つからない、または条件がずっと変わらないときの結果に気をつけます。takeUntil と takeWhile は全部の要素を返し、skipUntil と skipWhile は空のコレクションを返します。
sort と sortBy:並べかえる#
sort はキーを残したまま並べかえるので、連番に振り直すには values を続けます。
$collection = collect([5, 3, 1, 2, 4]);
$sorted = $collection->sort();
$sorted->values()->all();
// [1, 2, 3, 4, 5]
もっと細かい並べかたは、sort に自分の比べる関数を渡します(PHP の uasort と同じ使い方です)。入れ子の配列やオブジェクトは sortBy と sortByDesc を使います。
$collection = collect([
['name' => 'Desk', 'price' => 200],
['name' => 'Chair', 'price' => 100],
['name' => 'Bookcase', 'price' => 150],
]);
$sorted = $collection->sortBy('price');
$sorted->values()->all();
/*
[
['name' => 'Chair', 'price' => 100],
['name' => 'Bookcase', 'price' => 150],
['name' => 'Desk', 'price' => 200],
]
*/
2番目の引数に、PHP の並べかえのフラグ(並べかたの種類を決める印。下の例の SORT_NATURAL など)を渡せます。
$collection = collect([
['title' => 'Item 1'],
['title' => 'Item 12'],
['title' => 'Item 3'],
]);
$sorted = $collection->sortBy('title', SORT_NATURAL);
$sorted->values()->all();
/*
[
['title' => 'Item 1'],
['title' => 'Item 3'],
['title' => 'Item 12'],
]
*/
関数で並べかたを決めることもできます。
$collection = collect([
['name' => 'Desk', 'colors' => ['Black', 'Mahogany']],
['name' => 'Chair', 'colors' => ['Black']],
['name' => 'Bookcase', 'colors' => ['Red', 'Beige', 'Brown']],
]);
$sorted = $collection->sortBy(function (array $product, int $key) {
return count($product['colors']);
});
$sorted->values()->all();
/*
[
['name' => 'Chair', 'colors' => ['Black']],
['name' => 'Desk', 'colors' => ['Black', 'Mahogany']],
['name' => 'Bookcase', 'colors' => ['Red', 'Beige', 'Brown']],
]
*/
複数の条件で並べるときは、「キーと向き」の組を配列で渡します。
$collection = collect([
['name' => 'Taylor Otwell', 'age' => 34],
['name' => 'Abigail Otwell', 'age' => 30],
['name' => 'Taylor Otwell', 'age' => 36],
['name' => 'Abigail Otwell', 'age' => 32],
]);
$sorted = $collection->sortBy([
['name', 'asc'],
['age', 'desc'],
]);
$sorted->values()->all();
/*
[
['name' => 'Abigail Otwell', 'age' => 32],
['name' => 'Abigail Otwell', 'age' => 30],
['name' => 'Taylor Otwell', 'age' => 36],
['name' => 'Taylor Otwell', 'age' => 34],
]
*/
条件ごとに、比べる関数を渡すこともできます。
$sorted = $collection->sortBy([
fn (array $a, array $b) => $a['name'] <=> $b['name'],
fn (array $a, array $b) => $b['age'] <=> $a['age'],
]);
$sorted->values()->all();
/*
[
['name' => 'Abigail Otwell', 'age' => 32],
['name' => 'Abigail Otwell', 'age' => 30],
['name' => 'Taylor Otwell', 'age' => 36],
['name' => 'Taylor Otwell', 'age' => 34],
]
*/
sortDesc は sort の逆順です。sort とちがい、関数は渡せません。逆順にしたい関数があるなら、sort に渡して比べかたを逆にします。
$collection = collect([5, 3, 1, 2, 4]);
$sorted = $collection->sortDesc();
$sorted->values()->all();
// [5, 4, 3, 2, 1]
キーで並べるのは sortKeys です。
$collection = collect([
'id' => 22345,
'first' => 'John',
'last' => 'Doe',
]);
$sorted = $collection->sortKeys();
$sorted->all();
/*
[
'first' => 'John',
'id' => 22345,
'last' => 'Doe',
]
*/
sortKeysDesc は逆順です。sortKeysUsing は、比べる関数を渡します(PHP の uksort と同じ使い方で、関数は 0 より小さい・0・0 より大きい整数を返します)。
$collection = collect([
'ID' => 22345,
'first' => 'John',
'last' => 'Doe',
]);
$sorted = $collection->sortKeysUsing('strnatcasecmp');
$sorted->all();
/*
[
'first' => 'John',
'ID' => 22345,
'last' => 'Doe',
]
*/
flatten・collapse・dot:平らにする・入れ子にする#
$collection = collect([
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]);
$collection->collapse()->all();
// [1, 2, 3, 4, 5, 6, 7, 8, 9]
$collection = collect([
['first' => collect([1, 2, 3])],
['second' => [4, 5, 6]],
['third' => collect([7, 8, 9])]
]);
$collection->collapseWithKeys()->all();
// [
// 'first' => [1, 2, 3],
// 'second' => [4, 5, 6],
// 'third' => [7, 8, 9],
// ]
collapseWithKeys は、すでに平らなコレクションに使うと、空のコレクションを返します。
$collection = collect([
'name' => 'Taylor',
'languages' => [
'PHP', 'JavaScript'
]
]);
$collection->flatten()->all();
// ['Taylor', 'PHP', 'JavaScript'];
flatten に深さを渡すと、その段数だけ平らにします。
$collection = collect([
'Apple' => [
[
'name' => 'iPhone 6S',
'brand' => 'Apple'
],
],
'Samsung' => [
[
'name' => 'Galaxy S7',
'brand' => 'Samsung'
],
],
]);
$products = $collection->flatten(1);
$products->values()->all();
/*
[
['name' => 'iPhone 6S', 'brand' => 'Apple'],
['name' => 'Galaxy S7', 'brand' => 'Samsung'],
]
*/
深さを渡さずに flatten すると、この例は ['iPhone 6S', 'Apple', 'Galaxy S7', 'Samsung'] まで平らになります。
flatMap は、map のあとに1段だけ平らにします。
$collection = collect([
['name' => 'Sally'],
['school' => 'Arkansas'],
['age' => 28]
]);
$flattened = $collection->flatMap(function (array $values) {
return array_map('strtoupper', $values);
});
$flattened->all();
// ['name' => 'SALLY', 'school' => 'ARKANSAS', 'age' => '28'];
dot と undot は、入れ子とドット記法を行き来します。
$collection = collect(['products' => ['desk' => ['price' => 100]]]);
$collection->dot()->all();
// ['products.desk.price' => 100]
$person = collect([
'name.first_name' => 'Marie',
'name.last_name' => 'Valentine',
'address.line_1' => '2992 Eagle Drive',
'address.line_2' => '',
'address.suburb' => 'Detroit',
'address.state' => 'MI',
'address.postcode' => '48219'
]);
$person = $person->undot();
$person->toArray();
/*
[
"name" => [
"first_name" => "Marie",
"last_name" => "Valentine",
],
"address" => [
"line_1" => "2992 Eagle Drive",
"line_2" => "",
"suburb" => "Detroit",
"state" => "MI",
"postcode" => "48219",
],
]
*/
合わせる#
// combine:要素をキーにして、値と組にする
collect(['name', 'age'])->combine(['George', 29])->all();
// ['name' => 'George', 'age' => 29]
// concat:末尾に足す(キーは数字の連番に振り直される)
collect(['John Doe'])->concat(['Jane Doe'])->concat(['name' => 'Johnny Doe'])->all();
// ['John Doe', 'Jane Doe', 'Johnny Doe']
// merge:同じ文字列のキーは、渡したほうで上書き
collect(['product_id' => 1, 'price' => 100])->merge(['price' => 200, 'discount' => false])->all();
// ['product_id' => 1, 'price' => 200, 'discount' => false]
// merge:数字のキーは、末尾に足される
collect(['Desk', 'Chair'])->merge(['Bookcase', 'Door'])->all();
// ['Desk', 'Chair', 'Bookcase', 'Door']
// mergeRecursive:同じ文字列のキーの値は、配列にまとまる
collect(['product_id' => 1, 'price' => 100])->mergeRecursive([
'product_id' => 2,
'price' => 200,
'discount' => false
])->all();
// ['product_id' => [1, 2], 'price' => [100, 200], 'discount' => false]
// union:同じキーは、元のほうを優先
collect([1 => ['a'], 2 => ['b']])->union([3 => ['c'], 1 => ['d']])->all();
// [1 => ['a'], 2 => ['b'], 3 => ['c']]
// replace:数字のキーも上書きする
collect(['Taylor', 'Abigail', 'James'])->replace([1 => 'Victoria', 3 => 'Finn'])->all();
// ['Taylor', 'Victoria', 'James', 'Finn']
// zip:同じ位置どうしを組にする
collect(['Chair', 'Desk'])->zip([100, 200])->all();
// [['Chair', 100], ['Desk', 200]]
concat はキーを連番に振り直すので、文字列のキーを残したいときは merge を使います。
replaceRecursive は、入れ子の中でも置きかえます。
$collection = collect([
'Taylor',
'Abigail',
[
'James',
'Victoria',
'Finn'
]
]);
$replaced = $collection->replaceRecursive([
'Charlie',
2 => [1 => 'King']
]);
$replaced->all();
// ['Charlie', 'Abigail', ['James', 'King', 'Finn']]
crossJoin は、すべての組み合わせを作ります。
$collection = collect([1, 2]);
$matrix = $collection->crossJoin(['a', 'b']);
$matrix->all();
/*
[
[1, 'a'],
[1, 'b'],
[2, 'a'],
[2, 'b'],
]
*/
$matrix = $collection->crossJoin(['a', 'b'], ['I', 'II']);
$matrix->all();
/*
[
[1, 'a', 'I'],
[1, 'a', 'II'],
[1, 'b', 'I'],
[1, 'b', 'II'],
[2, 'a', 'I'],
[2, 'a', 'II'],
[2, 'b', 'I'],
[2, 'b', 'II'],
]
*/
違いと共通部分#
// diff:元にあって、渡したものに無い値
collect([1, 2, 3, 4, 5])->diff([2, 4, 6, 8])->all();
// [1, 3, 5]
// diffAssoc:キーと値の組で比べる
collect(['color' => 'orange', 'type' => 'fruit', 'remain' => 6])->diffAssoc([
'color' => 'yellow',
'type' => 'fruit',
'remain' => 3,
'used' => 6,
])->all();
// ['color' => 'orange', 'remain' => 6]
// diffKeys:キーで比べる
collect([
'one' => 10,
'two' => 20,
'three' => 30,
'four' => 40,
'five' => 50,
])->diffKeys([
'two' => 2,
'four' => 4,
'six' => 6,
'eight' => 8,
])->all();
// ['one' => 10, 'three' => 30, 'five' => 50]
// intersect:渡したものにもある値(キーは残る)
collect(['Desk', 'Sofa', 'Chair'])->intersect(['Desk', 'Chair', 'Bookcase'])->all();
// [0 => 'Desk', 2 => 'Chair']
// intersectAssoc:キーと値の組が同じもの
collect(['color' => 'red', 'size' => 'M', 'material' => 'cotton'])->intersectAssoc([
'color' => 'blue',
'size' => 'M',
'material' => 'polyester'
])->all();
// ['size' => 'M']
// intersectByKeys:キーが同じもの
collect(['serial' => 'UX301', 'type' => 'screen', 'year' => 2009])->intersectByKeys([
'reference' => 'UX404', 'type' => 'tab', 'year' => 2011,
])->all();
// ['type' => 'screen', 'year' => 2009]
diffAssocUsing・intersectUsing・intersectAssocUsing は、比べる関数を自分で渡します。関数は、0 より小さい・0・0 より大きい整数を返します(diffAssocUsing は、PHP の array_diff_uassoc を使っています)。
$collection = collect([
'color' => 'orange',
'type' => 'fruit',
'remain' => 6,
]);
$diff = $collection->diffAssocUsing([
'Color' => 'yellow',
'Type' => 'fruit',
'Remain' => 3,
], 'strnatcasecmp');
$diff->all();
// ['color' => 'orange', 'remain' => 6]
$collection = collect(['Desk', 'Sofa', 'Chair']);
$intersect = $collection->intersectUsing(['desk', 'chair', 'bookcase'], function (string $a, string $b) {
return strcasecmp($a, $b);
});
$intersect->all();
// [0 => 'Desk', 2 => 'Chair']
$collection = collect([
'color' => 'red',
'Size' => 'M',
'material' => 'cotton',
]);
$intersect = $collection->intersectAssocUsing([
'color' => 'blue',
'size' => 'M',
'material' => 'polyester',
], function (string $a, string $b) {
return strcasecmp($a, $b);
});
$intersect->all();
// ['Size' => 'M']
unique と duplicates:重複#
$collection = collect([1, 1, 2, 2, 3, 4, 2]);
$collection->unique()->values()->all();
// [1, 2, 3, 4]
collect(['a', 'b', 'a', 'c', 'b'])->duplicates();
// [2 => 'a', 4 => 'b']
入れ子の配列やオブジェクトには、キーや関数を渡せます。
$collection = collect([
['name' => 'iPhone 6', 'brand' => 'Apple', 'type' => 'phone'],
['name' => 'iPhone 5', 'brand' => 'Apple', 'type' => 'phone'],
['name' => 'Apple Watch', 'brand' => 'Apple', 'type' => 'watch'],
['name' => 'Galaxy S6', 'brand' => 'Samsung', 'type' => 'phone'],
['name' => 'Galaxy Gear', 'brand' => 'Samsung', 'type' => 'watch'],
]);
$unique = $collection->unique('brand');
$unique->values()->all();
/*
[
['name' => 'iPhone 6', 'brand' => 'Apple', 'type' => 'phone'],
['name' => 'Galaxy S6', 'brand' => 'Samsung', 'type' => 'phone'],
]
*/
$unique = $collection->unique(function (array $item) {
return $item['brand'].$item['type'];
});
$unique->values()->all();
/*
[
['name' => 'iPhone 6', 'brand' => 'Apple', 'type' => 'phone'],
['name' => 'Apple Watch', 'brand' => 'Apple', 'type' => 'watch'],
['name' => 'Galaxy S6', 'brand' => 'Samsung', 'type' => 'phone'],
['name' => 'Galaxy Gear', 'brand' => 'Samsung', 'type' => 'watch'],
]
*/
$employees = collect([
['email' => 'abigail@example.com', 'position' => 'Developer'],
['email' => 'james@example.com', 'position' => 'Designer'],
['email' => 'victoria@example.com', 'position' => 'Developer'],
]);
$employees->duplicates('position');
// [2 => 'Developer']
each・reduce・pipe・tap:くり返す・まとめる#
$collection = collect([1, 2, 3, 4]);
$collection->each(function (int $item, int $key) {
// ...
});
// 途中で止めたいときは false を返す
$collection->each(function (int $item, int $key) {
if (/* condition */) {
return false;
}
});
eachSpread は、入れ子の値をばらして関数に渡します。mapSpread も同じ形で、結果から新しいコレクションを作ります。
$collection = collect([['John Doe', 35], ['Jane Doe', 33]]);
$collection->eachSpread(function (string $name, int $age) {
// ...
});
$collection = collect([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
$chunks = $collection->chunk(2);
$sequence = $chunks->mapSpread(function (int $even, int $odd) {
return $even + $odd;
});
$sequence->all();
// [1, 5, 9, 13, 17]
reduce は、前の結果を次に渡しながら、1つの値にまとめます。1回目の $carry は null で、2番目の引数で初期値を決められます。
$collection = collect([1, 2, 3]);
$total = $collection->reduce(function (?int $carry, int $item) {
return $carry + $item;
});
// 6
$collection->reduce(function (int $carry, int $item) {
return $carry + $item;
}, 4);
// 10
キーも関数に渡されます。
$collection = collect([
'usd' => 1400,
'gbp' => 1200,
'eur' => 1000,
]);
$ratio = [
'usd' => 1,
'gbp' => 1.37,
'eur' => 1.22,
];
$collection->reduce(function (int $carry, int $value, string $key) use ($ratio) {
return $carry + ($value * $ratio[$key]);
}, 0);
// 4264
reduceInto は、渡した初期値を書きかえていくやり方です。reduce とちがい、関数は結果を返さなくてかまいません。
class OrderStats
{
public int $total = 0;
public int $count = 0;
}
$orders = collect([
['amount' => 100],
['amount' => 250],
['amount' => 50],
]);
$stats = $orders->reduceInto(new OrderStats, function (OrderStats $stats, array $order) {
$stats->total += $order['amount'];
$stats->count++;
});
$stats->total;
// 400
数字や配列にまとめるときは、関数が書きかえた結果が元に反映されるよう、参照(&。コピーではなく、元の変数そのものを受け取る書き方)で受け取ります。
$collection = collect([1, 2, 3, 4, 5]);
$even = $collection->reduceInto([], function (array &$result, int $value) {
if ($value % 2 === 0) {
$result[] = $value;
}
});
// [2, 4]
reduceSpread は、複数の初期値を持てる reduce です。
[$creditsRemaining, $batch] = Image::where('status', 'unprocessed')
->get()
->reduceSpread(function (int $creditsRemaining, Collection $batch, Image $image) {
if ($creditsRemaining >= $image->creditsRequired()) {
$batch->push($image);
$creditsRemaining -= $image->creditsRequired();
}
return [$creditsRemaining, $batch];
}, $creditsAvailable, collect());
pipe・pipeInto・pipeThrough は、コレクション全体を受け渡します。
$collection = collect([1, 2, 3]);
$piped = $collection->pipe(function (Collection $collection) {
return $collection->sum();
});
// 6
class ResourceCollection
{
/**
* Create a new ResourceCollection instance.
*/
public function __construct(
public Collection $collection,
) {}
}
$collection = collect([1, 2, 3]);
$resource = $collection->pipeInto(ResourceCollection::class);
$resource->collection->all();
// [1, 2, 3]
use Illuminate\Support\Collection;
$collection = collect([1, 2, 3]);
$result = $collection->pipeThrough([
function (Collection $collection) {
return $collection->merge([4, 5]);
},
function (Collection $collection) {
return $collection->sum();
},
]);
// 15
tap は、流れの途中でコレクションをのぞき見るときに使います。コレクション自体は変わりません。
collect([2, 4, 3, 1, 5])
->sort()
->tap(function (Collection $collection) {
Log::debug('Values after sorting', $collection->values()->all());
})
->shift();
// 1
mapInto は、値をコンストラクタに渡して、クラスのオブジェクトにします。
class Currency
{
/**
* Create a new currency instance.
*/
function __construct(
public string $code,
) {}
}
$collection = collect(['USD', 'EUR', 'GBP']);
$currencies = $collection->mapInto(Currency::class);
$currencies->all();
// [Currency('USD'), Currency('EUR'), Currency('GBP')]
when・unless:条件で動かす#
when は、最初の引数が true のときだけ関数を実行します。関数には、コレクションと最初の引数が渡されます。2番目の関数は、false のときに実行されます。
$collection = collect([1, 2, 3]);
$collection->when(true, function (Collection $collection, bool $value) {
return $collection->push(4);
});
$collection->when(false, function (Collection $collection, bool $value) {
return $collection->push(5);
});
$collection->all();
// [1, 2, 3, 4]
$collection = collect([1, 2, 3]);
$collection->when(false, function (Collection $collection, bool $value) {
return $collection->push(4);
}, function (Collection $collection, bool $value) {
return $collection->push(5);
});
$collection->all();
// [1, 2, 3, 5]
unless は逆で、最初の引数が false のときに関数を実行します(2番目の関数は true のとき)。
$collection = collect([1, 2, 3]);
$collection->unless(true, function (Collection $collection, bool $value) {
return $collection->push(4);
});
$collection->unless(false, function (Collection $collection, bool $value) {
return $collection->push(5);
});
$collection->all();
// [1, 2, 3, 5]
whenEmpty と whenNotEmpty は、からっぽかどうかで関数を実行します。2番目の関数は、条件が合わなかったときに実行されます。
$collection = collect(['Michael', 'Tom']);
$collection->whenEmpty(function (Collection $collection) {
return $collection->push('Adam');
});
$collection->all();
// ['Michael', 'Tom']
$collection = collect();
$collection->whenEmpty(function (Collection $collection) {
return $collection->push('Adam');
});
$collection->all();
// ['Adam']
$collection = collect(['Michael', 'Tom']);
$collection->whenEmpty(function (Collection $collection) {
return $collection->push('Adam');
}, function (Collection $collection) {
return $collection->push('Taylor');
});
$collection->all();
// ['Michael', 'Tom', 'Taylor']
$collection = collect(['Michael', 'Tom']);
$collection->whenNotEmpty(function (Collection $collection) {
return $collection->push('Adam');
});
$collection->all();
// ['Michael', 'Tom', 'Adam']
$collection = collect();
$collection->whenNotEmpty(function (Collection $collection) {
return $collection->push('Adam');
}, function (Collection $collection) {
return $collection->push('Taylor');
});
$collection->all();
// ['Taylor']
足す・消す(元のコレクションが変わる)#
$collection = collect([1, 2, 3, 4, 5]);
$collection->pop();
// 5(collection は [1, 2, 3, 4] になる)
$collection = collect([1, 2, 3, 4, 5]);
$collection->pop(3);
// collect([5, 4, 3])(collection は [1, 2] になる)
$collection = collect([1, 2, 3, 4, 5]);
$collection->shift();
// 1(collection は [2, 3, 4, 5] になる)
$collection = collect([1, 2, 3, 4, 5]);
$collection->shift(3);
// collect([1, 2, 3])(collection は [4, 5] になる)
$collection = collect([1, 2, 3, 4, 5]);
$collection->prepend(0);
// [0, 1, 2, 3, 4, 5]
$collection = collect(['one' => 1, 'two' => 2]);
$collection->prepend(0, 'zero');
// ['zero' => 0, 'one' => 1, 'two' => 2]
$collection = collect([1, 2, 3, 4]);
$collection->push(5);
// [1, 2, 3, 4, 5]
$collection->push(6, 7);
// [1, 2, 3, 4, 5, 6, 7]
$collection = collect(['product_id' => 1, 'name' => 'Desk']);
$collection->put('price', 100);
// ['product_id' => 1, 'name' => 'Desk', 'price' => 100]
$collection = collect(['product_id' => 'prod-100', 'name' => 'Desk']);
$collection->pull('name');
// 'Desk'(collection は ['product_id' => 'prod-100'] になる)
$collection = collect(['name' => 'Taylor', 'framework' => 'Laravel']);
$collection->forget('name');
// ['framework' => 'Laravel']
$collection->forget(['name', 'framework']);
// []
splice は、指定の位置から切り取って返します。2番目の引数で個数、3番目の引数でかわりに入れる要素を決められます。
$collection = collect([1, 2, 3, 4, 5]);
$chunk = $collection->splice(2);
$chunk->all();
// [3, 4, 5]
$collection->all();
// [1, 2]
$collection = collect([1, 2, 3, 4, 5]);
$chunk = $collection->splice(2, 1);
$chunk->all();
// [3]
$collection->all();
// [1, 2, 4, 5]
$collection = collect([1, 2, 3, 4, 5]);
$chunk = $collection->splice(2, 1, [10, 11]);
$chunk->all();
// [3]
$collection->all();
// [1, 2, 10, 11, 4, 5]
transform は、map の「元を書きかえる版」です。
$collection = collect([1, 2, 3, 4, 5]);
$collection->transform(function (int $item, int $key) {
return $item * 2;
});
$collection->all();
// [2, 4, 6, 8, 10]
そのほかの例#
// avg・average
collect([['foo' => 10], ['foo' => 10], ['foo' => 20], ['foo' => 40]])->avg('foo');
// 20
// count と countBy
collect([1, 2, 2, 2, 3])->countBy()->all();
// [1 => 1, 2 => 3, 3 => 1]
$collection = collect(['alice@gmail.com', 'bob@yahoo.com', 'carlos@gmail.com']);
$collection->countBy(function (string $email) {
return substr(strrchr($email, '@'), 1);
})->all();
// ['gmail.com' => 2, 'yahoo.com' => 1]
// flip
collect(['name' => 'Taylor', 'framework' => 'Laravel'])->flip()->all();
// ['Taylor' => 'name', 'Laravel' => 'framework']
// hasMany と hasSole
collect([1, 2, 3])->hasMany();
// true
collect(['1'])->hasSole();
// true
collect([1, 2, 3])->hasSole(fn (int $item) => $item === 2);
// true
// implode
collect([
['account_id' => 1, 'product' => 'Desk'],
['account_id' => 2, 'product' => 'Chair'],
])->implode('product', ', ');
// 'Desk, Chair'
collect([1, 2, 3, 4, 5])->implode('-');
// '1-2-3-4-5'
// join
collect(['a', 'b', 'c'])->join(', '); // 'a, b, c'
collect(['a', 'b', 'c'])->join(', ', ', and '); // 'a, b, and c'
collect(['a', 'b'])->join(', ', ' and '); // 'a and b'
collect(['a'])->join(', ', ' and '); // 'a'
collect([])->join(', ', ' and '); // ''
// keys
collect([
'prod-100' => ['product_id' => 'prod-100', 'name' => 'Desk'],
'prod-200' => ['product_id' => 'prod-200', 'name' => 'Chair'],
])->keys()->all();
// ['prod-100', 'prod-200']
// values
collect([
10 => ['product' => 'Desk', 'price' => 200],
11 => ['product' => 'Speaker', 'price' => 400],
])->values()->all();
// [0 => ['product' => 'Desk', 'price' => 200], 1 => ['product' => 'Speaker', 'price' => 400]]
// value
collect([
['product' => 'Desk', 'price' => 200],
['product' => 'Speaker', 'price' => 400],
])->value('price');
// 200
// multiply
collect([['name' => 'User #1'], ['name' => 'User #2']])->multiply(3);
// User #1・User #2 が、3回くり返される(全部で6件)
// nth
collect(['a', 'b', 'c', 'd', 'e', 'f'])->nth(4);
// ['a', 'e']
collect(['a', 'b', 'c', 'd', 'e', 'f'])->nth(4, 1);
// ['b', 'f'](2番目の引数は、はじめの位置)
// only と except
collect(['product_id' => 1, 'name' => 'Desk', 'price' => 100, 'discount' => false])
->only(['product_id', 'name'])->all();
// ['product_id' => 1, 'name' => 'Desk']
collect(['product_id' => 1, 'price' => 100, 'discount' => false])
->except(['price', 'discount'])->all();
// ['product_id' => 1]
// pad(マイナスなら左を埋める)
collect(['A', 'B', 'C'])->pad(5, 0)->all();
// ['A', 'B', 'C', 0, 0]
collect(['A', 'B', 'C'])->pad(-5, 0)->all();
// [0, 0, 'A', 'B', 'C']
// random
collect([1, 2, 3, 4, 5])->random();
// 4(ランダム)
collect([1, 2, 3, 4, 5])->random(3)->all();
// [2, 4, 5](ランダム)
// range・times
collect()->range(3, 6)->all();
// [3, 4, 5, 6]
Collection::times(10, function (int $number) {
return $number * 9;
})->all();
// [9, 18, 27, 36, 45, 54, 63, 72, 81, 90]
// reverse
collect(['a', 'b', 'c', 'd', 'e'])->reverse()->all();
// [4 => 'e', 3 => 'd', 2 => 'c', 1 => 'b', 0 => 'a']
// select
collect([
['name' => 'Taylor Otwell', 'role' => 'Developer', 'status' => 'active'],
['name' => 'Victoria Faith', 'role' => 'Researcher', 'status' => 'active'],
])->select(['name', 'role']);
// [['name' => 'Taylor Otwell', 'role' => 'Developer'], ['name' => 'Victoria Faith', 'role' => 'Researcher']]
// shuffle
collect([1, 2, 3, 4, 5])->shuffle()->all();
// [3, 2, 5, 1, 4](ランダム)
// toArray・toJson・toPrettyJson
collect(['name' => 'Desk', 'price' => 200])->toArray();
// ['name' => 'Desk', 'price' => 200]
collect(['name' => 'Desk', 'price' => 200])->toJson();
// '{"name":"Desk","price":200}'
// unwrap と wrap
Collection::unwrap(collect('John Doe'));
// ['John Doe']
Collection::unwrap('John Doe');
// 'John Doe'
Collection::wrap('John Doe')->all();
// ['John Doe']
// zip
collect(['Chair', 'Desk'])->zip([100, 200])->all();
// [['Chair', 100], ['Desk', 200]]
注意
toArray は、中にある Arrayable(配列に変えられるという印を持つクラス。Eloquent のモデルなど)のオブジェクトも配列に変えます。変えずに、中の配列をそのまま取り出したいときは all を使います。
random は、要素の数より多い数を指定すると InvalidArgumentException を投げます。数を決める関数も渡せます。
use Illuminate\Support\Collection;
$random = $collection->random(fn (Collection $items) => min(10, count($items)));
$random->all();
// [1, 2, 3, 4, 5] - (retrieved randomly)
collect は、遅延コレクションをふつうのコレクションに変えるときに主に使います。
$lazyCollection = LazyCollection::make(function () {
yield 1;
yield 2;
yield 3;
});
$collection = $lazyCollection->collect();
$collection::class;
// 'Illuminate\Support\Collection'
$collection->all();
// [1, 2, 3]
補足
Enumerable(Collection と LazyCollection が両方持つ約束ごと)のオブジェクトから、遅延しないふつうのコレクションを得たいときも、collect が使えます。
lazy は、とても大きなコレクションを、メモリ(コンピューターが作業中のデータを置いておく場所)をあまり使わずに絞りこむときに便利です。
$count = $hugeCollection
->lazy()
->where('country', 'FR')
->where('balance', '>', '100')
->count();
元のコレクションは、中身をメモリに持ったままです。しかし、そのあとの絞りこみでは、新しくメモリをほとんど使いません。
dump は中身を表示して処理をつづけ、dd は表示して止めます。
$collection = collect(['John Doe', 'Jane Doe']);
$collection->dump();
$collection->dd();
/*
array:2 [
0 => "John Doe"
1 => "Jane Doe"
]
*/
補足
Eloquent のコレクション(モデルが入ったコレクション)では、contains・containsStrict・diff・except・intersect・only・partition・unique の動きが少し変わります。くわしくはEloquent のコレクションのページを見てください。
Higher Order Messages(高階メッセージ)#
コレクションには「ハイヤー・オーダー・メッセージ」という近道があります。関数を書かずに、よくある作業を短く書けます。次のメソッドで使えます:average・avg・contains・each・every・filter・first・flatMap・groupBy・keyBy・map・max・min・partition・reject・skipUntil・skipWhile・some・sortBy・sortByDesc・sum・takeUntil・takeWhile・unique。
使うときは、メソッド名を「プロパティ(クラスの中の変数)」のように書き、そのあとに、各要素に呼びたいメソッドやプロパティを続けます。たとえば each で、全員のメソッドを呼べます。
use App\Models\User;
$users = User::where('votes', '>', 500)->get();
$users->each->markAsVip();
sum なら、全員の votes の合計が出せます。
$users = User::where('group', 'Development')->get();
return $users->sum->votes;
遅延コレクション(Lazy Collections)#
注意
遅延コレクションを学ぶ前に、PHP のジェネレータ(値を1つずつ返す関数のしくみ)を知っておくと、分かりやすくなります。
遅延コレクションとは#
LazyCollection は、PHP のジェネレータを使って、とても大きなデータを少ないメモリで扱うための入れ物です。ふつうのコレクションが「全部を一度に手元へ置く」のに対し、遅延コレクションは「必要になった1つずつを取り出す」ので、メモリを節約できます。
たとえば、数ギガバイトのログファイルを読むとき、ファイルを丸ごと読みこむ代わりに、ごく一部だけをメモリに置いて処理できます。
use App\Models\LogEntry;
use Illuminate\Support\LazyCollection;
LazyCollection::make(function () {
$handle = fopen('log.txt', 'r');
while (($line = fgets($handle)) !== false) {
yield $line;
}
fclose($handle);
})->chunk(4)->map(function (LazyCollection $lines) {
return LogEntry::fromLines($lines);
})->each(function (LogEntry $logEntry) {
// Process the log entry...
});
また、Eloquent のモデルが1万件あるとき、ふつうのコレクションだと、1万件が一度にメモリへ載ります。
use App\Models\User;
$users = User::all()->filter(function (User $user) {
return $user->id > 500;
});
クエリビルダ(SQL を書かずに、メソッドをつないでデータベースに問い合わせるしくみ)の cursor メソッドは LazyCollection を返します。データベースへの問い合わせは1回のまま、メモリにはモデルが1件ずつしか載りません。次の例では、filter の関数は、ユーザーを1人ずつ取り出すときにはじめて実行されます。
use App\Models\User;
$users = User::cursor()->filter(function (User $user) {
return $user->id > 500;
});
foreach ($users as $user) {
echo $user->id;
}
遅延コレクションを作る#
make メソッドに、PHP のジェネレータ関数を渡します。
use Illuminate\Support\LazyCollection;
LazyCollection::make(function () {
$handle = fopen('log.txt', 'r');
while (($line = fgets($handle)) !== false) {
yield $line;
}
fclose($handle);
});
Enumerable(共通の約束)#
Collection のほぼすべてのメソッドは、LazyCollection でも使えます。両方とも、Illuminate\Support\Enumerable という約束ごと(コントラクト)を持っているためです。Enumerable が決めているメソッドは、次のとおりです。
| 名前 | 名前 | 名前 |
|---|---|---|
all |
average |
avg |
chunk |
chunkBy |
chunkWhile |
collapse |
collect |
combine |
concat |
contains |
containsStrict |
count |
countBy |
crossJoin |
dd |
diff |
diffAssoc |
diffKeys |
dump |
duplicates |
duplicatesStrict |
each |
eachSpread |
every |
except |
filter |
first |
firstOrFail |
firstWhere |
flatMap |
flatten |
flip |
forPage |
get |
groupBy |
has |
implode |
intersect |
intersectAssoc |
intersectByKeys |
isEmpty |
isNotEmpty |
join |
keyBy |
keys |
last |
macro |
make |
map |
mapInto |
mapSpread |
mapToGroups |
mapWithKeys |
max |
median |
merge |
mergeRecursive |
min |
mode |
nth |
only |
pad |
partition |
pipe |
pluck |
random |
reduce |
reduceInto |
reject |
replace |
replaceRecursive |
reverse |
search |
shuffle |
skip |
slice |
sole |
some |
sort |
sortBy |
sortByDesc |
sortKeys |
sortKeysDesc |
split |
sum |
take |
tap |
times |
toArray |
toJson |
union |
unique |
uniqueStrict |
unless |
unlessEmpty |
unlessNotEmpty |
unwrap |
values |
when |
whenEmpty |
whenNotEmpty |
where |
whereStrict |
whereBetween |
whereIn |
whereInStrict |
whereInstanceOf |
whereNotBetween |
whereNotIn |
whereNotInStrict |
wrap |
zip |
注意
shift・pop・prepend のように、コレクション自体を書きかえるメソッドは、LazyCollection では使えません。
遅延コレクションだけのメソッド#
Enumerable のメソッドのほかに、LazyCollection には次のメソッドがあります。
| 名前 | 説明 |
|---|---|
takeUntilTimeout |
指定の時刻まで要素を取り出し、それ以降は止まる |
tapEach |
要素が1つずつ取り出されるたびに、関数を呼ぶ |
throttle |
指定の秒数ごとに1つずつ値を返す。ほかのサービスの API に回数の制限があるときに便利 |
remember |
取り出し済みの値を覚えて、次からは取り直さない |
withHeartbeat |
取り出しているあいだ、決まった間隔で関数を実行する |
takeUntilTimeout は、指定の時刻まで取り出して止まります。
$lazyCollection = LazyCollection::times(INF)
->takeUntilTimeout(now()->plus(minutes: 1));
$lazyCollection->each(function (int $number) {
dump($number);
sleep(1);
});
// 1
// 2
// ...
// 58
// 59
たとえば、データベースの請求書を cursor で順に送るアプリがあるとします。15分おきに動くスケジュール(決まった時刻に動かすしくみ)にして、最大14分だけ処理させる、という使い方ができます。
use App\Models\Invoice;
use Illuminate\Support\Carbon;
Invoice::pending()->cursor()
->takeUntilTimeout(
Carbon::createFromTimestamp(LARAVEL_START)->add(14, 'minutes')
)
->each(fn (Invoice $invoice) => $invoice->submit());
each は、呼んですぐに全要素へ関数を実行します。一方 tapEach は、要素が1つずつ取り出されるときにはじめて関数を呼びます。
// ここまでは、まだ何も表示されない
$lazyCollection = LazyCollection::times(INF)->tapEach(function (int $value) {
dump($value);
});
// 3つ取り出すので、3つ表示される
$array = $lazyCollection->take(3)->all();
// 1
// 2
// 3
throttle は、1つ取り出すごとに、指定の秒数を待ちます。
use App\Models\User;
User::where('vip', true)
->cursor()
->throttle(seconds: 1)
->each(function (User $user) {
// Call external API...
});
remember は、取り出した値を覚えておきます。
// まだ問い合わせは実行されていない
$users = User::cursor()->remember();
// 問い合わせが実行される
// 最初の5人をデータベースから取り出す
$users->take(5)->all();
// 最初の5人は、覚えておいたものを使う
// 残りは、データベースから取り出す
$users->take(20)->all();
withHeartbeat は、時間のかかる処理のあいだ、決まった間隔で関数を実行します。ロック(ほかの処理が同じ仕事を同時にしないための鍵)の延長や、進み具合の知らせに便利です。
use Carbon\CarbonInterval;
use Illuminate\Support\Facades\Cache;
$lock = Cache::lock('generate-reports', seconds: 60 * 5);
if ($lock->get()) {
try {
Report::where('status', 'pending')
->lazy()
->withHeartbeat(
CarbonInterval::minutes(4),
fn () => $lock->refresh()
)
->each(fn ($report) => $report->process());
} finally {
$lock->release();
}
}
関連するページ#
公式ドキュメント(英語)
2026年10月5日時点の内容をもとに、日本語でまとめています。