從本章開始就要揭開模型的神秘面紗了,本章主要學習模型的定義和基礎使用逮刨,以及和數(shù)據(jù)庫操作的區(qū)別,學習內(nèi)容主要包含:
模型和數(shù)據(jù)庫區(qū)別
在說模型和數(shù)據(jù)庫的區(qū)別之前昌渤,首先理解一點湿镀,5.0的數(shù)據(jù)庫抽象訪問層(我們后面用Db
類表示)和模型是一個整體,共同完成了ThinkPHP5.0
的ORM
(對象關系映射)姿染”澈ィ或者也可以理解為模型是數(shù)據(jù)訪問層的查詢構造器延伸,完成更高級的數(shù)據(jù)庫查詢操作罷了盔粹。
通過前面幾章的學習隘梨,看起來Db
類已經(jīng)非常的強大,但缺點仍然非常明顯:
- 不支持
ActiveRecord
實現(xiàn)舷嗡; - 缺乏靈活的事件機制轴猎;
- 數(shù)據(jù)自動處理能力弱;
- 數(shù)據(jù)關聯(lián)操作繁瑣并且不直觀进萄;
- 不能單獨封裝業(yè)務邏輯捻脖;
上面這些內(nèi)容我只是打擊下迷戀
Db
類的朋友锐峭,別無它意(因為本來就是故意設計的_)。
其實還有很多...當然可婶,原因并不是否定Db
類的實現(xiàn)沿癞,而是前面提到的,Db
和模型本來就是一個整體矛渴,只是各自的職責和分工不同椎扬,如果沒有Db
類的基石,模型也只是建在沙灘上的城堡罷了具温。
Db
和模型的存在只是ThinkPHP5.0
架構設計中的職責和定位不同蚕涤,Db
負責的只是數(shù)據(jù)(表)訪問,模型負責的是業(yè)務數(shù)據(jù)和業(yè)務邏輯铣猩。
當然揖铜,模型層可以分的更細,把數(shù)據(jù)模型和邏輯模型达皿,甚至服務模型分開天吓,這個暫時就不在目前的討論范疇了,只不過把模型層的職責和分工更細化峦椰。
如果你用框架只是用來管理一些數(shù)據(jù)的CURD
而沒有業(yè)務需要(其實本質上來說任何的系統(tǒng)都是CURD龄寞,業(yè)務邏輯都是抽象和封裝出來的,這是設計層面的問題了)们何,那么也許看起來Db
類已經(jīng)夠用了(你不覺得其實數(shù)據(jù)庫本身已經(jīng)可以完成了么)萄焦,但是作為一個業(yè)務系統(tǒng)或者平臺(無論是WEB
還是API
),通常每個數(shù)據(jù)表就對應了一個業(yè)務模型對象冤竹,甚至存在和其它業(yè)務模型的混合和關聯(lián)邏輯拂封。舉個用戶表的例子,用戶登錄這樣一個業(yè)務邏輯其實包含了很多的關聯(lián)操作鹦蠕,你得檢查用戶賬號是否正常冒签,用戶名和密碼是否正確,然后記錄用戶的最后登錄時間和IP(如果IP所在區(qū)域不符有些系統(tǒng)還需要給用戶發(fā)郵件提醒)钟病,還要給用戶增加積分萧恕,甚至可能還需要檢查用戶的權限,那么Db
類就顯得吃力了肠阱,這其實也是數(shù)組存儲結構和對象存儲設計的差異票唆,業(yè)務越復雜,這種差異越明顯屹徘,PHP的數(shù)組再強大也替代不了對象走趋。
Db
和模型最明顯的一個區(qū)別就是Db
查詢返回的數(shù)據(jù)類型為數(shù)組(對于一個沒有業(yè)務邏輯的數(shù)據(jù)而言,數(shù)組已經(jīng)足夠)噪伊,而模型的查詢返回類型的是模型對象實例簿煌。
也許前面幾個問題你根本不會在意(確實優(yōu)雅只是看起來舒服一些罷了氮唯,關聯(lián)用JOIN
還容易掌控之類的話我也經(jīng)常聽到,呵呵~)姨伟,但最后一個問題無法封裝業(yè)務邏輯是致命的惩琉,處理不當極易出現(xiàn)違反MVC
架構設計的混亂情況。
再說簡單一點夺荒,由于Db
類的數(shù)據(jù)操作并沒有一個唯一對應的對象實例瞒渠,也就無法封裝業(yè)務方法,就變成你的業(yè)務方法要么寫到控制器方法里面技扼,要么定義到另外一個所謂的“業(yè)務邏輯”層里面在孝,前者顯然是違反MVC
架構設計思想的,而后者其實就是一個模型類的概念存在了淮摔,那么是否需要擁抱模型就顯而易見,不用我多說了吧_
話說回來了始赎,有些人雖然用了模型和橙,但仍然在模型里面大量封裝直接操作Db類的代碼和方法,這也是一種偽模型設計造垛,并不可取魔招。
比較Db
和模型,不要單純從功能上做比較五辽,這是次要的办斑,也沒意義,畢竟職責定位不同杆逗。也不要在意性能上的差異乡翅,這個對于業(yè)務邏輯來說,一次查詢就抵消了罪郊。
總而言之蠕蚜,想要掌握模型,必須明白和理解下面幾個原則:
- 模型和數(shù)據(jù)庫層的定位和職責不同悔橄;
- 不要因為性能而放棄使用模型靶累,那是得不償失的;
- 用面向對象的方式來使用和設計模型癣疟;
- 模型的數(shù)據(jù)底層操作仍然是數(shù)據(jù)庫抽象訪問層挣柬,而且是自動的;
模型設計基于數(shù)據(jù)訪問層之上睛挚,并作了更高層次的封裝邪蛔,實現(xiàn)了Db
類本身不支持的功能,或者簡化了原本使用Db
類的復雜操作竞川。從查詢操作的角度來看店溢,可以理解為Db
類是數(shù)據(jù)表的查詢構造器叁熔,而模型是業(yè)務模型的查詢構造器,其實都屬于查詢構造器的范疇床牧。
很多人不習慣用模型的原因無非就幾個方面:
- 不理解模型的概念荣回;
- 嫌每個數(shù)據(jù)表都要定義模型麻煩;
- 模型的用法不容易掌握戈咳;
- 覺得模型的性能差心软;
我們會慢慢打消上述的這些困惑或顧慮,學完本書著蛙,你就會發(fā)現(xiàn)模型其實很簡單删铃,而且相對于Db
查詢來說犧牲的細微性能完全值得。
在控制器中永遠調(diào)用的是模型類踏堡,然后在模型類中封裝業(yè)務邏輯方法和數(shù)據(jù)處理猎唁,完成業(yè)務操作。對控制器來說顷蟆,模型就是一個業(yè)務邏輯接口诫隅,并且善于運用依賴注入機制來綁定模型對業(yè)務操作會帶來極大的便利。
模型定義
定義一個模型很簡單帐偎,下面是一個最簡單的模型類:
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
}
如果使用的是5.1版本逐纬,一定要注意,如果你的對應數(shù)據(jù)表的主鍵名不是
id
削樊,需要在模型中設置pk
屬性為你的實際主鍵名豁生。
模型定義有幾個要素:
- 通常會繼承
think\Model
(或者子類),虛擬模型除外漫贞; - 一個模型并不總是對應一個數(shù)據(jù)表(可能會有多個)甸箱,雖然默認如此;
- 模型名和數(shù)據(jù)表名也不是直接對應關系迅脐;
- 盡管一個空模型和使用Db類無異摇肌,但意義不同;
模型定義階段要達成的目的:
- 定義數(shù)據(jù)表(默認就是模型類名)
- 定義數(shù)據(jù)表主鍵(默認會自動獲纫羌省)
- 定義數(shù)據(jù)庫連接(默認使用數(shù)據(jù)庫配置)
- 定義數(shù)據(jù)處理邏輯(包括屬性和方法)
- 定義業(yè)務邏輯(方法)
下面的定義是不需要或者不支持的:
- 數(shù)據(jù)表字段(不需要围小,會自動獲取,并支持緩存機制)
- 數(shù)據(jù)表前綴(不支持树碱,模型不關心前綴)
大多數(shù)情況下肯适,數(shù)據(jù)表和數(shù)據(jù)庫連接是不需要定義的,數(shù)據(jù)處理邏輯和業(yè)務邏輯才是模型定義的重點成榜,如果你發(fā)現(xiàn)你的大多數(shù)模型類都是什么都沒定義框舔,那么就要思考下哪里出問題了,為什么你的模型成了形式和擺設。是沒業(yè)務需要還是職責分工有問題了刘绣?也許你在控制器中大量使用Db
類進行業(yè)務邏輯處理樱溉。無論怎樣,現(xiàn)在糾正思維纬凤,跟著教程擁抱和學習模型吧福贞。
一個模型并不總是對應一個數(shù)據(jù)表(例如關聯(lián)模型和聚合模型),但大多數(shù)情況下對應的是一個數(shù)據(jù)表停士,默認的對應關系是:模型類的名稱(注意不一定是類名挖帘,后面會解釋)轉換為小寫和下劃線就是對應的數(shù)據(jù)表:
模型名 | 對應數(shù)據(jù)表 |
---|---|
User | user |
UserType | user_type |
如果你的數(shù)據(jù)庫配置定義了前綴(假設數(shù)據(jù)庫的前綴定義是 think_
),那么對應關系就是:
模型名 | 對應數(shù)據(jù)表 |
---|---|
User | think_user |
UserType | think_user_type |
如果你的對應規(guī)則和上面的系統(tǒng)約定不符合恋技,那么需要設置模型類的數(shù)據(jù)表名稱屬性拇舀,以確保能夠找到對應的數(shù)據(jù)表。代碼如下:
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
protected $table = 'user_info';
}
table
屬性定義的是完整數(shù)據(jù)表名蜻底,如果你希望定義不帶前綴的數(shù)據(jù)表名骄崩,可以使用name
屬性來定義模型的名稱。
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
protected $name = 'user_info';
}
如果你同時定義了這兩個屬性薄辅,那么
table
屬性是優(yōu)先的刁赖。
模型的設計允許給單獨指定數(shù)據(jù)庫連接,也就說你可以將不同的數(shù)據(jù)庫的表進行統(tǒng)一的管理长搀,對于跨數(shù)據(jù)庫的應用尤其有用,對于跨庫的相同表名鸡典,我們可以建立不同名稱的模型或者放入不同的命名空間來解決源请。
指定模型的單獨數(shù)據(jù)庫連接方法如下:
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
protected $name = 'user_info';
protected $connection = 'db_config';
}
和Db
類的connect
方法一樣,模型類的connection
屬性允許使用數(shù)組彻况、字符串以及配置參數(shù)的方式定義谁尸,這里使用配置參數(shù)(在應用或者模塊的配置文件中單獨配置db_config
參數(shù))的方式,避免在模型里面寫死數(shù)據(jù)庫連接信息纽甘,全部交給配置文件去統(tǒng)一處理良蛮。
如果
connection
屬性使用數(shù)組方式配置,會和數(shù)據(jù)庫配置文件中的參數(shù)合并悍赢,因此你只需要定義有區(qū)別的參數(shù)决瞳,而無需定義全部的數(shù)據(jù)庫參數(shù)。
如果擔心模型的名稱和PHP關鍵字沖突左权,可以啟用類后綴功能皮胡,只需要在應用配置文件中設置:
// 開啟應用類庫后綴
'class_suffix' => true,
開啟后,所有的應用類庫定義的時候都需要加上對應后綴赏迟,包括控制器類屡贺。
這樣app\index\model\User
類定義就要改成
<?php
namespace app\index\model;
use think\Model;
class UserModel extends Model
{
}
并且類名也要改為UserModel.php
。
關于模型的連接對象和查詢對象,要清楚下面這些事實:
- 模型可以單獨設置數(shù)據(jù)庫連接甩栈;
- 模型的數(shù)據(jù)庫連接是惰性的(因為連接本身就是惰性)泻仙;
- 如果使用統(tǒng)一的數(shù)據(jù)庫配置,模型使用的連接對象是相同的量没;
- 模型使用的查詢對象是獨立的玉转;
- 模型可以使用自定義的查詢對象;
命令行生成
當你需要創(chuàng)建大量的模型類的時候允蜈,不妨考慮下命令行生成冤吨,可以快速創(chuàng)建模型類。
在windows下面饶套,使用Win+R
輸入cmd
進入命令控制臺漩蟆,切換到項目根目錄(也就是think
文件所在目錄),并執(zhí)行下面的指令可以生成index
模塊的Blog
模型類文件妓蛮。
>php think make:model index/Blog
生成的模型類文件如下:
<?php
namespace app\index\model;
use think\Model;
class Blog extends Model
{
//
}
注意怠李,如果使用
>php think make:model Blog
生成的是common
模塊下面的Blog
模型類。
模型調(diào)用
模型支持實例化調(diào)用和靜態(tài)調(diào)用(主要是查詢蛤克,查詢后會返回一個模型對象實例)捺癞。
// 實例化User模型
$user = new \app\index\model\User();
// 直接靜態(tài)查詢
$user = \app\index\model\User::get(1);
一般來說,我們會事先使用use
引入User
模型類构挤,就不需要每次都使用完整命名空間方式來調(diào)用User
模型類了髓介。
<?php
namespace app\index\controller;
use app\index\model\User;
class Index
{
public function index()
{
$user = User::get(1);
}
}
如果你開啟了應用類庫后綴的話,可以這樣使用
<?php
namespace app\index\controller;
use app\index\model\UserModel as User;
class IndexController
{
public function index()
{
$user = User::get(1);
}
}
我們后面的例子都是直接使用User
類名進行實例化或者靜態(tài)調(diào)用筋现,你必須明白為何可以如此調(diào)用唐础。
調(diào)用模型類的方法其實和調(diào)用一個普通的類沒有區(qū)別,不要覺得模型類有什么特殊矾飞。
如果你覺得每次引入比較麻煩一膨,系統(tǒng)還提供了一個助手函數(shù)幫助你快速實例化模型類而不必每次引入模型類。
你可以在任何地方使用
$user = model('User');
實例化User
模型類洒沦,并且model
函數(shù)采用單例實現(xiàn)豹绪,多次調(diào)用不會重復實例化。
使用model
助手函數(shù)的一個優(yōu)勢是即使你開啟了應用類庫后綴申眼,你仍然可以直接使用
$user = model('User');
而不必使用
$user = model('UserModel');
事實上瞒津,上面的用法是錯誤的。
我們還是建議使用
use
方式引入模型類后操作括尸,因為助手函數(shù)并不支持模型的靜態(tài)調(diào)用仲智,這個后面我們還會詳細說明。
我們甚至可以通過依賴注入直接把模型對象實例注入到控制器的操作方法中姻氨,而不需要每次都進行實例化钓辆。關于如何使用依賴注入,請參考《控制器從入門到精通》第五講的內(nèi)容。
對象化操作
了解如何定義和調(diào)用模型后前联,我們來具體了解下模型的使用功戚。
模型和Db
操作的一大顯性區(qū)別就是一個是對象操作和一個是數(shù)組操作,下面以一個user
數(shù)據(jù)表的查詢似嗤、取值啸臀、設置和更新的例子,來說明下兩種方式的區(qū)別烁落。
首先回顧下Db
類的用法:
// 查詢操作
$user = Db::table('user')->find(1);
// 取值操作
echo $user['name'];
echo $user['email'];
// 設置操作
$user['name'] = 'topthink';
$user['email'] = 'thinkphp@qq.com';
// 更新操作
Db::table('user')->update($user);
然后乘粒,如果是模型操作的話,就可以對應下面的代碼實現(xiàn):
// 查詢操作
$user = User::get(1);
// 取值操作
echo $user->name;
echo $user->email;
// 設置操作
$user->name = 'topthink';
$user->email = 'thinkphp@qq.com';
// 更新操作
$user->save();
事實上伤塌,由于模型類實現(xiàn)了ArrayAccess
接口灯萍,因此一樣可以使用數(shù)組方式操作:
// 查詢操作
$user = User::get(1);
// 取值操作
echo $user['name'];
echo $user['email'];
// 設置操作
$user['name'] = 'topthink';
$user['email'] = 'thinkphp@qq.com';
// 更新操作
$user->save();
是不是覺得很神奇,不過這個問題有點高級每聪,暫且不表旦棉,留給大家思考,答案后面章節(jié)會揭曉药薯。我們后面的模型例子還是以對象操作為例講解绑洛。
模型對象的取值和設置都不是表面上看起來那么簡單,可以設置很多自動化操作童本,取值的自動化操作就是讀取器真屯,設置的自動化操作就是修改器,這兩個概念我們會在下一章詳細講解穷娱。
模型的讀取和設置并不總是這樣操作绑蔫,這和模型的內(nèi)部實現(xiàn)有關,因為我們并沒有在模型里面定義user
數(shù)據(jù)表對應的public
類型的name
或者email
屬性鄙煤,模型的取值和設置都是通過__get
和__set
魔術方法完成,事實上模型的數(shù)據(jù)操作內(nèi)部都是操作模型類的data
屬性茶袒,在本書中梯刚,我們通常把$user->name
和$user->email
稱為模型數(shù)據(jù)而不是模型屬性。
那么問題來了薪寓,如果是在模型內(nèi)部進行取值和設置操作怎么辦亡资?
// 錯誤的讀取數(shù)據(jù)方式
echo $this->name;
echo $this->email;
// 錯誤的數(shù)據(jù)設置方式
$this->name = 'thinkphp';
$this->email = 'thinkphp@qq.com';
這樣,一旦數(shù)據(jù)表的字段名和模型的內(nèi)部屬性沖突就產(chǎn)生混淆了向叉,這是一個新手最容易產(chǎn)生困惑的地方锥腻。所以,如果是在模型內(nèi)部母谎,正確的獲取方式應該是:
// 模型內(nèi)部讀取數(shù)據(jù)
echo $this->getData('name');
echo $this->getAttr('email');
// 模型內(nèi)部設置數(shù)據(jù)
$this->data('name','thinkphp');
$this->setAttr('email','thinkphp@qq.com');
以name
屬性為例瘦黑,獲取模型數(shù)據(jù)的方式有下列三種:
場景 | 方法 |
---|---|
外部獲取模型數(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)過讀取器處理的數(shù)據(jù)幸斥,如果沒有定義數(shù)據(jù)讀取器的話匹摇,兩個方法的結果是相同的。
對應的設置模型數(shù)據(jù)的方式也有三種:
場景 | 方法 |
---|---|
外部設置模型數(shù)據(jù) | $model->name='thinkphp' |
內(nèi)部設置模型數(shù)據(jù)(經(jīng)過修改器) | $this->setAttr('name','thinkphp') |
內(nèi)部設置模型數(shù)據(jù) | $this->data('name','thinkphp') |
data
和setAttr
方法的區(qū)別前者是賦值最終數(shù)據(jù)甲葬,后者賦值的數(shù)據(jù)還會經(jīng)過修改器處理廊勃,如果沒有定義修改器的話,兩個方法的結果是相同的经窖。
對象化操作的神奇是可以級聯(lián)讀取或者設置坡垫,例如:
// 查詢操作
$user = User::get(1);
// 取值操作
echo $user->name;
echo $user->email;
// 關聯(lián)取值
echo $user->role->name;
echo $user->contact->phone;
// 設置操作
$user->name = 'topthink';
$user->email = 'thinkphp@qq.com';
// 更新操作
$user->save();
// 關聯(lián)設置
$user->role->name = 'admin';
$user->role->save();
$user->contact->phone = '123456789';
$user->contact->save();
這里使用了模型關聯(lián)的概念,如果感到摸不著頭腦不用擔心画侣,我們會在第八章給你詳細講解冰悠。
模型CURD操作
模型的主要功能包括數(shù)據(jù)處理和業(yè)務邏輯,而這些都離不開數(shù)據(jù)的CURD操作棉钧,因此我們首先來談下數(shù)據(jù)的CURD
操作屿脐,在掌握了數(shù)據(jù)庫Db
類的用法后,模型的CURD
操作就會很容易理解宪卿,因為本質上模型的CURD
操作最終調(diào)用的還是Db
類的操作的诵,區(qū)別在于使用了ActiveRecord
模式和單獨做了一層封裝而已,我們來看下兩種方式CURD
操作用法的簡單對比(其中模型會給出動態(tài)和靜態(tài)兩種實現(xiàn)方法佑钾,分別對應不同的場景)西疤。
創(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();
或者批量設置:
$user = new User;
$user->save([
'name' => 'thinkphp',
'email' => 'thinkphp@qq.com',
]);
上面兩種方式等效,當你的模型數(shù)據(jù)比較多不想一一賦值的時候休溶,可以使用后者代赁。
也許你咋一看還覺得麻煩了额划,又是實例化又是賦值的旦事,但好處多多,慢慢你就會體會到了捏境,看起來是一個簡單的賦值和保存操作其實內(nèi)里大有乾坤孽尽,可以觸發(fā)很多處理甚至事件窖壕。
save
方法的返回值不是自增主鍵的值(和Db的execute
方法一樣返回影響的記錄數(shù)),要獲取自增主鍵的值可以使用下面的方式:
$user = new User;
$user->name = 'thinkphp';
$user->email = 'thinkphp@qq.com';
$user->save();
// 獲取用戶的主鍵數(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
模型的對象實例瞻讽,而save
方法調(diào)用的時候本身就在對象實例里面。
很多開發(fā)者不習慣靜態(tài)調(diào)用熏挎,這里必須說明的是模型類的靜態(tài)CURD操作其實都是內(nèi)部自動實例化而已速勇,所以說白了提供的這些靜態(tài)操作方法只是對動態(tài)CURD操作方法的靜態(tài)封裝罷了。
至于靜態(tài)方法的場景坎拐,主要是不想實例化或者不方便實例化的需求烦磁,而且支持變量的靜態(tài)調(diào)用养匈,例如:
$model = '\app\index\model\User';
$user = $model::create([
'name' => 'thinkphp',
'email' => 'thinkphp@qq.com',
]);
創(chuàng)建操作用法小結:
方法 | 返回值 |
---|---|
save(動態(tài)) | 影響的記錄數(shù) |
create(靜態(tài)) | 模型對象實例 |
讀取Read
Db
類實現(xiàn)讀取單個記錄
$user = Db::table('user')
->where('id', 1)
->find();
// 或者
$user = Db::table('user')
->find(1);
echo $user['id'];
echo $user['name'];
模型實現(xiàn)讀取單個記錄要比Db
類簡單很多,而且更加符合對象的設計个初。
$user = User::get(1);
echo $user->id;
echo $user->name;
V5.0.8
版本之前模型的get方法如果沒有傳值或者傳入空值乖寒,會查詢第一個符合條件的數(shù)據(jù),這個問題在V5.0.8
版本已經(jīng)修正院溺,get
方法必須傳入非空的值楣嘁,否則直接返回Null。
Db
類的find
方法返回的是一個數(shù)組珍逸,模型類的get
方法返回的是一個User
模型對象實例逐虚。模型的讀取操作一般使用靜態(tài)方法讀取即可,返回模型對象實例谆膳。
很多用戶往往會寫出下面的代碼叭爱,理論上來說當然也沒有錯,其實是大可不必的漱病。
$user = new User;
$user->find(1);
除非你已經(jīng)在User
模型的對象實例內(nèi)部去調(diào)用find
方法讀取數(shù)據(jù)买雾,但這種方式不符合模型對象的設計原則,一個模型對象實例應該唯一對應數(shù)據(jù)表的一條記錄杨帽。
Db
類實現(xiàn)讀取多個記錄
// 查詢用戶數(shù)據(jù)集
$users = Db::table('user')
->where('id', '>', 1)
->limit(5)
->select();
// 遍歷讀取用戶數(shù)據(jù)
foreach ($users as $user) {
echo $user['id'];
echo $user['name'];
}
模型實現(xiàn)讀取多個記錄
// 查詢用戶數(shù)據(jù)集
$users = User::where('id', '>', 1)
->limit(5)
->select();
// 遍歷讀取用戶數(shù)據(jù)
foreach ($users as $user) {
echo $user->id;
echo $user->name;
}
模型的查詢操作比起Db
查詢有一個顯著的特征就是不需要每次調(diào)用table
或者name
方法漓穿,因為每個模型在創(chuàng)建的時候已經(jīng)自動對應了數(shù)據(jù)表。
在讀取多個記錄的方式上注盈,兩種方式的區(qū)別并不大晃危,只是默認返回數(shù)據(jù)集類型的區(qū)別,Db
方式返回的數(shù)據(jù)集是一個包含每個用戶數(shù)組的二維數(shù)組老客,而模型方式返回的數(shù)據(jù)集包含每個User
模型對象實例的數(shù)組僚饭。
事實上,這個差異在實際進行數(shù)據(jù)集處理的時候根本感覺不到胧砰,也就是說后者仍然可以使用前者的方式統(tǒng)一操作(這歸功于模型的神奇設計鳍鸵,這個后面章節(jié)會專門提到)。
對于多個主鍵的數(shù)據(jù)讀取尉间,模型還封裝了一個all
方法偿乖,用法如下:
// 查詢用戶數(shù)據(jù)集
// 相當于 Db::table('user')->select([1,2,3]);
$users = User::all([1, 2, 3]);
// 遍歷讀取用戶數(shù)據(jù)
foreach ($users as $user) {
echo $user->id;
echo $user->name;
}
關于模型的get
和all
方法的更多用法,而且也完全可以替代數(shù)據(jù)庫提供的find
和select
方法乌妒,我們會在模型高級用法一章中給你繼續(xù)深入汹想。
其實對于讀取數(shù)據(jù)的操作外邓,模型提供了很強大的處理機制撤蚊,為了避免你初期的時候混淆,我們暫且略過损话,會在以后專門講解侦啸。
讀取操作用法小結:
原則上模型的查詢都應該是靜態(tài)調(diào)用
方法 | 作用 | 返回值 |
---|---|---|
get | 查詢單個記錄 | 模型對象實例 |
find | 查詢單個記錄 | 模型對象實例 |
all | 根據(jù)主鍵查詢多個記錄 | 包含模型對象實例的數(shù)組或者數(shù)據(jù)集 |
select | 根據(jù)條件查詢多個記錄 | 包含模型對象實例的數(shù)組或者數(shù)據(jù)集 |
更新Update
Db
類實現(xiàn)
Db::table('user')
->where('id', 1)
->update([
'name' => 'topthink',
'email' => 'topthink@qq.com',
]);
模型實現(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
方法返回的則是模型的對象實例。模型和
Db
更新方法的最大區(qū)別是模型的更新方法只會更新有變化的數(shù)據(jù)光涂,沒有變化的數(shù)據(jù)是不會更新到數(shù)據(jù)庫的庞萍,如果所有數(shù)據(jù)都沒變化,那么根本就不會去執(zhí)行數(shù)據(jù)庫的更新操作忘闻。
所以你其實會發(fā)現(xiàn)后面的模型更新方法其實根本沒執(zhí)行(因為模型的更新數(shù)據(jù)和原有數(shù)據(jù)是一樣的钝计,沒任何變化,當然你有對數(shù)據(jù)作了自動操作另當別論)齐佳,但你更改name
或者email
屬性的值的話私恬,就會發(fā)現(xiàn)執(zhí)行了更新操作。
更新操作用法小結:
方法 | 作用 | 返回值 |
---|---|---|
save | 更新數(shù)據(jù) | 影響的記錄數(shù) |
update | 更新數(shù)據(jù)(靜態(tài)) | 返回模型對象實例 |
刪除Delete
Db類實現(xiàn)
Db::table('user')
->delete(1);
模型類實現(xiàn)
$user = User::get(1);
$user->delete();
或者靜態(tài)實現(xiàn)
User::destroy(1);
delete
方法沒有任何參數(shù)炼吴,因此只能刪除當前實例的模型數(shù)據(jù)本鸣,destroy
方法支持刪除指定主鍵或者查詢條件的數(shù)據(jù),例如:
// 根據(jù)主鍵刪除多個數(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
方法如果傳入空值硅蹦,會刪除數(shù)據(jù)表的所有數(shù)據(jù)荣德,該問題已經(jīng)在V5.0.9
版本得到修正(不會執(zhí)行任何刪除)。
在模型的刪除功能設計的時候童芹,應該盡量用軟刪除替代實際的刪除涮瞻,一方面是為了避免數(shù)據(jù)丟失,一方面也是為了性能考慮(數(shù)據(jù)庫的刪除操作會導致重建索引辐脖,數(shù)據(jù)量越大影響越大)饲宛,關于軟刪除的用法我們放到高級用法中描述。
刪除操作用法小結:
方法 | 作用 | 返回值 |
---|---|---|
delete | 刪除當前數(shù)據(jù) | 影響的記錄數(shù) |
destroy | 刪除指定數(shù)據(jù)(靜態(tài)) | 影響的記錄數(shù) |
現(xiàn)在我們已經(jīng)掌握了模型的基本CURD操作嗜价,我們來總結下方法區(qū)別:
用法 | Db類 | 模型(動態(tài)) | 模型(靜態(tài)) |
---|---|---|---|
創(chuàng)建 | insert |
save |
create |
更新 | update |
save |
update |
讀取單個 | find |
find |
get |
讀取多個 | select |
select |
all |
刪除 | delete |
delete |
destroy |
除了模型自己的方法操作外艇抠,還可以調(diào)用Db類的所有查詢方法,也就是說Db類的CURD操作方法都可以在模型類中被調(diào)用久锥。
不知道大家注意到一個細節(jié)沒家淤,模型的創(chuàng)建操作和更新操作的動態(tài)方法都是save
,而并沒區(qū)分瑟由。其實對于對象實例來說絮重,所有的數(shù)據(jù)變化都只需要有一個保存行為,至于是創(chuàng)建還是更新那是數(shù)據(jù)庫內(nèi)部的事情歹苦,對不起模型對象不關心青伤。模型會根據(jù)當前的場景自動判斷是創(chuàng)建還是更新操作。
然后要注意幾個注意事項:
- 模型類可以直接調(diào)用Db類的所有方法殴瘦;
- 模型類和Db類的查詢返回類型是完全不同的狠角,即便是調(diào)用同一個方法查詢;
- 模型類封裝的靜態(tài)方法本質上還是調(diào)用的動態(tài)方法蚪腋,只是為了方便不同的需求場景丰歌;
- 模型對象的查詢操作盡量使用靜態(tài)方法調(diào)用姨蟋;
使用查詢構造器
之前我們已經(jīng)知道了,Db
類的所有方法都可以在模型中調(diào)用立帖,因此查詢構造器的用法在模型類中沒有變化眼溶,并且還做了一些增強來支持模型的CURD封裝方法。
下面舉幾個例子說明晓勇,首先是直接使用查詢類提供的鏈式方法完成查詢:
$users = User::where('name', 'like', '%think')
->where('id', 'between', [1, 5])
->order('id desc')
->limit(5)
->select();
所有的鏈式方法都可以直接被模型類靜態(tài)調(diào)用堂飞,而且一樣不分先后次序,你只要掌握了數(shù)據(jù)庫的查詢構造器用法绑咱,就能掌握模型的查詢用法酝静,而且模型類不需要調(diào)用table
方法來指定數(shù)據(jù)表名稱,因為模型已經(jīng)有自己的對應數(shù)據(jù)表規(guī)則羡玛,從這一點來說别智,模型的查詢操作應該比Db
類的查詢操作用法簡單_。
模型可以直接調(diào)用Db
類(確切的說是查詢類)的方法稼稿,無論是靜態(tài)還是動態(tài)調(diào)用薄榛,也就是說你可以把模型類當成Db
類一樣使用(雖然用法一樣,但其實區(qū)別很大让歼,可能查詢條件敞恋、查詢結果和返回類型都不同),這得益于模型類和查詢器類的友好邦交谋右,在某些特殊情況下(例如不希望執(zhí)行全局查詢范圍)可以這樣調(diào)用查詢器類的方法硬猫。
$user = (new User)
->db(false)
->where('name', 'thinkphp')
->find();
db
方法是獲取當前模型的數(shù)據(jù)庫查詢對象的方法,正常使用情況下我們不需要顯式調(diào)用db
方法改执,該方法當傳入false
的時候表示不使用全局查詢范圍啸蜜。
模型類提供的all
方法除了上面提過的根據(jù)主鍵值查詢之外,還支持使用閉包查詢辈挂,閉包方法中可以使用任何的查詢類方法(但不需要在閉包里面調(diào)用查詢)衬横,針對上面的查詢我們可以用閉包方式改造如下:
$users = User::all(function ($query) {
$query->where('name', 'like', '%think')
->where('id', 'between', [1, 5])
->order('id desc')
->limit(5);
});
閉包只有一個參數(shù),就是查詢對象终蒂。
如果你的查詢參數(shù)都是以查詢條件為主的話蜂林,可以給all
方法直接傳入數(shù)組查詢條件即可:
$users = User::all([
'name' => 'thinkphp',
'id' => ['>', 1],
]);
all
方法如果傳入索引數(shù)組,即表示查詢條件拇泣,如果是不帶索引的數(shù)組噪叙,表示查詢多個主鍵。
模型的CURD方法其實用法不僅如此霉翔,模型的get
和all
方法還有很多的用法和參數(shù)睁蕾,更多的功能我們會在后面慢慢敘說。不過相信到目前為止早龟,你已經(jīng)對模型的CURD操作基本了解了惫霸。
使用
Db
類操作數(shù)據(jù)庫的話,同一個連接器類調(diào)用的是同一個查詢器類實例葱弟,而使用模型進行查詢操作的話壹店,每個模型對應的是獨立的查詢器類實例。每個查詢器類實例都對應一個生成器類實例芝加。
數(shù)據(jù)集
模型的單個數(shù)據(jù)查詢返回的都是模型對象實例硅卢,但查詢多個數(shù)據(jù)的時候默認返回的是一個包含模型對象實例的數(shù)組〔卣龋框架提供了一個Collection
數(shù)據(jù)集對象來進行統(tǒng)一的模型的對象化操作将塑,替代默認的數(shù)組數(shù)據(jù)集更好的封裝自己的數(shù)據(jù)處理和業(yè)務邏輯。
設置數(shù)據(jù)集對象后蝌麸,查詢多個數(shù)據(jù)的方法(包括Db類的select
和模型類的all
方法)返回的結果類型就會變成think\model\Collection
對象實例点寥。
有兩種方式可以設置,第一種方式是全局設置數(shù)據(jù)庫的配置參數(shù)(默認設置為array
):
// 設置數(shù)據(jù)集返回類型
'resultset_type' => 'collection',
該設置會影響所有的查詢(包括Db類和模型類)来吩。
第二種方式是在模型類中添加屬性設置
// 設置模型的數(shù)據(jù)集返回類型
protected $resultSetType = 'collection';
該設置僅僅影響設置的模型中的查詢結果敢辩,如果需要多個模型或者全部模型支持,可以使用繼承或者使用第一種數(shù)據(jù)庫配置方式弟疆。
數(shù)據(jù)集對象和普通的二維數(shù)組在使用上的一個最大的區(qū)別就是數(shù)據(jù)是否為空的判斷戚长,二維數(shù)組的數(shù)據(jù)集判斷數(shù)據(jù)為空直接使用
$resultSet = User::all();
if (empty($resultSet)) {
echo '數(shù)據(jù)集為空';
}
如果使用數(shù)據(jù)集對象的話,需要改成:
$resultSet = User::all();
if ($resultSet->isEmpty()) {
echo '數(shù)據(jù)集為空';
}
通用的判斷數(shù)據(jù)是否為空的方式可以用
$resultSet = User::all();
if (0 == count($resultSet)) {
echo '數(shù)據(jù)集為空';
}
其它操作的區(qū)別就是一個是對象的方法操作怠苔,一個是數(shù)組函數(shù)的操作同廉,下面是數(shù)據(jù)集對象的方法和數(shù)組函數(shù)的對應關系:
作用 | 數(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ù)組迭代簡化 | reduce | array_reduce |
數(shù)據(jù)反序 | reverse | array_reverse |
首個元素出棧 | shift | array_shift |
開頭插入元素 | unshift | array_unshift |
元素回調(diào) | each | --- |
過濾元素 | filter | array_filter |
返回指定列 | column | array_column |
元素排序 | sort | array_sort |
打亂元素 | shuffle | shuffle |
截取部分元素 | slice | array_slice |
元素分割 | chunk | array_chunk |
轉換數(shù)組 | toArray | --- |
可以自定義數(shù)據(jù)集的返回對象,然后在里面封裝其它的方法柑司。
一般自定義的數(shù)據(jù)集對象建議繼承think\model\Collection
迫肖,然后在模型中設置resultSetType
屬性值為自定義查詢類的類名。
// 設置模型的數(shù)據(jù)集返回類型
protected $resultSetType = 'app\common\Collection';
總結下數(shù)據(jù)集的優(yōu)勢:
- 數(shù)據(jù)更對象化攒驰;
- 關聯(lián)操作更方便咒程;
- 數(shù)據(jù)集本身可以單獨定義獨立的業(yè)務方法;
分頁查詢
分頁查詢其實也是查詢多個數(shù)據(jù)的一種特殊方式讼育,表現(xiàn)出來通常是頁面上有很多的頁數(shù)帐姻、當前頁數(shù)和上下翻頁按鈕,而分頁數(shù)據(jù)通常是配合數(shù)據(jù)庫的limit
語法來實現(xiàn)分頁查詢奶段,普通的分頁查詢通常需要兩個步驟:首先查詢滿足條件的記錄總數(shù)饥瓷,然后查詢當前分頁的數(shù)據(jù)。然而內(nèi)置分頁查詢只需要調(diào)用paginate
方法就可以實現(xiàn)分頁查詢痹籍,下面是查詢代碼呢铆。
<?php
namespace app\index\controller;
use app\index\model\User;
use think\Controller;
class Index extends Controller
{
public function index($p=1)
{
// 查詢分頁數(shù)據(jù)
$list = User::where('status', 1)->paginate();
// 創(chuàng)建分頁顯示
$this->assign('page', $list);
// 模板渲染輸出
return $this->fetch();
}
}
模板文件中分頁輸出代碼如下:
<div>
總記錄數(shù):{$page->total()}
<ul>
{volist name='page' id='user'}
<li> {$user.name}</li>
{/volist}
</ul>
</div>
{$page->render()}
paginate
方法之后不需要調(diào)用任何的查詢方法,該方法本身就是一個查詢數(shù)據(jù)集的方法蹲缠,而且返回結果是一個think\Paginator
對象棺克,該對象具有數(shù)據(jù)集對象的類似特性悠垛。
使用paginate
方法查詢不需要單獨查詢記錄總數(shù),也不需要使用limit
或者page
方法娜谊,通常作為全局分頁配置可以在配置文件中設置下面的分頁參數(shù):
//分頁配置
'paginate' => [
// 分頁類
'type' => 'bootstrap',
// 分頁變量
'var_page' => 'p',
// 每頁記錄數(shù)
'list_rows' => 15,
],
除了這幾個參數(shù)外确买,還可以在paginate
方法調(diào)用的時候動態(tài)傳入
$list = User::where('status', 1)->paginate([
'type' => 'bootstrap',
'var_page' => 'p',
'list_rows' => 15,
]);
額外的分頁參數(shù)包括:
參數(shù) | 描述 |
---|---|
page | 指定當前頁 |
path | 當前url路徑 |
query | url額外參數(shù)(數(shù)組) |
fragment | url錨點 |
當你的分頁查詢條件來自于URL,需要傳入query參數(shù)纱皆。
大部分情況湾趾,可能只需要傳入每頁的記錄數(shù),直接傳入數(shù)字就表示設置分頁的每頁記錄數(shù)派草。
$list = User::where('status', 1)
->paginate(20);
對于一些復雜的查詢條件搀缠,尤其是使用了join
、group
之類的近迁,可能需要單獨查詢記錄總數(shù):
$total = User::where('status', 1)->count();
$list = User::where('status', 1)
->paginate(20, $total);
對于某些應用艺普,可能并不需要完整的分頁顯示,而只需要顯示上一頁和下一頁鉴竭,這種我們稱之為簡潔模式分頁衷敌,對于這種情況,我們只需要在第二個參數(shù)傳入true
即可拓瞪,簡潔模式的分頁優(yōu)勢是不需要查詢記錄總數(shù)缴罗。
// 簡潔模式分頁
$list = User::where('status', 1)
->paginate(20, true);
添加業(yè)務邏輯
模型的優(yōu)勢不是用來做基礎的CURD操作的,雖然CURD操作也是一種最常見的業(yè)務邏輯祭埂,只是這些基本邏輯無需再定義額外的方法了面氓,系統(tǒng)已經(jīng)內(nèi)置實現(xiàn)了。但實際的應用中蛆橡,一般都需要根據(jù)業(yè)務需求來增加額外的業(yè)務邏輯方法舌界。
以User
模型為例,假設我們需要實現(xiàn)下列功能:
- 用戶注冊泰演;
- 用戶登陸呻拌;
- 獲取用戶信息;
- 獲取用戶的身份角色睦焕;
- ...更多業(yè)務邏輯
那么可以在User
模型添加下面的邏輯方法:
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
/**
* 注冊一個新用戶
* @param array $data 用戶注冊信息
* @return integer|bool 注冊成功返回主鍵藐握,注冊失敗-返回false
*/
public function register($data = [])
{
$result = $this->validate(true)->allowField(true)->save($data);
if ($result) {
return $this->getData('id');
} else {
return false;
}
}
/**
* 用戶登錄認證
* @param string $username 用戶名
* @param string $password 用戶密碼
* @return integer 登錄成功-用戶ID,登錄失敗-返回0或-1
*/
public function login($username, $password)
{
$where['username'] = $username;
$where['status'] = 1;
/* 獲取用戶數(shù)據(jù) */
$user = $this->where($where)->find();
if ($user) {
if (md5($password) != $user->password) {
$this->error = '密碼錯誤';
return 0;
} else {
return $user->id;
}
} else {
$this->error = '用戶不存在或被禁用';
return -1;
}
}
/**
* 獲取用戶信息
* @param integer $uid 用戶主鍵
* @return array|integer 成功返回數(shù)組垃喊,失敗-返回-1
*/
public function info($uid)
{
$user = $this->where('id', $uid)->field('id,username,email,mobile,status')->find();
if ($user && 1 == $user->status) {
// 返回用戶數(shù)據(jù)
return $user->hidden('status')->toArray();
} else {
$this->error = '用戶不存在或被禁用';
return -1;
}
}
/**
* 獲取用戶角色
* @return integer 返回角色信息或者返回-1
*/
public function role()
{
$uid = $this->getData('id');
if ($uid) {
$role = $this->getUserRole($uid);
if ($role) {
return $role;
} else {
$this->error = '用戶未授權';
return 0;
}
} else {
$this->error = '請先登錄';
return -1;
}
}
protected function getUserRole($uid)
{
return $this->table('role')->where('uid', $uid)->find();
}
}
我們先不要在意方法的實現(xiàn)細節(jié)(這些實現(xiàn)代碼并非完美猾普,只是簡單的說明問題),里面的很多調(diào)用方法后面都會一一提及本谜,這里只是告訴你如何在模型類里面添加自己的業(yè)務邏輯初家,下面同時給出在控制器中的調(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('用戶注冊成功');
} 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 '用戶不存在';
}
}
protected function getUserRole()
{
$uid = Session::get('user_id');
$user = User::get($uid);
return $user->role();
}
}
控制器的詳細用法不屬于本書的討論范疇,如果有必要可以參考官方快速入門系列第三部:《控制器從入門到精通》溜在。
從上面的用法中我們可以注意幾點:
- 業(yè)務邏輯應當封裝到具體模型中陌知,并由控制器來調(diào)用;
-
register
和login
方法獲取用戶主鍵的方法區(qū)別掖肋; - 可以設置模型的錯誤信息仆葡,并且用
getError
方法獲取培遵;
總結
通過本章的學習,你應該掌握了模型的基本概念和基礎用法登刺,下一章我們就來學習模型的各種數(shù)據(jù)處理功能籽腕,下一章的內(nèi)容更精彩哦。
上一篇:第四章:高級查詢技巧
下一篇:第六章:模型數(shù)據(jù)處理