40秒で把握するCollectionのbefore()

こんにちは!ミノルです。今、隣にあるおにぎり(チキンライス)を横目にこの記事を書いています。この記事が読者の皆様にとっても美味しい記事になればいいなと思います。

Laravel11公式ドキュメントのCollectionメソッド beforeについてみていきます。段階的に見ていきますが、LaravelのCollectionについて把握していない方は、以下の記事を先に見るとこの記事が読みやすくなると思います。

LaravelのCollectionに触れる

この記事で得られること

  • before()の概要
  • before()の実装コードを見る体験

メソッドの概要を見る

Laravel – The PHP Framework For Web Artisans

公式ドキュメントを日本語訳してからの引用

メソッドは メソッド**beforeの逆ですafter。指定された項目の前の項目を返します。null**指定された項目が見つからないか、最初の項目である場合は が返されます。

$collection = collect([1, 2, 3, 4, 5]);
 
$collection->before(3);
 
// 2
 
$collection->before(1);
 
// null
 
collect([2, 4, 6, 8])->before('4', strict: true);
 
// null
 
collect([2, 4, 6, 8])->before(function (int $item, int $key) {
    return $item > 5;
});
 
// 4
  • 指定された項目の前の項目を返す
  • 指定された項目が見つからないか、最初の項目の場合はnullを返す
  • 第二引数にtrueを指定することで厳密な比較が可能になる。
    • デフォルトでは緩い比較

メソッドのソースを見る

概要は何となく掴めてきたので早速ソースを見ていきます。

vendor/laravel/framework/src/Illuminate/Collections/Collection.php

    /**
     * Get the item before the given item.
     *
     * @param  TValue|(callable(TValue,TKey): bool)  $value
     * @param  bool  $strict
     * @return TValue|null
     */
    public function before($value, $strict = false)
    {
        $key = $this->search($value, $strict);

        if ($key === false) {
            return null;
        }

        $position = $this->keys()->search($key);

        if ($position === 0) {
            return null;
        }

        return $this->get($this->keys()->get($position - 1));
    }

after()を以前に取り扱ったおかげでしょうか。読める…読めるぞ。

(解説に入ります)

$key = $this->search($value, $strict);

$key には元のコレクション内の指定された値と紐づいているキーが入ります。search()の第一引数に$value,つまり指定された項目を設定することでvalueのキーを取得しています。

// コレクションのつもり
{
	// 品物名 => 値段
	$items = collect([
    'りんご' => 300,
    'みかん' => 200,
    'ドリアン' => 500,
    'メロン' => 1000
	]);
}
	
// 200円の品物より一つ上の品物の値段を得る
dd($items->before(200));

↓cursorAI(GPT4)に書いてもらいました。
// 200円の品物より一つ上の品物の品物名を得る	
$previousKey = null;
$foundKey = $items->keys()->before(function ($key) use ($items, &$previousKey) {
    $item = $items[$key];
    $result = $item === 200;
    if ($result) {
        return true;
    }
    $previousKey = $key;
    return false;
});

dd($previousKey); // これは 'りんご' のキーを返す

少し、戻り値を加工したいと思った途端にコードが増えますね…。ただ、Collectionのメソッドを組み合わせることで出来る事の幅が広がるというのはいい発見でした。

if ($key === false) {
    return null;
}

$key がfalse つまり指定された項目が存在しない場合はnullを返します。

$position = $this->keys()->search($key);

if ($position === 0) {
    return null;
}

$position には元のコレクション内で指定された項目のキーがどの位置にあるかの情報が入ります。$position === 0 つまり指定された項目が最初の値だった場合nullを返します。

return $this->get($this->keys()->get($position - 1));

元のコレクション内で指定された項目の位置より前にある項目を返しています。

まとめ

コレクションのbeforeメソッドは以下のような特徴を持つ

  • 元のコレクション内で指定された項目の前の項目を返す。
  • 指定された項目が見つからないor 最初の項目の場合はnullを返す。
  • その実装は$keyと$positionの取得と加工によって行われている

「指定された項目」とか「アイテム」とか呼び方が何度も変わりそうになって大変でした。当事者のうちはまだいいかも知れないけれど、後から見返したときに統一されていないと読む時の認知負荷が高まるので気をつけていきたいと思います。

記事も書いた事だし、おにぎりをいただこうと思ったら、もう出勤の時間が迫ってました。もう少しゆっくり落ち着ける時にまた食べたいと思います。

おまけ AIが出したコードの検証中に見えた怪しい挙動

以下のようなコードで起きた事です。このコードはbeforeの返り値が「指定された項目」ではなく、「指定された項目のキー」になるようにしたものです。(上の例にも出てきたやつ)

このコードの期待する戻り値としては、nullでした。$itemsに2000と等しい値は無いためです。ただ、実際の戻り値としてはコレクション内の最後のアイテムが返るようでした。

そこで、AIにこのコードを修正してもらいました。

$items = collect([
    // 1 => 'りんご',2 => 'みかん', 3 => 'ドリアン'
    'りんご' => 300,
    'みかん' => 200,
    'ドリアン' => 500,
    'メロン' => 1000
]);

$previousKey = null;
$foundKey = $items->keys()->before(function ($key) use ($items, &$previousKey) {
    $item = $items[$key];
    $result = $item === 2000;
    if ($result) {
        return true;
    }
    $previousKey = $key;
    return false;
});

dd($previousKey);

修正してもらったコード。初期値がfalseの$foundが追加されたようです。条件が一度もtrueにならなかった場合は$previousKeyがnullになるようになりました。これで指定した項目が見つからなかった場合nullが返るようになりました。

$items = collect([
    'りんご' => 300,
    'みかん' => 200,
    'ドリアン' => 500,
    'メロン' => 1000
]);

$previousKey = null;
$found = false; // 追加: 条件が一度でもtrueになったかを追跡するフラグ

$foundKey = $items->keys()->before(function ($key) use ($items, &$previousKey, &$found) {
    $item = $items[$key];
    $result = $item === 2000;
    if ($result) {
        $found = true; // 条件がtrueになったことを記録
        return true;
    }
    $previousKey = $key;
    return false;
});

if (!$found) { // 条件が一度もtrueにならなかった場合
    $previousKey = null; // previousKeyをnullに設定
}

dd($previousKey); // 条件が一度もtrueにならなかった場合はnullが出力される

コメント

タイトルとURLをコピーしました