LoginSignup
5
5

More than 5 years have passed since last update.

PHPで どいひー☆彡シングルトンパティーン

Last updated at Posted at 2017-03-08

こんなふざけたタイトルなのに開いてくれてありがとう。

久しぶりに良いまっぴートマホークを見た1ので。

検証環境はPHP 7.1.2

シングルトン

ぶひぶひ

trait SingletonTrait {
    protected static $instance = null;

    private final function __construct()
    {}

    public static function getInstance()
    {
        return static::$instance ?? static::$instance = new self();
    }
}

class Sample {
    use SingletonTrait;
}

Sample::getInstance() === Sample::getInstance(); // is true

まぁお約束。

バッドン

大戦車軍団

お前はいつも同じクラスを返す。

trait BadtonTrait {
    protected static $instance = null;

    private final function __construct()
    {}

    public static function getInstance()
    {
        return static::$instance ?? static::$instance = new self();
    }
}

class Sample {
    use BadtonTrait;
}

class SampleCo extends Sample {
}

SampleCo::getInstance() === Sample::getInstance(); // is true

selfは"それを書いたクラス"のクラス名を返すからね。仕方ないね。
この場合は両方ともSampleインスタンスを持つ。
SampleCoではSampleCoを取れない。絶対に。

先にやればいいってのか?

前述の通り、selfは実行時に評価される訳ではないので、このままでは詰む。
そこでget_called_class()先生の出番ですよ。
遅延静的束縛、すなわち実行時時点でクラス名を確定してくれるのです。

trait BadtonTrait {
    protected static $instance = null;

    private final function __construct()
    {}

    public static function getInstance()
    {
        $class_path = get_called_class();
        return static::$instance ?? static::$instance = new $class_path();
    }
}

class Sample {
    use BadtonTrait;
}

class SampleCo extends Sample {
}

SampleCo::getInstance() === Sample::getInstance(); // is true

この場合は両方ともSampleCoインスタンスを持つ
static::$instanceはインスタンスをまたいで同一となるため、先に実行したクラスのインスタンスを持つ。
ちなみに new {get_called_class()}() とかはできない。かならず一度変数に入れる必要がある。
変数以外の可変クラス名でのnewはできないのです。

マルチトン

まるちぃ

trait MultitonTrait {
    protected static $instance = [];

    private final function __construct()
    {}

    public static function getInstance()
    {
        $class_path = get_called_class();
        return static::$instance[$class_path] ?? static::$instance[$class_path] = new $class_path();
    }
}

class Sample {
    use MultitonTrait;
}

class SampleCo extends Sample {
}

SampleCo::getInstance() === Sample::getInstance(); // is false

これでようやく期待したインスタンスを取れるようになった。

だがしかし、関数をやたらめったら呼び出すのは困るのだよチミィ。PHPは関数コールが糞重いからね。
インスタンスを取りにいくたびにget_called_class()が呼ばれるのは割と困る。
しかもget_called_class()はめっぽう重い。

更なる高速化を求めて

いつからかこんな真似ができるようになっていた。
PHP5.5から::classが使えるようになった。PHP: クラスの基礎 - Manual#::class

trait MultitonTrait {
    protected static $instance = [];

    private final function __construct()
    {}

    public static function getInstance()
    {
        return static::$instance[static::class] ?? static::$instance[static::class] = new static();
    }
}

class Sample {
    use MultitonTrait;
}

class SampleCo extends Sample {
}

SampleCo::getInstance() === Sample::getInstance(); // is false

ヒャッホウ。最高だぜぇ。
static::classでget_called_class()を代替できるようになりました。

余談:んで、get_called_class()ってどんだけ重いの?

class A {
    public static function f () {
        get_called_class();
    }

    public static function c () {
        static::class;
    }
}

class B extends A {}

$start = microtime(true);
for ($i = 0;$i < 10000;$i++) {
    A::f();
}
echo microtime(true) - $start, "\n";

$start = microtime(true);
for ($i = 0;$i < 10000;$i++) {
    A::c();
}
echo microtime(true) - $start, "\n";

$start = microtime(true);
for ($i = 0;$i < 10000;$i++) {
    B::f();
}
echo microtime(true) - $start, "\n";

$start = microtime(true);
for ($i = 0;$i < 10000;$i++) {
    B::c();
}
echo microtime(true) - $start, "\n"
呼び出し元 取り方 1万回実行時の経過秒 備考
親クラス get_called_class 0.00029110908508301 最遅
親クラス static::class 0.00019288063049316
子クラス get_called_class 0.00024700164794922
子クラス static::class 0.00019001960754395 最速

staticを使うと、親クラスで約35%、子クラスで約21%の高速化となる。
というか、get_called_classだと親子で有意な速度差でるのね。

注釈

5
5
0

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
5
5