こんなふざけたタイトルなのに開いてくれてありがとう。
久しぶりに良いまっぴートマホークを見た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だと親子で有意な速度差でるのね。