ある日の出来事
「複数件のデータが取得できるはずなのに一件分しか取得できてない!」とバグがあがりました。
調べてみると取得クエリのwhereInの引数に渡す際、laravelのpluckで$collection
から必要な要素を取り出した後にuniqueで重複削除処理をしている部分があるのですがそれによって
想定だと複数の要素が入ってくる予定なのに要素が一つだけになっていました。
// 想定だと [1,2,3,4]と複数が入ってくるはずなのに
// 結果は、[1]が返ってきた
$targetIds = $collection->pluck('target_id')->unique('target_id')
Target::whereIn('target_id',$targetIds)->get();
結論
->pluck('target_id')の部分で [1,2,3,4]のようなデータ構造になっていて
その後にuniqueメソッドの引数で存在してないkeyを渡していたことが原因でした
単なるミスなのですが
でもなぜ1つだけは返ってくるのかが気になったのでlaravelのコードを追ってみました
解説
lararvelのuniqueメソッドの定義
/**
* Return only unique items from the collection array.
*
* @param string|callable|null $key
* @param bool $strict
* @return static
*/
public function unique($key = null, $strict = false)
{
if (is_null($key) && $strict === false) {
return new static(array_unique($this->items, SORT_REGULAR));
}
$callback = $this->valueRetriever($key);
$exists = [];
return $this->reject(function ($item, $key) use ($callback, $strict, &$exists) {
if (in_array($id = $callback($item, $key), $exists, $strict)) {
return true;
}
$exists[] = $id;
});
}
rejectの定義
/**
* Create a collection of all elements that do not pass a given truth test.
*
* @param callable|mixed $callback
* @return static
*/
public function reject($callback = true)
{
$useAsCallable = $this->useAsCallable($callback);
return $this->filter(function ($value, $key) use ($callback, $useAsCallable) {
return $useAsCallable
? ! $callback($value, $key)
: $value != $callback;
});
}
valueRetrieverの定義
/**
* Get a value retrieving callback.
*
* @param callable|string|null $value
* @return callable
*/
protected function valueRetriever($value)
{
if ($this->useAsCallable($value)) {
return $value;
}
return function ($item) use ($value) {
return data_get($item, $value);
};
}
collect([1,2,3])->unique('a')のように実行すると以下のコードで
unique関数での戻り値の部分は実質このようになります
$exists = []
return $this->filter(function ($value, $key) use(&$exists) {
if(in_array($id = data_get($value, 'a'), $exists, $strict)) {
return false
}
$exists[] = $id;
return true
})
一番最初
1が評価されます、その時$id
にnullが入ります。
そして$exists
に1がpushされます
そしてreturn true
2番目
2が評価されます、その時$id
にnullが入ります。
$exists
にはnullが既に入っているのでifの条件式がtrueになります
そして return falseされます
その次以降
2番目と同じ全てfalseを返します
よって一番最初の要素のみが入ったコレクションが返却されるということになる
補足
unique関数での戻り値の部分の実質のコードへの変換する際に
以下の前提を考慮する必要がある
- PHPは戻り値を明示的に書かない場合は暗黙的にnullが返ってくる
- PHPは定義している関数の想定より引数が少ない状態で実行した場合はエラーになるが、引数を余計に渡している場合は無視してエラーは出ない