Help us understand the problem. What is going on with this article?

Singletonのデザインパターン

More than 5 years have passed since last update.

Singleton

GoFが考案した23種類のデザインパターンの一つです。
”Single”とあるように、複数のインスタンスを生成させないためのデザインパターンです。

Singletonを使わない例

共通の例として、クラス内部のPDOでデータベースに接続するクラスを作成しました。

Singletonを使わない場合

<?php

class DB
{
    /**
     * @var PDO
     */
    private $pdo;

    private $conf = [
        'host' => 'localhost',
        'db' => 'test',
        'user' => 'user',
        'password' => 'password',
        'charset' => 'utf8'
    ];

    public function __construct()
    {
        $dsn = "mysql:host={$this->conf['host']};dbname={$this->conf['db']};charset={$this->conf['charset']};";
        $options = [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
        ];

        $this->pdo = new PDO($dsn, $this->conf['user'], $this->conf['password'], $options);
    }

    public function find($id){
        //.....
    }

}

$dbs = [];
for ($i=0; $i<5; $i++) {
    //ループごとにMySQLのコネクションが増える
    $dbs[$i] = new DB();
    $dbs[$i]->find(1);
}

最後の部分でループの中で毎回インスタンスを生成するような処理を入れています。
これでは、最終的に5個のインスタンスが生成されて、MySQLにも5個のコネクションができてしまいます。

Singletonを使ったクラス

<?php

class DB
{
    /**
     * @var PDO
     */
    private $pdo;

    private $conf = [
        'host' => 'localhost',
        'db' => 'test',
        'user' => 'user',
        'password' => 'password',
        'charset' => 'utf8'
    ];

    //コンストラクタをprivateにするのが肝
    private function __construct()
    {
        $dsn = "mysql:host={$this->conf['host']};dbname={$this->conf['db']};charset={$this->conf['charset']};";
        $options = [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
        ];

        $this->pdo = new PDO($dsn, $this->conf['user'], $this->conf['password'], $options);
    }

    //外部からインスタンスを呼び出したい場合のメソッド
    final public static function getInstance()
    {
        static $instance;
        return $instance ?: $instance = new self;
    }

    //クローンを禁止する
    final public function __clone()
    {
        throw new Exception("this instance is singleton class.");
    }

    public function find($id){
        //.....
    }

}

$dbs = [];
for($i = 0; $i < 5; $i++){
    $dbs[$i] = DB::getInstance();
    $dbs[$i]->find(1);
}

ポイント

  1. コンストラクタがprivateメソッドになっている
  2. 外部からはDB::getInstanse()でインスタンスを取得する
  3. インスタンスのcloneの作成を禁止

コンストラクタをprivateメソッドにしていることで、クラスの外からnew DBと新しいインスタンスを生成できなくなっています。
外部からインスタンスを取得するときはDB::getInstanse()を使い、初回時に$instance = new selfでクラス内から自身のインスタンスを作成しています。
また、インスタンスのコピー(クローン)もできないよう、マジックメソッドで例外を発生させるようにしています。

トレイトを使ったクラス

トレイトはPHP5.4から使えるようになりました。

<?php

trait Singleton
{
    //コンストラクタはprotected
    protected function __construct(){}

    //さっきと同じ
    final public static function getInstance()
    {
        static $instance;
        return $instance ?: $instance = new static;
    }

    //さっきと同じ
    final public function __clone()
    {
        throw new Exception("this instance is singleton class.");
    }
}

class DB
{
    //トレイトを使用する宣言
    use Singleton

    /**
     * @var PDO
     */
    private $pdo;

    private $conf = [
        'host' => 'localhost',
        'db' => 'test',
        'user' => 'user',
        'password' => 'password',
        'charset' => 'utf8'
    ];

    //トレイトのコンストラクタを上書き
    protected function __construct()
    {
        $dsn = "mysql:host={$this->conf['host']};dbname={$this->conf['db']};charset={$this->conf['charset']};";
        $options = [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
        ];

        $this->pdo = new PDO($dsn, $this->conf['user'], $this->conf['password'], $options);
    }

    public function find($id){
        //.....
    }

}

$dbs = [];
for($i = 0; $i < 5; $i++){
    $dbs[$i] = DB::getInstance();
    $dbs[$i]->find(1);
}

ポイント

  1. use SingletonでSingletonトレイトの使用を宣言する
  2. Singletonの骨格はトレイトの方に記述
  3. DBクラスでコンストラクタが使えるように、トレイトコンストラクタをprotectedにしている

「Singletonを使ったクラス」をDBクラスとSingletonトレイトの2つに分け、Singletonのパターンを他のクラスでも作成できるよう、トレイトに纏めて、使用するクラスの時にuse Singletonを宣言しています。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした