たまにはちょっとお馬鹿い考察を(笑
Singletonで顕著なのですが、インスタンスを「1つにしたい」ってケースで。
まぁ勿論規約で縛るとかってのもよいのですが、縛っても踏み越えて乗り越えてくるケースってのはあるので。
ある程度くらいまでは「不可能な状況」を作るのも、程度問題ですが、大事だと思うのです。
んと…つまり。
プロパティは「アクセサ経由でアクセスさせたいんなら、プロパティ自体はprivateないしせめてprotectedにしとけよ」と。
そんな感じです。
さて。
まずとりあえずざっくりしたSingletonのクラスを。
class hoge {
private function __construct() {
}
static public function get_instance() {
static $obj = null;
if (null === $obj) {
$obj = new static();
}
return $obj;
}
}
//
$obj = hoge::get_instance();
var_dump($obj);
$obj2 = $obj;
var_dump($obj2);
//
$obj3 = clone $obj;
var_dump($obj3);
$obj3が、cloneによって「二つ目のインスタンス」作れてしまいます。
勿論規約的に「Singletonのインスタンスをcloneすんな」って書いておけばよいのですが、書いたってやるヤツはやらかします。
ここはしっかりロックしてみましょう。
class hoge {
private function __construct() {
}
static public function get_instance() {
static $obj = null;
if (null === $obj) {
$obj = new static();
}
return $obj;
}
// clone禁止
private function __clone() {
throw new Exception('させねぇよ!');
}
}
//
$obj = hoge::get_instance();
var_dump($obj);
$obj2 = $obj;
var_dump($obj2);
//
$obj3 = clone $obj;
var_dump($obj3);これでcloneできません。
これで仕舞い…かと思ったのですが、ヤな事を思いついてみたので書いてみます。
class hoge {
private function __construct() {
}
static public function get_instance() {
static $obj = null;
if (null === $obj) {
$obj = new static();
}
return $obj;
}
// clone禁止
private function __clone() {
throw new Exception('させねぇよ!');
}
}
//
$obj = hoge::get_instance();
var_dump($obj);
$obj2 = $obj;
var_dump($obj2);
//
//$obj3 = clone $obj;
//var_dump($obj3);
$obj4 = unserialize(serialize($obj));
var_dump($obj4);可能ですねぇ。
馬鹿をなめちゃいけませんこーゆー「無駄で不必要で有害な工夫」だけは、しっかりやってきます。
とりあえす、シリアライズできなきゃどうにかなるでしょう。
ただ、以下の書き方は「誤り」になります。
class hoge {
private function __construct() {
}
static public function get_instance() {
static $obj = null;
if (null === $obj) {
$obj = new static();
}
return $obj;
}
// clone禁止
private function __clone() {
throw new Exception('させねぇよ!');
}
// serialize禁止
private function __sleep() {
throw new Exception('させねぇよ!');
}
}
//
$obj = hoge::get_instance();
var_dump($obj);
$obj2 = $obj;
var_dump($obj2);
//
//$obj3 = clone $obj;
//var_dump($obj3);
$obj4 = unserialize(serialize($obj));
var_dump($obj4);
Warning: Invalid callback hoge::__sleep, cannot access private method hoge::__sleep() in ..................
単純に「callbackが上手く呼べなかったから警告出しとくよ〜」で終わってしまいます。
なので、ここは「メソッドを潰す」のではなくて「受け入れた上で締め上げる」作戦でいきましょう。
class hoge {
private function __construct() {
}
static public function get_instance() {
static $obj = null;
if (null === $obj) {
$obj = new static();
}
return $obj;
}
// clone禁止
private function __clone() {
throw new Exception('させねぇよ!');
}
// serialize禁止
public function __sleep() {
throw new Exception('させねぇよ!');
}
}
//
$obj = hoge::get_instance();
var_dump($obj);
$obj2 = $obj;
var_dump($obj2);
//
//$obj3 = clone $obj;
//var_dump($obj3);
$obj4 = unserialize(serialize($obj));
var_dump($obj4);これでよいか…と思いきや、もう一つ方法があります困った事に。
コードの最後にこれをいれると、やっぱり「インスタンスのcopy」が可能になります。
$obj5 = unserialize('O:4:"hoge":0:{}');
var_dump($obj5);「自力でserializeの文字列組み立てるなよ」とか思うのですが、やるヤツはやらかします。
なので、 __wakeup() マジックメソッドも、しっかりと締め上げておきましょう。
かくして、締めまくったのが、以下になります。
class hoge {
private function __construct() {
}
static public function get_instance() {
static $obj = null;
if (null === $obj) {
$obj = new static();
}
return $obj;
}
// clone禁止
private function __clone() {
throw new Exception('させねぇよ!');
}
// serialize禁止
public function __sleep() {
throw new Exception('させねぇよ!');
}
// unserialize禁止
public function __wakeup() {
throw new Exception('させねぇよ!');
}
}
//
$obj = hoge::get_instance();
var_dump($obj);
$obj2 = $obj;
var_dump($obj2);
//
//$obj3 = clone $obj;
//var_dump($obj3);
//$obj4 = unserialize(serialize($obj));
//var_dump($obj4);
//$obj5 = unserialize('O:4:"hoge":0:{}');
//var_dump($obj5);
これが「本当に役に立つ」シーンは稀でしょうし、可能ならンなシーンに遭遇したくないだろうなぁ、というのは恐らく全世界の人類共通の願いではあるのですが。
たまにはこんな思考実験も面白いンじゃないかなぁ? と思いました。
「privateメソッドを無理くりcallする」以外の方法で、抜けそうな方法をご連絡いただければアップデートしたいなぁ、と思うので。
興味のある方は、遊び心で参加いただければ幸いでございます。