ArrayObjectで指定できるフラグ

SPLのArrayObjectはコンストラクタの第二引数やsetFlags()でフラグを指定できるのだが、これの意味がよく分からなかったので調べてみた……けどやっぱりよくわからなかったので実際に試してみた。


コンストラクタの説明を見ると「setFlags()を見ろ」と書いてあるのでsetFlags()を見るとこう書かれている。


Set behavior flags.

Parameters:
$flags bitmask as follows: 0 set: properties of the object have their normal functionality when accessed as list (var_dump, foreach, etc.) 1 set: array indices can be accessed as properties in read/write

内容もさることながら指定の仕方もよくわからない。
0とか1ってのが値を示してるのか位置を示しているのか。ビットマスクなので位置だとは思うんだが。

結論から言うとビットを立てる位置を示していて、一つ目の機能なら最初のビットを立てる……すなわち(int)1を指定する。
二つ目の機能なら2番目のビットで(int)2を指定する。両方なら(int)3となる。
が、実はArrayObjectにはSTD_PROP_LISTとARRAY_AS_PROPSという定数(const)が密かに用意されていて、これらを指定すればよいっぽい。……そう書いておけよ!


で、肝心の挙動なんだけど、英語力が足りないのでいくら読んでもどういう動きをするのかさっぱりわからない。

properties of the object have their normal functionality when accessed as list (var_dump, foreach, etc.)
var_dumpとかforeachみたいにリストとしてアクセスすると、オブジェクトのプロパティは通常のように振る舞います。
array indices can be accessed as properties in read/write
プロパティを読み書きするかのように配列のインデックスにアクセスできます。

頑張って訳してみたけど、1番目がさっぱりわからん。
分かったことと言えば、プロパティという単語が出てきたところで、あー、そういえばPHP連想配列も普通の配列と同じように扱うんだっけーということ。
ArrayObjectにはいわゆるリスト(数字の添え字)しか突っ込むことを想定してなかったよ。たぶんこいつは連想配列(key/value)も扱えるんだろう。

実際に試してみた

<?php
// ArrayObjectのフラグのテスト

$list = array('apple', 'orange', 'banana');

// フラグ無しの場合
echo '[Case 1:フラグ無し]' . PHP_EOL;
$ao = new ArrayObject($list);
$ao->property = 'property access';
$ao['type'] = 'array access';
foreach ($ao as $key => $value) {
    echo "$key = $value" . PHP_EOL;
}
print_r($ao);

// ARRAY_AS_PROPSの場合
echo PHP_EOL . '[Case 2:ARRAY_AS_PROPS]' . PHP_EOL;
$ao = new ArrayObject($list, ArrayObject::ARRAY_AS_PROPS);
$ao->property = 'property access';
$ao['type'] = 'array access';
foreach ($ao as $key => $value) {
    echo "$key = $value" . PHP_EOL;
}
print_r($ao);

// STD_PROP_LISTの場合
echo PHP_EOL . '[Case 3:STD_PROP_LIST]' . PHP_EOL;
$ao = new ArrayObject($list, ArrayObject::STD_PROP_LIST);
$ao->property = 'property access';
$ao['type'] = 'array access';
foreach ($ao as $key => $value) {
    echo "$key = $value" . PHP_EOL;
}
print_r($ao);

// ARRAY_AS_PROPSとSTD_PROP_LISTの場合
echo PHP_EOL . '[Case 4:ARRAY_AS_PROPS | STD_PROP_LIST]' . PHP_EOL;
$ao = new ArrayObject($list, ArrayObject::ARRAY_AS_PROPS | ArrayObject::STD_PROP_LIST);
$ao->property = 'property access';
$ao['type'] = 'array access';
foreach ($ao as $key => $value) {
    echo "$key = $value" . PHP_EOL;
}
print_r($ao);
?>
[Case 1:フラグ無し]
0 = apple
1 = orange
2 = banana
type = array access
ArrayObject Object
(
    [0] => apple
    [1] => orange
    [2] => banana
    [type] => array access
)

[Case 2:ARRAY_AS_PROPS]
0 = apple
1 = orange
2 = banana
property = property access
type = array access
ArrayObject Object
(
    [0] => apple
    [1] => orange
    [2] => banana
    [property] => property access
    [type] => array access
)

[Case 3:STD_PROP_LIST]
0 = apple
1 = orange
2 = banana
type = array access
ArrayObject Object
(
    [property] => property access
)

[Case 4:ARRAY_AS_PROPS | STD_PROP_LIST]
0 = apple
1 = orange
2 = banana
property = property access
type = array access
ArrayObject Object
(

上記の結果から、ARRAY_AS_PROPSを指定すると$ao->hogeといったプロパティアクセスも配列へのアクセスとして扱われるということが分かった。
この指定が無い場合もプロパティアクセスは可能だが、それはあくまでオブジェクトのプロパティに対するアクセスであって、配列の要素には影響を与えない。


で、問題のSTD_PROP_LISTのほうは、自分の試した環境(PHP 5.2.4-2ubuntu5.3)ではバグがあるようで挙動がおかしい。なぜforeachとprint_r()の両方をわざわざ出しているかというと、Case 3と4を見てもらうとわかるように出力が異なっているというバグがあるから。


少し基本に戻ってPHPの仕様を確認すると、


PHP 5は、アイテムのリストに関して反復処理、例えば、 foreach命令、 を可能とするように、オブジェクトを定義する手段を提供します。 デフォルトで、 全ての アクセス権限がある プロパティは、反復処理に使用することができます。

と書いてあるように、オブジェクトに対するイテレーションはプロパティに対する処理となる。
一方、ArrayObjectは自身の管理する配列データをイレテーションできるようにするクラス。
機能かぶっとる。
というわけで、本来のオブジェクトのようにプロパティをイレテーションするための設定がSTD_PROP_LISTなのかなぁと思うんだけど……Case 3おかしいよね? print_r()は正しいんだけどforeachは普通に配列データが回っちゃってる。
で、ぐぐってみたらバグとして報告されていた。


ちなみにCase 4はARRAY_AS_PROPSにプロパティアクセスが食われて配列データになっちゃうので、前述のバグが無いとすれば両方とも空っぽの出力になるのではないかと思われる。

ARRAY_AS_PROPSについて補足

上のテストコードに書き忘れたので追試。

<?php
echo '[フラグ無し]' . PHP_EOL;
$ao = new ArrayObject(array('x' => 10, 'y' => 20));
$ao->z = 30;
print_r($ao);
echo 'z = ' . $ao->z . PHP_EOL;

echo PHP_EOL . '[ARRAY_AS_PROPS]' . PHP_EOL;
$ao = new ArrayObject(array('x' => 10, 'y' => 20), ArrayObject::ARRAY_AS_PROPS);
$ao->z = 30;
print_r($ao);
echo 'z = ' . $ao->z . PHP_EOL;
?>
[フラグ無し]
ArrayObject Object
(
    [x] => 10
    [y] => 20
)
z = 30

[ARRAY_AS_PROPS]
ArrayObject Object
(
    [x] => 10
    [y] => 20
    [z] => 30
)
z = 30

ARRAY_AS_PROPSがついてない場合のプロパティアクセスは、配列とは何の関係もないただのプロパティになる。値は配列要素ではなくオブジェクト本体に存在する。