1 年半ほど前の記事ですが.

A Set of Objects in PHP: Arrays vs. SplObjectStorage

この記事を簡単にまとめると, 以下のような感じです.

  • PHP でオブジェクトのユニークな集合を扱う方法には, 普通の Array を使う方法と, SplObjectStorage を使う方法がある.
  • Array を使う場合は, spl_object_hash() でオブジェクトのハッシュを取り, それをキーに, オブジェクトを値にしたハッシュ変数として実装できる.
  • SplObjectStorage は, PHP 5.2 以降に搭載された便利なコンテナクラスである.
  • SplObjectStorage は, オブジェクトをユニークに格納することを保証する.
  • SplObjectStorege は, C により実装されているので高速である. (ベンチマークでもそういう結果が出た)
  • メモリの使用量においても, SplObjectStorage の方が少なくて済む.

というわけで, 手製のベンチマーキングフレームワークを使って, 自分でも計測してみました.

コンテナへの挿入

コンテナに 10,000 のユニークなオブジェクトを挿入する, というのを 100 回ずつ繰り返し, 計測してみます.

結果は以下のとおり.

array():          2.8187828sec
SplObjectStorage: 2.5617448sec

割と近い数値ですが, SplObjectStorage の方が速いようです.

オブジェクトの存在確認

10,000 個のオブジェクトを格納したコンテナを生成し, 新たに作ったオブジェクトの存在確認を 10,000 回行う.
つまり, 「存在しない値」なので, 全体に対してチェックが走ることになります.
とはいっても, 何らかのデータ構造を利用するはずなので 10,000 回のチェックが走るわけでは無いと思いますが.

そして実行結果.

array():          0.0226268sec
SplObjectStorage: 0.0183608sec

こちらも SplObjectStorage に軍配.

コンテナのイテレーション

コンテナに 10,000 個のオブジェクトを格納し, それらを foreach でイテレーションする, というのを 100 回繰り返す.

結果は以下のとおり.

array():          0.6820778sec
SplObjectStorage: 1.8508288sec

これについては Array が圧倒的な速さを見せました !

まとめ

  • PHP でオブジェクトのユニークな集合を扱うコンテナは, SplObjectStorage が便利.
  • SplObjectStorage は格納/存在確認ともに速い.
  • ただし, イテレーションは Array の方が速い.
  • 最終的にどっちを使うのが速いかはアプリケーションに依存する. (と思います)

個人的には, Observer Pattern の実装をするときに, Observer を格納するコンテナとしてよく利用します.
API もシンプルなので, まだ使ってない人も, 試してみてはいかがでしょう.

,

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

, ,