PHP
singleton
シングルトン

シングルトンの使いどころは? PHPのデータベースアクセスを例に

この記事は stackoverflow.com Is there a use-case for singletons with database access in PHP? を翻訳して一部加筆訂正したものです。

質問

PHPのPDOを使用してMySQLデータベースにアクセスしています。
私は最初、下記のように、PDOのインスタンスをグローバル変数に入れて使用していました。

$db = new PDO('mysql:host=127.0.0.1;dbname=toto', 'root', 'pwd');

function some_function() {
    global $db;
    $db->query('...');
}

これは「悪い例」とされています。
ググったところ「シングルトンパターン」を使え、とあります。曰く、

クラスのインスタンスが1つしか必要ない場合に利用する。

今回のケースをシングルトンで書くと、例えばこのようになります。

class Database {
    private static $instance;
    private $db; //※

    private function __construct(){}

    static function singleton() {
        if(!isset(self::$instance))
            self::$instance = new __CLASS__;

        return self:$instance;
    }

    function get() {
        if(!isset(self::$db))
            self::$db = new PDO('mysql:host=127.0.0.1;dbname=toto', 'user', 'pwd')

        return self::$db;
    }
}

function some_function() {
    $db = Database::singleton();
    $db->get()->query('...');
}

some_function();

なぜ、これが必要なのでしょうか?
シングルトンパターンを実現するために、このような比較的大きなコードを書き足す必要があるのでしょうか?

代わりに、このように static を使用して書くこともできます。これはこれで完全に動作していますが、これではダメなのでしょうか?

class Database {
    private static $db;

    private function __construct(){}

    static function get() {
        if(!isset(self::$db))
            self::$db = new PDO('mysql:host=127.0.0.1;dbname=toto', 'user', 'pwd');

        return self::$db;
    }
}

function some_function() {
    Database::get()->query('...');
}

some_function();

シングルトンをもっと短く書くことはできませんか? もしくは私の気づいていないPHPのシングルトンの事例が他にありますでしょうか?

回答

私もキャリアをスタートしてしばらくの間、同じように疑問に思いました。さて、今回のケース、グローバル変数や静的クラスを使用しないことを選択する理由は2つあります。どちらもとても重要なものです。

1つは、よくあることですが、この先、2つ以上必要になることがある、ということです。2台目の端末、2種類のデータベース、2台目のサーバー…なんでもそうです。

そうなったとき、グローバル変数や静的クラスを使用していた場合のリファクタリングは絶望的です。もちろんシングルトンだったとしても簡単なことではありませんが、ずっと楽に、ファクトリーパターンやDIパターンに変更することができます。たとえば、シングルトンのインスタンスがgetInstance()で取得されていたなら、これをgetInstance(databaseName)とするだけで、複数のデータベースに対応することができます。他にコードを変更する必要がありません。

もう1つは、テストです。例えばデータベースをモックデータベースに置き換えることもできます(実際には、これは2種類目のデータベースオブジェクトなので最初の問題と同じことです)。静的クラスを使用しているとこの置き換えが非常に難しいです。対してシングルトンを使用していた場合は、getInstance()メソッドをモックアウトするだけです。

これは慣例になっていますし、いろんな人が「グローバルは最悪だ」と言っている場合、彼らにはそう言うだけの強い理由があるのですが、あなたはまだ、あなた自身で問題にぶつかっていないので、それが明白ではないかもしれません。

あなたにとって最も大切なことは、質問することです(あなたがまさにそうしたように)。次に、あなた自身で手法を選択して、その結果がどうなるかをしっかりと受け止め、自分の経験とすることです。最初から正しいことをするよりも、あなたの自身のコードの進化を解釈するための「知識」を身につけていくことです。

※訳注 原文では$dbもstaticとしていましたが、static変数はDatabaseのインスタンスだけにしておくほうが「シングルトン」らしいと思います。理由は回答を読めばわかりますよね(^^)

追記

ちなみに「スタティックメソッドの使いどころは?」でググると、「インスタンスを1つも作ってない状態で使うことが自然なもの」といった解釈が自分の中でしっくりきています。例えば今回の getInstanceもそうです。でも逆に、例えば2つのインスタンスを比較するメソッドなど、ほとんどのスタティックメソッドはふつうのインスタンスメソッドに置き換えができるし、ほとんどのスタティッククラスはシングルトンで書けるので、そうしたほうが良いよ、ということのようです。

まぁそれも「慣例」であって、自分で問題にぶつかるまでは、あえて慣例に従わずにやってみたほうが経験になって良いよ、というアドバイスがステキだなと思ったので紹介させていただきました。(※ただし仕事以外でやれと 笑)

余談

よく「使用事例」と辞書にある use case を今回「使いどころ」と訳しましたが、これは最近、「使いどころ」を知ろうと思ったら use case とググれば良いことに気づいたためです。「事例」ではないんですよね。そんな日本語と英語の微妙な差異を感じた今日このごろ。