たまにはちょっとお馬鹿い考察を(笑
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する」以外の方法で、抜けそうな方法をご連絡いただければアップデートしたいなぁ、と思うので。
興味のある方は、遊び心で参加いただければ幸いでございます。