0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2025-03-09

初めに

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

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

いきなり結論

ArrayCollectionforeachできるのは、IteratorAggregateインターフェイスを継承しているためです。さらに言うと、IteratorAggregateTraversableを継承しているため、foreachを利用することができます。

Traversable インターフェイスとは

では、Traversableとはなんでしょうか。
公式ドキュメントを抜粋してきました。

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

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

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

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

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

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

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

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

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

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

IteratorとIteratorAggregate インターフェイス

IteratorIteratorAggregateは何者でしょうか。
それぞれ見てみました。

Iterator インターフェースとは

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

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を継承した標準のイテレーターがいくつか用意されています。

IteratorAggregate インターフェースとは

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

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を使用することができます。

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

ArrayCollectionの例

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

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

image.png

ポイント

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

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

終わりに

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

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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?