Laravelのコレクション(その9:要素の有無/各要素に対する操作)

0
308

Laravelコレクション掘り下げシリーズ第九弾。今回は特定の条件を満たす要素の有無を確認するメソッド、および各要素に対する操作を行うメソッドに関して確認していきます。
両者に特に密接な関係がある訳ではないのですが、これら分類に属するメソッド数が少ないため、まとめて整理します。

contains(some)  

$col = collect([1,1,2,3,5,8]);
$col->contains(3); // true
$col->contains(4); // false

引数で指定した値がコレクションに含まれているかどうかを真偽で返します。
多次元構造に関しては第一引数にキー、第二引数に値を指定することで判定対象を指定することができます。

$col = collect([
    [
        'a' => 1,
        'b' => [
            'b-1' => 2,
            'b-2' => 3,
        ],
    ],
    [
        'a' => 1,
        'b' => [
            'b-1' => 3,
            'b-2' => 5,
        ],
    ],
    [
        'a' => 1,
        'b' => [
            'b-1' => 5,
            'b-2' => 10,
        ],
    ],
]);
$col->contains('b.b-2', 5); // true
$col->contains('b.b-2', 6); // false

引数にコールバックを指定し、独自の判定条件を使用することもできます。

$col = collect([
    [
        'a' => 1,
        'b' => [
            'b-1' => 2,
            'b-2' => 3,
        ],
    ],
    [
        'a' => 1,
        'b' => [
            'b-1' => 3,
            'b-2' => 5,
        ],
    ],
    [
        'a' => 1,
        'b' => [
            'b-1' => 5,
            'b-2' => 10,
        ],
    ],
]);
$col->contains(function($val) {
    return ($val['b']['b-2'] % 3) === 0;
}); // true
$col->contains(function($val) {
    return ($val['b']['b-2'] % 4) === 0;
}); // false

containsStrict

containsの厳格版です。
比較がより厳密になるだけなので確認は割愛。

every

$col = collect([1,1,2,3,5,8]);
$col->every(function($value, $key) {
    return $value < 10;
}); // true
$col->every(function($value, $key) {
    return $value < 5;
}); // false

引数にコールバックを指定し、全要素がコールバックの判定をパスしたか(結果が真となったか)を真偽で返します。
蛇足ながら、本体配列が空の場合は真となるようです。

has

$col = collect([
    'a' => 1,
    'b' => 2,
    'c' => 3,
]);
$col->has('b'); // true
$col->has('d'); // false

本体配列に引数で指定したキーが存在するかどうかを真偽で返します。
複数のキーを配列で指定することもでき、この場合は全てのキーが含まれる場合のみ真となります。

$col = collect([
    'a' => 1,
    'b' => 2,
    'c' => 3,
]);
$col->has(['b','c']); // true
$col->has(['b','d']); // false

なお、多次元構造の二次元以下のキーは対象にできないようです。

$col = collect([
    'a' => 1,
    'b' => [
        'b-1' => 2,
        'b-2' => 3,
    ],
]);
$col->has('b'); // true
$col->has('b.b-1'); // false
$col->has('b-1'); // false

isEmpty、isNotEmpty

$col1 = collect([]);
$col1->isEmpty(); // true
$col1->isNotEmpty(); // false

$col2 = collect([1]);
$col2->isEmpty(); // false
$col2->isNotEmpty(); // true

名称通りですが、isEmptyは本体配列が空であれば真を、空でなければ偽を返し、isNotEmptyはその逆です。

each

$col = collect([
    'a' => 1,
    'b' => 2,
    'c' => 3
]);
$col->each(function($val, $key) {
    dump([$key, $val]);
});

コールバックを引数とし、各要素をコールバックで処理します。
上記の結果は以下のようになります。

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

新しいコレクションを生成する訳でも、自身を更新する訳でもなく、ただ各要素に関する処理をするのみなので、コールバック内の処理自体が目的となります。
例えば上記のように表示やログ採取を行ったり、DBへの出力を行ったりと言ったところでしょうか。

なお、コールバックの戻り値でfalseを返すとループ処理を止められるようです。

$col = collect([
    'a' => 1,
    'b' => 2,
    'c' => 3
]);
$col->each(function($val, $key) {
    dump([$key, $val]);
    return $val < 2 ? true : false;
});

結果は以下の通り。

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

2番目の要素処理時にfalseを返すことになるので、そこでループ処理が終了しています。

eachSpread

$col = collect([
    [
        1,
        'a',
        'one'
    ],
    [
        2,
        'b',
        'two'
    ],
    [
        3,
        'c',
        'three',
    ],
]);
$col->eachSpread(function($val1, $val2, $val3) {
    dump([$val1, $val2, $val3]);
});

eachと似ていますが、本体配列に多次元配列を想定し、一次元目の各要素(配列)内の各要素を引数としてコールバックを呼び出します。
上記の処理結果は以下のようになります。

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

では、連想配列の場合はどうでしょう?
実は二次元目の配列はキーが文字列ではダメで、数値であることが必要なようです。

例えば、

$col = collect([
    'key1' => [
        'key1-1' => 1,
        'key1-2' => 'a',
        'key1-3' => 'one'
    ],
]);
$col->eachSpread(function($val1, $val2, $val3) {
    dump([$val1, $val2, $val3]);
});

および

$col = collect([
    0 => [
        'key1-1' => 1,
        'key1-2' => 'a',
        'key1-3' => 'one'
    ],
]);
$col->eachSpread(function($val1, $val2, $val3) {
    dump([$val1, $val2, $val3]);
});

では「Error: Cannot unpack array with string keys」なるエラーが発生します。
以下はOKです。

$col = collect([
    'key1' => [
        0 => 1,
        1 => 'a',
        2 => 'one'
    ],
]);
$col->eachSpread(function($val1, $val2, $val3) {
    dump([$val1, $val2, $val3]);
});

では、二次元目の配列のキーが連番でなければどうでしょう?

$col = collect([
    [
        1 => 1,
        3 => 'a',
        5 => 'one'
    ],
]);
$col->eachSpread(function($val1, $val2, $val3) {
    dump([$val1, $val2, $val3]);
});

結果は以下の通り。

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

連番でなくても処理できているようです。
では、何でキーが数値であることにこだわったんでしょうか?

そもそもeachでも同様の操作が可能ですから、あえてこのようなメソッドを用意した理由が何かありそうな気もしますが。

総括

本カテゴリの中では、contains、each辺りはよく使います。

hasは使ったことがありませんが、array_key_existsと同じ位置付けと考えると使う機会はありそうです。

isEmpty、isNotEmptyに関しても個人的には使用したことがなく、countの戻りが0かどうかの判断で代替していました。しかし、意味的にはisEmpty、isNotEmptyの方が明確ですし、countが文字通り要素数を数えた上で結果を戻すような処理になっていた場合はisEmpty、isNotEmptyの方が処理が早そうな気がします(isEmpty、isNotEmptyは要素の有無を確認しただけで結果を出せますので)。
そもそもcountで完全に代替可能であればこのようなメソッドを用意する必要もないはずなので、今後は積極的にisEmpty、isNotEmptyを活用していきたいと思います。