LoginSignup
9
10

More than 5 years have passed since last update.

PHPのクラスのメソッドで、動的・静的呼び出し両対応したい(できなさそう)

Last updated at Posted at 2015-11-08

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 functionfunction に変えると、の静的呼び出しの方(Sample::StaticMethod)で Strict エラーになる。

いろいろググッて探してみて(といっても見つかったのはこれくらい)、「できるよ」的な感じだったんだけど、「呼び出せる」というだけで、上記サンプルのように結局どちらも静的になる模様。


オーバーロードを使った(強引な)解決方法

PHP 5.3 以上なら __callStatic が使えるので、これを使ってみる。
下記サンプルなら、動的呼び出しでも静的呼び出しでも、いずれも「動的」で呼び出せる。

サンプル2
<?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 で遠回りに呼び出されるのでパフォーマンスは落ちる(と思う)ので、結局のところは動的か静的かどちらかでしか使えない仕様にした方が良いかも。

9
10
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
10