どもです。
今回は最近の悩みについて。
プロジェクトを1から立ち上げるとき、共用のクラスをどうするかが問題になる。
だいたいはstatic関数にしてしまうか都度newするかで解決するのだけど、データベースを扱うクラスだけは簡単にいかない。
●前提
・このプロジェクトで使用するデータベースは1つのみであり、データベース情報は定数で宣言する。
・DBクラスはプログラムの開始時に一度だけ宣言し、全てのクラスで同じインスタンスを使いまわす。
・DB操作はmysqli関数を使用する。(PDOでも同じことだけれど)
class DB{
protected $mysql = null;
function __construct(){
$this->mysql = null;
if($this->mysql = mysqli_connect(host, user, pass)) {
if(!mysqli_select_db($this->mysql, name)){
throw new \Exception("データベースへの接続に失敗しました。");
}
}
}
function query($sql){
return mysqli_query($this->mysql, $sql);
}
}Menu
①グローバル変数にする
$db = new DB();
class test{
function a($sql){
$GLOBALS['db']->query($sql);
}
}ggった場合に出てくる解決法はだいたいグローバル化だが、クラスの中で使用するとこうなるのでスマートとは言い難い。
あとグローバル変数は使うな勢力が幅を利かせている昨今、このやり方は文句を言われそうだ。
②エントリーポイントで取得したインスタンスを処理クラスに渡す
class Main{
function __construct(){
$db = new DB();
$test = new test($db);
$test->a($sql);
}
}
class test{
function __construct($db){
$this->db = $db;
}
function a($sql){
$this->db->query($sql);
}
}次に思いつくのは、共通処理の最初に宣言したインスタンスを処理クラスのコンストラクトに手渡しする手段。
しかし、こちらもスマートと言えたものではない。明示的に手渡ししたクラスでしかDBが使えないから拡張性が死ぬし、コンストラクタがぐちゃぐちゃするのはNGだ。
③DBクラスをシングルトンにする
class DB{
protected static $mysql = null;
protected static $instance = null;
private function __construct(){
略
}
static function query($sql){
return mysqli_query(self::$mysql, $sql);
}
static function createInstance(){
if (!isset(self::$instance)) {
self::$instance = new DB();
}
}
}
class Main{
function __construct(){
DB::createInstance(); //エントリーポイントでDBを宣言
$test = new test();
$test->a($sql);
}
}
class test{
function a($sql){
DB::query($sql); //既にDB::$mysqlがあるので、個々のクラスで宣言せずとも自由に使える
}
}辿り着いたのがコレ。シングルトンという概念を知らなかったので目から鱗だった。
なるほど、DBクラスが自身の単一のインスタンスをstaticに持ってしまえば、staticで同じ接続を取り出すことができるわけだ。実にスマートだ。
④trait(多重継承) を使ってみる
trait DB{
protected $mysql = null;
function __construct(){
略
}
function query($sql){
return mysqli_query($this->mysql, $sql);
}
}
class test{
use DB;
function a($sql){
$this->query($sql);
}
}単一責任の原則のもとDBにアクセスできるクラスは限定的であるべき??
ならphp5.4以降はtraitを用いた多重継承ができるので、DBクラスを多重継承してみようか。
これなら明示的にDBをUSEしないと使えないので、単一責任の原則に従いながら要件を満たせる…
ということは無くて、上記だとクラスごとに$mysql::thread_idが変わる=新しい接続を行ってしまう。
trait DB{
protected static $mysql = null;
function __construct(){
$this->connect();
}
function connect(){
if(self::$mysql == null){
if(self::$mysql = mysqli_connect(host, user, pass)) {
if(!mysqli_select_db(self::$mysql, name)){
throw new \Exception("データベースへの接続に失敗しました。");
}
}
}
}
function query($sql){
return mysqli_query(self::$mysql, $sql);
}
}接続をstaticにすることで、traitでも単一thread_idを保つことは可能だ。
単一インスタンスの使いまわしという前提からは外れるが、流派によってはこちらを採用すると良い。
⑤接続さえstaticにすれば、都度newでもできはする
class DB{
protected static $mysql = null;
略
}
class test{
function a($sql){
$db = new DB();
$db->query($sql);
}
}美しくないが、これでも単一thread_idを保てるので最低限の要件を満たせる。美しくはない。
一通りやってみたが、僕としてはシングルトンがスマートでtraitもアリといった感じの結論になった。