Laravelは配列が扱いにくい気がします。新しい配列を簡単につくれたり、順番を並び替えたりするいい方法はないでしょうか?
Laravelは配列を扱いやすくするcollectionクラスを用意しています。メソッドは100個を超えて、いきなり全てをマスターするのは難しいので、「開発現場でよく見るもの」を9個厳選しました!
collectionとは
collectionは配列を扱いやすい形に変換してくれる仕組みです。javascriptのmap、filterメソッドのように新しい配列を作成したり、条件に合致する配列を抽出できたりします。
配列をcollectionクラスのインスタンスにすることで、collectionクラスのメソッドを利用できるようになります。詳しくは最後に説明します。
おすすめのcollectionメソッド
collect:コレクションをつくる
collectの引数に配列を入れることでcollectionインスタンスになります。collectionメソッドを利用する時は配列を変換する必要があります。
$users = [ ['id' => 1, 'name' => 'tack'], ['id' => 2, 'name' => 'hamu'], ['id' => 3, 'name' => 'tanaka'], ]; dump($users); $collection_users = collect($users); dump($collection_users);
その他にもEloquentを利用してデータを抽出した場合はコレクションインスタンスになります。
dd(User::all());
src/Illuminate/Database/Eloquent/Collection.php
use Illuminate\Support\Collection as BaseCollection; use Illuminate\Support\Str; use LogicException; class Collection extends BaseCollection implements QueueableCollection {
Illuminate/Database/Eloquent/CollectionクラスはIlluminate\Support\Collectionクラスを継承しているため、collectionメソッドが使えるだけではなく、さらに便利なメソッドが定義されています。
map:配列を加工して新しい配列を作成
配列を加工して新しい配列をつくりたいときはmapメソッドを使います。
mapメソッドの引数には関数をわたします。引数として渡される関数のことを「コールバック関数」といいましたね。$userは配列を展開した値がわたされます。今回は新たにofficeというキーにtokyoをいれて新しい配列にしています。
戻り値は必ずリターンする必要があります。コールバック関数の戻り値が何なのかを具体的に定める必要があるからです。
$users = [ ['id' => 1, 'name' => 'tack'], ['id' => 2, 'name' => 'hamu'], ['id' => 3, 'name' => 'tanaka'], ]; $new_users = collect($users)->map(function ($user) { $user['office'] = 'tokyo'; return $user; }); dd($new_users); //実行結果 0 => array:3 [▼ "id" => 1 "name" => "tack" "office" => "tokyo" ] 1 => array:3 [▶] 2 => array:3 [▶] ]
配列を展開する時は、原則次のルールが適用されます。
第2引数にはキーが渡されます。
$users = [ ['id' => 1, 'name' => 'tack'], //0 ['id' => 2, 'name' => 'hamu'], //1 ['id' => 3, 'name' => 'tanaka'], //2 ]; $new_users = collect($users)->map(function ($user, $key) { dump($key); return $user; }); //実行結果 0,1,2
外部からの値を渡したい時はuseを使います。
$users = [ ['id' => 1, 'name' => 'tack'], ['id' => 2, 'name' => 'hamu'], ['id' => 3, 'name' => 'tanaka'], ]; $office = 'tokyo'; $new_users = collect($users)->map(function ($user, $key) use ($office) { $user['office'] = $office; return $user; }); dd($new_users); //実行結果 0 => array:3 [▼ "id" => 1 "name" => "tack" "office" => "tokyo" ] 1 => array:3 [▶] 2 => array:3 [▶] ]
実行結果は最初と一緒ですね。
filter:配列から条件に合致した値の配列を作成する
配列から条件に合致した値の配列を作成したときはfilterメソッドを利用します。
ユーザーの中からofficeがnaganoの者を抽出した配列をつくりたいとします。filterメソッドのreturnに条件を記載すると条件がtrueの行のみの配列が作成されます。
$users = [ ['id' => 1, 'name' => 'tack', 'office' => 'nagano'], ['id' => 2, 'name' => 'hamu', 'office' => 'nagano'], ['id' => 3, 'name' => 'tanaka', 'office' => 'tokyo'], ]; $new_users = collect($users)->filter(function ($user) { return $user['office'] === 'nagano'; }); //実行結果 #items: array:2 [▼ 0 => array:3 [▼ "id" => 1 "name" => "tack" "office" => "nagano" ] 1 => array:3 [▼ "id" => 2 "name" => "hamu" "office" => "nagano" ] ]
tackとhamuの二人の配列が作成されています。
isEmpty・isNotEmpty:collectionが空か判定する
値の取得結果を判定したい場合はisEmpty・isNotEmptyメソッドを利用します。isEmptyは空の場合がtrue、isNotEmptyは値がある場合にtrueを返します。
officeがakitaのユーザがいるかチェックしています。
$users = [ ['id' => 1, 'name' => 'tack', 'office' => 'nagano'], ['id' => 2, 'name' => 'hamu', 'office' => 'nagano'], ['id' => 3, 'name' => 'tanaka', 'office' => 'tokyo'], ]; $new_users = collect($users)->filter(function ($user) { return $user['office'] === 'akita'; }); dd($new_users->isEmpty()); //実行結果 true
値がnullの場合は、collectionインスタンスが空配列でリターンされるので、実行結果はtrueになります。
$new_users = collect(null); //戻り値[] dd($new_users->isEmpty()); //実行結果 true
sortBy・sortByDesc:順番を並び替える
指定したキーの順番で並び替えたい時はsortBy、sortByDescを使います。sortByが昇順、sortByDescが降順です。
$users = [ ['id' => 1, 'name' => 'tack', 'office' => 'nagano'], ['id' => 2, 'name' => 'hamu', 'office' => 'nagano'], ['id' => 3, 'name' => 'tanaka', 'office' => 'tokyo'], ]; $new_users = collect($users)->sortByDesc('id'); //実行結果 #items: array:3 [▼ 2 => array:3 [▼ "id" => 3 "name" => "tanaka" "office" => "tokyo" ] 1 => array:3 [▼ "id" => 2 "name" => "hamu" "office" => "nagano" ] 0 => array:3 [▼ "id" => 1 "name" => "tack" "office" => "nagano" ] ]
注意したいのが、そのまま利用すると配列のキーが元の配列のままになっていることです。2、1、0の順番になっていますね。
新しい配列は新しい順番にしたいことが多いです。その場合はvaluesメソッドを使って、キーをリセットします。この処理を忘れると意図しない挙動になることがあるので注意が必要です。
$new_users = collect($users)->sortByDesc('id')->values(); //実行結果 #items: array:3 [▼ 0 => array:3 [▼ "id" => 3 "name" => "tanaka" "office" => "tokyo" ] 1 => array:3 [▼ "id" => 2 "name" => "hamu" "office" => "nagano" ] 2 => array:3 [▼ "id" => 1 "name" => "tack" "office" => "nagano" ] ]
pluck:指定したキーの値を抽出する
特定の値を利用したい時はpluckを使います。idを抽出してwhereInしたいときなど、使うケースは多いです。
値のみ抽出する場合
$users = [ ['id' => 1, 'name' => 'tack', 'office' => 'nagano'], ['id' => 2, 'name' => 'hamu', 'office' => 'nagano'], ['id' => 3, 'name' => 'tanaka', 'office' => 'tokyo'], ]; $new_users = collect($users)->pluck('id'); //実行結果 #items: array:3 [▼ 0 => 1 1 => 2 2 => 3 ]
キー、バリューで抽出する場合
キー、バリューで抽出したいときは、第2引数にキーとなる値を設定します。idをキーに、nameがバリューに設定されています。
$users = [ ['id' => 1, 'name' => 'tack', 'office' => 'nagano'], ['id' => 2, 'name' => 'hamu', 'office' => 'nagano'], ['id' => 3, 'name' => 'tanaka', 'office' => 'tokyo'], ]; $new_users = collect($users)->pluck('name', 'id'); //実行結果 #items: array:3 [▼ 1 => "tack" 2 => "hamu" 3 => "tanaka" ]
ネストした値を抽出する場合
ネストされた値を抽出したい時は「.」(ドット)でつなぎます。
$users = [ [ 'hobbies' => [ 'tack' => ['baseball', 'soccer'], 'hamu' => ['baseball', 'cooking'], ], 'favorite_foods' => [ 'tack' => ['apple', 'sushi'], 'hamu' => ['wine', 'beer'], ], ], ]; $favorite_foods_hamu = collect($users)->pluck('favorite_foods.hamu'); //実行結果 #items: array:1 [▼ 0 => array:2 [▼ 0 => "wine" 1 => "beer" ] ]
containsStrict:配列に指定した値があるかチェックする
配列に指定した値があるかチェックしたいときはcontainsStrictを利用します。strictのとおり、厳密比較を行います。ゆるく比較したいときはcontainsがありますが、バグの温床になるので厳密比較を使うほうがいいかと思います。
配列・連想配列の場合
チェック値を第1引数に指定します。
$user = ['id' => 1, 'name' => 'tack', 'office' => 'nagano']; $new_user = collect($user)->containsStrict('nagano'); //実行結果 true
配列内の連想配列の場合
第1引数にキー、第2引数にバリューを設定してチェックします。
$users = [ ['id' => 1, 'name' => 'tack', 'office' => 'nagano'], ['id' => 2, 'name' => 'hamu', 'office' => 'nagano'], ['id' => 3, 'name' => 'tanaka', 'office' => 'tokyo'], ]; $new_users = collect($users)->containsStrict('office', 'tokyo'); //実行結果 true
merge:配列やコレクションを結合する
配列を結合したい場合はmergeを使います。結合する値($user2の部分)は配列でもcollectionインスタンスでも大丈夫です。戻り値はcollectionになります。
マージの場合
$users1 = [ ['id' => 1, 'name' => 'tack', 'office' => 'nagano'], ['id' => 2, 'name' => 'hamu', 'office' => 'nagano'], ]; $user2 =[ ['id' => 3, 'name' => 'tanaka', 'office' => 'tokyo', 'age' => 20], ] ; $new_users = collect($users1)->merge($user2); //実行結果 #items: array:3 [▼ 0 => array:3 [▼ "id" => 1 "name" => "tack" "office" => "nagano" ] 1 => array:3 [▼ "id" => 2 "name" => "hamu" "office" => "nagano" ] 2 => array:4 [▼ "id" => 3 "name" => "tanaka" "office" => "tokyo" "age" => 20 ] ]
オーバーライドの場合
元のcollectionのキーと一致する場合は上書きされます。
$user_old = ['id' => 1, 'name' => 'tack', 'office' => 'nagano']; $user_new = ['id' => 1, 'name' => 'tack', 'office' => 'tokyo', 'age' => 20]; $user = collect($user_old)->merge($user_new); //実行結果 #items: array:4 [▼ "id" => 1 "name" => "tack" "office" => "tokyo" "age" => 20 ]
toArray:戻り値を配列にする
戻り値を配列にしたい場合はtoArrayを使います。logでデバックする時は見やすくなるので便利です。
$user = ['id' => 1, 'name' => 'tack', 'office' => 'nagano'];
$user_new = collect($user)->toArray();
//実行結果
array:3 [▼ //Illuminate\Support\Collectionではなくarrayになっている。
"id" => 1
"name" => "tack"
"office" => "nagano"
]
collectionを深ぼる
最後に少し深ぼってcollectionを見てみます。
collectionは正確にはcollectionインスタンスを利用して、collectionクラスに定義されたメソッドを利用して配列を操作しています。
まずcollectメソッドを使うとcollectionインスタンスを生成します。
src/Illuminate/Collections/helpers.php
<?php use Illuminate\Support\Arr; use Illuminate\Support\Collection; if (! function_exists('collect')) { /** * Create a collection from the given value. * * @param mixed $value * @return \Illuminate\Support\Collection */ function collect($value = null) { return new Collection($value); } }
Collectionクラスを見るとインスタンスを生成したときに、$this->getArrayableItemsで値を加工してから、クラス定数に配列で値を格納しています。
src/Illuminate/Collections/Collection.php
class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerable { use EnumeratesValues, Macroable; /** * The items contained in the collection. * * @var array */ protected $items = []; /** * Create a new collection. * * @param mixed $items * @return void */ public function __construct($items = []) { $this->items = $this->getArrayableItems($items); }
実際に値を使う時は、インスタンス作成時に定義した値を加工してから値を返しています。
mapメソッドを例に見てみましょう。
public function map(callable $callback) { $keys = array_keys($this->items); $items = array_map($callback, $this->items, $keys); return new static(array_combine($keys, $items)); }
array_keysで配列のキーを取得しています。array_mapで元の配列に対して、$callbackで定義されたメソッドに基づき修正を行っています。
最後にarray_combineでキーと新しいバリュー値を持つ新しい配列を作成して、new static()で新しcollectionインスタンスとしてリターンしています。
これらの加工用メソッドを自分で作るとなったら大変な労力です。元々のPHPの機能を利用して、配列の加工に特化したメソッドを用意してくれているので本当にありがたいです。利用しない手はありません!
SQL的な使い方や、JavaScriptのSplice、重複した値の抽出など、本当にたくさんあります!業務効率化のためにどんどん使っていきたいですね!
コメント