自編PHP框架之?dāng)?shù)據(jù)庫PDO層封裝和模型類部分方法的編寫
如果你是噴子屋灌,問我造輪子花這么多精力有什么用的話洁段,那就走,看看我的這篇文章 為什么我要寫自己的框架共郭?
框架所有的代碼都在筆者的Github上做展示祠丝,并且有一個庫存管理信息系統(tǒng)的實例,Github賬號請看筆者簡介
這是自編寫框架的第一篇除嘹,之后計劃寫路由篇写半、小組件篇、工廠篇等
數(shù)據(jù)庫操作可以說是網(wǎng)頁應(yīng)用程序的核心尉咕,他直接決定了你的程序是干什么的
看大標(biāo)題叠蝇,很明顯的發(fā)現(xiàn)這是一篇和PDO封裝有關(guān)系的文章,再一次感謝Yii2.0框架給我設(shè)計模式方面的啟發(fā)年缎。廢話不多說蟆肆,我們開始
封裝分為兩個類:
Connection類 | Command類
首先,作為PHP晦款,一個請求就會對應(yīng)一個PHP線程,在這個大環(huán)境下枚冗,一個線程有多個數(shù)據(jù)庫連接豈不是很浪費缓溅,因此,我運用單例模式讓請求的整個生命周期內(nèi)共享數(shù)據(jù)庫連接
//Connection類
private static $_instance;
private function __construct() {}
private function __clone() {}
//單例
public static function getinstance() {
if (!(self::$_instance instanceof self)) {
self::$_instance = new self();
}
return self::$_instance;
}
//類被回收的時候調(diào)用赁温,此時關(guān)閉數(shù)據(jù)庫連接
public function __destruct() {
$this->close();
}
有了單例模式之后我們就需要進行實際的連接數(shù)據(jù)庫操作了
通過config配置數(shù)據(jù)庫坛怪,運用PDO獨特的語法dsn:
return [
'dsn' => 'mysql:host=127.0.0.1;dbname=dbname',
'user' => 'root',
'password' => 'pass',
'charset' => 'utf8',
'slaves' => [
[
'dsn' => 'mysql:host=127.0.0.1;dbname=dbname',
'user' => 'root',
'password' => 'pass',
'charset' => 'utf8',
],
],
];
下面這些是Connection的變量,這個設(shè)計把從服務(wù)器所有的連接實例化都儲存在一個私有變量中股囊,統(tǒng)一進行管理
//實例化后數(shù)據(jù)庫連接屬性
public $connect;
private $db; //數(shù)據(jù)庫連接信息
//服務(wù)器信息袜匿,私有屬性
private $dsn;
private $user;
private $pass;
private $charset;
private $rightNowDb; //當(dāng)前服務(wù)器信息私有屬性的服務(wù)器名
private $PDOSlaves; //從服務(wù)器實例化后數(shù)據(jù)庫連接屬性
獲取數(shù)據(jù)庫連接信息,一旦信息不完整
- 拋出錯誤:One of the PDO::DB Parameter is empty!
- 記錄在系統(tǒng)的錯誤日之內(nèi)(errordb.log文件夾內(nèi))稚疹,LogWrite類是筆者自己封裝的日志記錄類居灯,采用鏈?zhǔn)秸{(diào)用。
private function getInfo() {
$this->db = require (dirname(__FILE__).'/../../config/db.php');
if ($this->db['dsn'] && $this->db['user'] && $this->db['password']) {
$this->dsn = $this->db['dsn'];
$this->user = $this->db['user'];
$this->pass = $this->db['password'];
$this->charset = $this->db['charset']?$this->db['charset']:'utf8';
$this->rightNowDb = 'master';
} else {
$this->err('One of the PDO::DB Parameter is empty!');
}
}
private function err($err) {
$err = 'ErrInfo: '.$err;
LogWrite::getinstance()->IntoWhere('errordb')->Info($err)->execute();
throw new Exception($err);
}
PDO連接方法,這就是PDO的連接方法怪嫌,運用new PDO進行操作
- exec方法執(zhí)行sql語句
- getAttribute獲取屬性
private function nowConnect() {
try {
$connect = new PDO($this->dsn, $this->user, $this->pass);
} catch (PDOException $e) {
$this->err($e->getMessage());
}
if (!$connect) {
$this->err('PDO connect error');
}
$connect->exec('SET NAMES '.$this->charset);
$connect->getAttribute(constant("PDO::ATTR_SERVER_VERSION"));
return $connect;
}
數(shù)據(jù)庫關(guān)閉义锥、數(shù)據(jù)庫是否活躍方法(簡單,直接貼代碼岩灭,不做解釋)
public function getConnect() {
$this->getInfo();
if ($this->isActive()) {
return $this->connect;
} else {
$this->connect = $this->nowConnect();
return $this->connect;
}
}
//if there is active
public function isActive() {
return $this->connect;
}
//close connection
public function close() {
if ($this->isActive()) {
$this->connect = null;
}
}
下面就到了我們實際使用數(shù)據(jù)庫連接的方法了拌倍,如果程序員使用到數(shù)據(jù)庫連接,這些方法是最常用的噪径,那就是獲取連接
- 獲取連接使用判斷柱恤,如果不是active,則調(diào)用封裝的PDO連接方法找爱,賦值給類內(nèi)的connect屬性梗顺,返回此屬性
public function getConnect() {
$this->getInfo();
if ($this->isActive()) {
return $this->connect;
} else {
$this->connect = $this->nowConnect();
return $this->connect;
}
}
下面就是從服務(wù)器連接的代碼
- 從服務(wù)器的話則從PDOSlaves屬性內(nèi)拿值,判斷同主服務(wù)器缴允,不同的從服務(wù)器連接對應(yīng)PDOSlaves內(nèi)不同的鍵值對
- setToSlaves方法使私有數(shù)據(jù)庫連接屬性變成需要操作的數(shù)據(jù)庫
- setMaster私有數(shù)據(jù)庫連接屬性變回主服務(wù)器
- 大家都看到了
return $this;
了吧荚守,這就是鏈?zhǔn)秸{(diào)用的核心!
public function getSlavesConnect($num) {
$this->setToSlaves($num);
$key = 'slave'.$num;
if ($this->PDOSlaves[$key]) {
return $this->PDOSlaves[$key];
} else {
$connect = $this->nowConnect();
$this->PDOSlaves[$key] = $connect;
return $this->PDOSlaves[$key];
}
}
//Serval attributes change to slaver DataBase
public function setToSlaves($num) {
if ($this->db['slaves'][$num]['dsn'] && $this->db['slaves'][$num]['user'] && $this->db['slaves'][$num]['password']) {
$this->dsn = $this->db['slaves'][$num]['dsn'];
$this->user = $this->db['slaves'][$num]['user'];
$this->pass = $this->db['slaves'][$num]['password'];
$this->rightNowDb = 'slaves'.$num;
} else {
$this->err('slaves '.$num.':: missing info!');
}
}
public function setMaster() {
$this->getInfo();
return $this;
}
public function getRightNowDb() {
return $this->rightNowDb;
}
寫到這邊很多人就有疑問了练般,介紹了這么多方法矗漾,都是數(shù)據(jù)庫連接的,光連接數(shù)據(jù)庫沒有什么用處氨×稀敞贡!還是不對數(shù)據(jù)庫做一點的操作,而且按照現(xiàn)在的介紹應(yīng)該會new兩個類摄职,進行互不相干的聯(lián)系誊役,這樣不是很low!還不如封裝成一個谷市!
有這個想法的人很好蛔垢!說明在思考,因為我當(dāng)時在閱讀Yii2.0源碼的時候打開它也有這同樣的想法迫悠,但之后看到了他對這個的解決方法鹏漆,我茅塞頓開
- 在連接數(shù)據(jù)庫內(nèi)部有一個數(shù)據(jù)庫操作類的工廠方法
- sql可有可無
public function createCommand($sql = null) {
//first connect the db
$command = new Command([
'db' => $this->getConnect(),
'sql' => $sql,
]);
return $command;
}
public function createSlavesComm($num = 0, $sql = null) {
$command = new Command([
'db' => $this->getSlavesConnect($num),
'sql' => $sql,
]);
return $command;
}
這就是所有的數(shù)據(jù)庫連接的代碼,下面介紹Command類创泄,首先是構(gòu)造方法和一些屬性艺玲,數(shù)據(jù)庫連接很顯然就儲存在類內(nèi)pdo的屬性內(nèi)
//this save sql word
private $sql;
//pdo connect
private $pdo;
//the pdo statement
private $pdoStmt;
//the last db select is in here
private $lastCommandDb;
private $dataType = PDO::FETCH_ASSOC; //從數(shù)據(jù)庫內(nèi)取出數(shù)據(jù)的默認屬性
//Connection.php to new a command
public function __construct($arr) {
$this->sql = $arr['sql']?$arr['sql']:'';
$this->pdo = $arr['db'];
}
數(shù)據(jù)庫sql搜索,分為準(zhǔn)備和執(zhí)行兩步鞠抑,運用PDO的方法饭聚,一旦有錯誤,拋錯搁拙,記錄日志秒梳,沒有準(zhǔn)備就執(zhí)行的話法绵,系統(tǒng)會拋throw new PDOException('PDO is Fail to use execute before prepare!');
錯誤
//Must handle
private function prepare() {
//if there have stmt
if ($this->pdoStmt) {
$this->lastCommandDb = $this->pdoStmt;
}
$this->pdoStmt = $this->pdo->prepare($this->sql);
}
//execute it and return
private function execute($method) {
if ($this->pdoStmt) {
$pdoStmt = $this->pdoStmt;
$pdoStmt->execute();
$res = $pdoStmt->$method($this->dataType);
if (!$res) {
$msg = 'The result is empty, The sql word is :: '.$this->sql;
LogWrite::getinstance()->IntoWhere('errordb')->Info($msg)->execute();
return false;
}
return $res;
} else {
throw new PDOException('PDO is Fail to use execute before prepare!');
}
}
下面介紹事務(wù),作為一個數(shù)據(jù)庫操作端幼,那就肯定需要事務(wù)礼烈,沒有事務(wù)的話,往往就出現(xiàn)致命的錯誤婆跑,突然說到事務(wù)有可能會有點唐突此熬,下面就舉個很簡單的例子,關(guān)于銀行的
銀行內(nèi)部不同的人會對應(yīng)不同的賬戶滑进,每個賬戶都是一條數(shù)據(jù)庫記錄犀忱,下面模擬一個轉(zhuǎn)賬業(yè)務(wù):A轉(zhuǎn)賬給B 100塊
當(dāng)A轉(zhuǎn)賬給B的時候,A賬戶內(nèi)的錢減去100扶关,此時是成功的阴汇,當(dāng)給B內(nèi)的帳戶添加100塊的時候,由于某些原因(由于死鎖導(dǎo)致執(zhí)行時間超時节槐,系統(tǒng)內(nèi)部錯誤等)搀庶,B的帳戶內(nèi)沒有多100塊錢,這個轉(zhuǎn)賬事務(wù)其實是失敗的铜异,但是一旦沒有事務(wù)的約束哥倔,只有拋個錯,沒有紀錄和回滾的話其實對于一個轉(zhuǎn)賬業(yè)務(wù)來說這是致命的W嶙E剌铩!蚂子!
在這個基礎(chǔ)上沃测,就誕生了事務(wù)的概念,PDO也有對其的一套完整的解決方案食茎,那我們下面就來封裝它
//transction handle
private function transction() {
try {
$this->pdo->beginTransaction();
$res = $this->pdo->exec($this->sql);
if ($this->pdo->errorInfo()[0] != '00000') {
throw new PDOException('DB Error::Fail to change the database!! The sql is: '.$this->sql.' The Error is :: '.$this->pdo->errorInfo()[2]);
}
$this->pdo->commit();
return true;
} catch (PDOException $e) {
$this->pdo->rollback();
LogWrite::getinstance()->IntoWhere('errordb')->Info($e)->execute();
return false;
}
}
//change it lately
private function transctions(array $sqlArr = array()) {
try {
$this->pdo->beginTransaction();
foreach ($sqlArr as $value) {
$res = $this->pdo->exec($value);
print_r($this->pdo->errorInfo());
if ($this->pdo->errorInfo()[0] != '00000') {
throw new PDOException('DB Error::Fail to change the database!! The sql is: '.$value.' The Error is :: '.$this->pdo->errorInfo()[2]);
}
}
$this->pdo->commit();
return true;
} catch (PDOException $e) {
$this->pdo->rollback();
LogWrite::getinstance()->IntoWhere('errordb')->Info($e)->execute();
return false;
}
}
以上就是基礎(chǔ)的一些方法的封裝蒂破,那應(yīng)該如何使用這些方法吶,當(dāng)然就是增刪改查咯别渔!
public function queryAll($fetchMode = null) {
return $this->queryInit('fetchAll', $fetchMode);
}
public function queryOne($fetchMode = null) {
return $this->queryInit('fetch', $fetchMode);
}
//insert into database
public function insert($table, $arr) {
$this->sql = Merj::sql()->insert($table)->value($arr)->sqlVal();
return $this->transction();
}
//insert serval database
public function insertSomeVal($table, array $key, array $arr) {
$this->sql = Merj::sql()->insert($table)->servalValue($key, $arr)->sqlVal();
return $this->transction();
}
//update the database
public function update($table, $arr, $where) {
$this->sql = Merj::sql()->update($table)->set($arr)->where($where)->sqlVal();
return $this->transction();
}
public function updateTrans(array $sqlArr = array()) {
return $this->transctions($sqlArr);
}
//delete one record
public function delete($table, $whereArr) {
$this->sql = Merj::sql()->delete($table)->where($whereArr)->sqlVal();
return $this->transction();
}
private function queryInit($method, $fetchMode = null) {
if ($fetchMode) {
$this->dataType = $fetchMode;
}
if ($this->sql && $this->pdo) {
$this->prepare();
$result = $this->execute($method);
return $result?$result:'';
} else {
$err = 'Sql or PDO is empty; The sql is '.$this->sql;
LogWrite::getinstance()->IntoWhere('errordb')->Info($this->sql)->execute();
throw new PDOException($err);
return false;
}
}
方法很多寞蚌,其實是有規(guī)律的
- 這個工廠方法
Merj::sql()
是一個鏈?zhǔn)椒椒ǎ瑢嵗唇觭ql語句的類
public static function sql() {
return new QueryBuilder();
}
沒錯钠糊,框架運用到的就是sql語句的鏈?zhǔn)秸{(diào)用拼接方法
- 搜索方法調(diào)用queryInit方法
- 增刪改都運用剛剛提到的
transction
方法 - 如果需要使用完整的方法,只需要像下面這樣壹哺,是不是很方便抄伍!
$connect = Connection::getinstance();
$connect->createCommand($sql)->queryOne();
$connect->createCommand($sql)->queryAll();
$connect::db()->createCommand()->insert('tableName', [
'Val Key1' => 'Val1',
'Val Key2' => 'Val2',
]);
Merj::db()->createSlavesComm(0, 'SELECT * FROM content')->queryAll(); //從服務(wù)器操作數(shù)據(jù)庫
數(shù)據(jù)庫的模型類方法部分展示,所有模型繼承至此類:
/**
* 返回一條紀錄內(nèi)的一個值
* @param 想要取出的值
* @param 主鍵對應(yīng)的值
* @return 返回一個值
**/
public function findOnlyOne($target, $idVal) {
$sql = Merj::sql()->select($target)->from($this->tableName)->where([
$this->primKey => $idVal,
])->sqlVal();
$rows = Merj::db()->createCommand($sql)->queryOne();
if ($rows) {
return $rows[$target];
} else {
return false;
}
}
/**
* 返回一條紀錄
* @param 主鍵屬性
* @param 主鍵對應(yīng)的值
* @return 返回這條紀錄
**/
public function findOneRecord($userIdVal) {
$sql = Merj::sql()->select()->from($this->tableName)->where([
$this->primKey => $userIdVal,
])->sqlVal();
$rows = Merj::db()->createCommand($sql)->queryOne();
if ($rows) {
return $rows;
} else {
return false;
}
}
/**
* 通過sql語句查找
* @param sql words
* @return results
**/
public function findBySql($sql) {
return Merj::db()->createCommand($sql)->queryAll();
}
/**
* @param Insert info
* @return success or not
**/
public function insertOne($arr) {
return Merj::db()->createCommand()->insert($this->tableName, $arr);
}
/**
* @param Insert infos
* @return success or not
**/
public function insertNum(array $key = array(), array $arr = array()) {
return Merj::db()->createCommand()->insertSomeVal($this->tableName, $key, $arr);
}
/**
* 更新一條記錄
* @param
* @param
* @return success or not
**/
public function updateOneRec($arrUpDate, $idVal) {
return Merj::db()->createCommand()->update($this->tableName, $arrUpDate, [
$this->primKey => $idVal,
]);
}
/**
* 多個sql語句更新
* @param sql array
* @return success or not
**/
public function updateTrans($sqlArr) {
return Merj::db()->createCommand()->updateTrans($sqlArr);
}
/**
* 刪除一條記錄
* @param where $arr
* @return success or not
**/
public function deleteOne($arr) {
return Merj::db()->createCommand()->delete($this->tableName, $arr);
}
/**
* object to array
* @param object
* @return array
**/
public function obj_arr($obj) {
if (is_object($obj)) {
$array = (array) $obj;
}if (is_array($obj)) {
foreach ($obj as $key => $value) {
$array[$key] = $this->obj_arr($value);
}
}
return $array;
}
public function jsonUtf8Out($arr) {
foreach ($arr as $key => $value) {
if (is_array($value)) {
$arr[$key] = $this->jsonUtf8Out($value);
} else {
$arr[$key] = urlencode($value);
}
}
return $arr;
}
最后兩個方法為兩個遞歸方法
- 對象變成數(shù)組
- 數(shù)組內(nèi)元素urlencode管宵,由于變成json的json_encode方法會轉(zhuǎn)譯中文截珍,所以需要讓數(shù)組內(nèi)所有元素urlencode再urldecode
好了攀甚!說了這么多,筆者要去吃飯了岗喉!明天還有一天的高考秋度,祝大家高考順利!