LoginSignup
4
2

More than 5 years have passed since last update.

php で配列やオブジェクトをアクセスキーにする

Posted at

php で配列やオブジェクトをアクセスキーにする

ArrayAccess を読みつつ色々試していて気づいたんですが、どうも $offset 引数があらゆる型を受け入れてくれるみたいです。
それでふと思い至って下記のクラスを作ってみました。


class Map implements \ArrayAccess, \Iterator, \Countable
{
    private $keys = [];

    private $vals = [];

    private $position = 0;

    private function getKey($offset)
    {
        foreach ($this->keys as $n => $key) {
            // ここを書き換えれば同値判定を差し替えられる
            if ($key === $offset) {
                return $n;
            }
        }
        return null;
    }

    public function offsetExists($offset)
    {
        $key = $this->getKey($offset);
        return $key !== null;
    }

    public function offsetGet($offset)
    {
        $key = $this->getKey($offset);
        if ($key !== null) {
            return $this->vals[$key];
        }
        trigger_error("Undefined index: $offset");
    }

    public function offsetSet($offset, $value)
    {
        // 追加時の null もキー扱いとする
        $key = $this->getKey($offset);
        if ($key === null) {
            $this->keys[] = $offset;
            $this->vals[] = $value;
        }
        else {
            $this->keys[$key] = $offset;
            $this->vals[$key] = $value;
        }
    }

    public function offsetUnset($offset)
    {
        $key = $this->getKey($offset);
        if ($key !== null) {
            unset($this->keys[$key]);
            unset($this->vals[$key]);
        }
    }

    public function current()
    {
        return $this->vals[$this->position];
    }

    public function next()
    {
        $this->position++;
    }

    public function key()
    {
        return $this->keys[$this->position];
    }

    public function valid()
    {
        return isset($this->keys[$this->position]);
    }

    public function rewind()
    {
        $this->position = 0;
        // unset すると歯抜けができるはずなので連番振り直し
        $this->keys = array_values($this->keys);
        $this->vals = array_values($this->vals);
    }

    public function generate()
    {
        foreach ($this->keys as $n => $key) {
            yield $key => $this->vals[$n];
        }
    }

    public function count()
    {
        return count($this->keys);
    }
}

試してみたら動いてしまいました。
下記のように使います。

$map = new Map();

echo "# ACCESS\n";

// 配列をキーにできる
$key = ['akey'];
$map[$key] = 'hoge';
printf("array key value: %s\n", $map[$key]);

// オブジェクトをキーにできる
$key = new stdClass();
$map[$key] = 'fuga';
printf("object key value: %s\n", $map[$key]);

// 文字列をキーにできる
$key = 'key';
$map[$key] = 'piyo';
printf("string key value: %s\n", $map[$key]);

echo "\n# FOREACH\n";

// foreach で回せる
foreach ($map as $k => $v) {
    printf("key type: %s=%s\n", gettype($k), json_encode($k));
    printf("val: %s\n", $v);
}

echo "\n# GENERATOR\n";

// generator で回せる
foreach ($map->generate() as $k => $v) {
    printf("key type: %s=%s\n", gettype($k), json_encode($k));
    printf("val: %s\n", $v);
}

echo "\n# COUNT\n";

// count を取れる
printf("element count: %s\n", count($map));

上の例の出力は以下となります。

# ACCESS
array key value: hoge
object key value: fuga
string key value: piyo

# FOREACH
key type: array=["akey"]
val: hoge
key type: object={}
val: fuga
key type: string="key"
val: piyo

# GENERATOR
key type: array=["akey"]
val: hoge
key type: object={}
val: fuga
key type: string="key"
val: piyo

# COUNT
element count: 3

配列やオブジェクトをキーとして利用したい状況はまれによくあるため、そういうときに使えるかもしれません。
ただし下記に注意です。

  • ドキュメントに言及がない
    • 現状「たまたまそうなっている」とも言えるので良い実装ではない
    • $map[] = 123; で null が来るので多分それ関係?
  • 線形探索になる
    • 要素の数に比例して探索が遅くなる
    • serialize とかしてキーにしてもいいが、それなら普通に配列を使えばいい
  • 文字列と整数キーは別扱いになる
    • getKey を修正すれば同一視できると思う
  • キーは内部で保持されるため GC の対象にならない
  • $map[] = 123; で追加ではなく null キーになる
    • 多分両立できないので offsetSet を弄るしかないと思う
  • phpstorm に激しく怒られる
    • 「Illegal array key type」とかそういうの

一番上が割と致命的なので無理に ArrayAccess なんて使わないで普通にメソッドベースでいいと思うよ。

4
2
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
4
2