LaravelにおけるCollectionについて
LaravelにはCollectionという便利なAPIがあって、例えば次のように配列をColllectionに変換すると、メソッドチェーンのようにこの配列を変換することができます。C#におけるLINQみたいで素敵ですね。
$collection = collect(['taylor', 'abigail', null])->map(function ($name) {
return strtoupper($name);
})
->reject(function ($name) {
return empty($name);
});
上記の例は、Laravelのリファレンスに紹介された例で、これ$collectionの中身は['TAYLOR','ABIGAIL']
になります。
コレクション 5.8 Laravel
https://readouble.com/laravel/5.8/ja/collections.html
意外とハマりどころになるのが、CollectionはあくまでCollcetionなので配列ではないということです。なので、配列しか受け付けないPHPネイティブのメソッドとか、LaravelのメソッドでもRequestのパラメータみたいな、Collectionが来ることを前提としていないメソッドだと、たまに「あれ??」ってなります。そのときは、$collection->all()
で普通の配列に戻してあげるといいでしょう。
ユースケース
例えば次のようなフォーム画面を作って、データを送信すると……
<form>
<div>
<label>名前</label><input type="text" name="vocaloids[name][]">
<label>色</label><input type="text" name="vocaloids[color][]">
<label>誕生日</label><input type="text" name="vocaloids[birthday][]">
</div>
<div>
<label>名前</label><input type="text" name="vocaloids[name][]">
<label>色</label><input type="text" name="vocaloids[color][]">
<label>誕生日</label><input type="text" name="vocaloids[birthday][]">
</div>
<div>
<label>名前</label><input type="text" name="vocaloids[name][]">
<label>色</label><input type="text" name="vocaloids[color][]">
<label>誕生日</label><input type="text" name="vocaloids[birthday][]">
</div>
</form>
次のようなパラメータが送信されるでしょう。
{
vocaloids: {
name: ['初音ミク', '鏡音リン', '巡音ルカ'],
color: ['青','黄','桃'],
birthday: ['2007/8/31', '2007/12/27', '2009/1/30']
}
}
ミクの色って青なんでしょうか緑なんでしょうか。
これはこれで便利です。が、しかし、例えばVocaloidモデルのCollectionをforeachなどでまとめて作ってsaveなどが難しいです。
できればこんな感じのパラメタの配列がほしい。
{
vocaloids: [
{
name: '初音ミク',
color: '青',
birthday: '2007/8/31'
},
{
name: '鏡音リン',
color: '黄',
birthday: '2007/12/27'
},
{
name: '巡音ルカ',
color: '桃',
birthday: '2009/1/30'
}
]
}
実は、送信するname属性をvocaloids[0][name]
みたいな感じでインデックスをつけるとこのような形で送信されみたいですが、input要素が動的に増える場合とかですと、フロント側で頑張るほかありません。
惜しかった解決策
要は、「配列の連想配列」を「連想配列の配列」に転置したいです。
Collectionには自分でメソッドを定義するmacroという方法があります。
Laravelでtranspose - Qiita
https://qiita.com/kangyoosam/items/f5c4514ad34d9a5c201c
上記の記事では、このmacroを定義して配列を見事に転置しています。しかし、できれば……キーの情報も残したい! 「この配列の何番目にはこのキーの情報が入っている」というのは危なっかしいです。配列ではなく連想配列を転置したい!
解決策
macroの定義の仕方は上記のページを見ていただくとして、macroの定義を次のようにしました。
Collection::macro('transpose', function () {
$transpose = array_map(null, ...$this->values());
return collect($transpose)->map(function($v, $k){
return array_combine($this->keys()->all(), $v);
});
});
まず、PHPネイティブのarray_map関数を利用して配列を転置します。第一引数のコールバック関数にnullを指定し、第二引数以降に複数の配列を指定することで、配列の配列を構築します。結果的にこれが配列を転置することになります。この使い方の例はPHPの公式リファレンスでも触れられています。
PHP: array_map - Manual
https://www.php.net/manual/ja/function.array-map.php
これでできた配列は連想配列の配列ではなく配列の配列です。次にできた配列の配列をCollectionに戻し、mapメソッドを適用します。mapメソッドの中でPHPのネイティブのarray_combine関数を適用します。array_combine関数は第一引数をkey、第二引数をvalueとする連想配列を作る関数です。
PHP: array_combine - Manual
https://www.php.net/manual/ja/function.array-combine.php
転置前の配列の連想配列のkeyが、新しく作りたい配列の中に入っているそれぞれの連想配列のkeyになるので、$this->keys()
でそのkeyを取り出し、それをkey、$transposeの要素をvalueとして、新たに連想配列を構築します。これで望むようなmacroを定義できました。
使うときは、
collect($request->input('vocaoids'))->transpose();
で簡単。あとは、foreachで回すなり、Modelのcollectionに変換するなり好きにするといいです。