Advent Calendar 3日目 5.3.3 のみ違う振る舞いをするphpの挙動について

以前、見つけてある程度書いたものの放置していた記事を放出。

いちぉ、これ、技術系 Advent Calendar だったんですよ。



php 5.4 リポジトリに舞い戻ってきたクロージャの $this サポート - do_akiの徒然想記php のソースコードを眺めていたときの雑多な流れ - do_akiの徒然想記 を書く中で見つけたこと。
php のちょっと不思議な挙動についてです。

PHP Bug #51176 : Static calling in non-static method behaves like $this->

php5.3.4 : Reverted fix for bug #51176 (Static calling in non-static method behaves like $this->). (Felipe)
php5.3.3 : Fixed bug #51176 (Static calling in non-static method behaves like $this->). (Felipe)
(http://www.php.net/ChangeLog-5.php より抜粋)

このバグ(と報告されている問題)#51176 は、通常のメソッド内で静的に呼び出したメソッドが静的呼び出しと見なされない というもの。

Bug #51176 の再現コード (Reproduce code)
<?php
class Foo
{
	public function start()
	{
		self::bar();
		static::bar();
		Foo::bar();
	}
	
	public function __call($n, $a)
	{
		echo 'instance';
	}
	
	public static function __callStatic($n, $a)
	{
		echo 'static';
	}
}

$foo = new Foo();
$foo->start();

(https://bugs.php.net/bug.php?id=51176 より)

報告者は "static" "static" "static" を期待していたけど、実際には "instance" "instance" "instance" が表示されると主張している。

それに対して、一度は修正が入った(php5.3.3)が、後からその修正は取り消されて(php5.3.4)いる。そのときの理由がこれ。

Hello, I've reverted the wrong changes introduced by trying to fix the issue reported, but actually there is no bug at all. self::bar(), static::bar() and foo::bar() are being called in an object context, hence the __call() is called.


(リリースマネージャの指示で)一度は修正したものの、これは明らかにバグじゃないよね。
オブジェクトコンテクストによって呼び出されたのだから、(__callStatic ではなく) __call が呼ばれるのは当然でしょう? (意訳)


I.e.
$foo->start(); // invoke the __call method
foo::start(); // invoke the __callStatic method

Thanks.

とのこと。

いまいちわかりにくいのだけど、クラスの外からどう呼ばれたかが重要なのであり、クラス内でどう呼んでいるかは関係ないよってことらしい。

確かに、例で示されたとおり、上記 Reproduce code のクラスFoo に対して、Foo::start() を呼べば、 __callStatic が呼応する。("static" "static" "static" と表示される)

ま、start メソッドに static 修飾子がないから 「Strict Standards: Non-static method Foo::start() should not be called statically」 って怒られるけどね。

というわけで、php5.3.3 では、この最初の修正だけが入った状態でリリースされていて他のバージョンと挙動が異なるのです。

php5.3.4 以降、少なくとも確認した上では、 php5.3.8 と、php5.4.0RC2 では、php5.3.2 以前の挙動と同じです。

ちと問題なのは php5.3.3 って、ScientificLinux6.1では標準として入ってるのですよ。
ということは、恐らく、RHEL6.1 でも標準だし、 CentOS6.1 でもOracleLinux6.1 でも同様だと思う。

使う人は注意。

staticではないメソッドの中から __callStatic を呼び出す方法

じゃぁ、非staticメソッドの中から __callStatic を(警告なしに)呼び出すことはできないのか? というと、そんなことはない。
通常のメソッドから、 static メソッドを呼び出してやればいい。

こうしてやると、呼び出しそのものは static な呼び出しとなるので

<?php
class Foo
{
    public function start() {
        self::static_bar();
    }

    private static function static_bar() {
        self::bar();
        static::bar();
        Foo::bar();
    }

    public function __call($n, $a) {
        echo 'instance';
    }
	
    public static function __callStatic($n, $a) {
        echo 'static';
    }
}

$foo = new Foo();
$foo->start();

でも、__callStatic を呼び出したいが為に、わざわざメソッドを定義するのはイケテナイ。

ところで、php5.4 bで導入された static付きのクロージャ は、クロージャ(関数)に static 属性を追加しているものであった。
ならば、 static付きのクロージャ を作れば static function 同様に動作するのではないか。と思い、試してみた。

<?php
class Foo
{
    public function start() {
        $c = static function () {
            self::bar();
            static::bar();
            Foo::bar();
        };
        $c();
    }

    public function __call($n, $a) {
        echo 'instance';
    }
	
    public static function __callStatic($n, $a) {
        echo 'static';
    }
}

$foo = new Foo();
$foo->start();


予想通り、 "static" "static" "static" と表示された。

php 5.4RC2 での動きを確認しました。

こんな使い方誰がするんだろうか……。そもそも、 __callStatic() を明示的に呼びたいケースなんてあるのかどうかすらわからないけどね。