#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);
}
###ポイント
- コンストラクタがprivateメソッドになっている
- 外部からは
DB::getInstanse()
でインスタンスを取得する - インスタンスの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);
}
###ポイント
-
use Singleton
でSingletonトレイトの使用を宣言する - Singletonの骨格はトレイトの方に記述
- DBクラスでコンストラクタが使えるように、トレイトコンストラクタをprotectedにしている
「Singletonを使ったクラス」をDBクラスとSingletonトレイトの2つに分け、Singletonのパターンを他のクラスでも作成できるよう、トレイトに纏めて、使用するクラスの時にuse Singleton
を宣言しています。