13
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【PHP】配列ポインタ操作関数はIteratorを無視する

Posted at

配列ポインタ操作関数にオブジェクトを突っ込む

やたらたくさんあるPHPの配列関数ですが、大半は配列の全ての値に対して一律に処理を行う関数です。
キーと値を入れ替えるarray_flip、特定のカラムを取り出すarray_column、任意の関数を適用するarray_walkなどですね。

ところでこの中に一部、配列のポインタを直接触る関数群があります。
キーを取得するkey、値を取得するcurrent、ポインタをひとつ進めるnext、ポインタを先頭に戻すresetなどです。

よく見るとこれらの関数、シグネチャがarray|object &$arrayとなっていて、実は配列以外にオブジェクトも反復動作させることができます。

となればIteratorを使って操作したくなるわけですが、ここには罠が存在します。

class A implements IteratorAggregate{
	public $a = 1;
	public $b = 2;
	public function getIterator() {
		return new ArrayIterator([3, 4]);
	}
}

// foreach
$a = new A();
foreach ($a as $v) {
    var_dump($v); // 3, 4
}

// current
while ($v = current($a)) {
    var_dump($v); // 1, 2
    next($a);
}

なんかIterator無視するんですけど。

IteratorはPHPの機能のひとつで、オブジェクトの反復動作を任意に設定することができる機能です。
本来はオブジェクトをforeachすると可視のプロパティが順に出てくるのですが、それを異なる動作にすることができます。
のですが、配列ポインタ操作関数は反復動作とは関係ないので動作に影響しないというわけですね。
一見同じような機能なのに動作が違うということで、非常にわかりにくい挙動となっています。

しかも、このあたりの話はマニュアルにも全く書かれていません。
僅かにforeachに書かれている『foreach は、 current() や key() のような関数で使われる、内部的な配列のポインタを変更しない点に注意して下さい。』がほぼ唯一の言及ですが、これの一文だけでIteratorが効かないとまで解釈しきれる人はそう多くないでしょう。

対策としては、そもそも今どき配列ポインタ操作関数を自力で使わないのが一番です。
ループ制御はオブジェクトだろうが配列だろうがforeach一択です。

ちなみにArrayObjectを継承すると、getIteratorが存在するかどうかでforeachとnextの出力がかわります。
益々わけがわからない。

class A extends ArrayObject{}

// foreach
$a = new A([1, 2]);
foreach ($a as $v) {
    var_dump($v); // 1, 2
}

// current
while ($v = current($a)) {
    var_dump($v); // 何も出力されない
    next($a);
}


class B extends ArrayObject{
    public function getIterator() {
        return new ArrayIterator([3, 4]);
    }
}

// foreach
$b = new B([1, 2]);
foreach ($b as $v) {
    var_dump($v); // 何も出力されない
}

// current
while ($v = current($b)) {
    var_dump($v); // 3, 4
    next($b);
}

PHP8.1

この動作は非常にわかりにくいということで、PHP8.1以降は配列ポインタ操作関数にオブジェクトを突っ込めないようになります。
PHP8.1でE_DEPRECATED、PHP9でエラーになる予定です。

$a = new A([1, 2]);
while ($v = current($a)) {
    var_dump($v);
    next($a);
}
// PHP8.0まで エラー無し
// PHP8.1以降 E_DEPRECATED
// PHP9移行 エラー
13
5
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
13
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?