PHPのArrayAccessインタフェースをざっくり解説

初心者向け記事。
PHPには、それを実装することでクラスに言語構造レベルの機能を持たせることができるインタフェースがいくつかある。

[定義済みのインターフェイスとクラス] (https://www.php.net/manual/ja/reserved.interfaces.php)

Laravelなどのフレームワークの中でも使われるケースがあり、ソースコードを追ったりする際は知っておく必要が出てくる。今回はArrayAccessインタフェースを例にとって、どのように使われるのかをみていく。

（間違いなどあればご指摘ください...:pray:）

# ArrayAccessインタフェース

PHPのオブジェクトは、`ArrayAccess`インタフェースを実装することで配列のように操作することができるようになる。

```php
ArrayAccess {
    /* メソッド */
    abstract public offsetExists ( mixed $offset ) : bool
    abstract public offsetGet ( mixed $offset ) : mixed
    abstract public offsetSet ( mixed $offset , mixed $value ) : void
    abstract public offsetUnset ( mixed $offset ) : void
}
```

引数の`$offset`は配列のキー、`$value`は格納する値に相当する。
それぞれのメソッドの役割は以下のようになる。

- `offsetExists()`は指定したキーに値がセットされているかをbool値で返す。`isset()`による判定を可能にする。
- `offsetGet()`はキーを元に値を取得する。
- `offsetSet()`は指定したキーと値をペアで格納する。
- `offsetUnset()`は指定したキーとそのペアの値を削除。`unset()`による割り当ての解除を可能にする。

引数にはmixedが指定されており、様々な型を受け入れてくれることがわかる。

# `Illuminate\Container\Container`クラスの例

例えばLaravelのサービスコンテナ（`Illuminate\Foundation\Application`クラス）が継承している`Illuminate\Container\Container`クラスは`ArrayAccess`インタフェースを継承している。
以下は該当のテストコードを一部変更して引用したものになる。

```ContainerTest.php

use PHPUnit\Framework\TestCase;

class ContainerTest extends TestCase
{
    public function testArrayAccess()
    {
        $container = new Container;
        $container['something'] = 'foo'; // offsetSet()
        $this->assertTrue(isset($container['something'])); // offsetExists()
        $this->assertSame('foo', $container['something']); // offsetGet()
        
        unset($container['something']); // offsetUnset()
        $this->assertFalse(isset($container['something']));
    }
}
```
コードを見るとわかる通り、実装するContainerクラスは当然オブジェクトだが、配列と同じように操作していることがわかる。 ArrayAccessインタフェースを実装することで、このテストがパスすることを確認する。

## 実装

```Container.php

class Container implements ArrayAccess
{   
    private $container = [];

    public function offsetExists($key): bool
    {
        return isset($this->container[$key]);
    }

    public function offsetGet($key)
    {
        return $this->container[$key];
    }

    public function offsetSet($key, $value): void
    {
        $this->container[$key] = $value;
    }

    public function offsetUnset($key): void
    {
        unset($this->container[$key]);
    }
}
```

本来の`Illuminate\Container\Container`クラスでは結合処理などを行なっているのでもう少し混み入っているが、`ArrayAccess`インタフェースを使用してオブジェクトに配列操作を持たせるだけならこんな感じでも十分。

`Container`クラスのインスタンスは自身が保持する`$container`プロパティに実際に与えられるキーと値を格納する（実際のサービスコンテナの場合、ここにキーと解決処理などをバインドする）。

それぞれ4つのメソッドの中では実際に配列をもつプロパティ`$container`にアクセスして必要な操作を行うことになるため、`isset`や`unset`などをその中で使用することが普通。
`offsetExists`メソッドは`isset`を使用し、`$container`プロパティに指定されたキーをもつ値が格納されているかをboolで返す。`offsetUnset`メソッドも同様に`unset`を使用してキーとその値の割り当てを解除。`offsetGet`メソッドと`offsetSet`メソッドは`$container`プロパティに対し、指定されたキーでアクセスし、値を取得するか、新規に値を登録する。

これを実装したら、テストが通ることを確認する。

```
$ ./vendor/bin/phpunit tests/ContainerTest.php
PHPUnit 9.0.1 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 141 ms, Memory: 4.00 MB

OK (1 test, 3 assertions)
```

Laravelでは他にも`Eloquent\Model`クラスや`Http\Request`クラスなどで`ArrayAccess`インタフェースを実装してたりする。`Container`はまだしもこの辺は正直不要だと思うが、それでも実装されてしまっているので読む際には知らないと？？となりがちなので、基本的なPHPの知識として押さえておきましょう。。