Laravelのコレクション(その7:構造変換)

Date:

Share post:

Laravelコレクション掘り下げシリーズ第七弾。今回は本体配列の構造を変換するようなメソッドに関して確認していきます。

chunk

$col1 = collect([1,2,3,4,5]);
$col2 = $col1->chunk(2);

指定の数ごとに要素を分割します。
結果は以下の通り。

array:3 [
  0 => array:2 [
    0 => 1
    1 => 2
  ]
  1 => array:2 [
    2 => 3
    3 => 4
  ]
  2 => array:1 [
    4 => 5
  ]
]

例によって連想配列にも適用可能です。

$col1 = collect([
    'a' => 1,
    'b' => 2,
    'c' => 3,
    'd' => 4,
    'e' => 5
]);
$col2 = $col1->chunk(2);

結果は以下の通り。

array:3 [
  0 => array:2 [
    "a" => 1
    "b" => 2
  ]
  1 => array:2 [
    "c" => 3
    "d" => 4
  ]
  2 => array:1 [
    "e" => 5
  ]
]

このような使い方をすることがあるかどうかは分かりませんが。

collapse

$col1 = collect([
    [1,2],
    [3,4,5],
    [6]
]);
$col2 = $col1->collapse();

二次元配列を一次元配列に変換します。
結果は以下の通り。

array:6 [
  0 => 1
  1 => 2
  2 => 3
  3 => 4
  4 => 5
  5 => 6
]

多次元配列になった場合はどうでしょう。

$col1 = collect([
    [
        1,
        2,
    ],
    [
        [3,4,5],
        [6],
    ],
]);
$col2 = $col1->collapse();

結果は以下の通り。

array:4 [
  0 => 1
  1 => 2
  2 => array:3 [
    0 => 3
    1 => 4
    2 => 5
  ]
  3 => array:1 [
    0 => 6
  ]
]

あくまで二次元目の配列の要素が一次元目に並べられると言うことであって、多次元配列の様々な深度の要素がフラットに並べられると言うことではなさそうです。

もし変換前の多次元配列の一次元目に配列以外の要素が存在した場合はどうなるでしょう?

$col1 = collect([
    1,
    2,
    [3,4,5],
    [6],
]);
$col2 = $col1->collapse();

結果は以下の通り。

array:4 [
  0 => 3
  1 => 4
  2 => 5
  3 => 6
]

あくまで対象は二次元目の要素であり、一次元目は捨てられてしまうようです。

ついでに多次元の連想配列も試してみます。

$col1 = collect([
    'a' => 1,
    'b' => [
        'b-1' => 2,
    ],
    'c' => [
        'c-1' => [
            'c-1-1' => 3,
            'c-1-2' => 4,
            'c-1-3' => 5,
        ],
    ]
]);
$col2 = $col1->collapse(2);

結果は以下の通り。

array:2 [
  "b-1" => 2
  "c-1" => array:3 [
    "c-1-1" => 3
    "c-1-2" => 4
    "c-1-3" => 5
  ]
]

粛々と二次元目の要素をキーも含めて一次元目に並べ直すのみですね。

combine

$col1 = collect(['a','b']);
$col2 = $col1->combine(['1','2']);

本体配列にある要素をキーとし、引数で与えられた配列の値と組み合わせて新たな連想配列を生成します。
結果は以下の通り。

array:2 [
  "a" => "1"
  "b" => "2"
]

なお本体配列と引数で指定した配列の要素数が合っていないとエラーになるようです。

crossJoin

$col1 = collect(['a','b']);
$col2 = $col1->crossJoin(['1','2']);

本体配列に対して引数で指定された配列の要素との全組み合わせを持つ多次元配列を生成します。
結果は以下の通り。

array:4 [
  0 => array:2 [
    0 => "a"
    1 => "1"
  ]
  1 => array:2 [
    0 => "a"
    1 => "2"
  ]
  2 => array:2 [
    0 => "b"
    1 => "1"
  ]
  3 => array:2 [
    0 => "b"
    1 => "2"
  ]
]

引数で指定する配列が多次元だった場合はどうでしょう?

$col1 = collect(['a','b']);
$col2 = $col1->crossJoin([
    ['1','2'],
    ['3','4']
]);

結果は以下の通り。

array:4 [
  0 => array:2 [
    0 => "a"
    1 => array:2 [
      0 => "1"
      1 => "2"
    ]
  ]
  1 => array:2 [
    0 => "a"
    1 => array:2 [
      0 => "3"
      1 => "4"
    ]
  ]
  2 => array:2 [
    0 => "b"
    1 => array:2 [
      0 => "1"
      1 => "2"
    ]
  ]
  3 => array:2 [
    0 => "b"
    1 => array:2 [
      0 => "3"
      1 => "4"
    ]
  ]
]

あくまで一次元目の要素の組み合わせを作るようです。

一応本体配列が多次元の場合も確認してみます。

$col1 = collect([
    ['a','b'],
    ['c','d']
]);
$col2 = $col1->crossJoin(['1','2']);

結果は以下の通り。

array:4 [
  0 => array:2 [
    0 => array:2 [
      0 => "a"
      1 => "b"
    ]
    1 => "1"
  ]
  1 => array:2 [
    0 => array:2 [
      0 => "a"
      1 => "b"
    ]
    1 => "2"
  ]
  2 => array:2 [
    0 => array:2 [
      0 => "c"
      1 => "d"
    ]
    1 => "1"
  ]
  3 => array:2 [
    0 => array:2 [
      0 => "c"
      1 => "d"
    ]
    1 => "2"
  ]
]

まぁ、そうでしょうね。

両方多次元の場合はどうでしょう。

$col1 = collect([
    ['a','b'],
    ['c','d']
]);
$col2 = $col1->crossJoin([
    ['1','2'],
    ['3','4']
]);

結果は以下の通り。

array:4 [
  0 => array:2 [
    0 => array:2 [
      0 => "a"
      1 => "b"
    ]
    1 => array:2 [
      0 => "1"
      1 => "2"
    ]
  ]
  1 => array:2 [
    0 => array:2 [
      0 => "a"
      1 => "b"
    ]
    1 => array:2 [
      0 => "3"
      1 => "4"
    ]
  ]
  2 => array:2 [
    0 => array:2 [
      0 => "c"
      1 => "d"
    ]
    1 => array:2 [
      0 => "1"
      1 => "2"
    ]
  ]
  3 => array:2 [
    0 => array:2 [
      0 => "c"
      1 => "d"
    ]
    1 => array:2 [
      0 => "3"
      1 => "4"
    ]
  ]
]

やはり一次元での組み合わせしか行われません。

なお、引数で与える配列は複数指定可能です。

$col1 = collect(['a','b']);
$col2 = $col1->crossJoin(
    ['A','B'],
    ['1','2'],
    ['#','$'],
);

結果は以下の通り。

array:16 [
  0 => array:4 [
    0 => "a"
    1 => "A"
    2 => "1"
    3 => "#"
  ]
  1 => array:4 [
    0 => "a"
    1 => "A"
    2 => "1"
    3 => "$"
  ]
  2 => array:4 [
    0 => "a"
    1 => "A"
    2 => "2"
    3 => "#"
  ]
  3 => array:4 [
    0 => "a"
    1 => "A"
    2 => "2"
    3 => "$"
  ]
  4 => array:4 [
    0 => "a"
    1 => "B"
    2 => "1"
    3 => "#"
  ]
  5 => array:4 [
    0 => "a"
    1 => "B"
    2 => "1"
    3 => "$"
  ]
  6 => array:4 [
    0 => "a"
    1 => "B"
    2 => "2"
    3 => "#"
  ]
  7 => array:4 [
    0 => "a"
    1 => "B"
    2 => "2"
    3 => "$"
  ]
  8 => array:4 [
    0 => "b"
    1 => "A"
    2 => "1"
    3 => "#"
  ]
  9 => array:4 [
    0 => "b"
    1 => "A"
    2 => "1"
    3 => "$"
  ]
  10 => array:4 [
    0 => "b"
    1 => "A"
    2 => "2"
    3 => "#"
  ]
  11 => array:4 [
    0 => "b"
    1 => "A"
    2 => "2"
    3 => "$"
  ]
  12 => array:4 [
    0 => "b"
    1 => "B"
    2 => "1"
    3 => "#"
  ]
  13 => array:4 [
    0 => "b"
    1 => "B"
    2 => "1"
    3 => "$"
  ]
  14 => array:4 [
    0 => "b"
    1 => "B"
    2 => "2"
    3 => "#"
  ]
  15 => array:4 [
    0 => "b"
    1 => "B"
    2 => "2"
    3 => "$"
  ]
]

使ったことはないメソッドなのですが、使える予感はします。

flatMap

$col1 = collect([1,2,3]);
$col2 = $col1->flatMap(function($value) {
    return [$value * $value];
});

全要素に対してコールバックを呼び出し、コールバック内での加工結果による新しいコレクションを生成します。
コールバックの戻りが配列型であることが必要な点が要注意です。
結果は以下の通り。

array:3 [
  0 => 1
  1 => 4
  2 => 9
]

ちょっと分かり難いのですが、形式は配列で返しつつも、その値のみが元のキー(インデックス)の要素に反映されています。

戻りが配列型となると言うことでキーの指定ができるようです。
例えば以下のようなケースを考えてみます。

$col1 = collect([
    'a' => 1,
    'b' => 2,
    'c' => 3
]);
$col2 = $col1->flatMap(function($value, $key) {
    return [$key.$key => $value * $value];
});

結果は以下の通り。

array:3 [
  "aa" => 1
  "bb" => 4
  "cc" => 9
]

戻り配列に明示的にキーを指定すれば、そのキーと値がセットで採用されるようです。

と言うことですが、何か直感的に分かり難いインタフェースのように思います。
各要素に関して個別に加工した結果から新しいコレクションを生成する方法としては後述するmapの方がわかりやすいように思います(個人的にはmapの方を愛用しています)。

flatten

$col1 = collect([
    [1,2],
    [3,4]
]);
$col2 = $col1->flatten();

多次元連想配列を一次元配列に加工します。
結果は以下の通り。

array:4 [
  0 => 1
  1 => 2
  2 => 3
  3 => 4
]

階層が深くてもまとめて一次元化されるようです。

$col1 = collect([
    'a' => [
        'a-1' => 1,
        'a-2' => [
            'a-2-1' => 2,
            'a-2-2' => 3,
        ],
    ],
    'b' => [
        'b-1' => 4,
        'b-2' => [
            'b-2-1' => 5,
            'b-2-2' => 6,
        ],
    ],
]);
$col2 = $col1->flatten();

結果は以下の通り。

array:6 [
  0 => 1
  1 => 2
  2 => 3
  3 => 4
  4 => 5
  5 => 6
]

上記の通り、元の配列が連想配列であった場合でもキーは無視されて単純な一次元配列(キーは連番)になります。

なお、引数で減らす次元を指定できるようです。
先の例と同じ本体配列に対して次元指定してみます。

$col1 = collect([
    'a' => [
        'a-1' => 1,
        'a-2' => [
            'a-2-1' => 2,
            'a-2-2' => 3,
        ],
    ],
    'b' => [
        'b-1' => 4,
        'b-2' => [
            'b-2-1' => 5,
            'b-2-2' => 6,
        ],
    ],
]);
$col2 = $col1->flatten(1);

結果は以下の通り。

array:4 [
  0 => 1
  1 => array:2 [
    "a-2-1" => 2
    "a-2-2" => 3
  ]
  2 => 4
  3 => array:2 [
    "b-2-1" => 5
    "b-2-2" => 6
  ]
]

なお、次元数として2(本体配列のネストの最深度)を指定すると次元数を指定しなかった場合と同じ結果が得られますし、実際の次元数以上(本ケースでは3以上)を指定した場合もエラーにはならず、引数を指定しなかった場合と同じ結果が得られます。

flip

$col1 = collect([
    'a' => 1,
    'b' => 2,
]);
$col2 = $col1->flip();

キーと値を入れ替えます。
結果は以下の通り。

array:2 [
  1 => "a"
  2 => "b"
]

当然ながら、要素は数値か文字列であることが必要であり、配列等の場合はエラーになります。

groupBy

$col1 = collect([
    'key1' => [
        'attr1' => 1,
        'attr2' => 'a', 
    ],
    'key2' => [
        'attr1' => 2,
        'attr2' => 'b', 
    ],
    'key3' => [
        'attr1' => 3,
        'attr2' => 'a', 
    ],
]);
$col2 = $col1->groupBy('attr2');

指定したキーの値を新たなキーとして、その値が同じ要素をグループ化した新しいコレクションを生成します。
結果は以下の通り。

array:2 [
  "a" => array:2 [
    0 => array:2 [
      "attr1" => 1
      "attr2" => "a"
    ]
    1 => array:2 [
      "attr1" => 3
      "attr2" => "a"
    ]
  ]
  "b" => array:1 [
    0 => array:2 [
      "attr1" => 2
      "attr2" => "b"
    ]
  ]
]

上記の通り、本体配列の一次元目のキーは無視されますので注意が必要です。
キーを保持したい場合は第二引数にtrueを指定します。
その結果は以下のようになります。

array:2 [
  "a" => array:2 [
    "key1" => array:2 [
      "attr1" => 1
      "attr2" => "a"
    ]
    "key3" => array:2 [
      "attr1" => 3
      "attr2" => "a"
    ]
  ]
  "b" => array:1 [
    "key2" => array:2 [
      "attr1" => 2
      "attr2" => "b"
    ]
  ]
]

引数にコールバックを指定することで、各要素に対する新規キーの値を操作できます。

$col1 = collect([
    'key1' => [
        'attr1' => 1,
    ],
    'key2' => [
        'attr1' => 2,
    ],
    'key3' => [
        'attr1' => 3,
    ],
    'key4' => [
        'attr1' => 4,
    ],
]);
$col2 = $col1->groupBy(function($value) {
    return $value['attr1'] % 2;
});

結果は以下の通り。

array:2 [
  1 => array:2 [
    0 => array:1 [
      "attr1" => 1
    ]
    1 => array:1 [
      "attr1" => 3
    ]
  ]
  0 => array:2 [
    0 => array:1 [
      "attr1" => 2
    ]
    1 => array:1 [
      "attr1" => 4
    ]
  ]
]

複数のグルーピング条件を指定することもできるようです。
その場合は第一引数に指定する条件を配列で指定します。この配列にはコールバックを指定することもできます。

$col1 = collect([
    [
        'name' => 'taro',
        'age' => 38,
        'hobby' => '読書',
    ],
    [
        'name' => 'jiro',
        'age' => 53,
        'hobby' => '旅行',
    ],
    [
        'name' => 'saburo',
        'age' => 44,
        'hobby' => '読書',
    ],
    [
        'name' => 'shiro',
        'age' => 31,
        'hobby' => '読書',
    ],
]);
$col2 = $col1->groupBy([
    'hobby',
    function($value) {
        return (int)floor($value['age'] / 10) * 10;
    }
]);

「hobby」でグループ化した後に、さらにその中を年代で分ける処理です。
結果は以下の通り。

array:2 [
  "読書" => array:2 [
    30 => array:2 [
      0 => array:3 [
        "name" => "taro"
        "age" => 38
        "hobby" => "読書"
      ]
      1 => array:3 [
        "name" => "shiro"
        "age" => 31
        "hobby" => "読書"
      ]
    ]
    40 => array:1 [
      0 => array:3 [
        "name" => "saburo"
        "age" => 44
        "hobby" => "読書"
      ]
    ]
  ]
  "旅行" => array:1 [
    50 => array:1 [
      0 => array:3 [
        "name" => "jiro"
        "age" => 53
        "hobby" => "旅行"
      ]
    ]
  ]
]

keyBy

$col1 = collect([
    'key1' => [
        'attr1' => 1,
        'attr2' => 'a', 
    ],
    'key2' => [
        'attr1' => 2,
        'attr2' => 'b', 
    ],
    'key3' => [
        'attr1' => 3,
        'attr2' => 'c', 
    ],
]);
$col2 = $col1->keyBy('attr2');

指定した要素の値をキーとします。
結果は以下の通り。

array:3 [
  "a" => array:2 [
    "attr1" => 1
    "attr2" => "a"
  ]
  "b" => array:2 [
    "attr1" => 2
    "attr2" => "b"
  ]
  "c" => array:2 [
    "attr1" => 3
    "attr2" => "c"
  ]
]

groupByとは異なり同じキーとなる要素が複数存在する場合は問題となる訳で、その場合は最後の要素が採用されます。

$col1 = collect([
    'key1' => [
        'attr1' => 1,
        'attr2' => 'a', 
    ],
    'key2' => [
        'attr1' => 2,
        'attr2' => 'b', 
    ],
    'key3' => [
        'attr1' => 3,
        'attr2' => 'a', 
    ],
]);
$col2 = $col1->keyBy('attr2');

結果は以下の通り。

array:2 [
  "a" => array:2 [
    "attr1" => 3
    "attr2" => "a"
  ]
  "b" => array:2 [
    "attr1" => 2
    "attr2" => "b"
  ]
]

引数にコールバックを指定してキーの値を操作することもできます。

$col1 = collect([
    'key1' => [
        'attr1' => 1,
        'attr2' => 'a', 
    ],
    'key2' => [
        'attr1' => 2,
        'attr2' => 'b', 
    ],
    'key3' => [
        'attr1' => 3,
        'attr2' => 'a', 
    ],
]);
$col2 = $col1->keyBy(function($value) {
    return $value['attr2'].'-'.$value['attr1'];
});

結果は以下の通り。

array:3 [
  "a-1" => array:2 [
    "attr1" => 1
    "attr2" => "a"
  ]
  "b-2" => array:2 [
    "attr1" => 2
    "attr2" => "b"
  ]
  "a-3" => array:2 [
    "attr1" => 3
    "attr2" => "a"
  ]
]

keys

$col1 = collect([
    'key1' => [
        'attr1' => 1,
        'attr2' => 'a', 
    ],
    'key2' => [
        'attr1' => 2,
        'attr2' => 'b', 
    ],
    'key3' => [
        'attr1' => 3,
        'attr2' => 'c', 
    ],
]);
$col2 = $col1->keys();

キーのみのコレクションを生成します。
結果は以下の通り。

array:3 [
  0 => "key1"
  1 => "key2"
  2 => "key3"
]

あくまで一次元目のキーのみが対象です。

map

$col1 = collect([1,2,3,4]);
$col2 = $col1->map(function($value) {
    return [
        'val' => $value,
        'type' => $value % 2 === 0 ? 'even' : 'odd',
    ];
});

コールバックによって各要素を自由に加工し、その戻り値で新しいコレクションを生成します。
結果は以下の通り。

array:4 [
  0 => array:2 [
    "val" => 1
    "type" => "odd"
  ]
  1 => array:2 [
    "val" => 2
    "type" => "even"
  ]
  2 => array:2 [
    "val" => 3
    "type" => "odd"
  ]
  3 => array:2 [
    "val" => 4
    "type" => "even"
  ]
]

コレクションに関して何らかの加工をする場合に最も汎用性のあるメソッドかと思います。
個人的にも愛用しています。

mapInto

本メソッドはmapと似ているのですが、引数にクラス名を指定することで、各要素をコンストラクタの引数として指定しつつ当該クラスを生成し、そのインスタンスのコレクションを生成します。

例えば以下のような単純なクラスを考えます。

class Sample {
    public $attr;

    public function __construct($val)
    {
        $this->attr = $val * 2;
    }
}

上記に対して以下のような処理を実行します。

$col1 = collect([1,2,3,4]);
$col2 = $col1->mapInto(Sample::class);
$col2->each(function($obj) {
    echo $obj->attr."\n";
});

本体配列の各要素に対して先のクラスをインスタンス化しつつコレクションを生成し、生成したコレクションの各要素(インスタンス)のattrプロパティの内容を表示しています。
結果は以下の通り。

2
4
6
8

mapSpread

$col1 = collect([
    [1,2],
    [3,4],
    [5,6],
]);
$col2 = $col1->mapSpread(function($val1, $val2) {
    return $val1 * $val2;
});

一次元目の要素が配列である前提で、その配列内の各要素を引数としてコールバックを実行し、戻り値から新しいコレクションを生成します。
結果は以下の通り。

array:3 [
  0 => 2
  1 => 12
  2 => 30
]

なお、上記構造において一次元目は連想配列(キーが連番以外)でも問題ありませんが、引数に変換される二次元目の配列は連想配列ではエラーとなり、あくまで配列(キーが連番)であることが条件となるようです。
つまり、

$col1 = collect([
    'a' => [1,2],
    'b' => [3,4],
    'c' => [5,6],
]);
$col2 = $col1->mapSpread(function($val1, $val2) {
    return $val1 * $val2;
});

に関しては、

array:3 [
  "a" => 2
  "b" => 12
  "c" => 30
]

と言う結果を得られますが、

$col1 = collect([
    [
        'a' => 1,
        'b' => 2
    ],
    [
        'a' => 3,
        'b' => 4
    ],
    [
        'a' => 5,
        'b' => 6
    ],
]);
$col2 = $col1->mapSpread(function($val1, $val2) {
    return $val1 * $val2;
});

はエラーになると言うことです。

ところで、二次元目の配列の要素をコールバックの引数にばらすとして、数が不一致だった場合はどうなるでしょう?

$col1 = collect([
    [
        'a',
        'b',
        'c'
    ],
    [
        'd',
        'e',
        'f',
        'g'
    ],
    [
        'h',
        'i'
    ],
]);
$col2 = $col1->mapSpread(function($val1, $val2, $val3) {
    return $val1.$val2.$val3;
});

結果は以下の通り。

array:3 [
  0 => "abc"
  1 => "def"
  2 => "hi2"
]

引数と比較して配列の要素の方が多い場合は、単に引数に反映されない要素が無視されるだけのようなので良いですが、配列の要素の方が少ない場合に謎の値(上記ケースでは2)が設置されている点はいかがなものでしょうかね?
加えて言うとさらに引数の数を増やすとエラーになったりもします。

やはり対象となる配列の要素数と引数の数は合わせておくべきと言うことでしょう。

mapToGroups

$col1 = collect([
    'key1' => [
        'attr1' => 1,
        'attr2' => 'a', 
    ],
    'key2' => [
        'attr1' => 2,
        'attr2' => 'b', 
    ],
    'key3' => [
        'attr1' => 3,
        'attr2' => 'a', 
    ],
]);
$col2 = $col1->mapToGroups(function($value, $key) {
    return [
        $value['attr2'] => [
            'key' => $key,
            'val' => $value['attr1'],
        ],
    ];
});

引数にコールバックを指定し、コールバックの戻りにキーと値(任意の加工可能)のセットを指定することで、そのキーでグルーピングしつつ加工結果の値から構成されるコレクションを生成します。
要は名前の通りmapとgroupByを合わせたような機能ということです。
結果は以下の通り。

array:2 [
  "a" => array:2 [
    0 => array:2 [
      "key" => "key1"
      "val" => 1
    ]
    1 => array:2 [
      "key" => "key3"
      "val" => 3
    ]
  ]
  "b" => array:1 [
    0 => array:2 [
      "key" => "key2"
      "val" => 2
    ]
  ]
]

mapWithKeys

$col1 = collect([
    'key1' => [
        'attr1' => 1,
        'attr2' => 'a', 
    ],
    'key2' => [
        'attr1' => 2,
        'attr2' => 'b', 
    ],
    'key3' => [
        'attr1' => 3,
        'attr2' => 'c', 
    ],
]);
$col2 = $col1->mapWithKeys(function($value, $key) {
    return [
        $value['attr2'] => [
            'key' => $key,
            'val' => $value['attr1'],
        ],
    ];
});

引数にコールバックを指定し、コールバックの戻りにキーと値(任意の加工可能)のセットを指定することで、そのキーと加工結果の値から構成されるコレクションを生成します。
こちらも名前の通りmapとkeyByを合わせたような機能ということです。
結果は以下の通り。

array:3 [
  "a" => array:2 [
    "key" => "key1"
    "val" => 1
  ]
  "b" => array:2 [
    "key" => "key2"
    "val" => 2
  ]
  "c" => array:2 [
    "key" => "key3"
    "val" => 3
  ]
]

merge

$col1 = collect([1,2,3]);
$col2 = $col1->merge([4,5,6]);

本体配列に引数で指定された配列(コレクション)をマージします。
結果は以下の通り。

array:6 [
  0 => 1
  1 => 2
  2 => 3
  3 => 4
  4 => 5
  5 => 6
]

連想配列も対象とできますが、キーが重複した場合は引数で指定した側の要素が有効になる点に注意が必要です。

$col1 = collect([
    'a' => 1,
    'b' => 2
]);
$col2 = $col1->merge([
    'a' => 4,
    'c' => 5
]);

結果は以下の通り。

array:3 [
  "a" => 4
  "b" => 2
  "c" => 5
]

mergeRecursive

$col1 = collect([
    'key1' => [
        'attr1' => 1,
        'attr2' => 'a', 
    ],
    'key2' => [
        'attr1' => 2,
        'attr2' => 'b', 
    ],
    'key3' => [
        'attr1' => 3,
        'attr2' => 'c', 
    ],
]);
$col2 = $col1->mergeRecursive([
    'key1' => [
        'attr3' => 'append',
    ],
    'key3' => [
        'attr1' => 'modify',
    ],
]);

mergeでは一次元目の要素に関する追加・置換を行うのみでしたが、mergeRecursiveでは階層の深い位置の要素を対象とできます。
結果は以下の通り。

array:3 [
  "key1" => array:3 [
    "attr1" => 1
    "attr2" => "a"
    "attr3" => "append"
  ]
  "key2" => array:2 [
    "attr1" => 2
    "attr2" => "b"
  ]
  "key3" => array:2 [
    "attr1" => array:2 [
      0 => 3
      1 => "modify"
    ]
    "attr2" => "c"
  ]
]

注意が必要なのは、既存の要素と同じキーを指定した場合です。上記例では「key3.attr1」が該当します。
mergeでは同じキーの要素に関しては置換されていましたが、mergeRecursiveでは当該要素が配列化され、元の要素と追加要素の両方が含まれる形になります。

pluck

$col1 = collect([
    'key1' => [
        'attr1' => 1,
        'attr2' => 'a', 
    ],
    'key2' => [
        'attr1' => 2,
        'attr2' => 'b', 
    ],
    'key3' => [
        'attr1' => 3,
        'attr2' => 'c', 
    ],
]);
$col2 = $col1->pluck('attr2');

本体配列の各要素から指定したキーの値を抽出し、新しいコレクションを生成します。
結果は以下の通り。

array:3 [
  0 => "a"
  1 => "b"
  2 => "c"
]

第二引数に指定した要素を、新規コレクション生成時の各要素のキーとすることもできます。

$col1 = collect([
    'key1' => [
        'attr1' => 1,
        'attr2' => 'a', 
    ],
    'key2' => [
        'attr1' => 2,
        'attr2' => 'b', 
    ],
    'key3' => [
        'attr1' => 3,
        'attr2' => 'c', 
    ],
]);
$col2 = $col1->pluck('attr1','attr2');

結果は以下の通り。

array:3 [
  "a" => 1
  "b" => 2
  "c" => 3
]

引数の順番として、第一引数が値、第二引数がキーに対応する要素である点に注意が必要です。直感的には逆に考えがちかと思います。
これは第二引数(キー)に関しては省略可能であることも含めて考えれば妥当なのですが。

本メソッドもかなり多用します。DBから複数のレコードを抽出したきた後、各レコードのIDのみを再度抽出したい場合など。
習得必須メソッドの一つかと思います。

replace

$col1 = collect([1,2,3]);
$col2 = $col1->replace([4,5]);

指定した配列の各キーの要素を置換します。
結果は以下の通り。

array:3 [
  0 => 4
  1 => 5
  2 => 3
]

上記は通番をキーとして、それぞれの位置の要素を置換していることになります。
当然ながら連想配列では具体的に指定されたキーに準じて置換を行うことになります。

$col1 = collect([
    'a' => 1,
    'b' => 2
]);
$col2 = $col1->replace([
    'a' => 4,
    'c' => 5
]);

結果は以下の通り。

array:3 [
  "a" => 4
  "b" => 2
  "c" => 5
]

実はこの結果はmergeと変わりません。つまりmergeとreplaceの違いはキーが数値だった場合の扱いだけのようです。

replaceRecursive

$col1 = collect([
    'key1' => [
        'attr1' => 1,
        'attr2' => 'a', 
    ],
    'key2' => [
        'attr1' => 2,
        'attr2' => 'b', 
    ],
    'key3' => [
        'attr1' => 3,
        'attr2' => 'c', 
    ],
]);
$col2 = $col1->replaceRecursive([
    'key1' => [
        'attr3' => 'append',
    ],
    'key3' => [
        'attr1' => 'modify',
    ],
]);

replaceが一次元目を対象とするのに対して、replaceRecursiveは階層の深い位置の要素を対象とできます。
この辺はmergeとmergeRecursiveの関係と同じです。
結果は以下の通り。

array:3 [
  "key1" => array:3 [
    "attr1" => 1
    "attr2" => "a"
    "attr3" => "append"
  ]
  "key2" => array:2 [
    "attr1" => 2
    "attr2" => "b"
  ]
  "key3" => array:2 [
    "attr1" => "modify"
    "attr2" => "c"
  ]
]

mergeRecursiveとの違いは、既存の要素と同じキーを指定した場合(上記例では「key3.attr1」)に、mergeRecursiveでは元要素と新規要素の両方を含んだ配列になりましたが、replaceRecursiveでは元要素を新規要素で置換し元要素が残らない点です。

split

$col1 = collect([1,2,3,4,5],);
$col2 = $col1->split(3);

本体配列を引数で指定した数のグループに分割します。
結果は以下の通り。

array:3 [
  0 => array:2 [
    0 => 1
    1 => 2
  ]
  1 => array:2 [
    0 => 3
    1 => 4
  ]
  2 => array:1 [
    0 => 5
  ]
]

chunkに似ていますが、chunkでは指定した数の要素の配列に分割していたのに対し、splitでは指定した数の配列に分割している点が違いです。

transform

$col1 = collect([1,2,3,4]);
$col1->transform(function($value) {
    return [
        'val' => $value,
        'type' => $value % 2 === 0 ? 'even' : 'odd',
    ];
});

mapに似ていますが、mapが加工後の要素を持つ新しいコレクションを生成するのに対し、transformでは本体配列自体の各要素を加工後の要素に置換します。
結果は以下の通り。

array:4 [
  0 => array:2 [
    "val" => 1
    "type" => "odd"
  ]
  1 => array:2 [
    "val" => 2
    "type" => "even"
  ]
  2 => array:2 [
    "val" => 3
    "type" => "odd"
  ]
  3 => array:2 [
    "val" => 4
    "type" => "even"
  ]
]

union

$col1 = collect([
    'a' => 1,
    'b' => 2
]);
$col2 = $col1->union([
    'a' => 4,
    'c' => 5
]);

本体配列に引数で指定された配列(コレクション)をマージします。
mergeやreplaceに似ていますが、キーが重複した場合本体配列側の要素が有効になる点が違いです。
結果は以下の通り。

array:3 [
  "a" => 1
  "b" => 2
  "c" => 5
]

なお、mergeやreplaceと異なりRecursiveメソッドがないので、一応多次元配列に対して適用した場合も確認してみます。

$col1 = collect([
    'key1' => [
        'attr1' => 1,
        'attr2' => 'a', 
    ],
    'key2' => [
        'attr1' => 2,
        'attr2' => 'b', 
    ],
    'key3' => [
        'attr1' => 3,
        'attr2' => 'c', 
    ],
]);
$col2 = $col1->union([
    'key1' => [
        'attr3' => 'append',
    ],
    'key3' => [
        'attr1' => 'modify',
    ],
]);

結果は以下の通り。

array:3 [
  "key1" => array:2 [
    "attr1" => 1
    "attr2" => "a"
  ]
  "key2" => array:2 [
    "attr1" => 2
    "attr2" => "b"
  ]
  "key3" => array:2 [
    "attr1" => 3
    "attr2" => "c"
  ]
]

本体配列の内容から変化なしです。つまり二次元以下の階層は対象とならず、一次元目のキーが一致した段階で本体配列側の要素が採用されてしまうようです。

zip

$col1 = collect(['a','b']);
$col2 = $col1->zip([1,2]);

本体配列の要素と引数で指定した配列の要素を組み合わせた配列を要素とする新しいコレクションを生成します。
結果は以下の通り。

array:2 [
  0 => array:2 [
    0 => "a"
    1 => 1
  ]
  1 => array:2 [
    0 => "b"
    1 => 2
  ]
]

本体配列および引数で指定した配列が連想配列であってもキーは無視して要素の結合を行うようです。

$col1 = collect([
    'key1' => 'a',
    'key2' => 'b'
]);
$col2 = $col1->zip([
    'key3' => 1,
    'key4' => 2
]);

結果は以下の通り。

array:2 [
  0 => array:2 [
    0 => "a"
    1 => 1
  ]
  1 => array:2 [
    0 => "b"
    1 => 2
  ]
]

なお、本体配列と引数で指定する配列の要素数に差があると不足した部分に関してはNULLが設定されるようです。

総括

今回紹介したメソッドの中では、本命map、pluck、次点でgroupBy、transform辺りは愛用していますが、それ以外のメソッドはあまり使ったことがありません。
改めて確認してみると使えそうなメソッドも散見されますが、似ていて非なるメソッドも多く見られるため、それぞれの違いをしっかり把握しておかないとなかなか使いこなすところまで行かないかと思います。

また、名称から機能がイメージし難いものもいくつかある点も気になります。例えばunionやzipなどは、この名称から機能をイメージすることは難しいと思いますが…と言おうと思ったのですが、「zipってジッパーのジップか!」と気づいた瞬間、名称と機能が完全に合致しました。
zipと言われるとどうしてもファイル圧縮の方が先に思い付いてしまうので惑わされましたが、やはり適切な命名って重要ですね(いや、英語力の問題かも…)。

Related articles

ローカルSMTPメールサーバ(Mailpit)をE...

ローカル環境でのメール送受信テストにつ...

EC-CUBE 4系のプラグイン開発について その...

今回は、ちょっとハマったプラグインのイ...

EC-CUBE 4系のプラグイン開発について その...

前回のブログの最後でちょっと書いたので...

EC-CUBE 4系のプラグイン開発について その...

前回、プラグインを一旦有効化させて管理...