Born Too Late

Yuya's old tech blog.

5 分で分かる PHP の組込みインターフェイス

2010-12-31 05:08:00

PHP の interface や implements ってご存知ですか ? 知っている前提で話を進めます。

それでは PHP の組込みインターフェイスをご存知ですか ?
この記事ではそれについて紹介します。

なお、PHP のバージョンは 5.3 以降を前提としています。 (多分、 5.2 でも動く)

PHP の組込みインターフェイスとは

PHP でインターフェイスを定義するには、 interface 宣言を記述する必要があります。
しかし、以下のようなインターフェイスは PHP に予め組込まれており、定義すること無しに利用できます。

普通 interface といえば、メソッドの定義を強制することで、クラスの再利用性を高めるために用いられていると思います。
上記の組込みのインターフェイスについても、もちろんそういった利点があります。

ですが、組込みインターフェイスには、通常のクラス定義ではあり得ない振る舞いをオブジェクトに持たせることができるものがあります。
要は 普通じゃないオブジェクトを作れる ということです。

配列っぽいオブジェクトをつくる

ここからは、PHP の組込みインターフェイスの便利さを知ってもらうために、配列っぽいオブジェクトの作り方を説明します。

さて、配列っぽいってどういうことでしょうか ?
配列、つまり PHP の array 型は、以下のような使い方ができますね。

以下では、array 型が持つこれらの機能を、オブジェクトに実装していきます。

(0) コンストラクタ

まずは、 array をプロパティとして持つ ArrayLike クラスを定義します。

<?php
class ArrayLike
{
    protected $_arr;

    public function __construct($arr = array())
    {
        $this->_arr = $arr;
    }
}

ArrayLike オブジェクトは以下のように生成します。

<?php
require_once 'ArrayLike.php';

$arrObj = new ArrayLike(array('foo', 'bar', 'baz'));

以下では省略しますが、 $arrObj はすべてこのように生成しているという前提でお読みください。

(1) 要素数を数える (Countable)

現状ではこうなります。

<?php
echo count($arrObj), PHP_EOL; // => 1

3 となって欲しいところですが、なりません。
オブジェクトに count() 関数を適用すると、通常は常に 1 となるようです。
(count 関数のマニュアルには「オブジェクトに含まれるプロパティの数を数える」となってますが間違いのようです)

そこで、 ArrayLike クラスに次のような変更を加えましょう。
最初に宣言したプロパティや定義したメソッドは省略します。

<?php
class ArrayLike implements Countable
{
    public function count()
    {
        return count($this->_arr);
    }
}

Countable というインターフェイスに定義されている count() メソッドを実装しました。
すると、 count() に対する ArrayLike オブジェクトの振る舞いはいかのようになります。

<?php
echo count($arrObj), PHP_EOL; // => 3

期待通りの値が返ってきました !
これで配列に一歩近づきましたね。

(2) 要素を 1 つ 1 つ列挙する (Iterator)

foreach 構文にオブジェクトを渡すと、通常は public なプロパティが列挙されます。
ArrayLike オブジェクトは public なプロパティを持たないので、 foreach は空振りします。
構文エラーではありませんが、やはり配列として期待する動作とは違います。

そこで、再び ArrayLike クラスに変更を加えます。
先ほどまでに実装している部分は省略しています。
実装するメソッド数がさっきより多いので、詳細は気にせず「Iterator インターフェイスのメソッドを実装した」ことだけ理解していただけで問題ありません。

<?php
class ArrayLike implements Countable, Iterator
{
    protected $_index;

    public function rewind()
    {
        $this->_index = 0;
    }

    public function key()
    {
        return $this->_index;
    }

    public function current()
    {
        return $this->_arr[$this->key()];
    }

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

    public function valid()
    {
        return $this->_index < $this->count();
    }
}

すると、 foreach に対する ArrayLike オブジェクトの振る舞いは以下のようになります。

<?php
foreach ($arrObj as $key => $value) {
    echo "{$key}:{$value}", PHP_EOL;
}
// =>
//   0:foo
//   1:bar
//   2:baz

だいぶ配列に近づいてきましたね。

(3) 要素へ値の代入と取得 (ArrayAccess)

count や foreach のときと違い、オブジェクトに対して [] 演算子を使うと Fatal error となります。

<?php
$arrObj[1] = 'foo'; => PHP Fatal error:  Cannot use object of type ArrayLike as array in...

というわけで、さらに ArrayLike に手を加えます。
例によって、今までに実装したメソッドや宣言したプロパティは省略しています。

<php
class ArrayLike implements Countable, Iterator, ArrayAccess
{
    public function offsetSet($key, $value)
    {
        $this->_arr[$key] = $value;
    }

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

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

    public function offsetUnset($key)
    {
        unset($this->_arr[$key]);
    }
}

さて、再び [] 演算子を使ってみましょう。

<?php
$arrObj[1] = 'foo';
echo $arrObj[1], PHP_EOL; // => foo

これはもうほとんど配列ですね。

完成

ここまでで実装してきたものをまとめると以下のようになります。

まとめ

PHP の組込みインターフェイスを使うと、普通のクラス定義ではありえない振る舞いをオブジェクトに持たせることができます。
また、言語組込みのインターフェイスなので、とりあえずこれに従っておけば、メソッドの命名で喧嘩せずに済むかもしれません。

See also