Thinkphp 第五章:模型和對(duì)象

從本章開(kāi)始就要揭開(kāi)模型的神秘面紗了,本章主要學(xué)習(xí)模型的定義和基礎(chǔ)使用,以及和數(shù)據(jù)庫(kù)操作的區(qū)別,學(xué)習(xí)內(nèi)容主要包含:

模型和數(shù)據(jù)庫(kù)區(qū)別

在說(shuō)模型和數(shù)據(jù)庫(kù)的區(qū)別之前,首先理解一點(diǎn),5.0的數(shù)據(jù)庫(kù)抽象訪問(wèn)層(我們后面用Db類(lèi)表示)和模型是一個(gè)整體,共同完成了ThinkPHP5.0ORM(對(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')

getDatagetAttr方法的區(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')

datasetAttr方法的區(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)于模型的getall方法的更多用法,而且也完全可以替代數(shù)據(jù)庫(kù)提供的findselect方法,我們會(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):

  1. 模型類(lèi)可以直接調(diào)用Db類(lèi)的所有方法;
  2. 模型類(lèi)和Db類(lèi)的查詢(xún)返回類(lèi)型是完全不同的,即便是調(diào)用同一個(gè)方法查詢(xún);
  3. 模型類(lèi)封裝的靜態(tài)方法本質(zhì)上還是調(diào)用的動(dòng)態(tài)方法,只是為了方便不同的需求場(chǎng)景;
  4. 模型對(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í)用法不僅如此,模型的getall方法還有很多的用法和參數(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)條件,尤其是使用了joingroup之類(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)用;
  • registerlogin方法獲取用戶(hù)主鍵的方法區(qū)別;
  • 可以設(shè)置模型的錯(cuò)誤信息,并且用getError方法獲取;

總結(jié)

通過(guò)本章的學(xué)習(xí),你應(yīng)該掌握了模型的基本概念和基礎(chǔ)用法,下一章我們就來(lái)學(xué)習(xí)模型的各種數(shù)據(jù)處理功能,下一章的內(nèi)容更精彩哦。

上一篇:第四章:高級(jí)查詢(xún)技巧
下一篇:第六章:模型數(shù)據(jù)處理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 國(guó)家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報(bào)批稿:20170802 前言: 排版 ...
    庭說(shuō)閱讀 12,415評(píng)論 6 13
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類(lèi)型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,657評(píng)論 1 32
  • 本章主要來(lái)學(xué)習(xí)和使用查詢(xún)構(gòu)造器的用法,掌握查詢(xún)構(gòu)造器對(duì)于掌握數(shù)據(jù)庫(kù)和模型的查詢(xún)操作非常關(guān)鍵,學(xué)習(xí)內(nèi)容主要包含: 創(chuàng)...
    寒冬夜行人_51a4閱讀 1,558評(píng)論 0 2
  • 本章我們首先從ThinkPHP5.0的數(shù)據(jù)庫(kù)訪問(wèn)層架構(gòu)設(shè)計(jì)原理開(kāi)始,然后熟悉下數(shù)據(jù)庫(kù)的配置,并掌握如何進(jìn)行基礎(chǔ)的查...
    寒冬夜行人_51a4閱讀 2,175評(píng)論 11 6
  • ORA-00001: 違反唯一約束條件 (.) 錯(cuò)誤說(shuō)明:當(dāng)在唯一索引所對(duì)應(yīng)的列上鍵入重復(fù)值時(shí),會(huì)觸發(fā)此異常。 O...
    我想起個(gè)好名字閱讀 5,971評(píng)論 0 9

友情鏈接更多精彩內(nèi)容