PHPで どいひー☆彡シングルトンﾊﾟﾃｨｰﾝ

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

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

検証環境はPHP 7.1.2

# シングルトン

ぶひぶひ

```php
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

```

まぁお約束。

# バッドン

大戦車軍団

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

```php
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()](http://php.net/manual/ja/function.get-called-class.php)先生の出番ですよ。
[遅延静的束縛](http://php.net/manual/ja/language.oop5.late-static-bindings.php)、すなわち実行時時点でクラス名を確定してくれるのです。

```php
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はできないのです。

# マルチトン

まるちぃ

```php
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](http://php.net/manual/ja/language.oop5.basic.php#language.oop5.basic.class.class)

```php
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()ってどんだけ重いの？

```php
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だと親子で有意な速度差でるのね。

# 注釈
[^1]: [クラス内のメソッドで自身をnewするときはself()でいける？](http://qiita.com/abusan/items/15cc8e62bf9cf8c554ce)