初めに
今までDoctrineのArrayCollection
をforaeach
で回すときは配列に変換して配列に変換から利用していました。しかし、ArrayCollection
のままforeach
できることを知り、ふと「なぜできるんだ?」「そもそもforeach
できる条件ってなんだ?」という疑問が湧いてきました。
今回は、foreach
できる条件、ArrayCollection
がforeach
できる理由を調べてみようと思います。
いきなり結論
ArrayCollection
がforeach
できるのは、IteratorAggregate
インターフェイスを継承しているためです。さらに言うと、IteratorAggregate
がTraversable
を継承しているため、foreachを利用することができます。
Traversable インターフェイスとは
では、Traversable
とはなんでしょうか。
公式ドキュメントを抜粋してきました。
Traversable インターフェースとは...
そのクラスの中身が foreach を使用してたどれるかどうかを検出するインターフェイスです。
...ということらしいです。
つまり、Traversable
を継承しているとforeach
を使用できるという判定がされるようです。
Traversableを継承しただけではforeachは回せない
また、公式ドキュメントには以下のようにも書いてあります。
これは抽象インターフェイスであり、単体で実装することはできません。 IteratorAggregate あるいは Iterator を実装しなければなりません。
あくまでforeach
を使用できるという判定がされるだけで、これ単体ではforeach
を利用することができないということですね。
実装を見てみましたが抽象メソッドが一つもありません。たしかにこのままでは利用できなそう。。。
interface Traversable {
}
ドキュメントにもあるように、foreach
で利用するにはIteratorAggregate
あるいは Iterator
を継承する必要があるようです。
IteratorとIteratorAggregate インターフェイス
Iterator
とIteratorAggregate
は何者でしょうか。
それぞれ見てみました。
Iterator インターフェースとは
Iterator
はイテレーションロジックを作成するためのインターフェースです。
Iterator
を継承したクラスはforeach
で使用することができます。
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
を継承した標準のイテレーターがいくつか用意されています。
IteratorAggregate インターフェースとは
IteratorAggregate
は使用するイテレーターをforeach
に渡すためのインターフェースです。
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を使用することができます。
public function getIterator()
{
return new ArrayIterator($this->elements);
}
ArrayCollectionの例
最後に、ArrayCollectionではどのような実装になっているのか見てみようと思います。
以下がArrayCollectionとTraversableの関係を表したクラス図です。
下の図はPHP(SPL)としてしまいましたが、SPLに入っているのは一部です。後ほど修正します🙇
ポイント
- ArrayCollectionはCollectionなどを経てIteratorAggrigateを継承している
- ArrayCollectionでは、実装した
getIterator()
でArrayIteratorを生成している - ArrayIteratorはSPLのイテレーターであり、Iteratorを継承している
結論として、
ArrayCollectionはIteratorAggrigateを継承しているためforeachを使用することができ、foreachではArrayIteratorを利用してループを回している
ということが言えました!
終わりに
今まで気にせず利用していたforeachですが、改めて仕様を知るとより深く利用できる気がしてきます。
他にも何か気になったことがあったら調べてみようと思います。
ここまでご覧いただきありがとうございました!