PHP

Singletonのデザインパターン

More than 3 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を宣言しています。