PHPのクラスで、同じメソッドを静的・動的(というのか?)どちらでも呼び出せるようにしたい場合がたまにある。
いろいろと調べたんだけど、やっぱり最終的に「できない」ようだった。
というか正確にいうと**(同名にしたいなら)静的にしか呼び出せない** っぽい。
という調査結果をメモ。
まず、サンプルコードで見てみる。
あ、環境は PHP 5.6.3 。
<?php
class Sample
{
// 静的メソッドを定義
public static function StaticMethod()
{
echo var_export(array(
'this_exists' => isset($this),
'back_trace' => debug_backtrace()
), true);
}
}
// 動的(通常の)呼び出し
$sample = new Sample;
$sample->StaticMethod();
// 静的呼び出し
Sample::StaticMethod();
上記の結果は以下のようになる。
// 動的呼び出しの結果
array (
'this_exists' => false,// ←ここに注目!
'back_trace' => array (
0 => array (
'file' => '/path/to/sample.php',
'line' => 16,
'function' => 'StaticMethod',
'class' => 'Sample',
'type' => '::',// ←ここに注目!
'args' => array (),
),
),
)
// 静的呼び出しの結果
array (
'this_exists' => false,// ←ここに注目!
'back_trace' => array (
0 => array (
'file' => '/path/to/sample.php',
'line' => 19,
'function' => 'StaticMethod',
'class' => 'Sample',
'type' => '::',// ←ここに注目!
'args' => array (),
),
),
)
まず、結果が両方とも同じところが問題で、動的の方は動的な感じに呼び出したのに $this
がない。
また、「->
」を使って呼び出したのに、type
が「::
」になっている。
static function
で定義したメソッドなので仕様としては当然なんだろうけども、ここに違いがないと、同名のメソッドで自分が動的に呼び出されたのか静的に呼び出されたのか判別できない。
ということで「できない」という結論に。
ちなみに上記サンプルで static function
を function
に変えると、の静的呼び出しの方(Sample::StaticMethod
)で Strict エラーになる。
いろいろググッて探してみて(といっても見つかったのはこれくらい)、「できるよ」的な感じだったんだけど、「呼び出せる」というだけで、上記サンプルのように結局どちらも静的になる模様。
オーバーロードを使った(強引な)解決方法
PHP 5.3 以上なら __callStatic
が使えるので、これを使ってみる。
下記サンプルなら、動的呼び出しでも静的呼び出しでも、いずれも「動的」で呼び出せる。
<?php
class Sample2
{
// 呼び出されるメソッド名と違う名前のメソッドを定義
// (この場合は頭にアンダーバー)
private function _DynamicMethod()
{
echo var_export(array(
'this_exists' => isset($this),
'back_trace' => debug_backtrace()
), true).PHP_EOL;
}
// 静的呼び出しの際にインスタンス化するための変数とメソッド
private static $instance = null;
private static function getInstance()
{
if (null === self::$instance) {
self::$instance = new self;
}
return self::$instance;
}
// 「アクセス不能」な"動的"メソッドが呼び出された場合の処理
public function __call($name, $args)
{
$name = '_' . $name;
if (method_exists($this, $name)) {
return call_user_func_array(array($this, $name), $args);
}
}
// 「アクセス不能」な"静的"メソッドが呼び出された場合の処理
public static function __callStatic($name, $args)
{
$name = '_' . $name;
$instance = self::getInstance();
if (method_exists($instance, $name)) {
return call_user_func_array(array($instance, $name), $args);
}
}
}
// 動的(通常の)呼び出し → __call() が呼ばれる → $this->_DynamicMethod() が呼ばれる
$sample = new Sample2();
$sample->DynamicMethod();
// 静的呼び出し → __callStatic() が呼ばれる → $this->_DynamicMethod() が呼ばれる
Sample2::DynamicMethod();
静的呼び出しの目的としては「最初に定義した同じインスタンスを使いまわしたい」という点なので、これなら目的が達成できそう。
でも、毎回 __call
や __callStatic
で遠回りに呼び出されるのでパフォーマンスは落ちる(と思う)ので、結局のところは動的か静的かどちらかでしか使えない仕様にした方が良いかも。