【PHP/Doctrine】なぜ ArrayCollection は foreach で回せるのか？ foreachできる条件を調べました

# 初めに
今までDoctrineの`ArrayCollection`を`foraeach`で回すときは配列に変換して配列に変換から利用していました。しかし、`ArrayCollection`のまま`foreach`できることを知り、ふと「なぜできるんだ？」「そもそも`foreach`できる条件ってなんだ？」という疑問が湧いてきました。

今回は、**`foreach`できる条件**、**`ArrayCollection`が`foreach`できる理由**を調べてみようと思います。

# いきなり結論
`ArrayCollection`が`foreach`できるのは、`IteratorAggregate`インターフェイスを継承しているためです。さらに言うと、`IteratorAggregate`が`Traversable`を継承しているため、foreachを利用することができます。


# Traversable インターフェイスとは
では、`Traversable`とはなんでしょうか。
公式ドキュメントを抜粋してきました。

https://www.php.net/manual/ja/class.traversable.php

Traversable インターフェースとは...

> そのクラスの中身が foreach を使用してたどれるかどうかを検出するインターフェイスです。

...ということらしいです。
つまり、**`Traversable`を継承していると`foreach`を使用できる**という判定がされるようです。

# Traversableを継承しただけではforeachは回せない

また、公式ドキュメントには以下のようにも書いてあります。

> これは抽象インターフェイスであり、単体で実装することはできません。 IteratorAggregate あるいは Iterator を実装しなければなりません。

あくまで`foreach`を使用できるという判定がされるだけで、これ単体では`foreach`を利用することができないということですね。

実装を見てみましたが抽象メソッドが一つもありません。たしかにこのままでは利用できなそう。。。

```php:Traversableインターフェースには抽象メソッドが一つもない
interface Traversable {
}
```

ドキュメントにもあるように、`foreach`で利用するには`IteratorAggregate` あるいは `Iterator` を継承する必要があるようです。

# IteratorとIteratorAggregate インターフェイス
`Iterator`と`IteratorAggregate`は何者でしょうか。
それぞれ見てみました。

https://www.php.net/manual/ja/class.iterator.php

https://www.php.net/manual/ja/class.iteratoraggregate.php


## Iterator インターフェースとは
`Iterator`は**イテレーションロジックを作成するためのインターフェース**です。
`Iterator`を継承したクラスは`foreach`で使用することができます。

```php:Iterator
interface Iterator extends Traversable {
    /* メソッド */
    public current(): mixed
    public key(): mixed
    public next(): void
    public rewind(): void
    public valid(): bool
}
```

各メソッドの用途は以下（公式ドキュメントから抜粋）
| メソッド | 用途 |
|:-:|:-:|
|current|現在の要素を返す|
|key|現在の要素のキーを返す|
|next|次の要素に進む|
|rewind|イテレータの最初の要素に巻き戻す|
|valid|現在位置が有効かどうかを調べる|

また、SPL（Standard PHP Library）には`Iterator`を継承した標準のイテレーターがいくつか用意されています。

https://www.php.net/manual/ja/spl.iterators.php

## IteratorAggregate インターフェースとは
`IteratorAggregate`は**使用するイテレーターを`foreach`に渡すためのインターフェース**です。

```php:IteratorAggregateインターフェース
interface IteratorAggregate extends Traversable {
/* メソッド */
public getIterator(): Traversable
}
```
`getIterator()`で利用したいイテレータを返すことでforeachに渡すことができます。

## IteratorAggregateって必要？
ここまでの説明を聞くと、「`Iterator`だけでいいのでは？」「`IteratorAggregate`って必要？」という気がしていきますよね（私だけ？）。
結論、`IteratorAggregate`も使用したほうが良いです。理由は、**IteratorAggregateを使わないと、foreachを一度しか使用できくなってしまう**ためです。

というのも、Iteratorは以下の値を持つ必要があります。
* foreachの対象の配列
* どの要素を指しているかのポインター（またはカウンター）

`foreach`を一度行うとポインターが最後までいってしまいます。もう一度`foreach`を行うためには`rewind()`を使う必要がありますが、毎回戻すのは面倒ですよね。

この問題は`IteratorAggregate`を使用することで解決できます。**`getIterator()`の中で新しい`Iterator`を生成する**ことで繰り返しforeachを行うことができるようになるのです。

以下は`ArrayCollection`で使われている`getIterator()`です。
`new`した`Iterator`を返すことで常に新品のIteratorを使用することができます。

```php:ArrayCollectionで使われているgetIterator()
public function getIterator()
{
    return new ArrayIterator($this->elements);
}
```

# ArrayCollectionの例
最後に、ArrayCollectionではどのような実装になっているのか見てみようと思います。
以下がArrayCollectionとTraversableの関係を表したクラス図です。

:::note warn
下の図はPHP（SPL）としてしまいましたが、SPLに入っているのは一部です。後ほど修正します🙇
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430841/be474940-465b-43f7-acf6-4aa4b74b79d0.png)


ポイント
* ArrayCollectionはCollectionなどを経てIteratorAggrigateを継承している
* ArrayCollectionでは、実装した`getIterator()`でArrayIteratorを生成している
* ArrayIteratorはSPLのイテレーターであり、Iteratorを継承している

結論として、
**ArrayCollectionはIteratorAggrigateを継承しているためforeachを使用することができ、foreachではArrayIteratorを利用してループを回している**
ということが言えました！


# 終わりに
今まで気にせず利用していたforeachですが、改めて仕様を知るとより深く利用できる気がしてきます。
他にも何か気になったことがあったら調べてみようと思います。

ここまでご覧いただきありがとうございました！