Singletonのデザインパターン

#Singleton

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

#Singletonを使わない例

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

##Singletonを使わない場合


```php
<?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
<?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メソッドになっている
1. 外部からは`DB::getInstanse()`でインスタンスを取得する
1. インスタンスのcloneの作成を禁止

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

##トレイトを使ったクラス

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

```php
<?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トレイトの使用を宣言する
1. Singletonの骨格はトレイトの方に記述
1. DBクラスでコンストラクタが使えるように、トレイトコンストラクタをprotectedにしている

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