1.7 PhalApi 2.x Model數(shù)據(jù)層與數(shù)據(jù)庫(kù)操作

2018-07-28 21:23 更新

Model數(shù)據(jù)模型層與數(shù)據(jù)庫(kù)操作

Model層稱(chēng)為數(shù)據(jù)模型層,負(fù)責(zé)技術(shù)層面上對(duì)數(shù)據(jù)信息的提取、存儲(chǔ)、更新和刪除等操作,數(shù)據(jù)可來(lái)自?xún)?nèi)存,也可以來(lái)自持久化存儲(chǔ)媒介,甚至可以是來(lái)自外部第三方系統(tǒng)。雖然PhalApi的Model層是廣義上的數(shù)據(jù)層,但考慮到大部分?jǐn)?shù)據(jù)都是來(lái)自于數(shù)據(jù)庫(kù)的操作,所以這一章將重點(diǎn)講述如何進(jìn)行數(shù)據(jù)庫(kù)操作。

數(shù)據(jù)庫(kù)配置

數(shù)據(jù)庫(kù)的配置文件為./config/dbs.php,默認(rèn)使用的是MySQL數(shù)據(jù)庫(kù),所以需要配置MySQL的連接信息。servers選項(xiàng)用于配置數(shù)據(jù)庫(kù)服務(wù)器相關(guān)信息,可以配置多組數(shù)據(jù)庫(kù)實(shí)例,每組包括數(shù)據(jù)庫(kù)的賬號(hào)、密碼、數(shù)據(jù)庫(kù)名字等信息。不同的數(shù)據(jù)庫(kù)實(shí)例,使用不同標(biāo)識(shí)作為下標(biāo)?! ?/p>

servers數(shù)據(jù)庫(kù)配置項(xiàng) 說(shuō)明
host 數(shù)據(jù)庫(kù)域名
name 數(shù)據(jù)庫(kù)名字
user 數(shù)據(jù)庫(kù)用戶(hù)名
password 數(shù)據(jù)庫(kù)密碼
port 數(shù)據(jù)庫(kù)端口
charset 數(shù)據(jù)庫(kù)字符集

tables選項(xiàng)用于配置數(shù)據(jù)庫(kù)表的表前綴、主鍵字段和路由映射關(guān)系,可以配置多個(gè)表,下標(biāo)為不帶表前綴的表名,其中__default__下標(biāo)選項(xiàng)為缺省的數(shù)據(jù)庫(kù)路由,即未配置的數(shù)據(jù)庫(kù)表將使用這一份默認(rèn)配置。

表2-12 表配置項(xiàng)

tables表配置項(xiàng) 說(shuō)明
prefix 表前綴
key 表主鍵
map 數(shù)據(jù)庫(kù)實(shí)例映射關(guān)系,可配置多組。每組格式為:array('db' => 服務(wù)器標(biāo)識(shí), 'start' => 開(kāi)始分表標(biāo)識(shí), 'end' => 結(jié)束分表標(biāo)識(shí)),start和end要么都不提供,要么都提供

例如默認(rèn)數(shù)據(jù)庫(kù)配置為:

return array(
    /**
     * DB數(shù)據(jù)庫(kù)服務(wù)器集群
     */
    'servers' => array(
        'db_master' => array(                         //服務(wù)器標(biāo)記
            'host'      => '127.0.0.1',             //數(shù)據(jù)庫(kù)域名
            'name'      => 'phalapi',               //數(shù)據(jù)庫(kù)名字
            'user'      => 'root',                  //數(shù)據(jù)庫(kù)用戶(hù)名
            'password'  => '',                      //數(shù)據(jù)庫(kù)密碼
            'port'      => 3306,                  //數(shù)據(jù)庫(kù)端口
            'charset'   => 'UTF8',                  //數(shù)據(jù)庫(kù)字符集
        ),
    ),


    /**
     * 自定義路由表
     */
    'tables' => array(
        //通用路由
        '__default__' => array(
            'prefix' => 'tbl_',
            'key' => 'id',
            'map' => array(
                array('db' => 'db_master'),
            ),
        ),
    ),
);

其中,在servers中配置了名稱(chēng)為dbmaster數(shù)據(jù)庫(kù)實(shí)例,意為數(shù)據(jù)庫(kù)主庫(kù),其host為localhost,名稱(chēng)為phalapi,用戶(hù)名為root等。在tables中,只配置了通用路由,并且表前綴為tbl,主鍵均為id,并且全部使用db_demo數(shù)據(jù)庫(kù)實(shí)例。

溫馨提示:當(dāng)tables中配置的db數(shù)據(jù)庫(kù)實(shí)例不存在servers中時(shí),將會(huì)提示數(shù)據(jù)庫(kù)配置錯(cuò)誤。

如何排查數(shù)據(jù)庫(kù)連接錯(cuò)誤?

普通情況下,數(shù)據(jù)庫(kù)連接失敗時(shí)會(huì)這樣提示:

{
    "ret": 500,
    "data": [],
    "msg": "服務(wù)器運(yùn)行錯(cuò)誤: 數(shù)據(jù)庫(kù)db_demo連接失敗"
}

考慮到生產(chǎn)環(huán)境不方便爆露服務(wù)器的相關(guān)信息,故這樣簡(jiǎn)化提示。當(dāng)在開(kāi)發(fā)過(guò)程中,需要定位數(shù)據(jù)庫(kù)連接失敗的原因時(shí),可使用debug調(diào)試模式。開(kāi)啟調(diào)試后,當(dāng)再次失敗時(shí),會(huì)看到類(lèi)似這樣的提示:

{
    "ret": 500,
    "data": [],
    "msg": "服務(wù)器運(yùn)行錯(cuò)誤: 數(shù)據(jù)庫(kù)db_demo連接失敗,異常碼:1045,錯(cuò)誤原因:SQLSTATE[28000] [1045] ... ..."
}

然后,便可根據(jù)具體的錯(cuò)誤提示進(jìn)行排查解決。

NotORM簡(jiǎn)介

NotORM是一個(gè)優(yōu)秀的開(kāi)源PHP類(lèi)庫(kù),可用于操作數(shù)據(jù)庫(kù)。PhalApi的數(shù)據(jù)庫(kù)操作,主要是依賴(lài)此NotORM來(lái)完成。

參考:NotORM官網(wǎng):www.notorm.com

所以,如果了解NotORM的使用,自然而然對(duì)PhalApi中的數(shù)據(jù)庫(kù)操作也就一目了然了。但為了更符合接口類(lèi)項(xiàng)目的開(kāi)發(fā),PhalApi對(duì)NotORM的底層進(jìn)行優(yōu)化和調(diào)整。以下改動(dòng)點(diǎn)包括但不限于:

  • 將原來(lái)返回的結(jié)果全部從對(duì)象類(lèi)型改成數(shù)組類(lèi)型,便于數(shù)據(jù)流通
  • 添加查詢(xún)多條紀(jì)錄的接口:NotORM_Result::fetchAll()NotORM_Result::fetchRows()
  • 添加支持原生SQL語(yǔ)句查詢(xún)的接口:NotORM_Result::queryAll()NotORM_Result::queryRows()
  • limit 操作的調(diào)整,取消原來(lái)OFFSET關(guān)鍵字的使用
  • 當(dāng)數(shù)據(jù)庫(kù)操作失敗時(shí),拋出PDOException異常
  • 將結(jié)果集中以主鍵作為下標(biāo)改為以順序索引作為下標(biāo)
  • 禁止全表刪除,防止誤刪
  • 調(diào)整調(diào)試模式

如何獲取NotORM實(shí)例?

在PhalApi中獲取NotORM實(shí)例,有兩種方式:全局獲取方式、局部獲取方式。

  • 全局獲取:在任何地方,使用DI容器中的全局notorm服務(wù):\PhalApi\DI()->notorm
  • 局部獲取:在繼承PhalApi\Model\NotORMModel的子類(lèi)中使用:$this->getORM()

第一種全局獲取的方式,可以用于任何地方,這是因?yàn)槲覀円呀?jīng)在初始化文件中注冊(cè)了\PhalApi\DI()->notorm這一服務(wù)。
第二種局部獲取的方式,僅限用于繼承PhalApi\Model\NotORMModel的子類(lèi)中。首先需要實(shí)現(xiàn)相應(yīng)的Model子類(lèi),通常一個(gè)表對(duì)應(yīng)一個(gè)Model子類(lèi)。例如為user表創(chuàng)建相應(yīng)的Model類(lèi)。

PhalApi推薦使用封裝的第二種方式,并且下面所介紹的使用都是基于第二種快速方式。以下是獲取一個(gè)NotORM實(shí)例的示例。

class User extends NotORM {
    public function doSth() {
        $orm = $this->getORM(); 
    }
}

Model子類(lèi)與表名

如何新增一個(gè)Model類(lèi)?

通常情況下,一張表對(duì)應(yīng)一個(gè)Model類(lèi)。當(dāng)需要新增時(shí),可以繼承于PhalApi\Model\NotORMModel類(lèi),并放置在A(yíng)pp\Model命名空間下。例如,對(duì)于數(shù)據(jù)庫(kù)表tbl_user:

CREATE TABLE `tbl_user` (
  `id` int(11) NOT NULL,
  `name` varchar(45) DEFAULT NULL,
  `age` int(3) DEFAULT NULL,
  `note` varchar(45) DEFAULT NULL,
  `create_date` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

可以新增App\Model\User.php文件,并在里面放置以下代碼。

<?php
namespace App\Model;


use PhalApi\Model\NotORMModel as NotORM;


class User extends NotORM {
}

至此,便可得到一個(gè)基本的Model子類(lèi)了。

Model的基本操作

對(duì)于基本的Model子類(lèi),可以得到基本的數(shù)據(jù)庫(kù)操作。以下示例演示了Model的基本CURD操作。

$model = new App\Model\User();


// 查詢(xún)
$row = $model->get(1);
$row = $model->get(1, 'id, name'); //取指定的字段
$row = $model->get(1, array('id', 'name')); //可以數(shù)組取指定要獲取的字段


// 更新
$data = array('name' => 'test', 'update_time' => time());
$model->update(1, $data); //基于主鍵的快速更新


// 插入
$data = array('name' => 'phalapi');
$id = $model->insert($data);
//$id = $model->insert($data, 5); //如果是分表,可以通過(guò)第二個(gè)參數(shù)指定分表的參考ID


// 刪除
$model->delete(1);

數(shù)據(jù)庫(kù)表名配置

上面的App\Model\User類(lèi),自動(dòng)匹配的表名為:user,加上配置前綴“tbl_”,完整的表名是:tbl_usre。默認(rèn)表名的自動(dòng)匹配規(guī)則是:取“\Model\”后面部分的字符全部轉(zhuǎn)小寫(xiě),并且在轉(zhuǎn)化后會(huì)加上配置的表前綴。

雙如:

<?php
namespace App\Model\User;
use PhalApi\Model\NotORMModel as Model;


class Friends extends Model {
}

則類(lèi)App\Model\User\Friends自動(dòng)匹配的表名為user_friends。以下是2.x版本的一些示例:

2.x 的Model類(lèi)名 對(duì)應(yīng)的文件 自動(dòng)匹配的表名
App\Model\User ./src/app/Model/User.php user
App\ModelUser\Friends ./src/app/Model/User/Friends.php user_friends
App\User\Model\Friends ./src/app/user/Model/Friends.php friends
App\User\Model\User\Friends ./src/app/user/Model/User/Friends.php user_friends

但在以下場(chǎng)景或者其他需要手動(dòng)指定表名的情況,可以重寫(xiě)PhalApi\Model\NotORMModel::getTableName($id)方法并手動(dòng)指定表名。

  • 存在分表
  • Model類(lèi)名不含有“Model_”
  • 自動(dòng)匹配的表名與實(shí)際表名不符
  • 數(shù)據(jù)庫(kù)表使用蛇形命名法而類(lèi)名使用大寫(xiě)字母分割的方式

    如,當(dāng)Model_User類(lèi)對(duì)應(yīng)的表名為:my_user表時(shí),可這樣重新指定表名:

    
    <?php
    namespace App\Model;

use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM { protected function getTableName($id) { return 'my_user'; } }

其中,$id參數(shù)用于進(jìn)行分表的參考主鍵,只有當(dāng)存在分表時(shí)才需要用到。通常傳入的$id是整數(shù),然后對(duì)分表的總數(shù)進(jìn)行求余從而得出分表標(biāo)識(shí)。
即存在分表時(shí),需要返回的格式為:表名稱(chēng) + 下劃線(xiàn) + 分表標(biāo)識(shí)。分表標(biāo)識(shí)通常從0開(kāi)始,為連續(xù)的自然數(shù)。  


## 在Model內(nèi)的CURD基本操作


假設(shè)對(duì)于前面的tbl_user表,有以下數(shù)據(jù)。  


```sql
INSERT INTO `tbl_user` VALUES ('1', 'dogstar', '18', 'oschina', '2015-12-01 09:42:31');
INSERT INTO `tbl_user` VALUES ('2', 'Tom', '21', 'USA', '2015-12-08 09:42:38');
INSERT INTO `tbl_user` VALUES ('3', 'King', '100', 'game', '2015-12-23 09:42:42');

下面將結(jié)合示例,分別介紹NotORM更為豐富的數(shù)據(jù)庫(kù)操作。在開(kāi)始之前,假定已有:

class User extends NotORM {
    public function test() {
        $user = $this->getORM(); 
    }
}

SQL基本語(yǔ)句介紹

  • SELECT字段選擇

選擇單個(gè)字段:

// SELECT id FROM `tbl_user`
$user->select('id') 

選擇多個(gè)字段:

// SELECT id, name, age FROM `tbl_user`
$user->select('id, name, age') 

使用字段別名:

// SELECT id, name, MAX(age) AS max_age FROM `tbl_user`
$user->select('id, name, MAX(age) AS max_age') 

選擇全部表字段:

// SELECT * FROM `tbl_user`
$user->select('*') 

  • WHERE條件

單個(gè)條件:

// WHERE id = 1
$user->where('id', 1)
$user->where('id = ?', 1)
$user->where(array('id', 1))

多個(gè)AND條件:

// WHERE id > 1 AND age > 18
$user->where('id > ?', 1)->where('age > ?', 18)
$user->and('id > ?', 1)->and('age > ?', 18)
$user->where('id > ? AND age > ?', 1, 18)
$user->where(array('id > ?' => 1, 'age > ?' => 10))


// WHERE name = 'dogstar' AND age = 18
$user->where(array('name' => 'dogstar', 'age' => 18))

多個(gè)OR條件:

// WHERE name = 'dogstar' OR age = 18
$user->or('name', 'dogstar')->or('age', 18)

嵌套條件:

// WHERE ((name = ? OR id = ?)) AND (note = ?) -- 'dogstar', '1', 'xxx'


// 實(shí)現(xiàn)方式1:使用AND拼接
$user->where('(name = ? OR id = ?)', 'dogstar', '1')->and('note = ?', 'xxx')


// 實(shí)現(xiàn)方式2:使用WHERE,并順序傳遞多個(gè)參數(shù)
$user->where('(name = ? OR id = ?) AND note = ?', 'dogstar', '1', 'xxx')


// 實(shí)現(xiàn)方式3:使用WHERE,并使用一個(gè)索引數(shù)組順序傳遞參數(shù)
$user->where('(name = ? OR id = ?) AND note = ?', array('dogstar', '1', 'xxx'))


// 實(shí)現(xiàn)方式4:使用WHERE,并使用一個(gè)關(guān)聯(lián)數(shù)組傳遞參數(shù)
$user->where('(name = :name OR id = :id) AND note = :note', 
    array(':name' => 'dogstar', ':id' => '1', ':note' => 'xxx'))

IN查詢(xún):

// WHERE id IN (1, 2, 3)
$user->where('id', array(1, 2, 3))


// WHERE id NOT IN (1, 2, 3)
$user->where('NOT id', array(1, 2, 3))


// WHERE (id, age) IN ((1, 18), (2, 20))
$user->where('(id, age)', array(array(1, 18), array(2, 20)))

模糊匹配查詢(xún):

// WHERE name LIKE '%dog%'
$user->where('name LIKE ?', '%dog%')


// WHERE name NOT LIKE '%dog%'
$user->where('name NOT LIKE ?', '%dog%')

溫馨提示:需要模糊匹配時(shí),不可寫(xiě)成:where('name LIKE %?%', 'dog')。

NULL判斷查詢(xún):

// WHERE (name IS NULL)
$user->where('name', null)

非NULL判斷查詢(xún):

// WHERE (name IS NOT ?) LIMIT 1; -- NULL
$user->where('name IS NOT ?', null)

  • ORDER BY排序

單個(gè)字段升序排序:

// ORDER BY age
$user->order('age')
$user->order('age ASC')

單個(gè)字段降序排序:

// ORDER BY age DESC
$user->order('age DESC')

多個(gè)字段排序:

// ORDER BY id, age DESC
$user->order('id')->order('age DESC')
$user->order('id, age DESC')

  • LIMIT數(shù)量限制

限制數(shù)量,如查詢(xún)前10個(gè):

// LIMIT 10
$user->limit(10)

分頁(yè)限制,如從第5個(gè)位置開(kāi)始,查詢(xún)前10個(gè):

// LIMIT 5, 10
$user->limit(5, 10)

  • GROUP BY和HAVING

只有GROUP BY,沒(méi)有HAVING:

// GROUP BY note
$user->group('note')

既有GROUP BY,又有HAVING:

// GROUP BY note HAVING age > 10
$user->group('note', 'age > 10')

CURD之插入操作

插入操作可分為插入單條紀(jì)錄、多條紀(jì)錄,或根據(jù)條件插入。

操作 說(shuō)明 示例 備注 是否PhalApi新增
insert() 插入數(shù)據(jù) $user->insert($data); 全局方式需要再調(diào)用insert_id()獲取插入的ID
insert_multi() 批量插入 $user->insert_multi($rows); 可批量插入
insert_update() 插入/更新 接口簽名:insert_update(array $unique, array $insert, array $update = array() 不存時(shí)插入,存在時(shí)更新

插入單條紀(jì)錄數(shù)據(jù),注意,必須是保持狀態(tài)的同一個(gè)NotORM表實(shí)例,方能獲取到新插入的行ID,且表必須設(shè)置了自增主鍵ID。

// INSERT INTO tbl_user (name, age, note) VALUES ('PhalApi', 1, 'framework')
$data = array('name' => 'PhalApi', 'age' => 1, 'note' => 'framework');
$user->insert($data);
$id = $user->insert_id();
var_dump($id); 


// 輸出:新增的ID
int (4)


// 或者使用Model封裝的insert()方法
$model = new Model_User();
$id = $model->insert($data);
var_dump($id);

批量插入多條紀(jì)錄數(shù)據(jù):

// INSERT INTO tbl_user (name, age, note) VALUES ('A君', 12, 'AA'), ('B君', 14, 'BB'), ('C君', 16, 'CC')
$rows = array(
    array('name' => 'A君', 'age' => 12, 'note' => 'AA'),
    array('name' => 'B君', 'age' => 14, 'note' => 'BB'),
    array('name' => 'C君', 'age' => 16, 'note' => 'CC'),
);
$rs = $user->insert_multi($rows);
var_dump($rs);


// 輸出,成功插入的條數(shù)
int(3) 

插入/更新:

// INSERT INTO tbl_user (id, name, age, note) VALUES (8, 'PhalApi', 1, 'framework') 
// ON DUPLICATE KEY UPDATE age = 2
$unique = array('id' => 8);
$insert = array('id' => 8, 'name' => 'PhalApi', 'age' => 1, 'note' => 'framework');
$update = array('age' => 2);
$rs = $user->insert_update($unique, $insert, $update);
var_dump($rs); 


// 輸出影響的行數(shù)

CURD之更新操作

操作 說(shuō)明 示例 備注 是否PhalApi新增
update() 更新數(shù)據(jù) $user->where('id', 1)->update($data); 更新異常時(shí)返回false,數(shù)據(jù)無(wú)變化時(shí)返回0,成功更新返回1

根據(jù)條件更新數(shù)據(jù):

// UPDATE tbl_user SET age = 2 WHERE (name = 'PhalApi');
$data = array('age' => 2);
$rs = $user->where('name', 'PhalApi')->update($data);
var_dump($rs);


// 輸出
int(1)              //正常影響的行數(shù)
int(0)              //無(wú)更新,或者數(shù)據(jù)沒(méi)變化
boolean(false)      //更新異常、失敗

在使用update()進(jìn)行更新操作時(shí),如果更新的數(shù)據(jù)和原來(lái)的一樣,則會(huì)返回0(表示影響0行)。這時(shí),會(huì)和更新失?。ㄍ瑯佑绊?行)混淆。但NotORM是一個(gè)優(yōu)秀的類(lèi)庫(kù),它已經(jīng)提供了優(yōu)秀的解決文案。我們?cè)谑褂胾pdate()時(shí),只須了解這兩者返回結(jié)果的微妙區(qū)別即可。因?yàn)槭‘惓r(shí),返回false;而相同數(shù)據(jù)更新會(huì)返回0。即:

  • 1、更新相同的數(shù)據(jù)時(shí),返回0,嚴(yán)格來(lái)說(shuō)是:int(0)
  • 2、更新失敗時(shí),如更新一個(gè)不存在的字段,返回false,即:bool(false)

    用代碼表示,就是:

    
    $rs = DI()->notorm->user->where('id', $userId)->update($data);

if ($rs >= 1) { // 成功 } else if ($rs === 0) { // 相同數(shù)據(jù),無(wú)更新 } else if ($rs === false) { // 更新失敗 }



更新數(shù)據(jù),進(jìn)行加1操作: 
```php
// UPDATE tbl_user SET age = age + 1 WHERE (name = 'PhalApi')
$rs = $user->where('name', 'PhalApi')->update(array('age' => new NotORM_Literal("age + 1")));
var_dump($rs); 


// 輸出影響的行數(shù)

CURD之查詢(xún)操作

查詢(xún)操作主要有獲取一條紀(jì)錄、獲取多條紀(jì)錄以及聚合查詢(xún)等。

操作 說(shuō)明 示例 備注 是否PhalApi新增
fetch() 循環(huán)獲取每一行 while($row = $user->fetch()) { ... ... }
fetchOne() 只獲取第一行 $row = $user->where('id', 1)->fetchOne(); 等效于fetchRow()
fetchRow() 只獲取第一行 $row = $user->where('id', 1)->fetchRow(); 等效于fetchOne()
fetchPairs() 獲取鍵值對(duì) $row = $user->fetchPairs('id', 'name'); 第二個(gè)參數(shù)為空時(shí),可取多個(gè)值,并且多條紀(jì)錄
fetchAll() 獲取全部的行 $rows = $user->where('id', array(1, 2, 3))->fetchAll(); 等效于fetchRows()
fetchRows() 獲取全部的行 $rows = $user->where('id', array(1, 2, 3))->fetchRows(); 等效于fetchAll()
queryAll() 復(fù)雜查詢(xún)下獲取全部的行,默認(rèn)下以主鍵為下標(biāo) $rows = $user->queryAll($sql, $parmas); 等效于queryRows()
queryRows() 復(fù)雜查詢(xún)下獲取全部的行,默認(rèn)下以主鍵為下標(biāo) $rows = $user->queryRows($sql, $parmas); 等效于queryAll()
count() 查詢(xún)總數(shù) $total = $user->count('id'); 第一參數(shù)可省略
min() 取最小值 $minId = $user->min('id');
max() 取最大值 $maxId = $user->max('id');
sum() 計(jì)算總和 $sum = $user->sum('age');

循環(huán)獲取每一行,并且同時(shí)獲取多個(gè)字段:

// SELECT id, name FROM tbl_user WHERE (age > 18);
$user = $user->select('id, name')->where('age > 18');
while ($row = $user->fetch()) {
     var_dump($row);
}


// 輸出
array(2) {
  ["id"]=>
  string(1) "2"
  ["name"]=>
  string(3) "Tom"
}
array(2) {
  ["id"]=>
  string(1) "3"
  ["name"]=>
  string(4) "King"
}
... ...

循環(huán)獲取每一行,并且只獲取單個(gè)字段。需要注意的是,指定獲取的字段,必須出現(xiàn)在select里,并且返回的不是數(shù)組,而是字符串。

// SELECT id, name FROM tbl_user WHERE (age > 18);
$user = $user->select('id, name')->where('age > 18');
while ($row = $user->fetch('name')) {
     var_dump($row);
}


// 輸出
string(3) "Tom"
string(4) "King"
... ...

注意!以下是錯(cuò)誤的用法。還記得前面所學(xué)的NotORM狀態(tài)的保持嗎?因?yàn)檫@里每次循環(huán)都會(huì)新建一個(gè)NotORM表實(shí)例,所以沒(méi)有保持前面的查詢(xún)狀態(tài),從而死循環(huán)。

while ($row = DI()->notorm->user->select('id, name')->where('age > 18')->fetch('name')) {
     var_dump($row);
}

只獲取第一行,并且獲取多個(gè)字段,等同于fetchRow()操作。

// SELECT id, name FROM tbl_user WHERE (age > 18) LIMIT 1;
$rs = $user->select('id, name')->where('age > 18')->fetchOne();
var_dump($rs);


// 輸出
array(2) {
  ["id"]=>
  string(1) "2"
  ["name"]=>
  string(3) "Tom"
}

只獲取第一行,并且只獲取單個(gè)字段,等同于fetchRow()操作。

var_dump($user->fetchOne('name'));  


// 輸出 
string(3) "Tom"

獲取鍵值對(duì),并且獲取多個(gè)字段:

// SELECT id, name, age FROM tbl_user LIMIT 2;
$rs = $user->select('name, age')->limit(2)->fetchPairs('id'); //指定以ID為KEY
var_dump($rs);


// 輸出
array(2) {
  [1]=>
  array(3) {
    ["id"]=>
    string(1) "1"
    ["name"]=>
    string(7) "dogstar"
    ["age"]=>
    string(2) "18"
  }
  [2]=>
  array(3) {
    ["id"]=>
    string(1) "2"
    ["name"]=>
    string(3) "Tom"
    ["age"]=>
    string(2) "21"
  }
}

獲取鍵值對(duì),并且只獲取單個(gè)字段。注意,這時(shí)的值不是數(shù)組,而是字符串。

// SELECT id, name FROM tbl_user LIMIT 2
var_dump($user->limit(2)->fetchPairs('id', 'name')); //通過(guò)第二個(gè)參數(shù),指定VALUE的列


// 輸出
array(2) {
  [1]=>
  string(7) "dogstar"
  [2]=>
  string(3) "Tom"
}

獲取全部的行,相當(dāng)于fetchRows()操作。

// SELECT * FROM tbl_user
var_dump($user->fetchAll());  


// 輸出全部表數(shù)據(jù),結(jié)果略

使用原生SQL語(yǔ)句進(jìn)行查詢(xún),并獲取全部的行:

// SELECT name FROM tbl_user WHERE age > :age LIMIT 1
$sql = 'SELECT name FROM tbl_user WHERE age > :age LIMIT 1';
$params = array(':age' => 18);
$rs = $user->queryAll($sql, $params);
var_dump($rs);


// 輸出
array(1) {
  [0]=>
  array(1) {
    ["name"]=>
    string(3) "Tom"
  }
}


// 除了使用上面的關(guān)聯(lián)數(shù)組傳遞參數(shù),也可以使用索引數(shù)組傳遞參數(shù)
$sql = 'SELECT name FROM tbl_user WHERE age > ? LIMIT 1';
$params = array(18);
// 也使用queryRows()別名
$rs = $user->queryRows($sql, $params); 

在使用queryAll()queryRows()進(jìn)行原生SQL操作時(shí),需要特別注意:

  • 1、需要手動(dòng)填寫(xiě)完整的表名字,包括分表標(biāo)識(shí),并且需要通過(guò)任意表實(shí)例來(lái)運(yùn)行
  • 2、盡量使用參數(shù)綁定,而不應(yīng)直接使用參數(shù)來(lái)拼接SQL語(yǔ)句,慎防SQL注入攻擊

下面是不好的寫(xiě)法,很有可能會(huì)導(dǎo)致SQL注入攻擊

// 存在SQL注入的寫(xiě)法
$id = 1;
$sql = "SELECT * FROM tbl_demo WHERE id = $id";
$rows = $this->getORM()->queryAll($sql);

對(duì)于外部不可信的輸入數(shù)據(jù),應(yīng)改用參數(shù)傳遞的方式。

// 使用參數(shù)綁定方式
$id = 1;
$sql = "SELECT * FROM tbl_demo WHERE id = ?";
$rows = $this->getORM()->queryAll($sql, array($id));

查詢(xún)總數(shù):

// SELECT COUNT(id) FROM tbl_user
var_dump($user->sum('id'));


// 輸出
string(3) "3"

查詢(xún)最小值:

// SELECT MIN(age) FROM tbl_user
var_dump($user->min('age'));


// 輸出
string(2) "18"

查詢(xún)最大值:

// SELECT MAX(age) FROM tbl_user
var_dump($user->max('age'));


// 輸出
string(3) "100"

計(jì)算總和:

// SELECT SUM(age) FROM tbl_user
var_dump($user->sum('age'));


// 輸出
string(3) "139"

CURD之刪除操作

操作 說(shuō)明 示例 備注 是否PhalApi新增
delete() 刪除 $user->where('id', 1)->delete(); 禁止無(wú)where條件的刪除操作

按條件進(jìn)行刪除,并返回影響的行數(shù):

// DELETE FROM tbl_user WHERE (id = 404);
$user->where('id', 404)->delete();

請(qǐng)?zhí)貏e注意,PhalApi禁止全表刪除操作。即如果是全表刪除,將會(huì)被禁止,并拋出異常。如:

// Exception: sorry, you can not delete the whole table
$user->delete();

事務(wù)操作、關(guān)聯(lián)查詢(xún)和其他操作

事務(wù)操作

以下是事務(wù)操作的一個(gè)示例。

    // Step 1: 開(kāi)啟事務(wù)
    \PhalApi\DI()->notorm->beginTransaction('db_demo');


    // Step 2: 數(shù)據(jù)庫(kù)操作
    \PhalApi\DI()->notorm->user->insert(array('name' => 'test1'));
    \PhalApi\DI()->notorm->user->insert(array('name' => 'test2'));


    // Step 3: 提交事務(wù)/回滾
    \PhalApi\DI()->notorm->commit('db_demo');
    //\PhalApi\DI()->notorm->rollback('db_demo');

關(guān)聯(lián)查詢(xún)

對(duì)于關(guān)聯(lián)查詢(xún),簡(jiǎn)單的關(guān)聯(lián)可使用NotORM封裝的方式,而復(fù)雜的關(guān)聯(lián),如多個(gè)表的關(guān)聯(lián)查詢(xún),則可以使用PhalApi封裝的接口。

如果是簡(jiǎn)單的關(guān)聯(lián)查詢(xún),可以使用NotORM支持的寫(xiě)法,這樣的好處在于我們使用了一致的開(kāi)發(fā),并且能讓PhalApi框架保持分布式的操作方式。需要注意的是,關(guān)聯(lián)的表仍然需要在同一個(gè)數(shù)據(jù)庫(kù)。

以下是一個(gè)簡(jiǎn)單的示例。假設(shè)我們有這樣的數(shù)據(jù):

INSERT INTO `phalapi_user` VALUES ('1', 'wx_edebc', 'dogstar', '***', '4CHqOhe1', '1431790647', '');
INSERT INTO `phalapi_user_session_0` VALUES ('1', '1', 'ABC', '', '0', '0', '0', null);

那么對(duì)應(yīng)關(guān)聯(lián)查詢(xún)的代碼如下面:

// SELECT expires_time, user.username, user.nickname FROM phalapi_user_session_0 
// LEFT JOIN phalapi_user AS user 
// ON phalapi_user_session_0.user_id = user.id 
// WHERE (token = 'ABC') LIMIT 1
$rs = \PhalApi\DI()->notorm->user_session_0
    ->select('expires_time, user.username, user.nickname')
    ->where('token', 'ABC')
    ->fetchRow();


var_dump($rs);

會(huì)得到類(lèi)似這樣的輸出:

array(3) {
  ["expires_time"]=>
  string(1) "0"
  ["username"]=>
  string(35) "wx_edebc"
  ["nickname"]=>
  string(10) "dogstar"
}

這樣,我們就可以實(shí)現(xiàn)關(guān)聯(lián)查詢(xún)的操作。按照NotORM官網(wǎng)的說(shuō)法,則是:

If the dot notation is used for a column anywhere in the query ("$table.$column") then NotORM automatically creates left join to the referenced table. Even references across several tables are possible ("$table1.$table2.$column"). Referencing tables can be accessed by colon: $applications-&select("COUNT(application_tag:tag_id)").

所以->select('expires_time, user.username, user.nickname')這一行調(diào)用將會(huì)NotORM自動(dòng)產(chǎn)生關(guān)聯(lián)操作,而ON的字段,則是這個(gè)字段關(guān)聯(lián)你配置的表結(jié)構(gòu),外鍵默認(rèn)為:表名_id 。

如果是復(fù)雜的關(guān)聯(lián)查詢(xún),則是建議使用原生的SQL語(yǔ)句,但仍然可以保持很好的寫(xiě)法,如這樣一個(gè)示例:

$sql = 'SELECT t.id, t.team_name, v.vote_num '
    . 'FROM phalapi_team AS t LEFT JOIN phalapi_vote AS v '
    . 'ON t.id = v.team_id '
    . 'ORDER BY v.vote_num DESC';


$rows = $this->getORM()->queryAll($sql, array());
var_dump($rows);

如前面所述,這里需要手動(dòng)填寫(xiě)完整的表名,以及慎防SQL注入攻擊。

其他數(shù)據(jù)庫(kù)操作

有時(shí),我們還需要進(jìn)行一些其他的數(shù)據(jù)庫(kù)操作,如創(chuàng)建表、刪除表、添加表字段等。對(duì)于需要進(jìn)行的數(shù)據(jù)庫(kù)操作,而上面所介紹的方法未能滿(mǎn)足時(shí),可以使用更底層更通用的接口,即:NotORM_Result::query($query, $parameters)。

例如,刪除一張表。

$user->query('DROP TABLE tbl_user', array());

數(shù)據(jù)庫(kù)分表

分表的配置

假設(shè)有以下多個(gè)數(shù)據(jù)庫(kù)表,它們的表結(jié)構(gòu)一樣。

數(shù)據(jù)庫(kù)表 數(shù)據(jù)庫(kù)實(shí)例
tbl_demo db_master
tbl_demo_0 db_master
tbl_demo_1 db_master
tbl_demo_2 db_master

為了使用分表存儲(chǔ),可以修改數(shù)據(jù)庫(kù)表的配置,讓它支持分表的情況。

return array(
    'tables' => array(    
        'demo' => array(
            'prefix' => 'tbl_',
            'key' => 'id',
            'map' => array(
                array('db' => 'db_master'),
                array('start' => 0, 'end' => 2, 'db' => 'db_master'),
            ),
        ),
    ),
);

上面配置map選項(xiàng)中array('db' => 'master')用于指定缺省主表使用master數(shù)據(jù)庫(kù)實(shí)例,而下一組映射關(guān)系則是用于配置連續(xù)在同一臺(tái)數(shù)據(jù)庫(kù)實(shí)例的分表區(qū)間,即tbl_demo_0、tbl_demo_1、tbl_demo_2都使用了master數(shù)據(jù)庫(kù)實(shí)例。

溫馨提示:當(dāng)分表找不到時(shí),PhalApi會(huì)自動(dòng)退化使用缺省主表。

Model子類(lèi)實(shí)現(xiàn)分表邏輯

假設(shè)分別的規(guī)則是根據(jù)ID對(duì)3進(jìn)行求余。當(dāng)需要使用分表時(shí),在使用Model基類(lèi)的情況下,可以通過(guò)重寫(xiě)PhalApi\Model\NotORMModel::getTableName($id)實(shí)現(xiàn)相應(yīng)的分表規(guī)則。

<?php
namespace App\Model;


use PhalApi\Model\NotORMModel as NotORM;


class Demo extends NotORM {


    protected function getTableName($id) {
        $tableName = 'demo';
        if ($id !== null) {
            $tableName .= '_' . ($id % 3);
        }
        return $tableName;
    }
}

然后,便可使用之前一樣的CURD基本操作,但框架會(huì)自動(dòng)匹配分表的映射。例如:

$model = new App\Model\Demo();


$row = $model->get('3', 'id');   // 使用分表tbl_demo_0
$row = $model->get('10', 'id');  // 使用分表tbl_demo_1
$row = $model->get('2', 'id');   // 使用分表tbl_demo_2

回到使用Model基類(lèi)的上下文,更進(jìn)一步,我們可以通過(guò)$this->getORM($id)來(lái)獲取分表的實(shí)例從而進(jìn)行分表的操作。如:

<?php
namespace App\Model;


use PhalApi\Model\NotORMModel as NotORM;


class Demo extends NotORM {


    public function getNameById($id) {
        $row = $this->getORM($id)->select('name')->fetchRow();
        return !empty($row) ? $row['name'] : '';
    }
}

通過(guò)傳入不同的$id,即可獲取相應(yīng)的分表實(shí)例。

自動(dòng)生成SQL建表語(yǔ)句

把數(shù)據(jù)庫(kù)表的基本建表語(yǔ)句保存到./data目錄下,文件名與數(shù)據(jù)庫(kù)表名相同,后綴統(tǒng)一為“.sql”。如這里的./data/demo.sql文件。

`name` varchar(11) DEFAULT NULL,

需要注意的是,這里說(shuō)的基本建表語(yǔ)句是指:僅是這個(gè)表所特有的字段,排除已固定公共有的自增主鍵id、擴(kuò)展字段ext_data和CREATE TABLE關(guān)鍵字等。

然后可以使用phalapi-buildsqls腳本命令,快速自動(dòng)生成demo缺省主表和全部分表的建表SQL語(yǔ)句。如下:

$ ./bin/phalapi-buildsqls ./config/dbs.php demo

正常情況下,會(huì)生成類(lèi)似以下的SQL語(yǔ)句:

CREATE TABLE `demo` (
    `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
    `name` varchar(11) DEFAULT NULL,
    `ext_data` text COMMENT 'json data here',
     PRIMARY KEY (`id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

            
CREATE TABLE `tpl_demo_0` ... ...;
CREATE TABLE `tpl_demo_1`  ... ...;
CREATE TABLE `tpl_demo_2`  ... ...;

在將上面的SQL語(yǔ)句導(dǎo)入數(shù)據(jù)庫(kù)后,或者手動(dòng)創(chuàng)建數(shù)據(jù)庫(kù)表后,便可以像之前那樣操作數(shù)據(jù)庫(kù)了。

以上內(nèi)容是否對(duì)您有幫助:
在線(xiàn)筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)