從本章開(kāi)始就要揭開(kāi)模型的神秘面紗了,本章主要學(xué)習(xí)模型的定義和基礎(chǔ)使用,以及和數(shù)據(jù)庫(kù)操作的區(qū)別,學(xué)習(xí)內(nèi)容主要包含:
- 模型和數(shù)據(jù)庫(kù)區(qū)別
- 模型定義
- 模型調(diào)用
- 對(duì)象化操作
- 模型CURD操作
- 使用查詢(xún)構(gòu)造器
- 數(shù)據(jù)集
- 分頁(yè)查詢(xún)
- 添加業(yè)務(wù)邏輯
- 總結(jié)
模型和數(shù)據(jù)庫(kù)區(qū)別
在說(shuō)模型和數(shù)據(jù)庫(kù)的區(qū)別之前,首先理解一點(diǎn),5.0的數(shù)據(jù)庫(kù)抽象訪問(wèn)層(我們后面用Db類(lèi)表示)和模型是一個(gè)整體,共同完成了ThinkPHP5.0的ORM(對(duì)象關(guān)系映射)?;蛘咭部梢岳斫鉃槟P褪菙?shù)據(jù)訪問(wèn)層的查詢(xún)構(gòu)造器延伸,完成更高級(jí)的數(shù)據(jù)庫(kù)查詢(xún)操作罷了。
通過(guò)前面幾章的學(xué)習(xí),看起來(lái)Db類(lèi)已經(jīng)非常的強(qiáng)大,但缺點(diǎn)仍然非常明顯:
- 不支持
ActiveRecord實(shí)現(xiàn); - 缺乏靈活的事件機(jī)制;
- 數(shù)據(jù)自動(dòng)處理能力弱;
- 數(shù)據(jù)關(guān)聯(lián)操作繁瑣并且不直觀;
- 不能單獨(dú)封裝業(yè)務(wù)邏輯;
上面這些內(nèi)容我只是打擊下迷戀
Db類(lèi)的朋友,別無(wú)它意(因?yàn)楸緛?lái)就是故意設(shè)計(jì)的_)。
其實(shí)還有很多...當(dāng)然,原因并不是否定Db類(lèi)的實(shí)現(xiàn),而是前面提到的,Db和模型本來(lái)就是一個(gè)整體,只是各自的職責(zé)和分工不同,如果沒(méi)有Db類(lèi)的基石,模型也只是建在沙灘上的城堡罷了。
Db和模型的存在只是ThinkPHP5.0架構(gòu)設(shè)計(jì)中的職責(zé)和定位不同,Db負(fù)責(zé)的只是數(shù)據(jù)(表)訪問(wèn),模型負(fù)責(zé)的是業(yè)務(wù)數(shù)據(jù)和業(yè)務(wù)邏輯。
當(dāng)然,模型層可以分的更細(xì),把數(shù)據(jù)模型和邏輯模型,甚至服務(wù)模型分開(kāi),這個(gè)暫時(shí)就不在目前的討論范疇了,只不過(guò)把模型層的職責(zé)和分工更細(xì)化。
如果你用框架只是用來(lái)管理一些數(shù)據(jù)的CURD而沒(méi)有業(yè)務(wù)需要(其實(shí)本質(zhì)上來(lái)說(shuō)任何的系統(tǒng)都是CURD,業(yè)務(wù)邏輯都是抽象和封裝出來(lái)的,這是設(shè)計(jì)層面的問(wèn)題了),那么也許看起來(lái)Db類(lèi)已經(jīng)夠用了(你不覺(jué)得其實(shí)數(shù)據(jù)庫(kù)本身已經(jīng)可以完成了么),但是作為一個(gè)業(yè)務(wù)系統(tǒng)或者平臺(tái)(無(wú)論是WEB還是API),通常每個(gè)數(shù)據(jù)表就對(duì)應(yīng)了一個(gè)業(yè)務(wù)模型對(duì)象,甚至存在和其它業(yè)務(wù)模型的混合和關(guān)聯(lián)邏輯。舉個(gè)用戶(hù)表的例子,用戶(hù)登錄這樣一個(gè)業(yè)務(wù)邏輯其實(shí)包含了很多的關(guān)聯(lián)操作,你得檢查用戶(hù)賬號(hào)是否正常,用戶(hù)名和密碼是否正確,然后記錄用戶(hù)的最后登錄時(shí)間和IP(如果IP所在區(qū)域不符有些系統(tǒng)還需要給用戶(hù)發(fā)郵件提醒),還要給用戶(hù)增加積分,甚至可能還需要檢查用戶(hù)的權(quán)限,那么Db類(lèi)就顯得吃力了,這其實(shí)也是數(shù)組存儲(chǔ)結(jié)構(gòu)和對(duì)象存儲(chǔ)設(shè)計(jì)的差異,業(yè)務(wù)越復(fù)雜,這種差異越明顯,PHP的數(shù)組再?gòu)?qiáng)大也替代不了對(duì)象。
Db和模型最明顯的一個(gè)區(qū)別就是Db查詢(xún)返回的數(shù)據(jù)類(lèi)型為數(shù)組(對(duì)于一個(gè)沒(méi)有業(yè)務(wù)邏輯的數(shù)據(jù)而言,數(shù)組已經(jīng)足夠),而模型的查詢(xún)返回類(lèi)型的是模型對(duì)象實(shí)例。
也許前面幾個(gè)問(wèn)題你根本不會(huì)在意(確實(shí)優(yōu)雅只是看起來(lái)舒服一些罷了,關(guān)聯(lián)用JOIN還容易掌控之類(lèi)的話(huà)我也經(jīng)常聽(tīng)到,呵呵~),但最后一個(gè)問(wèn)題無(wú)法封裝業(yè)務(wù)邏輯是致命的,處理不當(dāng)極易出現(xiàn)違反MVC架構(gòu)設(shè)計(jì)的混亂情況。
再說(shuō)簡(jiǎn)單一點(diǎn),由于Db類(lèi)的數(shù)據(jù)操作并沒(méi)有一個(gè)唯一對(duì)應(yīng)的對(duì)象實(shí)例,也就無(wú)法封裝業(yè)務(wù)方法,就變成你的業(yè)務(wù)方法要么寫(xiě)到控制器方法里面,要么定義到另外一個(gè)所謂的“業(yè)務(wù)邏輯”層里面,前者顯然是違反MVC架構(gòu)設(shè)計(jì)思想的,而后者其實(shí)就是一個(gè)模型類(lèi)的概念存在了,那么是否需要擁抱模型就顯而易見(jiàn),不用我多說(shuō)了吧_
話(huà)說(shuō)回來(lái)了,有些人雖然用了模型,但仍然在模型里面大量封裝直接操作Db類(lèi)的代碼和方法,這也是一種偽模型設(shè)計(jì),并不可取。
比較Db和模型,不要單純從功能上做比較,這是次要的,也沒(méi)意義,畢竟職責(zé)定位不同。也不要在意性能上的差異,這個(gè)對(duì)于業(yè)務(wù)邏輯來(lái)說(shuō),一次查詢(xún)就抵消了。
總而言之,想要掌握模型,必須明白和理解下面幾個(gè)原則:
- 模型和數(shù)據(jù)庫(kù)層的定位和職責(zé)不同;
- 不要因?yàn)樾阅芏艞壥褂媚P?,那是得不償失的?/li>
- 用面向?qū)ο蟮姆绞絹?lái)使用和設(shè)計(jì)模型;
- 模型的數(shù)據(jù)底層操作仍然是數(shù)據(jù)庫(kù)抽象訪問(wèn)層,而且是自動(dòng)的;
模型設(shè)計(jì)基于數(shù)據(jù)訪問(wèn)層之上,并作了更高層次的封裝,實(shí)現(xiàn)了Db類(lèi)本身不支持的功能,或者簡(jiǎn)化了原本使用Db類(lèi)的復(fù)雜操作。從查詢(xún)操作的角度來(lái)看,可以理解為Db類(lèi)是數(shù)據(jù)表的查詢(xún)構(gòu)造器,而模型是業(yè)務(wù)模型的查詢(xún)構(gòu)造器,其實(shí)都屬于查詢(xún)構(gòu)造器的范疇。
很多人不習(xí)慣用模型的原因無(wú)非就幾個(gè)方面:
- 不理解模型的概念;
- 嫌每個(gè)數(shù)據(jù)表都要定義模型麻煩;
- 模型的用法不容易掌握;
- 覺(jué)得模型的性能差;
我們會(huì)慢慢打消上述的這些困惑或顧慮,學(xué)完本書(shū),你就會(huì)發(fā)現(xiàn)模型其實(shí)很簡(jiǎn)單,而且相對(duì)于Db查詢(xún)來(lái)說(shuō)犧牲的細(xì)微性能完全值得。
在控制器中永遠(yuǎn)調(diào)用的是模型類(lèi),然后在模型類(lèi)中封裝業(yè)務(wù)邏輯方法和數(shù)據(jù)處理,完成業(yè)務(wù)操作。對(duì)控制器來(lái)說(shuō),模型就是一個(gè)業(yè)務(wù)邏輯接口,并且善于運(yùn)用依賴(lài)注入機(jī)制來(lái)綁定模型對(duì)業(yè)務(wù)操作會(huì)帶來(lái)極大的便利。
模型定義
定義一個(gè)模型很簡(jiǎn)單,下面是一個(gè)最簡(jiǎn)單的模型類(lèi):
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
}
如果使用的是5.1版本,一定要注意,如果你的對(duì)應(yīng)數(shù)據(jù)表的主鍵名不是
id,需要在模型中設(shè)置pk屬性為你的實(shí)際主鍵名。
模型定義有幾個(gè)要素:
- 通常會(huì)繼承
think\Model(或者子類(lèi)),虛擬模型除外; - 一個(gè)模型并不總是對(duì)應(yīng)一個(gè)數(shù)據(jù)表(可能會(huì)有多個(gè)),雖然默認(rèn)如此;
- 模型名和數(shù)據(jù)表名也不是直接對(duì)應(yīng)關(guān)系;
- 盡管一個(gè)空模型和使用Db類(lèi)無(wú)異,但意義不同;
模型定義階段要達(dá)成的目的:
- 定義數(shù)據(jù)表(默認(rèn)就是模型類(lèi)名)
- 定義數(shù)據(jù)表主鍵(默認(rèn)會(huì)自動(dòng)獲?。?/li>
- 定義數(shù)據(jù)庫(kù)連接(默認(rèn)使用數(shù)據(jù)庫(kù)配置)
- 定義數(shù)據(jù)處理邏輯(包括屬性和方法)
- 定義業(yè)務(wù)邏輯(方法)
下面的定義是不需要或者不支持的:
- 數(shù)據(jù)表字段(不需要,會(huì)自動(dòng)獲取,并支持緩存機(jī)制)
- 數(shù)據(jù)表前綴(不支持,模型不關(guān)心前綴)
大多數(shù)情況下,數(shù)據(jù)表和數(shù)據(jù)庫(kù)連接是不需要定義的,數(shù)據(jù)處理邏輯和業(yè)務(wù)邏輯才是模型定義的重點(diǎn),如果你發(fā)現(xiàn)你的大多數(shù)模型類(lèi)都是什么都沒(méi)定義,那么就要思考下哪里出問(wèn)題了,為什么你的模型成了形式和擺設(shè)。是沒(méi)業(yè)務(wù)需要還是職責(zé)分工有問(wèn)題了?也許你在控制器中大量使用Db類(lèi)進(jìn)行業(yè)務(wù)邏輯處理。無(wú)論怎樣,現(xiàn)在糾正思維,跟著教程擁抱和學(xué)習(xí)模型吧。
一個(gè)模型并不總是對(duì)應(yīng)一個(gè)數(shù)據(jù)表(例如關(guān)聯(lián)模型和聚合模型),但大多數(shù)情況下對(duì)應(yīng)的是一個(gè)數(shù)據(jù)表,默認(rèn)的對(duì)應(yīng)關(guān)系是:模型類(lèi)的名稱(chēng)(注意不一定是類(lèi)名,后面會(huì)解釋?zhuān)┺D(zhuǎn)換為小寫(xiě)和下劃線(xiàn)就是對(duì)應(yīng)的數(shù)據(jù)表:
| 模型名 | 對(duì)應(yīng)數(shù)據(jù)表 |
|---|---|
| User | user |
| UserType | user_type |
如果你的數(shù)據(jù)庫(kù)配置定義了前綴(假設(shè)數(shù)據(jù)庫(kù)的前綴定義是 think_),那么對(duì)應(yīng)關(guān)系就是:
| 模型名 | 對(duì)應(yīng)數(shù)據(jù)表 |
|---|---|
| User | think_user |
| UserType | think_user_type |
如果你的對(duì)應(yīng)規(guī)則和上面的系統(tǒng)約定不符合,那么需要設(shè)置模型類(lèi)的數(shù)據(jù)表名稱(chēng)屬性,以確保能夠找到對(duì)應(yīng)的數(shù)據(jù)表。代碼如下:
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
protected $table = 'user_info';
}
table屬性定義的是完整數(shù)據(jù)表名,如果你希望定義不帶前綴的數(shù)據(jù)表名,可以使用name屬性來(lái)定義模型的名稱(chēng)。
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
protected $name = 'user_info';
}
如果你同時(shí)定義了這兩個(gè)屬性,那么
table屬性是優(yōu)先的。
模型的設(shè)計(jì)允許給單獨(dú)指定數(shù)據(jù)庫(kù)連接,也就說(shuō)你可以將不同的數(shù)據(jù)庫(kù)的表進(jìn)行統(tǒng)一的管理,對(duì)于跨數(shù)據(jù)庫(kù)的應(yīng)用尤其有用,對(duì)于跨庫(kù)的相同表名,我們可以建立不同名稱(chēng)的模型或者放入不同的命名空間來(lái)解決。
指定模型的單獨(dú)數(shù)據(jù)庫(kù)連接方法如下:
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
protected $name = 'user_info';
protected $connection = 'db_config';
}
和Db類(lèi)的connect方法一樣,模型類(lèi)的connection屬性允許使用數(shù)組、字符串以及配置參數(shù)的方式定義,這里使用配置參數(shù)(在應(yīng)用或者模塊的配置文件中單獨(dú)配置db_config參數(shù))的方式,避免在模型里面寫(xiě)死數(shù)據(jù)庫(kù)連接信息,全部交給配置文件去統(tǒng)一處理。
如果
connection屬性使用數(shù)組方式配置,會(huì)和數(shù)據(jù)庫(kù)配置文件中的參數(shù)合并,因此你只需要定義有區(qū)別的參數(shù),而無(wú)需定義全部的數(shù)據(jù)庫(kù)參數(shù)。
如果擔(dān)心模型的名稱(chēng)和PHP關(guān)鍵字沖突,可以啟用類(lèi)后綴功能,只需要在應(yīng)用配置文件中設(shè)置:
// 開(kāi)啟應(yīng)用類(lèi)庫(kù)后綴
'class_suffix' => true,
開(kāi)啟后,所有的應(yīng)用類(lèi)庫(kù)定義的時(shí)候都需要加上對(duì)應(yīng)后綴,包括控制器類(lèi)。
這樣app\index\model\User類(lèi)定義就要改成
<?php
namespace app\index\model;
use think\Model;
class UserModel extends Model
{
}
并且類(lèi)名也要改為UserModel.php。
關(guān)于模型的連接對(duì)象和查詢(xún)對(duì)象,要清楚下面這些事實(shí):
- 模型可以單獨(dú)設(shè)置數(shù)據(jù)庫(kù)連接;
- 模型的數(shù)據(jù)庫(kù)連接是惰性的(因?yàn)檫B接本身就是惰性);
- 如果使用統(tǒng)一的數(shù)據(jù)庫(kù)配置,模型使用的連接對(duì)象是相同的;
- 模型使用的查詢(xún)對(duì)象是獨(dú)立的;
- 模型可以使用自定義的查詢(xún)對(duì)象;
命令行生成
當(dāng)你需要?jiǎng)?chuàng)建大量的模型類(lèi)的時(shí)候,不妨考慮下命令行生成,可以快速創(chuàng)建模型類(lèi)。
在windows下面,使用Win+R輸入cmd進(jìn)入命令控制臺(tái),切換到項(xiàng)目根目錄(也就是think文件所在目錄),并執(zhí)行下面的指令可以生成index模塊的Blog模型類(lèi)文件。
>php think make:model index/Blog
生成的模型類(lèi)文件如下:
<?php
namespace app\index\model;
use think\Model;
class Blog extends Model
{
//
}
注意,如果使用
>php think make:model Blog
生成的是common模塊下面的Blog模型類(lèi)。
模型調(diào)用
模型支持實(shí)例化調(diào)用和靜態(tài)調(diào)用(主要是查詢(xún),查詢(xún)后會(huì)返回一個(gè)模型對(duì)象實(shí)例)。
// 實(shí)例化User模型
$user = new \app\index\model\User();
// 直接靜態(tài)查詢(xún)
$user = \app\index\model\User::get(1);
一般來(lái)說(shuō),我們會(huì)事先使用use引入User模型類(lèi),就不需要每次都使用完整命名空間方式來(lái)調(diào)用User模型類(lèi)了。
<?php
namespace app\index\controller;
use app\index\model\User;
class Index
{
public function index()
{
$user = User::get(1);
}
}
如果你開(kāi)啟了應(yīng)用類(lèi)庫(kù)后綴的話(huà),可以這樣使用
<?php
namespace app\index\controller;
use app\index\model\UserModel as User;
class IndexController
{
public function index()
{
$user = User::get(1);
}
}
我們后面的例子都是直接使用User類(lèi)名進(jìn)行實(shí)例化或者靜態(tài)調(diào)用,你必須明白為何可以如此調(diào)用。
調(diào)用模型類(lèi)的方法其實(shí)和調(diào)用一個(gè)普通的類(lèi)沒(méi)有區(qū)別,不要覺(jué)得模型類(lèi)有什么特殊。
如果你覺(jué)得每次引入比較麻煩,系統(tǒng)還提供了一個(gè)助手函數(shù)幫助你快速實(shí)例化模型類(lèi)而不必每次引入模型類(lèi)。
你可以在任何地方使用
$user = model('User');
實(shí)例化User模型類(lèi),并且model函數(shù)采用單例實(shí)現(xiàn),多次調(diào)用不會(huì)重復(fù)實(shí)例化。
使用model助手函數(shù)的一個(gè)優(yōu)勢(shì)是即使你開(kāi)啟了應(yīng)用類(lèi)庫(kù)后綴,你仍然可以直接使用
$user = model('User');
而不必使用
$user = model('UserModel');
事實(shí)上,上面的用法是錯(cuò)誤的。
我們還是建議使用
use方式引入模型類(lèi)后操作,因?yàn)橹趾瘮?shù)并不支持模型的靜態(tài)調(diào)用,這個(gè)后面我們還會(huì)詳細(xì)說(shuō)明。
我們甚至可以通過(guò)依賴(lài)注入直接把模型對(duì)象實(shí)例注入到控制器的操作方法中,而不需要每次都進(jìn)行實(shí)例化。關(guān)于如何使用依賴(lài)注入,請(qǐng)參考《控制器從入門(mén)到精通》第五講的內(nèi)容。
對(duì)象化操作
了解如何定義和調(diào)用模型后,我們來(lái)具體了解下模型的使用。
模型和Db操作的一大顯性區(qū)別就是一個(gè)是對(duì)象操作和一個(gè)是數(shù)組操作,下面以一個(gè)user數(shù)據(jù)表的查詢(xún)、取值、設(shè)置和更新的例子,來(lái)說(shuō)明下兩種方式的區(qū)別。
首先回顧下Db類(lèi)的用法:
// 查詢(xún)操作
$user = Db::table('user')->find(1);
// 取值操作
echo $user['name'];
echo $user['email'];
// 設(shè)置操作
$user['name'] = 'topthink';
$user['email'] = 'thinkphp@qq.com';
// 更新操作
Db::table('user')->update($user);
然后,如果是模型操作的話(huà),就可以對(duì)應(yīng)下面的代碼實(shí)現(xiàn):
// 查詢(xún)操作
$user = User::get(1);
// 取值操作
echo $user->name;
echo $user->email;
// 設(shè)置操作
$user->name = 'topthink';
$user->email = 'thinkphp@qq.com';
// 更新操作
$user->save();
事實(shí)上,由于模型類(lèi)實(shí)現(xiàn)了ArrayAccess接口,因此一樣可以使用數(shù)組方式操作:
// 查詢(xún)操作
$user = User::get(1);
// 取值操作
echo $user['name'];
echo $user['email'];
// 設(shè)置操作
$user['name'] = 'topthink';
$user['email'] = 'thinkphp@qq.com';
// 更新操作
$user->save();
是不是覺(jué)得很神奇,不過(guò)這個(gè)問(wèn)題有點(diǎn)高級(jí),暫且不表,留給大家思考,答案后面章節(jié)會(huì)揭曉。我們后面的模型例子還是以對(duì)象操作為例講解。
模型對(duì)象的取值和設(shè)置都不是表面上看起來(lái)那么簡(jiǎn)單,可以設(shè)置很多自動(dòng)化操作,取值的自動(dòng)化操作就是讀取器,設(shè)置的自動(dòng)化操作就是修改器,這兩個(gè)概念我們會(huì)在下一章詳細(xì)講解。
模型的讀取和設(shè)置并不總是這樣操作,這和模型的內(nèi)部實(shí)現(xiàn)有關(guān),因?yàn)槲覀儾](méi)有在模型里面定義user數(shù)據(jù)表對(duì)應(yīng)的public類(lèi)型的name或者email屬性,模型的取值和設(shè)置都是通過(guò)__get和__set魔術(shù)方法完成,事實(shí)上模型的數(shù)據(jù)操作內(nèi)部都是操作模型類(lèi)的data屬性,在本書(shū)中,我們通常把$user->name 和$user->email稱(chēng)為模型數(shù)據(jù)而不是模型屬性。
那么問(wèn)題來(lái)了,如果是在模型內(nèi)部進(jìn)行取值和設(shè)置操作怎么辦?
// 錯(cuò)誤的讀取數(shù)據(jù)方式
echo $this->name;
echo $this->email;
// 錯(cuò)誤的數(shù)據(jù)設(shè)置方式
$this->name = 'thinkphp';
$this->email = 'thinkphp@qq.com';
這樣,一旦數(shù)據(jù)表的字段名和模型的內(nèi)部屬性沖突就產(chǎn)生混淆了,這是一個(gè)新手最容易產(chǎn)生困惑的地方。所以,如果是在模型內(nèi)部,正確的獲取方式應(yīng)該是:
// 模型內(nèi)部讀取數(shù)據(jù)
echo $this->getData('name');
echo $this->getAttr('email');
// 模型內(nèi)部設(shè)置數(shù)據(jù)
$this->data('name','thinkphp');
$this->setAttr('email','thinkphp@qq.com');
以name屬性為例,獲取模型數(shù)據(jù)的方式有下列三種:
| 場(chǎng)景 | 方法 |
|---|---|
| 外部獲取模型數(shù)據(jù) | $model->name |
| 內(nèi)部獲取模型數(shù)據(jù) | $this->getAttr('name') |
| 內(nèi)部獲取(原始)模型數(shù)據(jù) | $this->getData('name') |
getData和getAttr方法的區(qū)別前者是原始數(shù)據(jù),后者是經(jīng)過(guò)讀取器處理的數(shù)據(jù),如果沒(méi)有定義數(shù)據(jù)讀取器的話(huà),兩個(gè)方法的結(jié)果是相同的。
對(duì)應(yīng)的設(shè)置模型數(shù)據(jù)的方式也有三種:
| 場(chǎng)景 | 方法 |
|---|---|
| 外部設(shè)置模型數(shù)據(jù) | $model->name='thinkphp' |
| 內(nèi)部設(shè)置模型數(shù)據(jù)(經(jīng)過(guò)修改器) | $this->setAttr('name','thinkphp') |
| 內(nèi)部設(shè)置模型數(shù)據(jù) | $this->data('name','thinkphp') |
data和setAttr方法的區(qū)別前者是賦值最終數(shù)據(jù),后者賦值的數(shù)據(jù)還會(huì)經(jīng)過(guò)修改器處理,如果沒(méi)有定義修改器的話(huà),兩個(gè)方法的結(jié)果是相同的。
對(duì)象化操作的神奇是可以級(jí)聯(lián)讀取或者設(shè)置,例如:
// 查詢(xún)操作
$user = User::get(1);
// 取值操作
echo $user->name;
echo $user->email;
// 關(guān)聯(lián)取值
echo $user->role->name;
echo $user->contact->phone;
// 設(shè)置操作
$user->name = 'topthink';
$user->email = 'thinkphp@qq.com';
// 更新操作
$user->save();
// 關(guān)聯(lián)設(shè)置
$user->role->name = 'admin';
$user->role->save();
$user->contact->phone = '123456789';
$user->contact->save();
這里使用了模型關(guān)聯(lián)的概念,如果感到摸不著頭腦不用擔(dān)心,我們會(huì)在第八章給你詳細(xì)講解。
模型CURD操作
模型的主要功能包括數(shù)據(jù)處理和業(yè)務(wù)邏輯,而這些都離不開(kāi)數(shù)據(jù)的CURD操作,因此我們首先來(lái)談下數(shù)據(jù)的CURD操作,在掌握了數(shù)據(jù)庫(kù)Db類(lèi)的用法后,模型的CURD操作就會(huì)很容易理解,因?yàn)楸举|(zhì)上模型的CURD操作最終調(diào)用的還是Db類(lèi)的操作,區(qū)別在于使用了ActiveRecord模式和單獨(dú)做了一層封裝而已,我們來(lái)看下兩種方式CURD操作用法的簡(jiǎn)單對(duì)比(其中模型會(huì)給出動(dòng)態(tài)和靜態(tài)兩種實(shí)現(xiàn)方法,分別對(duì)應(yīng)不同的場(chǎng)景)。
創(chuàng)建Create
Db用法:
Db::table('user')
->insert([
'name' => 'thinkphp',
'email' => 'thinkphp@qq.com',
]);
模型用法:
$user = new User;
$user->name = 'thinkphp';
$user->email = 'thinkphp@qq.com';
$user->save();
或者批量設(shè)置:
$user = new User;
$user->save([
'name' => 'thinkphp',
'email' => 'thinkphp@qq.com',
]);
上面兩種方式等效,當(dāng)你的模型數(shù)據(jù)比較多不想一一賦值的時(shí)候,可以使用后者。
也許你咋一看還覺(jué)得麻煩了,又是實(shí)例化又是賦值的,但好處多多,慢慢你就會(huì)體會(huì)到了,看起來(lái)是一個(gè)簡(jiǎn)單的賦值和保存操作其實(shí)內(nèi)里大有乾坤,可以觸發(fā)很多處理甚至事件。
save方法的返回值不是自增主鍵的值(和Db的execute方法一樣返回影響的記錄數(shù)),要獲取自增主鍵的值可以使用下面的方式:
$user = new User;
$user->name = 'thinkphp';
$user->email = 'thinkphp@qq.com';
$user->save();
// 獲取用戶(hù)的主鍵數(shù)據(jù)
echo $user->id;
可以使用靜態(tài)方法創(chuàng)建數(shù)據(jù)
$user = User::create([
'name' => 'thinkphp',
'email' => 'thinkphp@qq.com',
]);
echo $user->id;
和save方法不同,create方法的返回值是User模型的對(duì)象實(shí)例,而save方法調(diào)用的時(shí)候本身就在對(duì)象實(shí)例里面。
很多開(kāi)發(fā)者不習(xí)慣靜態(tài)調(diào)用,這里必須說(shuō)明的是模型類(lèi)的靜態(tài)CURD操作其實(shí)都是內(nèi)部自動(dòng)實(shí)例化而已,所以說(shuō)白了提供的這些靜態(tài)操作方法只是對(duì)動(dòng)態(tài)CURD操作方法的靜態(tài)封裝罷了。
至于靜態(tài)方法的場(chǎng)景,主要是不想實(shí)例化或者不方便實(shí)例化的需求,而且支持變量的靜態(tài)調(diào)用,例如:
$model = '\app\index\model\User';
$user = $model::create([
'name' => 'thinkphp',
'email' => 'thinkphp@qq.com',
]);
創(chuàng)建操作用法小結(jié):
| 方法 | 返回值 |
|---|---|
| save(動(dòng)態(tài)) | 影響的記錄數(shù) |
| create(靜態(tài)) | 模型對(duì)象實(shí)例 |
讀取Read
Db類(lèi)實(shí)現(xiàn)讀取單個(gè)記錄
$user = Db::table('user')
->where('id', 1)
->find();
// 或者
$user = Db::table('user')
->find(1);
echo $user['id'];
echo $user['name'];
模型實(shí)現(xiàn)讀取單個(gè)記錄要比Db類(lèi)簡(jiǎn)單很多,而且更加符合對(duì)象的設(shè)計(jì)。
$user = User::get(1);
echo $user->id;
echo $user->name;
V5.0.8版本之前模型的get方法如果沒(méi)有傳值或者傳入空值,會(huì)查詢(xún)第一個(gè)符合條件的數(shù)據(jù),這個(gè)問(wèn)題在V5.0.8版本已經(jīng)修正,get方法必須傳入非空的值,否則直接返回Null。
Db類(lèi)的find方法返回的是一個(gè)數(shù)組,模型類(lèi)的get方法返回的是一個(gè)User模型對(duì)象實(shí)例。模型的讀取操作一般使用靜態(tài)方法讀取即可,返回模型對(duì)象實(shí)例。
很多用戶(hù)往往會(huì)寫(xiě)出下面的代碼,理論上來(lái)說(shuō)當(dāng)然也沒(méi)有錯(cuò),其實(shí)是大可不必的。
$user = new User;
$user->find(1);
除非你已經(jīng)在User模型的對(duì)象實(shí)例內(nèi)部去調(diào)用find方法讀取數(shù)據(jù),但這種方式不符合模型對(duì)象的設(shè)計(jì)原則,一個(gè)模型對(duì)象實(shí)例應(yīng)該唯一對(duì)應(yīng)數(shù)據(jù)表的一條記錄。
Db類(lèi)實(shí)現(xiàn)讀取多個(gè)記錄
// 查詢(xún)用戶(hù)數(shù)據(jù)集
$users = Db::table('user')
->where('id', '>', 1)
->limit(5)
->select();
// 遍歷讀取用戶(hù)數(shù)據(jù)
foreach ($users as $user) {
echo $user['id'];
echo $user['name'];
}
模型實(shí)現(xiàn)讀取多個(gè)記錄
// 查詢(xún)用戶(hù)數(shù)據(jù)集
$users = User::where('id', '>', 1)
->limit(5)
->select();
// 遍歷讀取用戶(hù)數(shù)據(jù)
foreach ($users as $user) {
echo $user->id;
echo $user->name;
}
模型的查詢(xún)操作比起Db查詢(xún)有一個(gè)顯著的特征就是不需要每次調(diào)用table或者name方法,因?yàn)槊總€(gè)模型在創(chuàng)建的時(shí)候已經(jīng)自動(dòng)對(duì)應(yīng)了數(shù)據(jù)表。
在讀取多個(gè)記錄的方式上,兩種方式的區(qū)別并不大,只是默認(rèn)返回?cái)?shù)據(jù)集類(lèi)型的區(qū)別,Db方式返回的數(shù)據(jù)集是一個(gè)包含每個(gè)用戶(hù)數(shù)組的二維數(shù)組,而模型方式返回的數(shù)據(jù)集包含每個(gè)User模型對(duì)象實(shí)例的數(shù)組。
事實(shí)上,這個(gè)差異在實(shí)際進(jìn)行數(shù)據(jù)集處理的時(shí)候根本感覺(jué)不到,也就是說(shuō)后者仍然可以使用前者的方式統(tǒng)一操作(這歸功于模型的神奇設(shè)計(jì),這個(gè)后面章節(jié)會(huì)專(zhuān)門(mén)提到)。
對(duì)于多個(gè)主鍵的數(shù)據(jù)讀取,模型還封裝了一個(gè)all方法,用法如下:
// 查詢(xún)用戶(hù)數(shù)據(jù)集
// 相當(dāng)于 Db::table('user')->select([1,2,3]);
$users = User::all([1, 2, 3]);
// 遍歷讀取用戶(hù)數(shù)據(jù)
foreach ($users as $user) {
echo $user->id;
echo $user->name;
}
關(guān)于模型的get和all方法的更多用法,而且也完全可以替代數(shù)據(jù)庫(kù)提供的find和select方法,我們會(huì)在模型高級(jí)用法一章中給你繼續(xù)深入。
其實(shí)對(duì)于讀取數(shù)據(jù)的操作,模型提供了很強(qiáng)大的處理機(jī)制,為了避免你初期的時(shí)候混淆,我們暫且略過(guò),會(huì)在以后專(zhuān)門(mén)講解。
讀取操作用法小結(jié):
原則上模型的查詢(xún)都應(yīng)該是靜態(tài)調(diào)用
| 方法 | 作用 | 返回值 |
|---|---|---|
| get | 查詢(xún)單個(gè)記錄 | 模型對(duì)象實(shí)例 |
| find | 查詢(xún)單個(gè)記錄 | 模型對(duì)象實(shí)例 |
| all | 根據(jù)主鍵查詢(xún)多個(gè)記錄 | 包含模型對(duì)象實(shí)例的數(shù)組或者數(shù)據(jù)集 |
| select | 根據(jù)條件查詢(xún)多個(gè)記錄 | 包含模型對(duì)象實(shí)例的數(shù)組或者數(shù)據(jù)集 |
更新Update
Db類(lèi)實(shí)現(xiàn)
Db::table('user')
->where('id', 1)
->update([
'name' => 'topthink',
'email' => 'topthink@qq.com',
]);
模型實(shí)現(xiàn)
$user = User::get(1);
$user->name = 'topthink';
$user->email = 'topthink@qq.com';
$user->save();
或者使用
$user = User::get(1);
$user->save([
'name' => 'topthink',
'email' => 'topthink@qq.com',
]);
靜態(tài)調(diào)用
User::update([
'name' => 'topthink',
'email' => 'topthink@qq.com',
], ['id' => 1]);
save方法返回影響的記錄數(shù),而update方法返回的則是模型的對(duì)象實(shí)例。模型和
Db更新方法的最大區(qū)別是模型的更新方法只會(huì)更新有變化的數(shù)據(jù),沒(méi)有變化的數(shù)據(jù)是不會(huì)更新到數(shù)據(jù)庫(kù)的,如果所有數(shù)據(jù)都沒(méi)變化,那么根本就不會(huì)去執(zhí)行數(shù)據(jù)庫(kù)的更新操作。
所以你其實(shí)會(huì)發(fā)現(xiàn)后面的模型更新方法其實(shí)根本沒(méi)執(zhí)行(因?yàn)槟P偷母聰?shù)據(jù)和原有數(shù)據(jù)是一樣的,沒(méi)任何變化,當(dāng)然你有對(duì)數(shù)據(jù)作了自動(dòng)操作另當(dāng)別論),但你更改name或者email屬性的值的話(huà),就會(huì)發(fā)現(xiàn)執(zhí)行了更新操作。
更新操作用法小結(jié):
| 方法 | 作用 | 返回值 |
|---|---|---|
| save | 更新數(shù)據(jù) | 影響的記錄數(shù) |
| update | 更新數(shù)據(jù)(靜態(tài)) | 返回模型對(duì)象實(shí)例 |
刪除Delete
Db類(lèi)實(shí)現(xiàn)
Db::table('user')
->delete(1);
模型類(lèi)實(shí)現(xiàn)
$user = User::get(1);
$user->delete();
或者靜態(tài)實(shí)現(xiàn)
User::destroy(1);
delete方法沒(méi)有任何參數(shù),因此只能刪除當(dāng)前實(shí)例的模型數(shù)據(jù),destroy方法支持刪除指定主鍵或者查詢(xún)條件的數(shù)據(jù),例如:
// 根據(jù)主鍵刪除多個(gè)數(shù)據(jù)
User::destroy([1, 2, 3]);
// 指定條件刪除數(shù)據(jù)
User::destroy([
'status' => 0,
]);
// 使用閉包條件
User::destroy(function ($query) {
$query->where('id', '>', 0)
->where('status', 0);
});
早期版本的destroy方法如果傳入空值,會(huì)刪除數(shù)據(jù)表的所有數(shù)據(jù),該問(wèn)題已經(jīng)在V5.0.9版本得到修正(不會(huì)執(zhí)行任何刪除)。
在模型的刪除功能設(shè)計(jì)的時(shí)候,應(yīng)該盡量用軟刪除替代實(shí)際的刪除,一方面是為了避免數(shù)據(jù)丟失,一方面也是為了性能考慮(數(shù)據(jù)庫(kù)的刪除操作會(huì)導(dǎo)致重建索引,數(shù)據(jù)量越大影響越大),關(guān)于軟刪除的用法我們放到高級(jí)用法中描述。
刪除操作用法小結(jié):
| 方法 | 作用 | 返回值 |
|---|---|---|
| delete | 刪除當(dāng)前數(shù)據(jù) | 影響的記錄數(shù) |
| destroy | 刪除指定數(shù)據(jù)(靜態(tài)) | 影響的記錄數(shù) |
現(xiàn)在我們已經(jīng)掌握了模型的基本CURD操作,我們來(lái)總結(jié)下方法區(qū)別:
| 用法 | Db類(lèi) | 模型(動(dòng)態(tài)) | 模型(靜態(tài)) |
|---|---|---|---|
| 創(chuàng)建 | insert |
save |
create |
| 更新 | update |
save |
update |
| 讀取單個(gè) | find |
find |
get |
| 讀取多個(gè) | select |
select |
all |
| 刪除 | delete |
delete |
destroy |
除了模型自己的方法操作外,還可以調(diào)用Db類(lèi)的所有查詢(xún)方法,也就是說(shuō)Db類(lèi)的CURD操作方法都可以在模型類(lèi)中被調(diào)用。
不知道大家注意到一個(gè)細(xì)節(jié)沒(méi),模型的創(chuàng)建操作和更新操作的動(dòng)態(tài)方法都是save,而并沒(méi)區(qū)分。其實(shí)對(duì)于對(duì)象實(shí)例來(lái)說(shuō),所有的數(shù)據(jù)變化都只需要有一個(gè)保存行為,至于是創(chuàng)建還是更新那是數(shù)據(jù)庫(kù)內(nèi)部的事情,對(duì)不起模型對(duì)象不關(guān)心。模型會(huì)根據(jù)當(dāng)前的場(chǎng)景自動(dòng)判斷是創(chuàng)建還是更新操作。
然后要注意幾個(gè)注意事項(xiàng):
- 模型類(lèi)可以直接調(diào)用Db類(lèi)的所有方法;
- 模型類(lèi)和Db類(lèi)的查詢(xún)返回類(lèi)型是完全不同的,即便是調(diào)用同一個(gè)方法查詢(xún);
- 模型類(lèi)封裝的靜態(tài)方法本質(zhì)上還是調(diào)用的動(dòng)態(tài)方法,只是為了方便不同的需求場(chǎng)景;
- 模型對(duì)象的查詢(xún)操作盡量使用靜態(tài)方法調(diào)用;
使用查詢(xún)構(gòu)造器
之前我們已經(jīng)知道了,Db類(lèi)的所有方法都可以在模型中調(diào)用,因此查詢(xún)構(gòu)造器的用法在模型類(lèi)中沒(méi)有變化,并且還做了一些增強(qiáng)來(lái)支持模型的CURD封裝方法。
下面舉幾個(gè)例子說(shuō)明,首先是直接使用查詢(xún)類(lèi)提供的鏈?zhǔn)椒椒ㄍ瓿刹樵?xún):
$users = User::where('name', 'like', '%think')
->where('id', 'between', [1, 5])
->order('id desc')
->limit(5)
->select();
所有的鏈?zhǔn)椒椒ǘ伎梢灾苯颖荒P皖?lèi)靜態(tài)調(diào)用,而且一樣不分先后次序,你只要掌握了數(shù)據(jù)庫(kù)的查詢(xún)構(gòu)造器用法,就能掌握模型的查詢(xún)用法,而且模型類(lèi)不需要調(diào)用table方法來(lái)指定數(shù)據(jù)表名稱(chēng),因?yàn)槟P鸵呀?jīng)有自己的對(duì)應(yīng)數(shù)據(jù)表規(guī)則,從這一點(diǎn)來(lái)說(shuō),模型的查詢(xún)操作應(yīng)該比Db類(lèi)的查詢(xún)操作用法簡(jiǎn)單_。
模型可以直接調(diào)用Db類(lèi)(確切的說(shuō)是查詢(xún)類(lèi))的方法,無(wú)論是靜態(tài)還是動(dòng)態(tài)調(diào)用,也就是說(shuō)你可以把模型類(lèi)當(dāng)成Db類(lèi)一樣使用(雖然用法一樣,但其實(shí)區(qū)別很大,可能查詢(xún)條件、查詢(xún)結(jié)果和返回類(lèi)型都不同),這得益于模型類(lèi)和查詢(xún)器類(lèi)的友好邦交,在某些特殊情況下(例如不希望執(zhí)行全局查詢(xún)范圍)可以這樣調(diào)用查詢(xún)器類(lèi)的方法。
$user = (new User)
->db(false)
->where('name', 'thinkphp')
->find();
db方法是獲取當(dāng)前模型的數(shù)據(jù)庫(kù)查詢(xún)對(duì)象的方法,正常使用情況下我們不需要顯式調(diào)用db方法,該方法當(dāng)傳入false的時(shí)候表示不使用全局查詢(xún)范圍。
模型類(lèi)提供的all方法除了上面提過(guò)的根據(jù)主鍵值查詢(xún)之外,還支持使用閉包查詢(xún),閉包方法中可以使用任何的查詢(xún)類(lèi)方法(但不需要在閉包里面調(diào)用查詢(xún)),針對(duì)上面的查詢(xún)我們可以用閉包方式改造如下:
$users = User::all(function ($query) {
$query->where('name', 'like', '%think')
->where('id', 'between', [1, 5])
->order('id desc')
->limit(5);
});
閉包只有一個(gè)參數(shù),就是查詢(xún)對(duì)象。
如果你的查詢(xún)參數(shù)都是以查詢(xún)條件為主的話(huà),可以給all方法直接傳入數(shù)組查詢(xún)條件即可:
$users = User::all([
'name' => 'thinkphp',
'id' => ['>', 1],
]);
all方法如果傳入索引數(shù)組,即表示查詢(xún)條件,如果是不帶索引的數(shù)組,表示查詢(xún)多個(gè)主鍵。
模型的CURD方法其實(shí)用法不僅如此,模型的get和all方法還有很多的用法和參數(shù),更多的功能我們會(huì)在后面慢慢敘說(shuō)。不過(guò)相信到目前為止,你已經(jīng)對(duì)模型的CURD操作基本了解了。
使用
Db類(lèi)操作數(shù)據(jù)庫(kù)的話(huà),同一個(gè)連接器類(lèi)調(diào)用的是同一個(gè)查詢(xún)器類(lèi)實(shí)例,而使用模型進(jìn)行查詢(xún)操作的話(huà),每個(gè)模型對(duì)應(yīng)的是獨(dú)立的查詢(xún)器類(lèi)實(shí)例。每個(gè)查詢(xún)器類(lèi)實(shí)例都對(duì)應(yīng)一個(gè)生成器類(lèi)實(shí)例。
數(shù)據(jù)集
模型的單個(gè)數(shù)據(jù)查詢(xún)返回的都是模型對(duì)象實(shí)例,但查詢(xún)多個(gè)數(shù)據(jù)的時(shí)候默認(rèn)返回的是一個(gè)包含模型對(duì)象實(shí)例的數(shù)組??蚣芴峁┝艘粋€(gè)Collection數(shù)據(jù)集對(duì)象來(lái)進(jìn)行統(tǒng)一的模型的對(duì)象化操作,替代默認(rèn)的數(shù)組數(shù)據(jù)集更好的封裝自己的數(shù)據(jù)處理和業(yè)務(wù)邏輯。
設(shè)置數(shù)據(jù)集對(duì)象后,查詢(xún)多個(gè)數(shù)據(jù)的方法(包括Db類(lèi)的select和模型類(lèi)的all方法)返回的結(jié)果類(lèi)型就會(huì)變成think\model\Collection對(duì)象實(shí)例。
有兩種方式可以設(shè)置,第一種方式是全局設(shè)置數(shù)據(jù)庫(kù)的配置參數(shù)(默認(rèn)設(shè)置為array):
// 設(shè)置數(shù)據(jù)集返回類(lèi)型
'resultset_type' => 'collection',
該設(shè)置會(huì)影響所有的查詢(xún)(包括Db類(lèi)和模型類(lèi))。
第二種方式是在模型類(lèi)中添加屬性設(shè)置
// 設(shè)置模型的數(shù)據(jù)集返回類(lèi)型
protected $resultSetType = 'collection';
該設(shè)置僅僅影響設(shè)置的模型中的查詢(xún)結(jié)果,如果需要多個(gè)模型或者全部模型支持,可以使用繼承或者使用第一種數(shù)據(jù)庫(kù)配置方式。
數(shù)據(jù)集對(duì)象和普通的二維數(shù)組在使用上的一個(gè)最大的區(qū)別就是數(shù)據(jù)是否為空的判斷,二維數(shù)組的數(shù)據(jù)集判斷數(shù)據(jù)為空直接使用
$resultSet = User::all();
if (empty($resultSet)) {
echo '數(shù)據(jù)集為空';
}
如果使用數(shù)據(jù)集對(duì)象的話(huà),需要改成:
$resultSet = User::all();
if ($resultSet->isEmpty()) {
echo '數(shù)據(jù)集為空';
}
通用的判斷數(shù)據(jù)是否為空的方式可以用
$resultSet = User::all();
if (0 == count($resultSet)) {
echo '數(shù)據(jù)集為空';
}
其它操作的區(qū)別就是一個(gè)是對(duì)象的方法操作,一個(gè)是數(shù)組函數(shù)的操作,下面是數(shù)據(jù)集對(duì)象的方法和數(shù)組函數(shù)的對(duì)應(yīng)關(guān)系:
| 作用 | 數(shù)據(jù)集方法 | 數(shù)組函數(shù) |
|---|---|---|
| 合并數(shù)據(jù) | merge | array_merge |
| 比較數(shù)據(jù)差集 | diff | array_diff |
| 交換數(shù)組中的鍵和值 | flip | array_flip |
| 比較數(shù)組交集 | intersect | array_intersect |
| 返回鍵名 | keys | array_keys |
| 最后元素出棧 | pop | array_pop |
| 數(shù)組迭代簡(jiǎn)化 | reduce | array_reduce |
| 數(shù)據(jù)反序 | reverse | array_reverse |
| 首個(gè)元素出棧 | shift | array_shift |
| 開(kāi)頭插入元素 | unshift | array_unshift |
| 元素回調(diào) | each | --- |
| 過(guò)濾元素 | filter | array_filter |
| 返回指定列 | column | array_column |
| 元素排序 | sort | array_sort |
| 打亂元素 | shuffle | shuffle |
| 截取部分元素 | slice | array_slice |
| 元素分割 | chunk | array_chunk |
| 轉(zhuǎn)換數(shù)組 | toArray | --- |
可以自定義數(shù)據(jù)集的返回對(duì)象,然后在里面封裝其它的方法。
一般自定義的數(shù)據(jù)集對(duì)象建議繼承think\model\Collection,然后在模型中設(shè)置resultSetType屬性值為自定義查詢(xún)類(lèi)的類(lèi)名。
// 設(shè)置模型的數(shù)據(jù)集返回類(lèi)型
protected $resultSetType = 'app\common\Collection';
總結(jié)下數(shù)據(jù)集的優(yōu)勢(shì):
- 數(shù)據(jù)更對(duì)象化;
- 關(guān)聯(lián)操作更方便;
- 數(shù)據(jù)集本身可以單獨(dú)定義獨(dú)立的業(yè)務(wù)方法;
分頁(yè)查詢(xún)
分頁(yè)查詢(xún)其實(shí)也是查詢(xún)多個(gè)數(shù)據(jù)的一種特殊方式,表現(xiàn)出來(lái)通常是頁(yè)面上有很多的頁(yè)數(shù)、當(dāng)前頁(yè)數(shù)和上下翻頁(yè)按鈕,而分頁(yè)數(shù)據(jù)通常是配合數(shù)據(jù)庫(kù)的limit語(yǔ)法來(lái)實(shí)現(xiàn)分頁(yè)查詢(xún),普通的分頁(yè)查詢(xún)通常需要兩個(gè)步驟:首先查詢(xún)滿(mǎn)足條件的記錄總數(shù),然后查詢(xún)當(dāng)前分頁(yè)的數(shù)據(jù)。然而內(nèi)置分頁(yè)查詢(xún)只需要調(diào)用paginate方法就可以實(shí)現(xiàn)分頁(yè)查詢(xún),下面是查詢(xún)代碼。
<?php
namespace app\index\controller;
use app\index\model\User;
use think\Controller;
class Index extends Controller
{
public function index($p=1)
{
// 查詢(xún)分頁(yè)數(shù)據(jù)
$list = User::where('status', 1)->paginate();
// 創(chuàng)建分頁(yè)顯示
$this->assign('page', $list);
// 模板渲染輸出
return $this->fetch();
}
}
模板文件中分頁(yè)輸出代碼如下:
<div>
總記錄數(shù):{$page->total()}
<ul>
{volist name='page' id='user'}
<li> {$user.name}</li>
{/volist}
</ul>
</div>
{$page->render()}
paginate方法之后不需要調(diào)用任何的查詢(xún)方法,該方法本身就是一個(gè)查詢(xún)數(shù)據(jù)集的方法,而且返回結(jié)果是一個(gè)think\Paginator對(duì)象,該對(duì)象具有數(shù)據(jù)集對(duì)象的類(lèi)似特性。
使用paginate方法查詢(xún)不需要單獨(dú)查詢(xún)記錄總數(shù),也不需要使用limit或者page方法,通常作為全局分頁(yè)配置可以在配置文件中設(shè)置下面的分頁(yè)參數(shù):
//分頁(yè)配置
'paginate' => [
// 分頁(yè)類(lèi)
'type' => 'bootstrap',
// 分頁(yè)變量
'var_page' => 'p',
// 每頁(yè)記錄數(shù)
'list_rows' => 15,
],
除了這幾個(gè)參數(shù)外,還可以在paginate方法調(diào)用的時(shí)候動(dòng)態(tài)傳入
$list = User::where('status', 1)->paginate([
'type' => 'bootstrap',
'var_page' => 'p',
'list_rows' => 15,
]);
額外的分頁(yè)參數(shù)包括:
| 參數(shù) | 描述 |
|---|---|
| page | 指定當(dāng)前頁(yè) |
| path | 當(dāng)前url路徑 |
| query | url額外參數(shù)(數(shù)組) |
| fragment | url錨點(diǎn) |
當(dāng)你的分頁(yè)查詢(xún)條件來(lái)自于URL,需要傳入query參數(shù)。
大部分情況,可能只需要傳入每頁(yè)的記錄數(shù),直接傳入數(shù)字就表示設(shè)置分頁(yè)的每頁(yè)記錄數(shù)。
$list = User::where('status', 1)
->paginate(20);
對(duì)于一些復(fù)雜的查詢(xún)條件,尤其是使用了join、group之類(lèi)的,可能需要單獨(dú)查詢(xún)記錄總數(shù):
$total = User::where('status', 1)->count();
$list = User::where('status', 1)
->paginate(20, $total);
對(duì)于某些應(yīng)用,可能并不需要完整的分頁(yè)顯示,而只需要顯示上一頁(yè)和下一頁(yè),這種我們稱(chēng)之為簡(jiǎn)潔模式分頁(yè),對(duì)于這種情況,我們只需要在第二個(gè)參數(shù)傳入true即可,簡(jiǎn)潔模式的分頁(yè)優(yōu)勢(shì)是不需要查詢(xún)記錄總數(shù)。
// 簡(jiǎn)潔模式分頁(yè)
$list = User::where('status', 1)
->paginate(20, true);
添加業(yè)務(wù)邏輯
模型的優(yōu)勢(shì)不是用來(lái)做基礎(chǔ)的CURD操作的,雖然CURD操作也是一種最常見(jiàn)的業(yè)務(wù)邏輯,只是這些基本邏輯無(wú)需再定義額外的方法了,系統(tǒng)已經(jīng)內(nèi)置實(shí)現(xiàn)了。但實(shí)際的應(yīng)用中,一般都需要根據(jù)業(yè)務(wù)需求來(lái)增加額外的業(yè)務(wù)邏輯方法。
以User模型為例,假設(shè)我們需要實(shí)現(xiàn)下列功能:
- 用戶(hù)注冊(cè);
- 用戶(hù)登陸;
- 獲取用戶(hù)信息;
- 獲取用戶(hù)的身份角色;
- ...更多業(yè)務(wù)邏輯
那么可以在User模型添加下面的邏輯方法:
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
/**
* 注冊(cè)一個(gè)新用戶(hù)
* @param array $data 用戶(hù)注冊(cè)信息
* @return integer|bool 注冊(cè)成功返回主鍵,注冊(cè)失敗-返回false
*/
public function register($data = [])
{
$result = $this->validate(true)->allowField(true)->save($data);
if ($result) {
return $this->getData('id');
} else {
return false;
}
}
/**
* 用戶(hù)登錄認(rèn)證
* @param string $username 用戶(hù)名
* @param string $password 用戶(hù)密碼
* @return integer 登錄成功-用戶(hù)ID,登錄失敗-返回0或-1
*/
public function login($username, $password)
{
$where['username'] = $username;
$where['status'] = 1;
/* 獲取用戶(hù)數(shù)據(jù) */
$user = $this->where($where)->find();
if ($user) {
if (md5($password) != $user->password) {
$this->error = '密碼錯(cuò)誤';
return 0;
} else {
return $user->id;
}
} else {
$this->error = '用戶(hù)不存在或被禁用';
return -1;
}
}
/**
* 獲取用戶(hù)信息
* @param integer $uid 用戶(hù)主鍵
* @return array|integer 成功返回?cái)?shù)組,失敗-返回-1
*/
public function info($uid)
{
$user = $this->where('id', $uid)->field('id,username,email,mobile,status')->find();
if ($user && 1 == $user->status) {
// 返回用戶(hù)數(shù)據(jù)
return $user->hidden('status')->toArray();
} else {
$this->error = '用戶(hù)不存在或被禁用';
return -1;
}
}
/**
* 獲取用戶(hù)角色
* @return integer 返回角色信息或者返回-1
*/
public function role()
{
$uid = $this->getData('id');
if ($uid) {
$role = $this->getUserRole($uid);
if ($role) {
return $role;
} else {
$this->error = '用戶(hù)未授權(quán)';
return 0;
}
} else {
$this->error = '請(qǐng)先登錄';
return -1;
}
}
protected function getUserRole($uid)
{
return $this->table('role')->where('uid', $uid)->find();
}
}
我們先不要在意方法的實(shí)現(xiàn)細(xì)節(jié)(這些實(shí)現(xiàn)代碼并非完美,只是簡(jiǎn)單的說(shuō)明問(wèn)題),里面的很多調(diào)用方法后面都會(huì)一一提及,這里只是告訴你如何在模型類(lèi)里面添加自己的業(yè)務(wù)邏輯,下面同時(shí)給出在控制器中的調(diào)用參考。
<?php
namespace app\index\controller;
use app\index\model\User;
use think\Controller;
use think\Session;
class Index extends Controller
{
public function login()
{
return $this->fetch();
}
public function doLogin(User $user, $username, $password)
{
$uid = $user->login($username, $password);
if ($uid) {
Session::set('user_id', $uid);
$this->success('登錄成功');
} else {
$this->error('登錄失敗');
}
}
public function register()
{
return $this->fetch();
}
public function doRegister(User $user)
{
$data = $this->request->param();
$result = $user->register($data);
if ($result) {
$this->success('用戶(hù)注冊(cè)成功');
} else {
$this->error($user->getError());
}
}
public function getUserInfo(User $user, $uid)
{
$info = $user->info($uid);
if ($info) {
$this->assign('user', $info);
return $this->fetch();
} else {
return '用戶(hù)不存在';
}
}
protected function getUserRole()
{
$uid = Session::get('user_id');
$user = User::get($uid);
return $user->role();
}
}
控制器的詳細(xì)用法不屬于本書(shū)的討論范疇,如果有必要可以參考官方快速入門(mén)系列第三部:《控制器從入門(mén)到精通》。
從上面的用法中我們可以注意幾點(diǎn):
- 業(yè)務(wù)邏輯應(yīng)當(dāng)封裝到具體模型中,并由控制器來(lái)調(diào)用;
-
register和login方法獲取用戶(hù)主鍵的方法區(qū)別; - 可以設(shè)置模型的錯(cuò)誤信息,并且用
getError方法獲取;
總結(jié)
通過(guò)本章的學(xué)習(xí),你應(yīng)該掌握了模型的基本概念和基礎(chǔ)用法,下一章我們就來(lái)學(xué)習(xí)模型的各種數(shù)據(jù)處理功能,下一章的內(nèi)容更精彩哦。