PHP5はimmutable不可能?

元々マニュアルで「PHP 5はオブジェクト指向言語ではありません。」と宣言しているのだから、仕方ないといえば仕方ないんですが。

  • privateで宣言したメンバを守ってくれる。
  • finalで宣言したクラスは拡張できない。
  • privateコンストラクタ宣言可能

こういう実装なのにimmutableなクラスを作ることができない。
次のようなことをすると簡単にimmutableを破れたので開いた口がふさがらなくなってしまった。
例えば、こういうクラス宣言を書いたとすると。

<?php

final class Immutable {
    
    private static $instance;
    
    private $value;
    
    private function __construct($value) {
        $this->value = $value;
    }
    
    public function getValue() {
        return $this->value;
    }
    
    public function equals(Immutable $obj) {
        return($obj->getValue() == $this->getValue());
    }
    
    public static function getInstance() {
        if (Immutable::$instance === null) {
            Immutable::$instance = new Immutable('hoge');
        }
        return Immutable::$instance;
    }
    
}

?>

以下のようなスクリプトを実行すれば、

<?php

$immutable = Immutable::getInstance();
print("生成直後の状態\r\n");
var_dump($immutable);

/** 下のコメント文を実行すればエラーを起こす */
// $immutable->value = 'foo';
print("\r\n");

/** しかし下の文はエラーを起こさない */
$immutable->foo = 'bar';
print("未宣言メンバに値を入れ込んだ後の状態\r\n");
var_dump($immutable);

?>

このような結果になってしまった。*1

生成直後の状態
object(Immutable)#1 (1) {
  ["value:private"]=>
  string(4) "hoge"
}

未宣言メンバに値を入れ込んだ後の状態
object(Immutable)#1 (2) {
  ["value:private"]=>
  string(4) "hoge"
  ["foo"]=>
  string(3) "bar"
}

こういう事を実装されると、当然 == による同値性の保障は失われるわけで。

<?php

$immutable1 = Immutable::getInstance();
$immutable2 = Immutable::getInstance();
$serialize = serialize($immutable1);
$immutable3 = unserialize($serialize);

var_dump($immutable1 == $immutable2);       // return true;
var_dump($immutable1 == $immutable3);       // return true;
var_dump($immutable1 === $immutable2);      // return true;
var_dump($immutable1 === $immutable3);      // return false;
var_dump($immutable1->equals($immutable2)); // return true;
var_dump($immutable1->equals($immutable3)); // return true;

print("\r\n");

$immutable1->foo = 'bar';

var_dump($immutable1 == $immutable2);       // return true;
var_dump($immutable1 == $immutable3);       // return false;
var_dump($immutable1 === $immutable2);      // return true;
var_dump($immutable1 === $immutable3);      // return false;
var_dump($immutable1->equals($immutable2)); // return true;
var_dump($immutable1->equals($immutable3)); // return true;

?>

当たり前ですが、上にも書いたように == での同値性確認ではなく、常にequals()を実装するように心がけておけば、こんなことにはならないんですけれど、だからといってこういう実装もどうかと。*2
当然、PHP4からの移植性とかで残したものと思われるんですが、せめてfinal宣言のクラスでは不可能にしてほしかったな、と思うわけです。
まぁ、それはそれで言語としてややこしくなるので、どう考えてもいいことではないんですけど。

*1:実行環境はPHP 5.1.1 (cli) (built: Nov 27 2005 21:39:02) with Xdebug v2.0.0beta5-dev

*2:それ以前にserializeしたインスタンスを == で比較するようなスクリプト書くこと自体間違ってるんですけど