あまり知られていないLaravelのコレクションメソッド #2: concat

# 目次

1. [あまり知られていないLaravelのコレクションメソッド #1: macro](https://qiita.com/aminevg/items/e3a49c62fa805bef09bd)
1. あまり知られていないLaravelのコレクションメソッド #2: concat （本記事）

# 背景

Laravelのコレクション、使いこなしていますか？

以下の記事を先程読んで面白いと思いました。

「あなたは使ったことがないLaravelのコレクションメソッド10選」

https://backpackforlaravel.com/articles/tips-and-tricks/top-10-collection-methods-you-have-never-used

ただし、英語で書かれていますし、各メソッドの説明が不十分に感じました。
ということで、以上の記事を翻訳する上で、それぞれのメソッドの活用方法を説明していきたいです！
今回はCollectionのconcatメソッドを解説します！

# concat: コレクションにarrayや他コレクションの中身を追加する

### 概要

インスタンスメソッドになり、以下のように呼び出すことができます。

```php
$collection = collect([/* ... */]);
$collection->concat(/* ... */);
```

既存コレクションに、arrayの中身を追加できます。

```php
$collection = collect([1, 2, 3]);
$collection->concat([4]);
// [1, 2, 3, 4]
```

他コレクションの中身も追加できます！

```php
$collection = collect([1, 2, 3]);
$collection2 = collect([4]);
$collection->concat($collection2);
// [1, 2, 3, 4]
```

### ソースコードを読みましょう

concatメソッドのソースコードは以下になります。(Laravel 11.xのソースコードになります)

```php
/**
 * @template TKey of array-key
 *
 * @template-covariant TValue
 *
 * @implements \ArrayAccess<TKey, TValue>
 * @implements \Illuminate\Support\Enumerable<TKey, TValue>
 */
class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerable
{
    // ...

    /**
     * Push all of the given items onto the collection.
     *
     * @template TConcatKey of array-key
     * @template TConcatValue
     *
     * @param  iterable<TConcatKey, TConcatValue>  $source
     * @return static<TKey|TConcatKey, TValue|TConcatValue>
     */
    public function concat($source)
    {
        $result = new static($this);

        foreach ($source as $item) {
            $result->push($item);
        }

        return $result;
    }

    // ...
}
```

ソースコードを読むことで、2つのことがわかります。

#### 追加されるarrayやコレクションのキーが捨てられる

今までキーのないarrayやコレクションでconcatメソッドを使ってきましたが、キーを指定するとどうなるでしょう？

```php
$collection = collect([1, 2, 3]);
$collection->concat(['key' => 4]);
// [1, 2, 3, 4]
```

キーが全く考慮されず、値を追加しています。
ソースコードをもう一度読むと原因がわかります。

```php
        // ...

        foreach ($source as $item) {
            $result->push($item);
        }

        // ...
```

foreachを使って追加されるarrayを処理しているため、キーが捨てられます。

#### 既存コレクションにキーがあれば、追加キーがいい感じに追加される

追加元のコレクションにキーがある場合、キーはどう変わるでしょう？

実は[普通のarrayに値を追加する場合](https://www.php.net/manual/ja/language.types.array.php#language.types.array.syntax.modifying)と一緒です。

> 上記のように、キーを省略して新規要素を追加する場合、 追加される数値添字は、使用されている添字の最大値 +1 (ただし、少なくとも 0 以上) になります。 まだ数値添字が存在しない場合は、添字は 0 (ゼロ) となります。

つまりこういう感じです。

```php
$collection = collect([1 => 1, 2 => 2, 3 => 3]);
$collection->concat(['key' => 123]);
// [1 => 1, 2 => 2, 3 => 3, 4 => 123]
```

```php
$collection = collect(['key1' => 1, 'key2' => 2, 'key3' => 3]);
$collection->concat(['key' => 123]);
// ['key1' => 1, 'key2' => 2, 'key3' => 3, 0 => 123]
```


#### 型の定義がちゃんと変わる...?

*下記はPHPStanで検証しています*

上級PHPになりますが、コレクションにジェネリックスが入っています。

```php
$collection = collect([1, 2, 3]); // \Illuminate\Support\Collection<int,int>
```

concatメソッドを使うとジェネリックスがちゃんと変わります。
```php
$collection = collect([1, 2, 3]); // \Illuminate\Support\Collection<int,int>
$collection->concat(['a']); // \Illuminate\Support\Collection<int,int|string>
// [1, 2, 3, 'a']
```

ただし、stringのキーを持っているコレクションに値を追加すると...
```php
$collection = collect(['ab' => 1]); // \Illuminate\Support\Collection<string,int>
$collection->concat([2]); // \Illuminate\Support\Collection<string,int> !!!
// ['ab' => 1, 0 => 2] !!!
```

どうやら型と実際のarrayは一致しないようです。
PHPStanのバグ？Laravelのバグ？私の脳みそのバグ？はっきりわからないので調査中です。
もしわかる人がいれば是非コメントお願いします！！

### 参考

https://laravel.com/docs/11.x/collections#method-concat

https://readouble.com/laravel/11.x/ja/collections.html#method-concat

https://www.php.net/manual/ja/language.types.array.php#language.types.array.syntax.modifying

# まとめ

いかがでしたか？
今度はpadメソッドなど、あまり知られていないLaravelのコレクションメソッドをさらに解説していきたいです。
ではまた！