読者です 読者をやめる 読者になる 読者になる

arrayにobjectでアクセスするラッパー

ここ数ヶ月PHPばかり書いてるのですが、$ary['foo']よりも$obj->fooの方が書きやすいよなぁってのと、未定義のフィールドにアクセスした時にエラーになるようなのが欲しいってのでこんなのを考えてみた。

案1

<?php
class Accessor Implements IteratorAggregate
{
    private     $ary;
    function __construct(&$ary)
    {
        $this->ary =& $ary;
    }

    function __get($name)
    {
        if (isset($this->ary[$name])) {
            return $this->ary[$name];
        }
        throw new Exception();
    }

    function __set($name, $val)
    {
        $this->ary[$name] = $val;
    }

    function getIterator()
    {
        return new ArrayIterator($this->ary);
    }
}

$ary= array(
    'One' => 1,
);

$obj = new Accessor($ary);
$ary['two'] = 2;
$obj->three = 3;

foreach ($obj as $k => $v) {
    echo "$k = $v \n";
}
var_dump($obj);

実行
----------------
One = 1
two = 2
three = 3
object(Accessor)#1 (1) {
  ["ary:private"]=>
  &array(3) {
    ["One"]=>
    int(1)
    ["two"]=>
    int(2)
    ["three"]=>
    int(3)
  }
}

これは既存のarrayにバイントする感じ。元のarrayの参照を持ってるので、元が変更されればオブジェクト側も変更されます。欠点は元arrayが必要なので、new Accessor(array(...));みたいな書き方はできません。

そうじゃなくってインスタンス生成時にコピーしちゃうのならもっと単純。

案2

<?php
class Accessor
{
    function __construct($ary)
    {
        foreach ($ary as $k => $v) {
            $this->$k = $v;
        }
    }

    function __get($name)
    {
        if (isset($this->$name)) {
            return $this->name;
        }
        throw new Exception();
    }

    function __set($name, $val)
    {
        $this->$name = $val;
    }
}

$obj = new Accessor(array('One' => 1));
$obj->two = 2;

foreach ($obj as $k => $v) {
    echo "$k = $v \n";
}
var_dump($obj);

実行
----------------
One = 1
two = 2
object(Accessor)#1 (2) {
  ["One"]=>
  int(1)
  ["two"]=>
  int(2)
}

__set()を定義しなければImmutableにできるなぁとかネストした構造の場合どうしようとか。欠点として、identに使えないキーを扱うことができないってのがありますが。