基礎:
1. 基礎概念
LAMP
LAMP是基于Linux,Apache,MySQL和PHP的開放資源網絡開發平臺。這個術語來自歐洲,在那里這些程序常用來作為一種標準開發環境。名字來源于每個程序的第一個字母。每個程序在所有權里都符合開放源代碼標準:Linux是開放系統;Apache是最通用的網絡服務器;MySQL是帶有基于網絡管理附加工具的關系數據庫;PHP是流行的對象腳本語言,它包含了多數其它語言的優秀特征來使得它的網絡開發更加有效。開發者在Windows操作系統下使用這些Linux環境里的工具稱為使用WAMP。雖然這些開放源代碼程序本身并不是專門設計成同另外幾個程序一起工作的,但由于它們都是影響較大的開源軟件,擁有很多共同特點,這就導致了這些組件經常在一起使用。在過去的幾年里,這些組件的兼容性不斷完善,在一起的應用情形變得更加普遍。并且它們為了改善不同組件之間的協作,已經創建了某些擴展功能。目前,幾乎在所有的Linux發布版中都默認包含了這些產品。Linux操作系統、Apache服務器、MySQL數據庫和Perl、PHP或者 Python語言,這些產品共同組成了一個強大的Web應用程序平臺。
隨著開源潮流的蓬勃發展,開放源代碼的LAMP已經與J2EE和.Net商業軟件形成三足鼎立之勢,并且該軟件開發的項目在軟件方面的投資成本較低,因此受到整個IT界的關注。從網站的流量上來說,70%以上的訪問流量是LAMP來提供的,LAMP是最強大的網站解決方案.
OOP
面向對象編程(Object Oriented Programming,OOP,面向對象程序設計)是一種計算機編程架構。OOP 的一條基本原則是計算機程序是由單個能夠起到子程序作用的單元或對象組合而成。OOP 達到了軟件工程的三個主要目標:重用性、靈活性和擴展性。為了實現整體運算,每個對象都能夠接收信息、處理數據和向其它對象發送信息。OOP 主要有以下的概念和組件:?組件?- 數據和功能一起在運行著的計算機程序中形成的單元,組件在 OOP 計算機程序中是模塊和結構化的基礎。?
抽象性?- 程序有能力忽略正在處理中信息的某些方面,即對信息主要方面關注的能力。?
封裝?- 也叫做信息封裝:確保組件不會以不可預期的方式改變其它組件的內部狀態;只有在那些提供了內部狀態改變方法的組件中,才可以訪問其內部狀態。每類組件都提供了一個與其它組件聯系的接口,并規定了其它組件進行調用的方法。?
多態性?- 組件的引用和類集會涉及到其它許多不同類型的組件,而且引用組件所產生的結果得依據實際調用的類型。?
繼承性?- 允許在現存的組件基礎上創建子類組件,這統一并增強了多態性和封裝性。典型地來說就是用類來對組件進行分組,而且還可以定義新類為現存的類的擴展,這樣就可以將類組織成樹形或網狀結構,這體現了動作的通用性。?
由于抽象性、封裝性、重用性以及便于使用等方面的原因,以組件為基礎的編程在腳本語言中已經變得特別流行。
MVC
MVC是一個設計模式,它強制性的使應用程序的輸入、處理和輸出分開。使用MVC應用程序被分成三個核心部件:模型(M)、視圖(V)、控制器(C),它們各自處理自己的任務。?視圖 :視圖是用戶看到并與之交互的界面。對老式的Web應用程序來說,視圖就是由HTML元素組成的界面,在新式的Web應用程序中,HTML依舊在視圖中扮演著重要的角色,但一些新的技術已層出不窮,它們包括Adobe Flash和象XHTML,XML/XSL,WML等一些標識語言和Web services。如何處理應用程序的界面變得越來越有挑戰性。MVC一個大的好處是它能為你的應用程序處理很多不同的視圖。在視圖中其實沒有真正的處理發生,不管這些數據是聯機存儲的還是一個雇員列表,作為視圖來講,它只是作為一種輸出數據并允許用戶操縱的方式。?
模型 :模型表示企業數據和業務規則。在MVC的三個部件中,模型擁有最多的處理任務。例如它可能用象EJBs和ColdFusion Components這樣的構件對象來處理數據庫。被模型返回的數據是中立的,就是說模型與數據格式無關,這樣一個模型能為多個視圖提供數據。由于應用于模型的代碼只需寫一次就可以被多個視圖重用,所以減少了代碼的重復性。?
控制器 :控制器接受用戶的輸入并調用模型和視圖去完成用戶的需求。所以當單擊Web頁面中的超鏈接和發送HTML表單時,控制器本身不輸出任何東西和做任何處理。它只是接收請求并決定調用哪個模型構件去處理請求,然后確定用哪個視圖來顯示模型處理返回的數據。?
現在我們總結MVC的處理過程,首先控制器接收用戶的請求,并決定應該調用哪個模型來進行處理,然后模型用業務邏輯來處理用戶的請求并返回數據,最后控制器用相應的視圖格式化模型返回的數據,并通過表示層呈現給用戶。
ORM
對象-關系映射(Object/Relation Mapping,簡稱ORM),是隨著面向對象的軟件開發方法發展而產生的。面向對象的開發方法是當今企業級應用開發環境中的主流開發方法,關系數據庫是企業級應用環境中永久存放數據的主流數據存儲系統。對象和關系數據是業務實體的兩種表現形式,業務實體在內存中表現為對象,在數據庫中表現為關系數據。內存中的對象之間存在關聯和繼承關系,而在數據庫中,關系數據無法直接表達多對多關聯和繼承關系。因此,對象-關系映射(ORM)系統一般以中間件的形式存在,主要實現程序對象到關系數據庫數據的映射。面向對象是從軟件工程基本原則(如耦合、聚合、封裝)的基礎上發展起來的,而關系數據庫則是從數學理論發展而來的,兩套理論存在顯著的區別。為了解決這個不匹配的現象,對象關系映射技術應運而生。
AOP
AOP(Aspect-Oriented Programming,面向方面編程),可以說是OOP(Object-Oriented Programing,面向對象編程)的補充和完善。OOP引入封裝、繼承和多態性等概念來建立一種對象層次結構,用以模擬公共行為的一個集合。當我們需要為分散的對象引入公共行為的時候,OOP則顯得無能為力。也就是說,OOP允許你定義從上到下的關系,但并不適合定義從左到右的關系。例如日志功能。日志代碼往往水平地散布在所有對象層次中,而與它所散布到的對象的核心功能毫無關系。對于其他類型的代碼,如安全性、異常處理和透明的持續性也是如此。這種散布在各處的無關的代碼被稱為橫切(cross-cutting)代碼,在OOP設計中,它導致了大量代碼的重復,而不利于各個模塊的重用。而AOP技術則恰恰相反,它利用一種稱為“橫切”的技術,剖解開封裝的對象內部,并將那些影響了多個類的公共行為封裝到一個可重用模塊,并將其名為“Aspect”,即方面。所謂“方面”,簡單地說,就是將那些與業務無關,卻為業務模塊所共同調用的邏輯或責任封裝起來,便于減少系統的重復代碼,降低模塊間的耦合度,并有利于未來的可操作性和可維護性。AOP代表的是一個橫向的關系,如果說“對象”是一個空心的圓柱體,其中封裝的是對象的屬性和行為;那么面向方面編程的方法,就仿佛一把利刃,將這些空心圓柱體剖開,以獲得其內部的消息。而剖開的切面,也就是所謂的“方面”了。然后它又以巧奪天功的妙手將這些剖開的切面復原,不留痕跡。使用“橫切”技術,AOP把軟件系統分為兩個部分:核心關注點和橫切關注點。業務處理的主要流程是核心關注點,與之關系不大的部分是橫切關注點。橫切關注點的一個特點是,他們經常發生在核心關注點的多處,而各處都基本相似。比如權限認證、日志、事務處理。Aop 的作用在于分離系統中的各種關注點,將核心關注點和橫切關注點分離開來。正如Avanade公司的高級方案構架師Adam Magee所說,AOP的核心思想就是“將應用程序中的商業邏輯同對其提供支持的通用服務進行分離。”
CURD
CURD是一個數據庫技術中的縮寫詞,一般的項目開發的各種參數的基本功能都是CURD。它代表創建(Create)、更新(Update)、讀取(Read)和刪除(Delete)操作。CURD 定義了用于處理數據的基本原子操作。之所以將CURD 提升到一個技術難題的高度是因為完成一個涉及在多個數據庫系統中進行CURD操作的匯總相關的活動,其性能可能會隨數據關系的變化而有非常大的差異。CURD在具體的應用中并非一定使用create、update 、read和delete字樣的方法,但是他們完成的功能是一致的。例如,ThinkPHP就是使用add、save、select和delete方法表示模型的CURD操作。
ActiveRecord
Active Record(中文名:活動記錄)是一種領域模型模式,特點是一個模型類對應關系型數據庫中的一個表,而模型類的一個實例對應表中的一行記錄。Active Record 和 Row Gateway (行記錄入口)十分相似,但前者是領域模型,后者是一種數據源模式。關系型數據庫往往通過外鍵來表述實體關系,Active Record 在數據源層面上也將這種關系映射為對象的關聯和聚集。 Active Record 適合非常簡單的領域需求,尤其在領域模型和數據庫模型十分相似的情況下。如果遇到更加復雜的領域模型結構(例如用到繼承、策略的領域模型),往往需要使用分離數據源的領域模型,結合 Data Mapper (數據映射器)使用。Active Record 驅動框架一般兼有 ORM 框架的功能,但 Active Record 不是簡單的 ORM,正如和 Row Gateway 的區別。由Rails最早提出,遵循標準的ORM模型:表映射到記錄,記錄映射到對象,字段映射到對象屬性。配合遵循的命名和配置慣例,能夠很大程度的快速實現模型的操作,而且簡潔易懂。
單一入口
單一入口通常是指一個項目或者應用具有一個統一(但并不一定是唯一)的入口文件,也就是說項目的所有功能操作都是通過這個入口文件進行的,并且往往入口文件是第一步被執行的。單一入口的好處是項目整體比較規范,因為同一個入口,往往其不同操作之間具有相同的規則。另外一個方面就是單一入口帶來的好處是控制較為靈活,因為攔截方便了,類似如一些權限控制、用戶登錄方面的判斷和操作可以統一處理了。
或者有些人會擔心所有網站都通過一個入口文件進行訪問,是否會造成太大的壓力,其實這是杞人憂天的想法。
2. 目錄結構
目錄/文件 | 說明 |
---|---|
ThinkPHP.php | 框架入口文件 |
Common | 框架公共文件目錄 |
Conf | 框架配置文件目錄 |
Lang | 框架系統語言目錄 |
Lib | 系統核心基類庫目錄 |
Tpl | 系統模板目錄 |
Extend | 框架擴展目錄(關于擴展目錄的詳細信息請參考后面的擴展章節) |
3. 命名規范
- 類文件都是以.class.php為后綴(這里是指的ThinkPHP內部使用的類庫文件,不代表外部加載的類庫文件),使用駝峰法命名,并且首字母大寫,例如DbMysql.class.php;
- 確保文件的命名和調用大小寫一致,是由于在類Unix系統上面,對大小寫是敏感的(而ThinkPHP在調試模式下面,即使在Windows平臺也會嚴格檢查大小寫);
- 類名和文件名一致(包括上面說的大小寫一致),例如 UserAction類的文件命名是UserAction.class.php, InfoModel類的文件名是InfoModel.class.php, 并且不同的類庫的類命名有一定的規范;
- 函數、配置文件等其他類庫文件之外的一般是以.php為后綴(第三方引入的不做要求);
- 函數的命名使用小寫字母和下劃線的方式,例如 get_client_ip;
- 方法的命名使用駝峰法,并且首字母小寫或者使用下劃線“_”,例如 getUserName,_parseType,通常下劃線開頭的方法屬于私有方法;
- 屬性的命名使用駝峰法,并且首字母小寫或者使用下劃線“_”,例如 tableName、_instance,通常下劃線開頭的屬性屬于私有屬性;
- 以雙下劃線“__”打頭的函數或方法作為魔法方法,例如 __call 和 __autoload;
- 常量以大寫字母和下劃線命名,例如 HAS_ONE和 MANY_TO_MANY;
- 配置參數以大寫字母和下劃線命名,例如HTML_CACHE_ON;
- 語言變量以大寫字母和下劃線命名,例如MY_LANG,以下劃線打頭的語言變量通常用于系統語言變量,例如 _CLASS_NOT_EXIST_;
- 對變量的命名沒有強制的規范,可以根據團隊規范來進行;
- ThinkPHP的模板文件默認是以.html 為后綴(可以通過配置修改);
- 數據表和字段采用小寫加下劃線方式命名,并注意字段名不要以下劃線開頭,例如 think_user 表和 user_name字段,類似 _username 這樣的數據表字段可能會被過濾。
4. CBD架構
5. 開發流程
使用ThinkPHP創建應用的一般開發流程是:- 系統設計、創建數據庫和數據表;(可選)
- 項目命名并創建項目入口文件,開啟調試模式;
- 完成項目配置;
- 創建項目函數庫;(可選)
- 開發項目需要的擴展(模式、驅動、標簽庫等);(可選)
- 創建控制器類;
- 創建模型類;(可選)
- 創建模板文件;
- 運行和調試、分析日志;
- 開發和設置緩存功能;(可選)
- 添加路由支持;(可選)
- 安全檢查;(可選 )
- 部署到生產環境。
6. 入口文件
ThinkPHP采用單一入口模式進行項目部署和訪問,無論完成什么功能,一個項目都有一個統一(但不一定是唯一)的入口。應該說,所有項目都是從入口文件開始的,并且所有的項目的入口文件是類似的,入口文件中主要包括:- 定義框架路徑、項目路徑和項目名稱(可選)
- 定義調試模式和運行模式的相關常量(可選)
- 載入框架入口文件(必須)
7. 項目目錄
目錄 | 說明 |
---|---|
Common | 項目公共文件目錄,一般放置項目的公共函數 |
Conf | 項目配置目錄,項目所有的配置文件都放在這里 |
Lang | 項目語言包目錄(可選?如果不需要多語言支持?可刪除) |
Lib | 項目類庫目錄,通常包括Action和Model子目錄 |
Tpl | 項目模板目錄,支持模板主題 |
Runtime | 項目運行時目錄,包括Cache(模板緩存)、Temp(數據緩存)、Data(數據目錄)和Logs(日志文件)子目錄,如果存在分組的話,則首先是分組目錄。 |
- <?php
- ????//定義項目名稱
- ????define('APP_NAME',?'App');
- ????//定義項目路徑
- ????define('APP_PATH',?'./App/');
- ????//加載框架入文件
- ????require?'./App/ThinkPHP/ThinkPHP.php';
APP_PATH?是指項目路徑(必須以“/”結束),項目路徑是指項目的Common、Lib目錄所在的位置,而不是項目入口文件所在的位置。
注意:在類Unix或者Linux環境下面Runtime目錄需要可寫權限。
8. 部署目錄
目錄/文件 | 說明 |
---|---|
ThinkPHP | 系統目錄(下面的目錄結構同上面的系統目錄) |
Public | 網站公共資源目錄(存放網站的Css、Js和圖片等資源) |
Uploads | 網站上傳目錄(用戶上傳的統一目錄) |
Home | 項目目錄(下面的目錄結構同上面的應用目錄) |
Admin | 后臺管理項目目錄 |
…… 更多的項目目錄 | |
index.php | 項目Home的入口文件 |
admin.php | 項目Admin的入口文件 |
…… 更多的項目入口文件 |
事實上,系統目錄和項目目錄可以放到非WEB訪問目錄下面,網站目錄下面只需要放置Public公共目錄和入口文件,從而提高網站的安全性。
?
如果希望自己設置目錄,可以在入口文件里面更改RUNTIME_PATH常量進行更改,例如:- define('RUNTIME_PATH','./App/temp/');
除了自定義編譯緩存目錄之外,還支持自定義編譯緩存文件名,例如:
- define('RUNTIME_FILE','./App/temp/runtime_cache.php');
- //項目配置文件
- ?return?array(
- ????'DEFAULT_MODULE'?????=>?'Index',?//默認模塊
- ????'URL_MODEL'??????????=>?'2',?//URL模式
- ????'SESSION_AUTO_START'?=>?true,?//是否開啟session
- ????//更多配置參數
- ????//...
- ?);
- //項目配置文件
- ?return?array(
- ????'DEFAULT_MODULE'?????=>?'Index',?//默認模塊
- ????'URL_MODEL'??????????=>?'2',?//URL模式
- ????'SESSION_AUTO_START'?=>?true,?//是否開啟session
- ????'USER_CONFIG'????????=>?array(
- ????????'USER_AUTH'?=>?true,
- ????????'USER_TYPE'?=>?2,
- ????),
- ????//更多配置參數
- ????//...
- ?);
9. 慣例配置和項目配置,調試配置
慣例重于配置是系統遵循的一個重要思想,系統內置有一個慣例配置文件(位于系統目錄下面的Conf\convention.php),按照大多數的使用對常用參數進行了默認配置。在項目配置文件里面除了添加內置的參數配置外,還可以額外添加項目需要的配置參數。
- 'APP_STATUS'?=>?'debug',?//應用調試模式狀態
如果想在調試模式下面增加應用狀態,例如測試狀態,則可以在項目配置文件中改變設置如下:
- 'APP_STATUS'?=>?'test',?//應用調試模式狀態
注意:一旦關閉調試模式,項目的調試配置文件即刻失效。
10. 分組配置和讀取配置,動態配置
如果啟用了模塊分組,則可以在對每個分組單獨定義配置文件,分組配置文件位于:項目配置目錄/分組名稱/config.php
可以通過如下配置啟用分組:
- 'APP_GROUP_LIST'?=>?'Home,Admin',?//項目分組設定
- ?'DEFAULT_GROUP'??=>?'Home',?//默認分組
Conf/Home/config.php
Conf/Admin/config.php
每個分組的配置文件僅在當前分組有效,分組配置的定義格式和項目配置是一樣的。
注意:分組名稱區分大小寫,必須和定義的分組名一致。
- C('參數名稱')//獲取已經設置的參數值
- C('USER_CONFIG.USER_TYPE')//獲取用戶配置中的用戶類型設置
設置新的值:
- C('參數名稱','新的參數值');
- C('DATA_CACHE_TIME','60');
獲取已經設置的參數值:
- C('USER_CONFIG.USER_TYPE');
- C('USER_CONFIG.USER_TYPE','1');
- C($array,'name');
獲取緩存的設置列表數據 可以用
- C('','name');?//或者C(null,'name');
11. 擴展配置
項目配置文件在部署模式的時候會納入編譯緩存,也就是說編譯后再修改項目配置文件就不會立刻生效,需要刪除編譯緩存后才能生效。擴展配置文件則不受此限制影響,即使在部署模式下面,修改配置后可以實時生效,并且配置格式和項目配置一樣。設置擴展配置的方式如下(多個文件用逗號分隔):
- 'LOAD_EXT_CONFIG'?=>?'user,db',?//?加載擴展配置文件
- 'LOAD_EXT_CONFIG'?=>?array(
- ????'USER'?=>?'user',?//用戶配置
- ????'DB'???=>?'db',?//數據庫配置
- ?),?//加載擴展配置文件
- C('USER.USER_AUTH_ID');
文件名 | 說明 |
---|---|
config.php | 項目配置文件 |
tags.php | 項目行為配置文件 |
alias.php | 項目別名定義文件 |
debug.php | 項目調試模式配置文件(以及項目設置的APP_STATUS對應的配置文件) |
core.php | 項目追加的核心編譯列表文件(不會覆蓋核心編譯列表) |
12. 函數庫
系統函數庫
庫系統函數庫位于系統的Common目錄下面,有三個文件:common.php是全局必須加載的基礎函數庫,在任何時候都可以直接調用;
functions.php是框架標準模式的公共函數庫,其他模式可以替換加載自己的公共函數庫或者對公共函數庫中的函數進行重新定義;
runtime.php是框架運行時文件,僅在調試模式或者編譯過程才會被加載,因此其中的方法在項目中不能直接調用;
項目函數庫
庫項目函數庫通常位于項目的Common目錄下面,文件名為common.php,該文件會在執行過程中自動加載,并且合并到項目編譯統一緩存,如果使用了分組部署方式,并且該目錄下存在"分組名稱/function.php"文件,也會根據當前分組執行時對應進行自動加載,因此項目函數庫的所有函數也都可以無需手動載入而直接使用。如果項目配置中使用了動態函數加載配置的話,項目Common目錄下面可能會存在更多的函數文件,動態加載的函數文件不會納入編譯緩存。
在特殊的情況下,模式可以改變自動加載的項目函數庫的位置或者名稱。
擴展函數庫
庫我們可以在項目公共目錄下面定義擴展函數庫,方便需要的時候加載和調用。擴展函數庫的函數定義規范和項目函數庫一致,只是函數庫文件名可以隨意命名,一般來說,擴展函數庫并不會自動加載,除非你設置了動態載入。函數加載
系統函數庫和項目函數庫中的函數無需加載就可以直接調用,對于項目的擴展函數庫,可以采用下面兩種方式調用:動態載入
我們可以在項目配置文件中定義LOAD_EXT_FILE參數,例如:
- "LOAD_EXT_FILE"=>"user,db"
手動載入
如果你的函數只是個別模塊偶爾使用,則不需要采用自動加載方式,可以在需要調用的時候采用load方法手動載入,方式如下:
- load("@.user")
13. 類庫
ThinkPHP的類庫包括基類庫和應用類庫,系統的類庫命名規則如下:類庫 | 規則 | 示例 |
---|---|---|
控制器類 | 模塊名+Action | 例如?UserAction、InfoAction |
模型類 | 模型名+Model | 例如?UserModel、InfoModel |
行為類 | 行為名+Behavior | 例如CheckRouteBehavior |
Widget類 | Widget名+Widget | 例如BlogInfoWidget |
驅動類 | 引擎名+驅動名 | 例如DbMysql表示mysql數據庫驅動、CacheFile表示文件緩存驅動 |
基類庫
基類庫是指符合ThinkPHP類庫規范的系統類庫,包括ThinkPHP的核心基類庫和擴展基類庫。核心基類庫目錄位于系統的Lib目錄,核心基類庫也就是Think類庫,擴展基類庫位于Extend/Library目錄,可以擴展ORG 、Com擴展類庫。核心基類庫的作用是完成框架的通用性開發而必須的基礎類和內置支持類等,包含有:目錄 | 調用路徑 | 說明 |
---|---|---|
Lib/Core | Think.Core | 核心類庫包 |
Lib/Behavior | Think.Behavior | 內置行為類庫包 |
Lib/Driver | Think.Driver | 內置驅動類庫包 |
Lib/Template | Think.Template | 內置模板引擎類庫包 |
類名 | 說明 |
---|---|
Action | 系統基礎控制器類 |
App | 系統應用類 |
Behavior | 系統行為基礎類 |
Cache | 系統緩存類 |
Db | 系統抽象數據庫類 |
Dispatcher | URL調度類 |
Log | 系統日志類 |
Model | 系統基礎模型類 |
Think | 系統入口和靜態類 |
ThinkException | 系統基礎異常類 |
View | 視圖類 |
Widget | 系統Widget基礎類 |
應用類庫
應用類庫是指項目中自己定義或者使用的類庫,這些類庫也是遵循ThinkPHP的命名規范。應用類庫目錄位于項目目錄下面的Lib目錄。應用類庫的范圍很廣,包括Action類庫、Model類庫或者其他的工具類庫,通常包括:目錄 | 調用路徑 | 說明 |
---|---|---|
Lib/Action | @.Action或自動加載 | 控制器類庫包 |
Lib/Model | @.Model或自動加載 | 模型類庫包 |
Lib/Behavior | 用B方法調用或自動加載 | 應用行為類庫包 |
Lib/Widget | 用W方法在模板中調用 | 應用Widget類庫包 |
類庫導入
一、Import顯式導入
ThinkPHP模擬了Java的類庫導入機制,統一采用import方法進行類文件的加載。import方法是ThinkPHP內建的類庫導入方法,提供了方便和靈活的文件導入機制,完全可以替代PHP的require和include方法。例如:- import("Think.Util.Session");
- ?import("App.Model.UserModel");
- import("Think.Util.Session");
- ?import("ORG.Util.Page");
- import("MyApp.Action.UserAction");
- ?import("MyApp.Model.InfoModel");
- import("@.Action.UserAction");
- ?import("@.Model.InfoModel");
二,別名導入
除了命名空間的導入方式外,import方法還可以支持別名導入,要使用別名導入,首先要定義別名,我們可以在項目配置目錄下面增加alias.php 用以定義項目中需要用到的類庫別名,例如:- return?array(
- ????'rbac'?=>LIB_PATH.'Common/Rbac.class.php',
- ????'page'?=>LIB_PATH.'Common/Page.class.php',
- ?);
- import("rbac");
- ?import("page");
導入第三方類庫
第三方類庫統一放置在系統擴展目錄下的Vendor 目錄,并且使用vendor 方法導入,其參數和 import 方法是 一致的,只是默認的值有針對變化。?例如,我們把 Zend 的 Filter\Dir.php 放到 Vendor 目錄下面,這個時候 Dir 文件的路徑就是?- Vendor('Zend.Filter.Dir');
Vendor方法也可以支持和import方法一樣的基礎路徑和文件名后綴參數,例如:
- Vendor('Zend.Filter.Dir',dirname(__FILE__),'.class.php');
自動加載
在大多數情況下,我們無需手動導入類庫,而是通過配置采用自動加載機制即可,自動加載機制是真正的按需加載,可以很大程度的提高性能。自動加載有三種情況,按照加載優先級從高到低分別是:別名自動加載、系統規則自動加載和自定義路徑自動加載。一、別名自動加載
在前面我們提到了別名的定義方式,并且采用了import方法進行別名導入,其實所有定義別名的類庫都無需再手動加載,系統會按需自動加載。二、 系統規則自動加載
果你沒有定義別名的話,系統會首先按照內置的規則來判斷加載,系統規則僅針對行為類、模型類和控制器類,搜索規則如下:類名 | 規則 | 說明 |
---|---|---|
行為類 | 規則1 | 搜索系統類庫目錄下面的Behavior目錄 |
規則2 | 搜索系統擴展目錄下面的Behavior目錄 | |
規則3 | 搜索應用類庫目錄下面的Behavior目錄 | |
規則4 | 如果啟用了模式擴展,則搜索模式擴展目錄下面的Behavior目錄 | |
模型類 | 規則1 | 如果啟用分組,則搜索應用類庫目錄的Model/當前分組 目錄 |
規則2 | 搜索應用類庫下面的Model目錄 | |
規則3 | 搜索系統擴展目錄下面的Model目錄 | |
控制器類 | 規則1 | 如果啟用分組,則搜索應用類庫目錄的Action/當前分組 目錄 |
規則2 | 搜索項目類庫目錄下面的Action目錄 | |
規則3 | 搜索系統擴展目錄下面的Action目錄 |
三、 自定義路徑自動加載
當你的類庫比較集中在某個目錄下面,而且不想定義太多的別名導入的話,可以使用自定義路徑自動加載方式,這種方式需要在項目配置文件中添加自動加載的搜索路徑,例如:- 'APP_AUTOLOAD_PATH'?=>'@.Common,@.Tool',
注意:自動搜索路徑定義只能采用命名空間方式,也就是說這種方式只能自動加載項目類庫目錄和基類庫目錄下面的類庫文件。
控制器:
1. URL模式
采用傳統的URL參數模式
- http://serverName/appName/?m=module&a=action&id=1
默認情況使用PATHINFO模式,ThinkPHP內置強大的PATHINFO支持,提供靈活和友好URL支持。PATHINFO模式自動識別模塊和操作,例如
- http://serverName/appName/module/action/id/1/或者
- http://serverName/appName/module,action,id,1/
該URL模式和PATHINFO模式功能一樣,除了可以不需要在URL里面寫入口文件,和可以定義.htaccess 文件外。在開啟了Apache的URL_REWRITE模塊后,就可以啟用REWRITE模式了,具體參考下面的URL重寫部分。
兼容模式是普通模式和PATHINFO模式的結合,并且可以讓應用在需要的時候直接切換到PATHINFO模式而不需要更改模板和程序,還可以和URL_WRITE模式整合。兼容模式URL可以支持任何的運行環境。
- http://serverName/appName/?s=/module/action/id/1/
- http://serverName/appName/?s=module~action~id~1
2. 模塊和操作
http://域名/項目名/分組名/模塊名/操作名/其他參數Dispatcher會根據URL地址來獲取當前需要執行的項目、分組(如果有定義的話)模塊、操作以及其他參數,在某些情況下,項目名可能不會出現在URL地址中(通常情況下入口文件則代表了某個項目,而且入口文件可以被隱藏)。
每一個模塊就是一個控制器類,通常位于項目的Lib\Action目錄下面。
例如,如果設置:
- 'ACTION_SUFFIX'=>'Act'
3. 定義控制器和空操作,空模塊
一個應用如果不需要和數據庫交互的時候可以不需要定義模型類,但是必須定義Action控制器,一般位于項目的Lib/Action目錄下面。Action控制器的定義非常簡單,只要繼承Action基礎類就可以了,例如:
- Class?UserAction?extends?Action{}
4. 模塊分組
配置參數 | 說明 |
---|---|
APP_GROUP_LIST | 項目分組列表(配置即表示開啟分組) |
DEFAULT_GROUP | 默認分組(默認值為Home) |
TMPL_FILE_DEPR | 分組模板下面模塊和操作的分隔符,默認值為“/” |
VAR_GROUP | 分組的URL參數名,默認為g(普通模式URL才需要) |
- 'APP_GROUP_LIST'?=>?'Home,Admin',?//項目分組設定
- ?'DEFAULT_GROUP'??=>?'Home',?//默認分組
5. URL偽靜態
ThinkPHP支持偽靜態URL設置,可以通過設置URL_HTML_SUFFIX參數隨意在URL的最后增加你想要的靜態后綴,而不會影響當前操作的正常執行。例如,我們設置- 'URL_HTML_SUFFIX'=>'shtml'
- http://serverName/Blog/read/id/1
- http://serverName/Blog/read/id/1.shtml
注意:偽靜態后綴設置時可以不包含后綴中的“.”。所以,下面的配置其實是等效的:
- 'URL_HTML_SUFFIX'=>'.shtml'
- 'URL_HTML_SUFFIX'=>'html|shmtl|xml'?//?多個用?|?分割
6. URL路由
ThinkPHP支持URL路由功能,要啟用路由功能,需要設置URL_ROUTER_ON 參數為true。開啟路由功能后,并且配置URL_ROUTE_RULES參數后,系統會自動進行路由檢測,如果在路由定義里面找到和當前URL匹配的路由名稱,就會進行路由解析和重定向。7. URL重寫
詳情見:http://doc.thinkphp.cn/manual/url_rewrite.html
8. URL生成
為了配合所使用的URL模式,我們需要能夠動態的根據當前的URL設置生成對應的URL地址,為此,ThinkPHP內置提供了U方法,用于URL的動態生成,可以確保項目在移植過程中不受環境的影響。
U方法的定義規則如下(方括號內參數根據實際應用決定):
- U('[分組/模塊/操作]?參數'?[,'參數','偽靜態后綴','是否跳轉','顯示域名'])
如果不定義項目和模塊的話 就表示當前項目和模塊名稱,下面是一些簡單的例子:
- U('User/add')?//?生成User模塊的add操作的URL地址
- U('Blog/read?id=1')?//?生成Blog模塊的read操作?并且id為1的URL地址
- U('Admin/User/select')?//?生成Admin分組的User模塊的select操作的URL地址
U方法的第二個參數支持數組和字符串兩種定義方式,如果只是字符串方式的參數可以在第一個參數中定義,例如:
- U('Blog/cate',array('cate_id'=>1,'status'=>1))
- U('Blog/cate','cate_id=1&status=1')
- U('Blog/cate?cate_id=1&status=1')
三種方式是等效的,都是 生成Blog模塊的cate操作 并且cate_id為1 status為1的URL地址
但是不允許使用下面的定義方式來傳參數
- U('Blog/cate/cate_id/1/status/1')
- U('Blog/read?id=1')這個定義為例。
http://serverName/index.php?m=Blog&a=read&id=1
如果當前URL設置為PATHINFO模式的話,同樣的方法最后生成的URL地址是:?
http://serverName/index.php/Blog/read/id/1
如果當前URL設置為REWRITE模式的話,同樣的方法最后生成的URL地址是:?
http://serverName/Blog/read/id/1
如果當前URL設置為REWRITE模式,并且設置了偽靜態后綴為.html的話,同樣的方法最后生成的URL地址是:?
http://serverName/Blog/read/id/1.html
U方法還可以支持路由,如果我們定義了一個路由規則為:
- ?'news/:id\d'=>'News/read'
- U('/news/1')
- http://serverName/index.php/news/1
注意:如果你是在模板文件中直接使用U方法的話,需要采用 {:U('參數1', '參數2'…)} 的方式,具體參考模板引擎章節的8.3 使用函數內容。
如果你的應用涉及到多個子域名的操作地址,那么也可以在U方法里面指定需要生成地址的域名,例如:
- U('Blog/read@blog.thinkphp.cn','id=1');
@后面傳入需要指定的域名即可。
此外,U方法的第5個參數如果設置為true,表示自動識別當前的域名,并且會自動根據子域名部署設置APP_SUB_DOMAIN_DEPLOY和APP_SUB_DOMAIN_RULES自動匹配生成當前地址的子域名。
如果開啟了URL_CASE_INSENSITIVE,則會統一生成小寫的URL地址。
?
9. URL大小寫
只要在項目配置中,增加:
?
- 'URL_CASE_INSENSITIVE'?=>true
就可以實現URL訪問不再區分大小寫了。
這里需要注意一個地方,如果我們定義了一個UserTypeAction的模塊類,那么URL的訪問應該是:
- http://serverName/index.php/user_type/list
- ?//而不是
- http://serverName/index.php/usertype/list
利用系統提供的U方法可以為你自動生成相關的URL地址。
如果設置
- 'URL_CASE_INSENSITIVE'?=>false
的話,URL就又變成:
- http://serverName/index.php/UserType/list
注意:URL不區分大小寫并不會改變系統的命名規范,并且只有按照系統的命名規范后才能正確的實現URL不區分大小寫。
?
10. 前置和后置操作
系統會檢測當前操作是否具有前置和后置操作,如果存在就會按照順序執行,前置和后置操作的方法名是在要執行的方法前面加 _before_和_after_,例如:
?
- class?CityAction?extends?Action{
- ????//前置操作方法
- ????public?function?_before_index(){
- ????????echo?'before<br/>';
- ????}
- ????public?function?index(){
- ????????echo?'index<br/>';
- ????}
- ????//后置操作方法
- ????public?function?_after_index(){
- ????????echo?'after<br/>';
- ????}
- ?}
需要注意的是,在有些方法里面使用了exit或者錯誤輸出之類的話 有可能不會再執行后置方法了。
例如,如果在當前操作里面調用了系統Action的error方法,那么將不會再執行后置操作,但是不影響success方法的后置方法執行。
11. 跨模塊調用
例如,我們在Index模塊調用User模塊的操作方法
- class?IndexAction?extends?Action{
- ????public?function?index(){
- ????????//實例化UserAction
- ????????$User?=?new?UserAction();
- ????????//其他用戶操作
- ?????????//...
- ????????$this->display();?//輸出頁面模板
- ????}
- ?}
并且為了方便跨模塊調用,系統內置了A方法和R方法。 ???$User?=?A('User');
事實上,A方法還支持跨分組或者跨項目調用,默認情況下是調用當前項目下面的模塊。
跨項目調用的格式是:
A('[項目名://][分組名/]模塊名')
例如:
- A('User')?//表示調用當前項目的User模塊
- A('Admin://User')?//表示調用Admin項目的User模塊
- A('Admin/User')?//表示調用Admin分組的User模塊
- A('Admin://Tool/User')?//表示調用Admin項目Tool分組的User模塊
R('[項目名://][分組名/]模塊名/操作名',array('參數1','參數2'…))
例如:
- R('User/info')?//表示調用當前項目的User模塊的info操作方法
- R('Admin/User/info')?//表示調用Admin分組的User模塊的info操作方法
- R('Admin://Tool/User/info')?//表示調用Admin項目Tool分組的User模塊的info操作方法
- class?UserAction?extends?Action{
- ????protected?function?info($id){
- ????????$User?=?M('User');
- ????????$User->find($id);
- ????????//...
- ????}
- ?}
- R('User/info',array(15))
12. 頁面跳轉
系統的Action類內置了兩個跳轉方法success和error,用于頁面跳轉提示,而且可以支持ajax提交。使用方法很簡單,舉例如下:
- $User?=?M('User');?//實例化User對象
- $result?=?$User->add($data);?
- if($result){
- ????//設置成功后跳轉頁面的地址,默認的返回頁面是$_SERVER['HTTP_REFERER']
- ????$this->success('新增成功',?'User/list');
- }?else?{
- ????//錯誤頁面的默認跳轉頁面是返回前一頁,通常不需要設置
- ????$this->error('新增失敗');
- }
- //默認錯誤跳轉對應的模板文件
- 'TMPL_ACTION_ERROR'?=>?THINK_PATH?.?'Tpl/dispatch_jump.tpl';
- //默認成功跳轉對應的模板文件
- 'TMPL_ACTION_SUCCESS'?=>?THINK_PATH?.?'Tpl/dispatch_jump.tpl';
- //默認錯誤跳轉對應的模板文件
- 'TMPL_ACTION_ERROR'?=>?'Public:error';
- //默認成功跳轉對應的模板文件
- 'TMPL_ACTION_SUCCESS'?=>?'Public:success';
$msgTitle | 操作標題 |
$message | 頁面提示信息 |
$status | 操作狀態 1表示成功 0 表示失敗 具體還可以由項目本身定義規則 |
$waitSecond | 跳轉等待時間 單位為秒 |
$jumpUrl | 跳轉頁面地址 |
- $this->assign('var1','value1');
- $this->assign('var2','value2');
- $this->error('錯誤的參數','要跳轉的URL地址');
當采用AJAX方式提交的時候,會自動調用ajaxReturn方法傳值過去(包括跳轉的URL地址url和狀態值status)
13. 重定向
Action類的redirect方法可以實現頁面的重定向功能。redirect方法的參數用法和U函數的用法一致(參考上面的URL生成部分),例如:
- //重定向到New模塊的Category操作
- $this->redirect('New/category',?array('cate_id'?=>?2),?5,?'頁面跳轉中...');
如果你僅僅是想重定向要一個指定的URL地址,而不是到某個模塊的操作方法,可以直接使用redirect方法重定向,例如:
- //重定向到指定的URL地址
- redirect('/New/category/cate_id/2',?5,?'頁面跳轉中...')
14. 獲取系統變量
- $this->方法名("變量名",["過濾方法"],["默認值"])
方法名可以支持:
方法名 | 含義 |
---|---|
_get | 獲取GET參數 |
_post | 獲取POST參數 |
_param | 自動判斷請求類型獲取GET、POST或者PUT參數(3.1新增) |
_request | 獲取REQUEST?參數 |
_put | 獲取PUT?參數 |
_session | 獲取?$_SESSION?參數 |
_cookie | 獲取?$_COOKIE?參數 |
_server | 獲取?$_SERVER?參數 |
_globals | 獲取?$GLOBALS參數 |
變量名:(必須)是要獲取的系統變量的名稱
過濾方法:(可選)可以用任何的內置函數或者自定義函數名,如果沒有指定的話,采用默認的htmlspecialchars函數進行安全過濾(由DEFAULT_FILTER 參數配置),參數就是前面方法名獲取到的值,也就是說如果調用:
- $this->_get("name");
最終調用的結果就是 htmlspecialchars($_GET["name"]),如果要改變過濾方法,可以使用:
- $this->_get("name","strip_tags");
默認值:(可選)是要獲取的參數變量不存在的情況下設置的默認值,例如:
- $this->_get("id","strip_tags",0);
如果$_GET["id"] 不存在的話,會返回0。
如果沒有設置任何默認值的話,系統默認返回NULL。
也可以支持多函數過濾。
例如,可以設置:
- ?'DEFAULT_FILTER'=>'htmlspecialchars,strip_tags'
那么在控制器類如果調用
- $this->_get('id');
的話,會依次對$_GET['id'] 變量進行htmlspecialchars和strip_tags方法過濾后返回結果。
下面調用方式也同樣支持:
- $this->_get('id','htmlspecialchars,strip_tags',0);
其他變量獲取方法用法相同。
支持獲取全部變量,例如:
- $this->_get();
表示獲取$_GET變量值。
支持不過濾處理
如果不希望過濾某個參數,可以使用
- $this->_get('id',false);
- $this->_post('id',false);
- ?//或者
- $this->_get('id','');
- $this->_post('id','');
第二個參數使用false或者空字符串則表示不作任何過濾處理,即使我們有配置默認的過濾方法。
如果我們忽略第二個參數調用的話
- $this->_get('id');
- $this->_post('id');
則表示調用默認的過濾方法(由DEFAULT_FILTER參數進行配置)。
3.1版本開始,Action類增加_param方法,可以自動根據當前請求類型(例如GET POST)獲取參數。
例如:
- $this->_param('id');
當前為get方式提交的時候,就是獲取$_GET['id'](進行默認過濾后)的值
當前為post方式提交的時候,就是獲取$_POST['id'](進行默認過濾后)的值
還可以用_param方法獲取URL中的參數
- $this->_param(0);?//?獲取PATHINFO地址中的第一個參數
- $this->_param(2);?//?獲取PATHINFO地址中的第3個參數
15. 判斷請求類型
系統Action類內置了一些判斷方法用于判斷請求類型,包括:
方法 | 說明 |
---|---|
isGet | 判斷是否是GET方式提交 |
isPost | 判斷是否是POST方式提交 |
isPut | 判斷是否是PUT方式提交 |
isDelete | 判斷是否是DELETE方式提交 |
isHead | 判斷是否是HEAD提交 |
使用舉例如下:
- class?UserAction?extends?Action{
- ????public?function?update(){
- ????????if?($this->isPost()){
- ????????????$User?=?M('User');
- ????????????$User->create();
- ????????????$User->save();
- ????????????$this->success('保存完成');
- ????????}else{
- ????????????$this->error('非法請求');
- ????????}
- ????}
- ?}
isAjax 是否屬于AJAX提交
需要注意的是,如果使用的是ThinkAjax或者自己寫的Ajax類庫的話,需要在表單里面添加一個隱藏域,告訴后臺屬于ajax方式提交,默認的隱藏域名稱是ajax(可以通過VAR_AJAX_SUBMIT配置),如果是JQUERY類庫的話,則無需添加任何隱藏域即可自動判斷。
16. 獲取URL參數
我們可以把URL地址 News/archive/2012/01/15 按照“/”分成多個參數,$_GET["_URL_"][0] 獲取的就是News,$_GET["_URL_"][1]獲取的就是archive,依次類推,可以通過數字索引獲取所有的URL參數。
VAR_URL_PARAMS參數,默認值是:
- ????'VAR_URL_PARAMS'??????=>?'_URL_',?//?PATHINFO?URL參數變量
我們訪問
- http://serverName.com/index.php/Blog/read/2012/03
$GET['_URL_'][2] 獲取參數,表示獲取PATH_INFO的URL參數
Blog/read/2012/03中的第3個參數(數組索引從0開始)
- $year?=?$GET['_URL_'][2];?//?2012
- $month?=?$GET['_URL_'][3];?//??03
_param方法的用法同_get和_post等方法,區別在于,_param方法能夠自動根據當前請求類型自動獲取相應的參數,例如:
如果當前是get請求方式,
- $this->_param('id');?
當采用POST請求方式的時候,同樣的代碼將會返回$_POST['id']的處理數據
如果采用的是PUT請求,那么會自動返回PUT的處理數據,而無需開發人員進行判斷。
并且需要注意的是,無論是什么方式的請求,系統都可以支持URL參數的獲取,如果C('VAR_URL_PARAMS')設置不為空的話,就可以使用:
- $this->_param(1);
- $this->_param(2);
- $year?=?$this->_param(2);
- $month?=?$this->_param(3);
這樣的好處是可以不需要使用路由功能就可以獲取某個不規則的URL地址中的參數。
17. AJAX返回
系統支持任何的AJAX類庫,Action類提供了ajaxReturn方法用于AJAX調用后返回數據給客戶端。并且支持JSON、XML和EVAL三種方式給客戶端接受數據,通過配置DEFAULT_AJAX_RETURN進行設置,默認配置采用JSON格式返回數據,在選擇不同的AJAX類庫的時候可以使用不同的方式返回數據。要使用ThinkPHP的ajaxReturn方法返回數據的話,需要遵守一定的返回數據的格式規范。
status | 操作狀態 |
info | 提示信息 |
data | 返回數據 |
- $this->ajaxReturn(返回數據,提示信息,操作狀態);
- $User?=?M("User");?//?實例化User對象
- $result?=?$User->add($data);
- ?if?($result){
- ????//?成功后返回客戶端新增的用戶ID,并返回提示信息和操作狀態
- ????$this->ajaxReturn($result,"新增成功!",1);
- ?}else{
- ????//?錯誤后返回錯誤的操作狀態和提示信息
- ????$this->ajaxReturn(0,"新增錯誤!",0);
- ?}
在客戶端接受數據的時候,根據使用的編碼格式進行解析即可。
如果需要改變Ajax返回的數據格式,可以在控制器Action中增加ajaxAssign方法定義,定義格式如下:
- public?function?ajaxAssign(&$result)?{
- ????//?返回數據中增加url屬性
- ????$result['url']?=?$this->url;
- ?}
3.1版本以后,ajaxReturn方法可以更加靈活的進行ajax傳值,并且廢棄了ajaxAssign方法擴展。能夠完全定義傳值的數組和類型,例如:
- $data['status']?=?1;
- $data['info']?=?'info';
- $data['size']?=?9;
- $data['url']?=?$url;
- $this->ajaxReturn($data,'JSON');
data傳值數組可以隨意定義。
改進后的ajaxReturn方法也兼容之前的寫法:
- $this->ajaxReturn($data,'info',1);
系統會自動把info和1兩個參數并入$data數組中,等同于賦值
- $data['info']?=?'info';
- $data['status']?=?1;
18. Action參數綁定
http://doc.thinkphp.cn/manual/action_param_bind.html
19. 多層控制器支持
3.1版本開始,控制器支持自定義分層。同時A方法增加第二個參數layer,用于設置控制器分層。例如
- A('User','Event');
UserEvent如果繼承Action類的話,可以使用Action類所有的功能。
- A('User','Api');
分層控制器僅用于內部調用,URL訪問的控制器還是Action層,但是可以配置
DEFAULT_C_LAYER修改默認控制器層名稱(該參數默認值為Action)。
模型:
模型名(類名) | 約定對應數據表(假設數據庫的前綴定義是?think_) |
---|---|
UserModel | think_user |
UserTypeModel | think_user_type |
屬性 | 說明 |
---|---|
tableName | 不包含表前綴的數據表名稱,一般情況下默認和模型名稱相同,只有當你的表名和當前的模型類的名稱不同的時候才需要定義。 |
trueTableName | 包含前綴的數據表名稱,也就是數據庫中的實際表名,該名稱無需設置,只有當上面的規則都不適用的情況或者特殊情況下才需要設置。 |
dbName | 定義模型當前對應的數據庫名稱,只有當你當前的模型類對應的數據庫名稱和配置文件不同的時候才需要定義。 |
1. 模型實例化
在沒有定義任何模型的時候,我們可以使用下面的方法實例化一個模型類來進行操作:
- //實例化User模型
- $User?=?new?Model('User');
- ?//或者使用M()快捷方法實例化,和上面的方法是等效的
- $User?=?M('User');
- ?//執行其他的數據操作
- $User->select();
- $User?=?new?CommonModel('User');
模型類的實例化方法有三個參數,第一個參數是模型名稱,第二個參數用于設置數據表的前綴(留空則取當前項目配置的表前綴),第三個參數用于設置當前使用的數據庫連接信息(留空則取當前項目配置的數據庫連接信息),例如:
- $User?=?new?CommonModel('User','think_','db_config');
- $User?=?M('CommonModel:User','think_','db_config');
這種情況是使用的最多的,一個項目不可避免的需要定義自身的業務邏輯實現,就需要針對每個數據表定義一個模型類,例如UserModel 、InfoModel等等。
定義的模型類通常都是放到項目的Lib\Model目錄下面。例如,
- <?php
- ????class?UserModel?extends?Model{
- ????????public?function?getTopUser(){
- ????????????//添加自己的業務邏輯
- ?????????????//?...
- ????????}
- ????}
- <?php
- ????//實例化自定義模型
- ????$User?=?new?UserModel();
- ????//或者使用D快捷方法
- ????$User?=?D('User');
- ????//執行具體的數據操作
- ????$User->select();
D方法還可以支持跨項目和分組調用,需要使用:
- //實例化Admin項目的User模型
- D('Admin://User')
- ?//實例化Admin分組的User模型
- D('Admin/User')
2. 字段定義
字段緩存保存在Runtime/Data/_fields/ 目錄下面,緩存機制是每個模型對應一個字段緩存文件(而并非每個數據表對應一個字段緩存文件),命名格式是:數據庫名.模型名.php
- 'DB_FIELDS_CACHE'=>false
如果需要顯式獲取當前數據表的字段信息,可以使用模型類的getDbFields方法來獲取當前數據對象的全部字段信息,例如:
- $fields?=?$User->getDbFields();
如果不希望依賴字段緩存或者想提高性能,也可以在模型類里面手動定義數據表字段的名稱,可以避免IO加載的效率開銷,在模型類里面添加fields屬性即可,定義格式如下:
- <?php
- ????class?UserModel?extends?Model{
- ????????protected?$fields?=?array(
- ????????????'id',?'username',?'email',?'age',?'_pk'?=>?'id',?'_autoinc'?=>?true
- ????????);
- ????}
3. 數據主鍵
ThinkPHP的默認約定每個數據表的主鍵名采用統一的id作為標識,并且是自動增長類型的。系統會自動識別當前操作的數據表的字段信息和主鍵名稱,所以即使你的主鍵不是id,也無需進行額外的設置,系統會自動識別。要在外部獲取當前數據對象的主鍵名稱,請使用下面的方法:- $pk?=?$Model->getPk();
?4. 屬性訪問
ThinkPHP的模型對象實例本身也是一個數據對象,可以支持對象和數組兩種方式來訪問數據屬性,例如下面的方式采用數據對象的方式來訪問User模型的屬性:
- //實例化User模型
- $User?=?D('User');
- ?//查詢用戶數據
- $User->find(1);
- ?//獲取name屬性的值
- echo?$User->name;
- ?//設置name屬性的值
- $User->name?=?'ThinkPHP';
除了find方法會產生數據對象屬性外,data方法和create方法也會產生數據對象,例如:
- $User?=?D('User');
- $User->create();
- echo?$User->name;
- //實例化User模型
- $User?=?D('User');
- ?//查詢用戶數據
- $data?=?$User->find(1);
- ?//獲取name屬性的值
- echo?$data['name'];
- ?//設置name屬性的值
- $data['name']?=?'ThinkPHP';
5. 跨庫操作
ThinkPHP可以支持模型的同一數據庫服務器的跨庫操作,跨庫操作只需要簡單配置一個模型所在的數據庫名稱即可,例如,假設UserModel對應的數據表在數據庫user下面,而InfoModel對應的數據表在數據庫info下面,那么我們只需要進行下面的設置即可。- class?UserModel?extends?Model?{
- ????protected?$dbName?=?'user';
- ?}
- ?class?InfoModel?extends?Model?{
- ????protected?$dbName?=?'info';
- ?}
- $User?=?D('User');?
- $User->select();
- echo?$User->getLastSql();
- ?//?輸出的SQL語句為?select?*?from?user.think_user?
- protected?$tablePrefix?=?'other_';
- $User?=?M('user.User','other_');?
6. 連接數據庫
ThinkPHP內置了抽象數據庫訪問層,把不同的數據庫操作封裝起來,我們只需要使用公共的Db類進行操作,而無需針對不同的數據庫寫不同的代碼和底層實現,Db類會自動調用相應的數據庫驅動來處理。如果應用需要使用數據庫,必須配置數據庫連接信息,數據庫的配置文件有多種定義方式。
常用的配置方式是在項目配置文件中添加下面的參數:
- <?php
- ????//項目配置文件
- ????return?array(
- ????????//數據庫配置信息
- ????????'DB_TYPE'???=>?'mysql',?//?數據庫類型
- ????????'DB_HOST'???=>?'localhost',?//?服務器地址
- ????????'DB_NAME'???=>?'thinkphp',?//?數據庫名
- ????????'DB_USER'???=>?'root',?//?用戶名
- ????????'DB_PWD'????=>?'',?//?密碼
- ????????'DB_PORT'???=>?3306,?//?端口
- ????????'DB_PREFIX'?=>?'think_',?//?數據庫表前綴?
- ????????//其他項目配置參數
- ????????//?...
- ????);
或者采用如下配置
- 'DB_DSN'?=>?'mysql://username:password@localhost:3306/DbName'
注意:如果要設置分布式數據庫,暫時不支持DB_DSN方式配置。
- //PDO連接方式
- ?'DB_TYPE'???=>?'pdo',?//?數據庫類型
- ?'DB_USER'???=>?'root',?//?用戶名
- ?'DB_PWD'????=>?'',?//?密碼
- ?'DB_PREFIX'?=>?'think_',?//?數據庫表前綴?
- ?'DB_DSN'????=>?'mysql:host=localhost;dbname=thinkphp;charset=UTF-8'
配置文件定義的數據庫連接信息一般是系統默認采用的,因為一般一個項目的數據庫訪問配置是相同的。該方法系統在連接數據庫的時候會自動獲取,無需手動連接。可以對每個項目和不同的分組定義不同的數據庫連接信息,如果開啟了調試模式的話,還可以在不同的應用狀態的配置文件里面定義獨立的數據庫配置信息。
第二種 在模型類里面定義connection屬性
如果在某個模型類里面定義了connection屬性的話,則實例化該自定義模型的時候會采用定義的數據庫連接信息,而不是配置文件中設置的默認連接信息,通常用于某些數據表位于當前數據庫連接之外的其它數據庫,例如:
- //在模型里單獨設置數據庫連接信息
- ?protected?$connection?=?array(
- ????'db_type'??=>?'mysql',
- ????'db_user'??=>?'root',
- ????'db_pwd'???=>?'1234',
- ????'db_host'??=>?'localhost',
- ????'db_port'??=>?'3306',
- ????'db_name'??=>?'thinkphp'
- ?);
也可以采用DSN方式定義,例如:
- //或者使用DSN定義
- ?protected?$connection?=?'mysql://root:1234@localhost:3306/thinkphp';
如果我們已經在配置文件中配置了額外的數據庫連接信息,例如:
- //數據庫配置1
- ?'DB_CONFIG1'?=>?array(
- ????'db_type'??=>?'mysql',
- ????'db_user'??=>?'root',
- ????'db_pwd'???=>?'1234',
- ????'db_host'??=>?'localhost',
- ????'db_port'??=>?'3306',
- ????'db_name'??=>?'thinkphp'
- ?),
- ?//數據庫配置2
- ?'DB_CONFIG2'?=>?'mysql://root:1234@localhost:3306/thinkphp';
那么,我們可以把模型類的屬性定義改為:
- //調用配置文件中的數據庫配置1
- ?protected?$connection?=?'DB_CONFIG1';
- ?//調用配置文件中的數據庫配置2
- ?protected?$connection?=?'DB_CONFIG2';
- $User?=?M('User','other_','mysql://root:1234@localhost/demo');?
- $User?=?M('User','other_','DB_CONFIG2');?
- $User?=?M('User',Null,'DB_CONFIG2');?
7. 切換數據庫
只需要調用Model類的db方法,用法:
- Model->db("數據庫編號","數據庫配置");
數據庫編號用數字格式,對于已經調用過的數據庫連接,是不需要再傳入數據庫連接信息的,系統會自動記錄。對于默認的數據庫連接,內部的數據庫編號是0,因此為了避免沖突,請不要再次定義數據庫編號為0的數據庫配置。
數據庫配置的定義方式和模型定義connection屬性一樣,支持數組、字符串以及調用配置參數三種格式。
Db方法調用后返回當前的模型實例,直接可以繼續進行模型的其他操作,所以該方法可以在查詢的過程中動態切換,例如:
- $this->db(1,"mysql://root:123456@localhost:3306/test")->query("查詢SQL");
該方法添加了一個編號為1的數據庫連接,并自動切換到當前的數據庫連接。
當第二次切換到相同的數據庫的時候,就不需要傳入數據庫連接信息了,可以直接使用:
- $this->db(1)->query("查詢SQL");
如果需要切換到默認的數據庫連接,只需要調用:
- $this->db(0);
如果我們已經在項目配置中定義了其他的數據庫連接信息,例如:
- //數據庫配置1
- ?'DB_CONFIG1'?=?array(
- ????'db_type'??=>?'mysql',
- ????'db_user'??=>?'root',
- ????'db_pwd'???=>?'1234',
- ????'db_host'??=>?'localhost',
- ????'db_port'??=>?'3306',
- ????'db_name'??=>?'thinkphp'
- ?),
- ?//數據庫配置2
- ?'DB_CONFIG2'?=>?'mysql://root:1234@localhost:3306/thinkphp';
我們就可以直接在db方法中調用配置進行連接了:
- $this->db(1,"DB_CONFIG1")->query("查詢SQL");
- $this->db(2,"DB_CONFIG2")->query("查詢SQL");
如果切換數據庫之后,數據表和當前不一致的話,可以使用table方法指定要操作的數據表:
- $this->db(1)->table("top_user")->find();
我們也可以直接用M方法切換數據庫,例如:
- M("User","think_","mysql://root:123456@localhost:3306/test")->query("查詢SQL");
或者
- M("User","think_","DB_CONFIG1")->query("查詢SQL");
8. 分布式數據庫
ThinkPHP內置了分布式數據庫的支持,包括主從式數據庫的讀寫分離,但是分布式數據庫必須是相同的數據庫類型。配置DB_DEPLOY_TYPE 為1 可以采用分布式數據庫支持。如果采用分布式數據庫,定義數據庫配置信息的方式如下:
- //在項目配置文件里面定義
- ?return?array(
- ????//分布式數據庫配置定義
- ????'DB_TYPE'???=>?'mysql',?//分布式數據庫類型必須相同
- ????'DB_HOST'???=>?'192.168.0.1,192.168.0.2',
- ????'DB_NAME'???=>?'thinkphp',?//如果相同可以不用定義多個
- ????'DB_USER'???=>?'user1,user2',
- ????'DB_PWD'????=>?'pwd1,pwd2',
- ????'DB_PORT'???=>?'3306',
- ????'DB_PREFIX'?=>?'think_',
- ????//其他配置參數
- ????//?...
- ?);
- 'DB_RW_SEPARATE'=>true,
CURD操作系統會自動判斷當前執行的方法的讀操作還是寫操作,如果你用的是原生SQL,那么需要注意系統的默認規則:
寫操作必須用模型的execute方法,讀操作必須用模型的query方法,否則會發生主從讀寫錯亂的情況。
注意:主從數據庫的數據同步工作不在框架實現,需要數據庫考慮自身的同步或者復制機制。
?
9. 創建數據
在進行數據操作之前,我們往往需要手動創建需要的數據,例如對于提交的表單數據:
?
- //?獲取表單的POST數據
- $data['name']?=?$_POST['name'];
- $data['email']?=?$_POST['email'];
- ?//?更多的表單數據值獲取
- ?//……
很簡單的例子:
- //?實例化User模型
- $User?=?M('User');
- ?//?根據表單提交的POST數據創建數據對象
- $User->create();
- ?//?把創建的數據對象寫入數據庫
- $User->add();
Create方法支持從其它方式創建數據對象,例如,從其它的數據對象,或者數組等
- $data['name']?=?'ThinkPHP';
- $data['email']?=?'ThinkPHP@gmail.com';
- $User->create($data);
甚至還可以支持從對象創建新的數據對象
- //?從User數據對象創建新的Member數據對象
- $User?=?M("User");
- $User->find(1);
- $Member?=?M("Member");
- $Member->create($User);
- $User?=?M('User');
- $User->create();?//創建User數據對象
- $User->status?=?1;?//?設置默認的用戶狀態
- $User->create_time?=?time();?//?設置用戶的創建時間
- $User->add();?//?把用戶對象寫入數據庫
使用如下:
- //?實例化User模型
- $User?=?M('User');
- ?//?創建數據后寫入到數據庫
- $data['name']?=?'ThinkPHP';
- $data['email']?=?'ThinkPHP@gmail.com';
- $User->data($data)->add();
安全提示:
create方法如果沒有傳值,默認取$_POST數據,如果用戶提交的變量內容,含有可執行的html代碼,請進行手工過濾。
- $_POST['title']?=?"<script>alert(1);</script>";
非法html代碼可以使用htmlspecialchars進行編碼,以防止用戶提交的html代碼在展示時被執行,以下是兩種安全處理方法。
- $_POST['title']?=?htmlspecialchars($_POST['title']);
- M('User')->create();
- $data['title']?=?$this->_post('title',?'htmlspecialchars');
- M('User')->create($data);
- Class?UserModel?extends?Model{
- ????protected?$_map?=?array(
- ????????'name'?=>'username',?//?把表單中name映射到數據表的username字段
- ????????'mail'??=>'email',?//?把表單中的mail映射到數據表的email字段
- ????);
- ?}
- //?實例化User模型
- $User?=?M('User');
- $data?=?$User->find(3);
- $data?=?$User->parseFieldsMap($data);
?11. 連貫操作
- $User->where('status=1')->order('create_time')->limit(10)->select();
- $User->order('create_time')->limit(10)->where('status=1')->select();
- $User->select(array('order'=>'create_time','where'=>'status=1','limit'=>'10'));
- $User->where('id=1')->field('id,name,email')->find();?
- $User->where('status=1?and?id=1')->delete();
系統支持的連貫操作方法有:
連貫操作 | 作用 | 支持的參數類型 |
---|---|---|
where | 用于查詢或者更新條件的定義 | 字符串、數組和對象 |
table | 用于定義要操作的數據表名稱 | 字符串和數組 |
alias | 用于給當前數據表定義別名 | 字符串 |
data | 用于新增或者更新數據之前的數據對象賦值 | 數組和對象 |
field | 用于定義要查詢的字段(支持字段排除) | 字符串和數組 |
order | 用于對結果排序 | 字符串和數組 |
limit | 用于限制查詢結果數量 | 字符串和數字 |
page | 用于查詢分頁(內部會轉換成limit) | 字符串和數字 |
group | 用于對查詢的group支持 | 字符串 |
having | 用于對查詢的having支持 | 字符串 |
join* | 用于對查詢的join支持 | 字符串和數組 |
union* | 用于對查詢的union支持 | 字符串、數組和對象 |
distinct | 用于查詢的distinct支持 | 布爾值 |
lock | 用于數據庫的鎖機制 | 布爾值 |
cache | 用于查詢緩存 | 支持多個參數 |
relation | 用于關聯查詢(需要關聯模型支持) | 字符串 |
12. CURD操作
創建(Create)
在ThinkPHP中使用add方法新增數據到數據庫(而并不是create方法)。
- $User?=?M("User");?//?實例化User對象
- $data['name']?=?'ThinkPHP';
- $data['email']?=?'ThinkPHP@gmail.com';
- $User->add($data);
- $User->data($data)->add();
- $User?=?M("User");?//?實例化User對象
- ?//?根據表單提交的POST數據創建數據對象
- $User->create();
- $User->add();?//?根據條件保存修改的數據
讀取(Read)
在ThinkPHP中讀取數據的方式很多,通常分為讀取數據和讀取數據集。
- $User?=?M("User");?//?實例化User對象
- ?//?查找status值為1的用戶數據?以創建時間排序?返回10條數據
- $list?=?$User->where('status=1')->order('create_time')->limit(10)->select();
- $User?=?M("User");?//?實例化User對象
- ?//?查找status值為1name值為think的用戶數據?
- $User->where('status=1?AND?name="think"')->find();
- $User?=?M("User");?//?實例化User對象
- ?//?獲取ID為3的用戶的昵稱?
- $nickname?=?$User->where('id=3')->getField('nickname');
如果需要返回數組,可以用:
- $this->getField('id',true);?//?獲取id數組
- $User?=?M("User");?//?實例化User對象
- ?//?獲取所有用戶的ID和昵稱列表?
- $list?=?$User->getField('id,nickname');
如果傳入多個字段的名稱,例如:
- $list?=?$User->getField('id,nickname,email');
如果我們傳入一個字符串分隔符:
- $list?=?$User->getField('id,nickname,email',':');
getField方法的sepa參數還可以支持限制數量,例如:
- $this->getField('id,name',5);?//?限制返回5條記錄
- $this->getField('id',3);?//?獲取id數組?限制3條記錄
更新(Update)
在ThinkPHP中使用save方法更新數據庫,并且也支持連貫操作的使用。
- $User?=?M("User");?//?實例化User對象
- ?//?要修改的數據對象屬性賦值
- $data['name']?=?'ThinkPHP';
- $data['email']?=?'ThinkPHP@gmail.com';
- $User->where('id=5')->save($data);?//?根據條件保存修改的數據
為了保證數據庫的安全,避免出錯更新整個數據表,如果沒有任何更新條件,數據對象本身也不包含主鍵字段的話,save方法不會更新任何數據庫的記錄。
因此下面的代碼不會更改數據庫的任何記錄
- $User->save($data);?
除非使用下面的方式:
- $User?=?M("User");?//?實例化User對象
- ?//?要修改的數據對象屬性賦值
- $data['id']?=?5;
- $data['name']?=?'ThinkPHP';
- $data['email']?=?'ThinkPHP@gmail.com';
- $User->save($data);?//?根據條件保存修改的數據
如果id是數據表的主鍵的話,系統自動會把主鍵的值作為更新條件來更新其他字段的值。
還有一種方法是通過create或者data方法創建要更新的數據對象,然后進行保存操作,這樣save方法的參數可以不需要傳入。
- $User?=?M("User");?//?實例化User對象
- ?//?要修改的數據對象屬性賦值
- $data['name']?=?'ThinkPHP';
- $data['email']?=?'ThinkPHP@gmail.com';
- $User->where('id=5')->data($data)->save();?//?根據條件保存修改的數據
使用create方法的例子:
- $User?=?M("User");?//?實例化User對象
- ?//?根據表單提交的POST數據創建數據對象
- $User->create();
- $User->save();?//?根據條件保存修改的數據
使用示例:
- $User?=?M("User");?//?實例化User對象
- ?//?更改用戶的name值
- $User->?where('id=5')->setField('name','ThinkPHP');
setField方法支持同時更新多個字段,只需要傳入數組即可,例如:
- $User?=?M("User");?//?實例化User對象
- ?//?更改用戶的name和email的值
- $data?=?array('name'=>'ThinkPHP','email'=>'ThinkPHP@gmail.com');
- $User->?where('id=5')->setField($data);
- $User?=?M("User");?//?實例化User對象
- $User->where('id=5')->setInc('score',3);?//?用戶的積分加3
- $User->where('id=5')->setInc('score');?//?用戶的積分加1
- $User->where('id=5')->setDec('score',5);?//?用戶的積分減5
- $User->where('id=5')->setDec('score');?//?用戶的積分減1
刪除(Delete)
在ThinkPHP中使用delete方法刪除數據庫中的記錄。
示例如下:
- $User?=?M("User");?//?實例化User對象
- $User->where('id=5')->delete();?//?刪除id為5的用戶數據
- $User->where('status=0')->delete();?//?刪除所有狀態為0的用戶數據
delete方法可以用于刪除單個或者多個數據,主要取決于刪除條件,也就是where方法的參數,也可以用order和limit方法來限制要刪除的個數,例如:
- //?刪除所有狀態為0的5?個用戶數據?按照創建時間排序
- $User->where('status=0')->order('create_time')->limit('5')->delete();?
13. ActiveRecord
ThinkPHP實現了ActiveRecords模式的ORM模型,采用了非標準的ORM模型:表映射到類,記錄映射到對象。
一、創建數據
- $User?=?M("User");?//?實例化User對象
- ?//?然后直接給數據對象賦值
- $User->name?=?'ThinkPHP';
- $User->email?=?'ThinkPHP@gmail.com';
- ?//?把數據對象添加到數據庫
- $User->add();
如果使用了create方法創建數據對象的話,仍然可以在創建完成后進行賦值
- $User?=?D("User");
- $User->create();?//?創建User數據對象,默認通過表單提交的數據進行創建
- ?//?增加或者更改其中的屬性
- $User->status?=?1;
- $User->create_time?=?time();
- ?//?把數據對象添加到數據庫
- $User->add();?
- $User?=?M("User");?//?實例化User對象
- ?//?查找id為8的用戶數據
- $User->where('id=8')->find();
用AR模式的話可以直接寫成:
- $User->find(8);
如果要根據某個字段查詢,例如查詢姓名為ThinkPHP的可以用:
- $User?=?M("User");?//?實例化User對象
- $User->getByName("ThinkPHP");
如果要查詢數據集,可以直接使用:
- ?//?查找主鍵為1、3、8的多個數據
- $userList?=?$User->select('1,3,8');?
三、更新記錄
在完成查詢后,可以直接修改數據對象然后保存到數據庫。
- $User->find(1);?//?查找主鍵為1的數據
- $User->name?=?'TOPThink';?//?修改數據對象
- $User->save();?//?保存當前數據對象
上面這種方式僅僅是示例,不代表保存操作之前一定要先查詢。因為下面的方式其實是等效的:
- $User->id?=?1;
- $User->name?=?'TOPThink';?//?修改數據對象
- $User->save();?//?保存當前數據對象
四、刪除記錄
可以刪除當前查詢的數據對象
- $User->find(2);
- $User->delete();?//?刪除當前的數據對象
或者直接根據主鍵進行刪除
- $User->delete(8);?//?刪除主鍵為8的數據
- $User->delete('5,6');?//?刪除主鍵為5、6的多個數據
14. 自動驗證
大多數情況下面,數據對象是由表單提交的$_POST數據創建。需要使用系統的自動驗證功能,只需要在Model類里面定義$_validate屬性,是由多個驗證因子組成的二維數組。
驗證因子格式:
- array(驗證字段,驗證規則,錯誤提示,[驗證條件,附加規則,驗證時間])
示例:
- protected?$_validate?=?array(
- ????array('verify','require','驗證碼必須!'),?//默認情況下用正則進行驗證
- ????array('name','','帳號名稱已經存在!',0,'unique',1),?//?在新增的時候驗證name字段是否唯一
- ????array('value',array(1,2,3),'值的范圍不正確!',2,'in'),?//?當值不為空的時候判斷是否在一個范圍內
- ????array('repassword','password','確認密碼不正確',0,'confirm'),?//?驗證確認密碼是否和密碼一致
- ????array('password','checkPwd','密碼格式不正確',0,'function'),?//?自定義函數驗證密碼格式
- ?);
當使用系統的create方法創建數據對象的時候會自動進行數據驗證操作,代碼示例:
- $User?=?D("User");?//?實例化User對象
- ?if?(!$User->create()){
- ????//?如果創建失敗?表示驗證沒有通過?輸出錯誤提示信息
- ?exit($User->getError());
- ?}else{
- ????//?驗證通過?可以進行其他數據操作
- ?}
通常來說,每個數據表對應的驗證規則是相對固定的,但是有些特殊的情況下面可能會改變驗證規則,我們可以動態的改變驗證規則來滿足不同條件下面的驗證:
- $User?=?D("User");?//?實例化User對象
- $validate?=?array(
- ????array('verify','require','驗證碼必須!'),?//?僅僅需要進行驗證碼的驗證
- ?);
- $User->?setProperty("_validate",$validate);
- $result?=?$User->create();
- ?if?(!$result){
- ????//?如果創建失敗?表示驗證沒有通過?輸出錯誤提示信息
- ????exit($User->getError());
- ?}else{
- ????//?驗證通過?可以進行其他數據操作
- ?}
自動驗證功能中的function和callback規則可以支持多字段。
例子:
- protected?$_validate?=?array(
- ????array('user_id,good_id',?'checkIfOrderToday',?'今天已經購買過,請明天再來',?1,'callback',?1),
- ?);
- ?protected?function?checkIfOrderToday($data){
- ????$map?=?$data;
- ????$map['ctime']?=?array(array('gt',[開始時間]),?array('lt',?[結束時間]));
- ????if($this->where($map)->find())
- ????????return?false;
- ????else
- ????????return?true;
- ?}
新版支持數據的批量驗證功能,只需要在模型類里面設置patchValidate屬性為true( 默認為false),設置批處理驗證后,getError() 方法返回的錯誤信息是一個數組,返回格式是:
- array("字段名1"=>"錯誤提示1","字段名2"=>"錯誤提示2"...?)
手動驗證
3.1版本開始,可以使用validate方法實現動態和批量手動驗證,例如:
- $this->validate($validate)->create();
其中$validate變量的規范和_validate屬性的定義規則一致,而且還可以支持函數調用(由于PHP本身的限制,在類的屬性定義中不能調用函數)。
通過這一改進,以前需要支持數據自動驗證,必須定義模型類的情況已經不再出現,你完全可以通過M方法實例化模型類后使用動態設置完成自動驗證操作。
另外還有一個check方法,用于對單個數據的手動驗證,支持部分自動驗證的規則,用法如下:
- ?check('驗證數據','驗證規則','驗證類型')?
驗證類型支持 in between equal length regex expire ip_allow ip_deny,默認為regex?
結果返回布爾值
- $model->check($value,'email');?
- $model->check($value,'1,2,3','in');
15. 命名范圍
首先定義_scope屬性:- class?NewsModel?extends?Model?{
- ????protected?$_scope?=?array(
- ????????//?命名范圍normal
- ????????'normal'=>array(
- ????????????'where'=>array('status'=>1),
- ????????),
- ????????//?命名范圍latest
- ????????'latest'=>array(
- ????????????'order'=>'create_time?DESC',
- ????????????'limit'=>10,
- ????????),
- ????);
- ?}
調用某個命名范圍
最簡單的調用方式就直接調用某個命名范圍,例如:
- $Model->scope('normal')->select();
- $Model->scope('latest')->select();
生成的SQL語句分別是:
- SELECT?*?FROM?think_news?WHERE?status=1
- SELECT?*?FROM?think_news?ORDER?BY?create_time?DESC?LIMIT?10
調用多個命名范圍
也可以支持同時調用多個命名范圍定義,例如:- $Model->scope('normal')->scope('latest')->select();
- $Model->scope('normal,latest')->select();
- SELECT?*?FROM?think_news?WHERE?status=1?ORDER?BY?create_time?DESC?LIMIT?10
默認命名范圍
系統支持默認命名范圍功能,如果你定義了一個default命名范圍,例如:
- ????protected?$_scope?=?array(
- ????????//?默認的命名范圍
- ????????'default'=>array(
- ????????????'where'=>array('status'=>1),
- ????????????'limit'=>10,
- ????????),
- ????);
那么調用default命名范圍可以直接使用:
- $Model->scope()->select();
命名范圍調整
如果你需要在normal命名范圍的基礎上增加額外的調整,可以使用:
- $Model->scope('normal',array('limit'=>5))->select();
生成的SQL語句是:
- SELECT?*?FROM?think_news?WHERE?status=1?LIMIT?5
當然,也可以在兩個命名范圍的基礎上進行調整,例如:
- $Model->scope('normal,latest',array('limit'=>5))->select();
生成的SQL是:
- SELECT?*?FROM?think_news?WHERE?status=1?ORDER?BY?create_time?DESC?LIMIT?5
自定義命名范圍
又或者,干脆不用任何現有的命名范圍,我直接傳入一個命名范圍:- $Model->scope(array('field'=>'id,title','limit'=>5,'where'=>'status=1','order'=>'create_time?DESC'))->select();
- SELECT?id,title?FROM?think_news?WHERE?status=1?ORDER?BY?create_time?DESC?LIMIT?5
與連貫操作混合使用
命名范圍一樣可以和之前的連貫操作混合使用,例如定義了命名范圍_scope屬性:- protected?$_scope?=?array(
- ????'normal'=>array(
- ????????'where'=>array('status'=>1),
- ????????'field'=>'id,title',
- ????????'limit'=>10,
- ????),
- ?);
- $Model->scope('normal')->limit(8)->order('id?desc')->select();
- SELECT?id,title?FROM?think_news?WHERE?status=1?ORDER?BY?id?desc?LIMIT?8
如果是這樣調用:
- $Model->limit(8)->scope('normal')->order('id?desc')->select();
- SELECT?id,title?FROM?think_news?WHERE?status=1?ORDER?BY?id?desc?LIMIT?10
16. 自動完成
在Model類定義 $_auto 屬性,可以完成數據自動處理功能,用來處理默認值、數據過濾以及其他系統寫入字段。$_auto屬性是由多個填充因子組成的數組。
填充因子格式:
- array(填充字段,填充內容,[填充條件,附加規則])
- protected?$_auto?=?array?(?
- ????array('status','1'),??//?新增的時候把status字段設置為1
- ????array('password','md5',1,'function')?,?//?對password字段在新增的時候使md5函數處理
- ????array('name','getName',1,'callback'),?//?對name字段在新增的時候回調getName方法
- ????array('create_time','time',2,'function'),?//?對create_time字段在更新的時候寫入當前時間戳
- ?);
和自動驗證一樣,自動完成機制需要使用create方法才能生效。并且,也可以在操作方法中動態的更改自動完成的規則。
- $auto?=?array?(?
- ????array('password','md5',1,'function')?//?對password字段在新增的時候使md5函數處理
- ?);
- $User->?setProperty("_auto",$auto);
- $User->create();
還可以使用auto方法動態設置自動完成規則,例如:
- $this->auto($auto)->create();
通過這一改進,以前需要支持數據自動完成,必須定義模型類的情況已經不再出現,你完全可以通過M方法實例化模型類后使用動態設置完成自動完成操作。
17. 查詢語言
查詢方式
ThinkPHP可以支持直接使用字符串作為查詢條件,但是大多數情況推薦使用索引數組或者對象來作為查詢條件,因為會更加安全。
一、使用字符串作為查詢條件
這是最傳統的方式,但是安全性不高,例如:
- $User?=?M("User");?//?實例化User對象
- $User->where('type=1?AND?status=1')->select();?
最后生成的SQL語句是
SELECT * FROM think_user WHERE type=1 AND status=1
- $User?=?M("User");?//?實例化User對象
- $condition['name']?=?'thinkphp';
- $condition['status']?=?1;
- ?//?把查詢條件傳入查詢方法
- $User->where($condition)->select();?
SELECT * FROM think_user WHERE `name`='thinkphp' AND status=1
如果進行多字段查詢,那么字段之間的默認邏輯關系是 邏輯與 AND,但是用下面的規則可以更改默認的邏輯判斷,通過使用 _logic 定義查詢邏輯:
- $User?=?M("User");?//?實例化User對象
- $condition['name']?=?'thinkphp';
- $condition['account']?=?'thinkphp';
- $condition['_logic']?=?'OR';
- ?//?把查詢條件傳入查詢方法
- $User->where($condition)->select();?
SELECT * FROM think_user WHERE `name`='thinkphp' OR `account`='thinkphp'
- $User?=?M("User");?//?實例化User對象
- ?//?定義查詢條件
- $condition?=?new?stdClass();?
- $condition->name?=?'thinkphp';?
- $condition->status=?1;?
- $User->where($condition)->select();?
SELECT * FROM think_user WHERE `name`='thinkphp' AND status=1
使用對象方式查詢和使用數組查詢的效果是相同的,并且是可以互換的,大多數情況下,我們建議采用數組方式更加高效,后面我們會以數組方式為例來講解具體的查詢語言用法。
表達式查詢
上面的查詢條件僅僅是一個簡單的相等判斷,可以使用查詢表達式支持更多的SQL查詢語法,并且可以用于數組或者對象方式的查詢(下面僅以數組方式為例說明),查詢表達式的使用格式:$map['字段名'] = array('表達式','查詢條件');
表達式不分大小寫,支持的查詢表達式有下面幾種,分別表示的含義是:
表達式 | 含義 |
---|---|
EQ | 等于(=) |
NEQ | 不等于(<>) |
GT | 大于(>) |
EGT | 大于等于(>=) |
LT | 小于(<) |
ELT | 小于等于(<=) |
LIKE | 模糊查詢 |
[NOT]?BETWEEN | (不在)區間查詢 |
[NOT]?IN | (不在)IN?查詢 |
EXP | 表達式查詢,支持SQL語法 |
快捷查詢
新版增加了快捷查詢方式,可以進一步簡化查詢條件的寫法,例如:一、實現不同字段相同的查詢條件
- $User?=?M("User");?//?實例化User對象
- $map['name|title']?=?'thinkphp';
- ?//?把查詢條件傳入查詢方法
- $User->where($map)->select();?
二、實現不同字段不同的查詢條件
- $User?=?M("User");?//?實例化User對象
- $map['status&title']?=array('1','thinkphp','_multi'=>true);
- ?//?把查詢條件傳入查詢方法
- $User->where($map)->select();?
$map['status&score&title']?=array('1',array('gt','0'),'thinkphp','_multi'=>true);
查詢條件就變成?status=?1 AND score >0 AND title = 'thinkphp'
注意:快捷查詢方式中“|”和“&”不能同時使用。
區間查詢
ThinkPHP支持對某個字段的區間查詢,例如:- $map['id']?=?array(array('gt',1),array('lt',10))?;
- $map['id']?=?array(array('gt',3),array('lt',10),?'or')?;
- $map['id']??=?array(array('neq',6),array('gt',3),'and');?
最后一個可以是AND、 OR或者 XOR運算符,如果不寫,默認是AND運算。
組合查詢
組合查詢的主體還是采用數組方式查詢,只是加入了一些特殊的查詢支持,包括字符串模式查詢(_string)、復合查詢(_complex)、請求字符串查詢(_query),混合查詢中的特殊查詢每次查詢只能定義一個,由于采用數組的索引方式,索引相同的特殊查詢會被覆蓋。
數組條件還可以和字符串條件混合使用,例如:
- $User?=?M("User");?//?實例化User對象
- $map['id']?=?array('neq',1);
- $map['name']?=?'ok';
- $map['_string']?=?'status=1?AND?score>10';
- $User->where($map)->select();?
( `id` != 1 ) AND ( `name` = 'ok' ) AND ( status=1 AND score>10 )
請求字符串查詢是一種類似于URL傳參的方式,可以支持簡單的條件相等判斷。
- $map['id']?=?array('gt','100');
- $map['_query']?=?'status=1&score=100&_logic=or';
- $where['name']??=?array('like',?'%thinkphp%');
- $where['title']??=?array('like','%thinkphp%');
- $where['_logic']?=?'or';
- $map['_complex']?=?$where;
- $map['id']??=?array('gt',1);
(?id?>?1)?AND?( (?name?like?'%thinkphp%')?OR?(?title?like?'%thinkphp%') )
統計查詢
方法 | 說明 |
---|---|
Count | 統計數量,參數是要統計的字段名(可選) |
Max | 獲取最大值,參數是要統計的字段名(必須) |
Min | 獲取最小值,參數是要統計的字段名(必須) |
Avg | 獲取平均值,參數是要統計的字段名(必須) |
Sum | 獲取總分,參數是要統計的字段名(必須) |
- $User?=?M("User");?//?實例化User對象
- $userCount?=?$User->count();
- $userCount?=?$User->count("id");
- $maxScore?=?$User->max('score');
- $minScore?=?$User->where('score>0')->min('score');
- $avgScore?=?$User->avg('score');
- $sumScore?=?$User->sum('score');
定位查詢
ThinkPHP支持定位查詢,但是要求當前模型必須繼承高級模型類才能使用,可以使用getN方法直接返回查詢結果中的某個位置的記錄。例如:?
?獲取符合條件的第3條記錄:
- $User->where('score>0')->order('score?desc')->getN(2);
?獲取符合條件的最后第二條記錄:
- $User->?where('score>80')->order('score?desc')->getN(-2);
?獲取第一條記錄:
- $User->where('score>80')->order('score?desc')->first();
?獲取最后一條記錄:
- $User->where('score>80')->order('score?desc')->last();
SQL查詢
query??執行SQL查詢操作 | |
---|---|
用法 | query($sql,$parse=false) |
參數 | query(必須):要查詢的SQL語句 parse(可選):是否需要解析SQL |
返回值 | 如果數據非法或者查詢錯誤則返回false 否則返回查詢結果數據集(同select方法) |
- $Model?=?new?Model()?//?實例化一個model對象?沒有對應任何數據表
- $Model->query("select?*?from?think_user?where?status=1");
execute用于更新和寫入數據的sql操作 | |
---|---|
用法 | execute($sql,$parse=false) |
參數 | query(必須):要執行的SQL語句 parse(可選):是否需要解析SQL |
返回值 | 如果數據非法或者查詢錯誤則返回false? 否則返回影響的記錄數 |
- $Model?=?new?Model()?//?實例化一個model對象?沒有對應任何數據表
- $Model->execute("update?think_user?set?name='thinkPHP'?where?status=1");
自動獲取當前表名
通常使用原生SQL需要手動加上當前要查詢的表名,如果你的表名以后會變化的話,那么就需要修改每個原生SQL查詢的sql語句了,針對這個情況,系統還提供了一個小的技巧來幫助解決這個問題。
例如:
- $model?=?M("User");
- $model->query('select?*?from?__TABLE__?where?status>1');
支持連貫操作和SQL解析
新版對query和execute兩個原生SQL操作方法增加第二個參數支持, 表示是否需要解析SQL (默認為false 表示直接執行sql ),如果設為true 則會解析SQL中的特殊字符串 (需要配合連貫操作)。
例如,支持 如下寫法:
- $model->table("think_user")
- ??????->where(array("name"=>"thinkphp"))
- ??????->field("id,name,email")
- ??????->query('select?%FIELD%?from?%TABLE%?%WHERE%',true);
其中query方法中的%FIELD%、%TABLE%和%WHERE%字符串會自動替換為同名的連貫操作方法的解析結果SQL,支持的替換字符串包括:
替換字符串 | 對應連貫操作方法 |
---|---|
%FIELD% | field |
%TABLE% | table |
%DISTINCT% | distinct |
%WHERE% | where |
%JOIN% | join |
%GROUP% | group |
%HAVING% | having |
%ORDER% | order |
%LIMIT% | limit |
%UNION% | union |
動態查詢
借助PHP5語言的特性,ThinkPHP實現了動態查詢,包括下面幾種:
方法名 | 說明 | 舉例 |
---|---|---|
getBy | 根據某個字段的值查詢數據 | 例如,getByName,getByEmail |
getFieldBy | 根據某個字段查詢并返回某個字段的值 | 例如,getFieldByName |
top | 獲取前多少條記錄(需要高級模型支持) | 例如,top8,top12 |
該查詢方式針對數據表的字段進行查詢。例如,User對象擁有id,name,email,address 等屬性,那么我們就可以使用下面的查詢方法來直接根據某個屬性來查詢符合條件的記錄。
- $user?=?$User->getByName('liu21st');
- $user?=?$User->getByEmail('liu21st@gmail.com');
- $user?=?$User->getByAddress('中國深圳');
針對某個字段查詢并返回某個字段的值,例如
- $user?=?$User->getFieldByName('liu21st','id');
三、top動態查詢
ThinkPHP還提供了另外一種動態查詢方式,就是獲取符合條件的前N條記錄(和定位查詢一樣,也要求當前模型類必須繼承高級模型類后才能使用)。例如,我們需要獲取當前用戶中積分大于0,積分最高的前5位用戶 :
- $User->?where('score>80')->order('score?desc')->top5();
要獲取積分的前8位可以改成:
- $User->?where('score>80')->order('score?desc')->top8();
子查詢
新版新增了子查詢支持,有兩種使用方式:1、使用select方法
當select方法的參數為false的時候,表示不進行查詢只是返回構建SQL,例如:
- //?首先構造子查詢SQL?
- $subQuery?=?$model->field('id,name')->table('tablename')->group('field')->where($where)->order('status')->select(false);?
- $subQuery?=?$model->field('id,name')->table('tablename')->group('field')->where($where)->order('status')->buildSql();?
- //?利用子查詢進行查詢?
- $model->table($subQuery.'?a')->where()->order()->select()?
18. 查詢鎖定
ThinkPHP支持查詢或者更新的鎖定,只需要在查詢或者更新之前使用lock方法即可。
查詢鎖定使用:
- $list?=?$User->lock(true)->where('status=1')->order('create_time')->limit(10)->select();
更新鎖定使用:
- $list?=?$User->lock(true)->where('status=1')->data($data)->save();
19. 字段排除
當使用下面的字段排除方式查詢的時候- $Model->field('create_time,read_count,comment_count',true);?
生成的SQL語句就變成了SELECT id,name,title,status FROM article
20. 事務支持
ThinkPHP提供了單數據庫的事務支持,如果要在應用邏輯中使用事務。事務是針對數據庫本身的,所以可以跨模型操作的 。
- //??在User模型中啟動事務
- $User->startTrans();
- ?//?進行相關的業務邏輯操作
- $Info?=?M("Info");?//?實例化Info對象
- $Info->save($User);?//?保存用戶信息
- ?if?(操作成功){
- ????//?提交事務
- ????$User->commit();?
- ?}else{
- ???//?事務回滾
- ???$User->rollback();?
- ?}
21. 高級模型
http://doc.thinkphp.cn/manual/adv_model.html
22. 視圖模型
http://doc.thinkphp.cn/manual/view_model.html
23. 關聯模型
http://doc.thinkphp.cn/manual/relation_model.html
24. Mongo模型
http://doc.thinkphp.cn/manual/mongo_model.html
25. 動態模型
你可以從基本模型切換到高級模型或者視圖模型,而當前的數據不會丟失,并可以控制要傳遞的參數和動態賦值。
要切換模型,可以使用:
- $User?=?M("User");?//?實例化User對象?是基礎模型類的實例
- ?//?動態切換到高級模型類?執行top10查詢操作
- $User->switchModel("Adv")->top10();
上面的寫法也可以改成
- $User?=?M("AdvModel:User");?//?實例化User對象?是基礎模型類的實例
- $User->top10();
如果要傳遞參數,可以使用:
- $User?=?D("User");?//?實例化User對象?是基礎模型類的實例
- ?//?動態切換到視圖模型類?并傳入viewFields屬性
- $UserView?=?$User->switchModel("View",array("viewFields"));
如果要動態賦值,可以使用:
- $User?=?M("User");?//?實例化User對象?是基礎模型類的實例
- ?//?動態切換到關聯模型類?并傳入data屬性
- $advUser?=?$User->switchModel("Relation");
- ?//?或者在切換模型后再動態賦值給新的模型
- $advUser->setProperty("_link",$link);
- ?//?查找關聯數據
- $user?=?$advUser->relation(true)->find(1);
26. 虛擬模型
第一種:繼承Model類
- Class?UserModel?extends?Model?{
- ????Protected?$autoCheckFields?=?false;
- ?}
- Class?UserModel?{?}
實例化UserModel類(默認的情況)
文件位于項目的Lib/Model/UserModel.class.php
- D('User');
文件位于項目的Lib/Logic/UserLogic.class.php
- D('User','Logic');
文件位于項目的Lib/Service/UserService.class.php
- D('User','Service');
視圖:
ThinkPHP的視圖有兩個部分組成:View類和模板文件。Action控制器直接和View視圖類打交道,把要輸出的數據通過模板變量賦值的方式傳遞到視圖類,而具體的輸出工作則交由View視圖類來進行,同時視圖類還和模板引擎進行接口,包括完成布局渲染、輸出替換、頁面Trace等功能。
1. 模板定義
為了對模板文件更加有效的管理,ThinkPHP對模板文件進行目錄劃分,默認的模板文件定義規則是:模板目錄/[分組名/][模板主題/]模塊名/操作名+模板后綴
模板目錄默認是項目下面的Tpl, 當定義分組的情況下,會按照分組名分開子目錄,新版模板主題默認是空(表示不啟用模板主題功能),模板主題功能是為了多模板切換而設計的,如果有多個模板主題的話,可以用DEFAULT_THEME參數設置默認的模板主題名。
在每個模板主題下面,是以項目的模塊名為目錄,然后是每個模塊的具體操作模板文件,例如:
User模塊的add操作 對應的模板文件就應該是:
- Tpl/User/add.html?
如果項目啟用了模塊分組功能(假設User模塊屬于Home分組),那么默認對應的模板文件可能變成 :
- Tpl/Home/User/add.html?
例如 TMPL_FILE_DEPR如果配置成“_”的話,默認的模板文件就變成了:
- Tpl/Home/User_add.html
2. 模板賦值
要在模板中輸出變量,必須在在Action類中把變量傳遞給模板,視圖類提供了assign方法對模板變量賦值,無論何種變量類型都統一使用assign賦值。- $this->assign('name',$value);
- ?//?下面的寫法是等效的
- $this->name?=?$value;
如果要同時輸出多個模板變量,可以使用下面的方式:
- $array['name']????=????'thinkphp';
- $array['email']????=????'liu21st@gmail.com';
- $array['phone']????=????'12335678';
- $this->assign($array);
模板變量賦值后,怎么在模板文件中輸出,需要根據選擇的模板引擎來用不同的方法,如果使用的是內置的模板引擎,請參考后面的模板指南部分。如果你使用的是PHP本身作為模板引擎的話 ,就可以直接在模板文件里面輸出了,如下:
- <?php?
- ????echo?$name.'['.$email.''.$phone.']';
如果要獲得全部的模板變量,可以調用View類的get方法支持獲取全部模板變量的值,例如:
- $this->get('name');?//?獲取name模板變量的值
- $this->get();?//?獲取所有模板賦值變量的值
3. 模板輸出
模板變量賦值后就需要調用模板文件來輸出相關的變量,模板調用通過display方法來實現。我們在操作方法的最后使用:- $this->display();
一、調用當前模塊的其他操作模板
格式:display('操作名')例如,假設當前操作是User模塊下面的read操作,我們需要調用User模塊的edit操作模版,使用:
- $this->display('edit');?
二、調用其他模塊的操作模板
格式:display('模塊名:操作名')
例如,當前是User模塊,我們需要調用Member模塊的read操作模版?,使用:
- $this->display('Member:read');?
三、調用其他主題的操作模板
格式:display('主題名:模塊名:操作名')例如我們需要?調用Xp主題的User模塊的edit操作模版,使用:
- $this->display('Xp:User:edit');?
四、直接全路徑輸出模板
格式:display('模板文件名')例如,我們直接輸出當前的Public目錄下面的menu.html模板文件,使用:?
- $this->display('./Public/menu.html');
- $this->display('./Public/menu.tpl');
五、直接解析內容
Action類的display方法如果傳入第四個參數,表示不讀取模板文件而是直接解析內容。例如:
- $this->assign('foo','ThinkPHP');?
- $this->show('Hello,?{$foo}!');
會在頁面輸出: Hello,ThinkPHP!?
直接輸出的內容仍然支持模板布局功能。
show方法也可以支持指定編碼和輸出格式,例如:
- $this->show($content,?'utf-8',?'text/xml');?
事實上,display方法還有其他的參數和用法。
有時候某個模板頁面我們需要輸出指定的編碼,而不是默認的編碼,可以使用:
- $this->display('Member:read',?'gbk');?
或者輸出的模板文件不是text/html格式的,而是XML格式的,可以用:
- $this->display('Member:read',?'utf-8',?'text/xml');?
如果你的網站輸出編碼不是默認的編碼,可以使用:
- 'DEFAULT_CHARSET'=>?'gbk'?
如果要輸出XML格式的,可以用:
- 'TMPL_CONTENT_TYPE'=>?'text/xml'
4. 模板替換
在進行模板輸出之前,系統還會對渲染的模板結果進行一些模板的特殊字符串替換操作,也就是實現了模板輸出的替換和過濾。模板替換適用于所有的模板引擎,包括原生的PHP模板。這個機制可以使得模板文件的定義更加方便,默認的替換規則有:../Public: 會被替換成當前項目的公共模板目錄 通常是 /項目目錄/Tpl/當前主題/Public/?
__TMPL__: 會替換成項目的模板目錄 通常是 /項目目錄/Tpl/當前主題/
(注:為了部署安全考慮,../Public和__TMPL__不再建議使用)
__PUBLIC__:會被替換成當前網站的公共目錄 通常是 /Public/
__ROOT__: 會替換成當前網站的地址(不含域名)?
__APP__: 會替換成當前項目的URL地址 (不含域名)
__GROUP__:會替換成當前分組的URL地址 (不含域名)
__URL__: 會替換成當前模塊的URL地址(不含域名)
__ACTION__:會替換成當前操作的URL地址 (不含域名)
__SELF__: 會替換成當前的頁面URL
注意這些特殊的字符串是嚴格區別大小寫的,并且這些特殊字符串的替換規則是可以更改或者增加的,我們只需要在項目配置文件中配置TMPL_PARSE_STRING就可以完成。如果有相同的數組索引,就會更改系統的默認規則。例如:
- 'TMPL_PARSE_STRING'??=>array(
- ?????'__PUBLIC__'?=>?'/Common',?//?更改默認的/Public?替換規則
- ?????'__JS__'?=>?'/Public/JS/',?//?增加新的JS類庫路徑替換規則
- ?????'__UPLOAD__'?=>?'/Uploads',?//?增加新的上傳路徑替換規則
- ?)
- 'TMPL_PARSE_STRING'??=>array(
- ?????'--PUBLIC--'?=>?'__PUBLIC__',?//?采用新規則輸出/Public字符串
- ?)
5. 獲取內容
有些時候我們不想直接輸出模板內容,而是希望對內容再進行一些處理后輸出,就可以使用fetch方法來獲取解析后的模板內容,在Action類里面使用:- $content?=?$this->fetch();
- $content?=?$this->fetch('Member:read');?
注意,fetch方法仍然會執行上面的模板替換操作。
6. 模板引擎
系統支持原生的PHP模板,而且本身內置了一個基于XML的高效的編譯型模板引擎,系統默認使用的模板引擎是內置模板引擎,關于這個模板引擎的標簽詳細使用可以參考模板指南部分。內置的模板引擎也可以直接支持在模板文件中采用PHP原生代碼和模板標簽的混合使用,如果需要完全使用PHP本身作為模板引擎,可以配置:
- 'TMPL_ENGINE_TYPE'?=>'PHP'
如果你使用了其他的模板引擎,只需要設置TMPL_ENGINE_TYPE參數為相關的模板引擎名稱即可。
模板引擎:
ThinkPHP內置了一個基于XML的性能卓越的模板引擎 ThinkTemplate,這是一個專門為ThinkPHP服務的內置模板引擎。ThinkTemplate是一個使用了XML標簽庫技術的編譯型模板引擎,支持兩種類型的模板標簽,使用了動態編譯和緩存技術,而且支持自定義標簽庫。
內置的模板引擎支持普通標簽和XML標簽方式兩種標簽定義,分別用于不同的目的:
普通標簽 | 主要用于輸出變量和做一些基本的操作 |
XML標簽 | 主要完成一些邏輯判斷、控制和循環輸出,并且可擴展 |
1. 變量輸出
如果我們在Action中賦值了一個name模板變量:- $name?=?'ThinkPHP';
- $this->assign('name',$name);
- {$name}
- <?php?echo($name);?>
普通標簽默認開始標記是 {,結束標記是 }。也可以通過設置TMPL_L_DELIM和TMPL_R_DELIM進行更改。例如,我們在項目配置文件中定義:
- 'TMPL_L_DELIM'=>'<{',
- ?'TMPL_R_DELIM'=>'}>',
那么,上面的變量輸出標簽就應該改成:
- <{$name}>
{$user.name}和{$user['name']}等效,也就是輸出數組變量。
如果TMPL_VAR_IDENTIFY設置為obj,那么
{$user.name}和{$user:name}等效,也就是輸出對象的屬性。
如果TMPL_VAR_IDENTIFY留空的話,系統會自動判斷要輸出的變量是數組還是對象,這種方式會一定程度上影響效率,而且只支持二維數組和兩級對象屬性。
如果TMPL_VAR_IDENTIFY留空的話,系統會自動判斷要輸出的變量是數組還是對象,這種方式會一定程度上影響效率,而且只支持二維數組和兩級對象屬性。
如果是多維數組或者多層對象屬性的輸出,可以使用下面的定義方式:
- {$user.sub.name}//?使用點語法輸出
或者使用
- {$user['sub']['name']}//?輸出三維數組的值
- ?{$user:sub:name}//?輸出對象的多級屬性
2. 系統變量
除了常規變量的輸出外,模板引擎還支持系統變量和系統常量、以及系統特殊變量的輸出。它們的輸出不需要事先賦值給某個模板變量。系統變量的輸出必須以$Think.打頭,并且仍然可以支持使用函數。常用的系統變量輸出包括下面:
用法 | 含義 | 例子 |
---|---|---|
$Think.server | 獲取$_SERVER | {$Think.server.php_self} |
$Think.get | 獲取$_GET | {$Think.get.id} |
$Think.post | 獲取$_POST | {$Think.post.name} |
$Think.request | 獲取$_REQUEST | {$Think.request.user_id} |
$Think.cookie | 獲取$_COOKIE | {$Think.cookie.username} |
$Think.session | 獲取$_SESSION | {$Think.session.user_id} |
$Think.config | 獲取系統配置參數 | {$Think.config.app_status} |
$Think.lang | 獲取系統語言變量 | {$Think.lang.user_type} |
$Think.const | 獲取系統常量 | {$Think.const.app_name}或{$Think.APP_NAME} |
$Think.env | 獲取環境變量 | {$Think.env.HOSTNAME} |
$Think.version | 獲取框架版本號 | {$Think.version} |
$Think.now | 獲取當前時間 | {$Think.now} |
$Think.template | 獲取當前模板 | {$Think.template} |
$Think.ldelim | 獲取模板左界定符 | {$Think.ldelim} |
$Think.rdelim | 獲取模板右界定符 | {$Think.rdelim} |
4、配置參數:輸出項目的配置參數值
- {$Think.config.db_charset}
輸出的值和C('db_charset') 的返回結果是一樣的。
也可以輸出二維的配置參數,例如:
- {$Think.config.user.user_name}
- {$Think.lang.page_error}
3. 使用函數
- {$varname|function1|function2=arg1,arg2,###?}
- {$webTitle|md5|strtoupper|substr=0,3}
編譯后的PHP代碼就是:
- <?php?echo?(substr(strtoupper(md5($webTitle)),0,3));??>
注意函數的定義和使用順序的對應關系,通常來說函數的第一個參數就是前面的變量或者前一個函數調用的返回結果,如果你的變量并不是函數的第一個參數,需要使用定位符號,例如:
- {$create_time|date="y-m-d",###}
編譯后的PHP是:
- <?php?echo?(date("y-m-d",$create_time));??>
- TMPL_DENY_FUNC_LIST=>"echo,exit,halt"
并且還提供了在模板文件中直接調用函數的快捷方法,這種方式更加直接明了,而且無需通過模板變量,包括兩種方式:
1、執行函數并輸出返回值:
格式:{:function(…)}?
例如,輸出U函數的返回值:
- {:U('User/insert')}
編譯后的PHP代碼是
- <?php?echo?U('User/insert');?>
2、執行函數但不輸出:
格式:{~function(…)}?
例如,調用say_hello函數:
- {~say_hello('ThinkPHP')}
編譯后的PHP代碼是:
- <?php?say_hello('ThinkPHP');?>
4. 默認值輸出
如果輸出的模板變量沒有值,但是我們需要在顯示的時候賦予一個默認值的話,可以使用default語法,格式:{$變量|default="默認值"}
這里的default不是函數,而是系統的一個語法規則,例如:
- {$user.nickname|default="這家伙很懶,什么也沒留下"}
- {$Think.post.name|default="名稱為空"}
5. 使用運算符
內置模板引擎包含了運算符的支持,包括對“+”“ –” “*” “/”和“%”的支持。
- {$user.score+10}?是錯誤的
- ?{$user['score']+10}?是正確的
- ?{$user['score']*$user['level']}?正確的
- ?{$user['score']|myFun*10}?錯誤的
- ?{$user['score']+myFun($user['level'])}?正確的
6. 內置標簽
系統內置標簽庫的所有標簽無需引入標簽庫即可直接使用。XML標簽有兩種,包括閉合標簽和開放標簽,一個標簽在定義的時候就已經決定了是否是閉合標簽還是開放標簽,不可混合使用,例如:
閉合標簽:
- <include?file="read"?/>
開放標簽:
- <gt?name="name"?value="5">value</gt>
內置支持的標簽和屬性列表如下:
標簽名 | 作用 | 包含屬性 |
---|---|---|
include | 包含外部模板文件(閉合) | file |
import | 導入資源文件(閉合?包括js?css?load別名) | file,href,type,value,basepath |
volist | 循環數組數據輸出 | name,id,offset,length,key,mod |
foreach | 數組或對象遍歷輸出 | name,item,key |
for | For循環數據輸出 | name,from,to,before,step |
switch | 分支判斷輸出 | name |
case | 分支判斷輸出(必須和switch配套使用) | value,break |
default | 默認情況輸出(閉合?必須和switch配套使用) | 無 |
compare | 比較輸出(包括eq?neq?lt?gt?egt?elt?heq?nheq等別名) | name,value,type |
range | 范圍判斷輸出(包括in?notin?between?notbetween別名) | name,value,type |
present | 判斷是否賦值 | name |
notpresent | 判斷是否尚未賦值 | name |
empty | 判斷數據是否為空 | name |
notempty | 判斷數據是否不為空 | name |
defined | 判斷常量是否定義 | name |
notdefined | 判斷常量是否未定義 | name |
define | 常量定義(閉合) | name,value |
assign | 變量賦值(閉合) | name,value |
if | 條件判斷輸出 | condition |
elseif | 條件判斷輸出(閉合??必須和if標簽配套使用) | condition |
else | 條件不成立輸出(閉合?可用于其他標簽) | 無 |
php | 使用php代碼 | 無 |
7. 包含文件
可以使用Include標簽來包含外部的模板文件,使用方法如下:
include標簽(包含外部模板文件) | |
---|---|
閉合 | 閉合標簽 |
屬性 | file(必須):要包含的模板文件,支持變量 |
1、 使用完整文件名包含
格式:<include file="完整模板文件名" />
- <include?file="./Tpl/default/Public/header.html"?/>
2、包含當前模塊的其他操作模板文件
格式:<include file="操作名" />
- <include?file="read"?/>
操作模板無需帶后綴。
4、包含其他模板主題的模塊操作模板
格式:<include file="主題名:模塊名:操作名" />
- <include?file="blue:User:read"?/>
格式:<include file="$變量名" />
- <include?file="$tplName"?/>
3.1版本開始,include標簽支持導入多個模板,用逗號分割即可,例如:
- <include?file='file1,file2'?/>
8. 導入文件
import標簽(包含外部模板文件) | |
---|---|
閉合 | 閉合標簽 |
屬性 | file(必須):要包含的模板文件,支持變量 |
- <import?type='js'?file="Js.Util.Array"?/>
- <import?file="Js.Util.Array,Js.Util.Date"?/>
導入外部CSS文件必須指定type屬性的值,例如:
- <import?type='css'?file="Css.common"?/>
上面的方式默認的import的起始路徑是網站的Public目錄,如果需要指定其他的目錄,可以使用basepath屬性,例如:
- <import?file="Js.Util.Array"??basepath="./Common"?/>
load標簽(采用url方式引入資源文件) | |
---|---|
閉合 | 閉合標簽 |
屬性 | href(必須):要引入的資源文件url地址,支持變量 |
- <load?href="/Book/Tpl/Home/Public/Js/Common.js"?/>
- ?<load?href="/Book/Tpl/Home/Public/Css/common.css"?/>
系統還提供了兩個標簽別名js和css 用法和load一致,例如:
- <js?href="/Public/Js/Common.js"?/>
- ?<css?href="/Book/Tpl/Home/Public/Css/common.css"?/>
9. Volist標簽
Volist標簽主要用于在模板中循環輸出數據集或者多維數組。
volist標簽(循環輸出數據) | |
---|---|
閉合 | 非閉合標簽 |
屬性 | name(必須):要輸出的數據模板變量 id(必須):循環變量 offset(可選):要輸出數據的offset length(可選):輸出數據的長度 key(可選):循環的key變量,默認值為i mod(可選):對key值取模,默認為2 empty(可選):如果數據為空顯示的字符串 |
通常模型的select方法返回的結果是一個二維數組,可以直接使用volist標簽進行輸出。
在Action中首先對模版賦值:
- $User?=?M('User');
- $list?=?$User->select();
- $this->assign('list',$list);
在模版定義如下,循環輸出用戶的編號和姓名:
- <volist?name="list"?id="vo">
- {$vo.id}
- {$vo.name}
- ?</volist>
輸出循環變量
- <volist?name="list"?id="vo"?key="k"?>
- {$k}.{$vo.name}
- ?</volist>
如果沒有指定key屬性的話,默認使用循環變量i,例如:
- <volist?name="list"?id="vo"??>
- {$i}.{$vo.name}
- ?</volist>
如果要輸出數組的索引,可以直接使用key變量,和循環變量不同的是,這個key是由數據本身決定,而不是循環控制的,例如:
- <volist?name="list"?id="vo"??>
- {$key}.{$vo.name}
- ?</volist>
從2.1版開始允許在模板中直接使用函數設定數據集,而不需要在控制器中給模板變量賦值傳入數據集變量,如:
- <volist?name=":fun('arg')"?id="vo">{$vo.name}</volist>
10. Foreach標簽
foreach標簽也是用于循環輸出foreach標簽(循環輸出數據) | |
---|---|
閉合 | 非閉合標簽 |
屬性 | name(必須):要輸出的數據模板變量 item(必須):循環單元變量 key(可選):循環的key變量,默認值為key |
- <foreach?name="list"?item="vo">
- ????{$vo.id}
- ????{$vo.name}
- ?</foreach>
11. For標簽
For標簽用于實現for循環,格式為:
for標簽(循環輸出數據) | |
---|---|
閉合 | 非閉合標簽 |
屬性 | start(必須):循環變量開始值 end(必須):循環變量結束值 name(可選):循環變量名,默認值為i step(可選):步進值,默認值為1 comparison(可選):判斷條件,默認為lt |
- <for?start="開始值"?end="結束值"?comparison=""?step="步進值"?name="循環變量名"?>
- ?</for>
- <for?start="1"?end="100">
- {$i}
- ?</for>
解析后的代碼是
- for?($i=1;$i<100;$i+=1){
- ????echo?$i;
- ?}
12. Switch標簽
- <switch?name="變量"?>
- ?<case?value="值1"?break="0或1">輸出內容1</case>
- ?<case?value="值2">輸出內容2</case>
- ?<default?/>默認情況
- ?</switch>
其中name屬性可以使用函數以及系統變量,例如:
- <switch?name="Think.get.userId|abs">
- ????<case?value="1">admin</case>
- ????<default?/>default
- ?</switch>
對于case的value屬性可以支持多個條件的判斷,使用”|”進行分割,例如:
- <switch?name="Think.get.type">
- ????<case?value="gif|png|jpg">圖像格式</case>
- ????<default?/>其他格式
- ?</switch>
Case標簽還有一個break屬性,表示是否需要break,默認是會自動添加break,如果不要break,可以使用:
- <switch?name="Think.get.userId|abs">
- ????<case?value="1"?break="0">admin</case>
- ????<case?value="2">admin</case>
- ????<default?/>default
- ?</switch>
- <switch?name="User.userId">
- ????<case?value="$adminId">admin</case>
- ????<case?value="$memberId">member</case>
- ????<default?/>default
- ?</switch>
13. 比較標簽
- <比較標簽?name="變量"?value="值">內容</比較標簽>
系統支持的比較標簽以及所表示的含義分別是:
eq或者 equal | 等于 |
neq 或者notequal | 不等于 |
gt | 大于 |
egt | 大于等于 |
lt | 小于 |
elt | 小于等于 |
heq | 恒等于 |
nheq | 不恒等于 |
例如,要求name變量的值等于value就輸出,可以使用:
- <eq?name="name"?value="value">value</eq>
當name變量的值不小于5就輸出
- <egt?name="name"?value="5">value</egt>
當vo對象的屬性(或者數組,或者自動判斷)等于5就輸出
- <eq?name="vo.name"?value="5">{$vo.name}</eq>
而且還可以支持對變量使用函數?
當vo對象的屬性值的字符串長度等于5就輸出
- <eq?name="vo:name|strlen"?value="5">{$vo.name}</eq>
變量名可以支持系統變量的方式,例如:
- <eq?name="Think.get.name"?value="value">相等<else/>不相等</eq>
14. 三元運算
模板可以支持三元運算符,例如:- {$status?'正常':'錯誤'}
- ?{$info['status']?$info['msg']:$info['error']}
15. 范圍判斷標簽
Range標簽用于判斷某個變量是否在某個范圍之內:
范圍判斷標簽(包括innotinbetween notbetween) | |
---|---|
閉合 | 非閉合標簽 |
屬性 | name(必須):變量名 value(必須):要比較的范圍值,支持變量 |
可以使用in標簽來判斷模板變量是否在某個范圍內,例如:
- <in?name="id"value="1,2,3">輸出內容1</in>
如果判斷不再某個范圍內,可以使用:
- <notin?name="id"value="1,2,3">輸出內容2</notin>
可以把上面兩個標簽合并成為:
- <in?name="id"value="1,2,3">輸出內容1<else/>輸出內容2</in>
- <between?name="id"value="1,10">輸出內容1</between>
- <notbetween?name="id"value="1,10">輸出內容1</notbetween>
- <in?name="id"value="$var">輸出內容1</in>
也可以直接使用range標簽,替換in和notin的用法:
- <range?name="id"value="1,2,3"type="in">輸出內容1</range>
16. Present標簽和Empty標簽
可以使用present標簽來判斷模板變量是否已經賦值,
present標簽和notpresent標簽 | |
---|---|
閉合 | 非閉合標簽 |
屬性 | name(必須):變量名 |
配合 | 可以結合else標簽一起使用 |
- <present?name="name">name已經賦值</present>
如果判斷沒有賦值,可以使用:
- <notpresent?name="name">name還沒有賦值</notpresent>
可以把上面兩個標簽合并成為:
- <present?name="name">name已經賦值<else?/>?name還沒有賦值</present>
可以使用empty標簽判斷模板變量是否為空,
empty標簽和notempty標簽 | |
---|---|
閉合 | 非閉合標簽 |
屬性 | name(必須):變量名 |
配合 | 可以結合else標簽一起使用 |
- <empty?name="name">name為空值</empty>
如果判斷沒有賦值,可以使用:
- <notempty?name="name">name不為空</notempty>
可以把上面兩個標簽合并成為:
- <empty?name="name">name為空<else?/>?name不為空</empty>
17. Defined標簽和Define標簽
可以使用defined標簽判斷常量是否已經有定義:
defined標簽和notdefined標簽 | |
---|---|
閉合 | 非閉合標簽 |
屬性 | name(必須):變量名 |
- <defined?name="NAME">NAME常量已經定義</defined>
如果判斷沒有被定義,可以使用:
- <notdefined?name="NAME">NAME常量未定義</notdefined>
可以把上面兩個標簽合并成為:
- <defined?name="NAME">NAME常量已經定義<else?/>?NAME常量未定義</defined>
defined標簽和notdefined標簽 | |
---|---|
閉合 | 閉合標簽 |
屬性 | name(必須):常量名 value(必須):常量值,支持變量 |
配合 | 可以結合else標簽一起使用 |
- <define?name="MY_DEFINE_NAME"value="3"/>
18. Assign標簽
可以使用assign標簽進行賦值:assign標簽(在模板中給變量賦值) | |
---|---|
閉合 | 閉合標簽 |
屬性 | name(必須):模板變量名 value(必須):變量值,支持變量 |
- <assign?name="var"?value="123"?/>
19. IF標簽
用法示例:
- <if?condition="($name?eq?1)?OR?($name?gt?100)?">?value1
- ?<elseif?condition="$name?eq?2"/>value2
- ?<else?/>?value3
- ?</if>
除此之外,我們可以在condition屬性里面使用php代碼,例如:
- <if?condition="strtoupper($user['name'])?neq?'THINKPHP'">ThinkPHP
- ?<else?/>?other?Framework
- ?</if>
自動判斷user變量是數組還是對象
- <if?condition="$user.name?neq?'ThinkPHP'">ThinkPHP
- ?<else?/>?other?Framework
- ?</if>
- <if?condition="$user:name?neq?'ThinkPHP'">ThinkPHP
- ?<else?/>?other?Framework
- ?</if>
20. 標簽嵌套
模板引擎支持標簽的多層嵌套功能,可以對標簽庫的標簽指定可以嵌套。系統內置的標簽中,volist、switch、if、elseif、else、foreach、compare(包括所有的比較標簽)、(not)present、(not)empty、(not)defined等標簽都可以嵌套使用。例如:
- <volist?name="list"?id="vo">
- ????<volist?name="vo['sub']"?id="sub">
- ????????{$sub.name}
- ????</volist>
- ?</volist>
- 'TAG_NESTED_LEVEL'?=>5
21. 使用PHP代碼
- <php>echo?'Hello,world!';</php>
注意:php標簽或者php代碼里面就不能再使用標簽(包括普通標簽和XML標簽)了,因此下面的幾種方式都是無效的:
- <php><eq?name='name'value='value'>value</eq></php>
如果設置了TMPL_DENY_PHP參數為true,就不能在模板中使用原生的PHP代碼,但是仍然支持PHP標簽輸出。
22. 模板布局
第一種方式是 以布局模板為入口的方式該方式需要配置開啟LAYOUT_ON 參數(默認不開啟),并且設置布局入口文件名LAYOUT_NAME(默認為layout)。
開啟LAYOUT_ON后,我們的模板渲染流程就有所變化,例如:
- Class?UserAction?extends?Action?{
- ????Public?function?add()?{
- ????$this->display('add');
- ????}
- ?}
- {__CONTENT__}
采用這種布局方式的情況下,一旦User/add.html 模板文件或者layout.html布局模板文件發生修改,都會導致模板重新編譯。
如果項目需要使用不同的布局模板,可以動態的配置LAYOUT_NAME參數實現。
如果某些頁面不需要使用布局模板功能,可以在模板文件開頭加上 {__NOLAYOUT__} 字符串。
如果上面的User/add.html 模板文件里面包含有{__NOLAYOUT__},則即使當前開啟布局模板,也不會進行布局模板解析。
以前面的輸出模板為例,這種方式的入口還是在User/add.html 模板,但是我們可以修改下add模板文件的內容,在頭部增加下面的布局標簽:
- <layout?name="layout"?/>
如果需要使用其他的布局模板,可以改變layout的name屬性,例如:
- <layout?name="new_layout"?/>
- <include?file="Public:header"?/>
- ?<div?id="main"?class="main"?>
- {__CONTENT__}
- ?</div>
- ?<include?file="Public:bottom"?/>?
- <layout?name="menu"?/>
也可以采用兩種布局方式的結合,可以實現更加復雜的模板布局以及嵌套功能。
23. 模板繼承
模板繼承的優勢其實是設計基礎模板中的區塊(block)和子模板中替換這些區塊。每個區塊由<block></block>標簽組成,并且不支持block標簽的嵌套。
下面就是基礎模板中的一個典型的區塊設計(用于設計網站標題):
- <block?name="title"><title>網站標題</title></block>
block標簽必須指定name屬性來標識當前區塊的名稱,這個標識在當前模板中應該是唯一的,block標簽中可以包含任何模板內容,包括其他標簽和變量,例如:
- <block?name="title"><title>{$web_title}</title></block>
你甚至還可以在區塊中加載外部文件:
- <block?name="include"><include?file="Public:header"?/></block>
24. 原樣輸出
literal標簽(保持原樣輸出) | |
---|---|
閉合 | 非閉合標簽 |
屬性 | 無 |
- <literal>
- ????<if?condition="$name?eq?1?">?value1
- ????<elseif?condition="$name?eq?2"/>value2
- ????????<else?/>?value3
- ????</if>
- ?</literal>
總之,所有可能和內置模板引擎的解析規則沖突的地方都可以使用literal標簽處理。
25. 模板注釋
模板支持注釋功能,該注釋文字在最終頁面不會顯示,僅供模板制作人員參考和識別。
- {//?這是模板注釋內容?}
- ?{/*?這是模板
- 注釋內容*/?}
26. 引入標簽庫
格式:<tagLib name="標簽庫1[,標簽庫2,…]"/>可以同時導入多個標簽庫,用逗號分隔,例如:
- <tagLib?name="html"/>
- <html:select?options='name'?selected='value'?/>
- ?<html:link?href='/path/to/common.js'?/>
- <HTML:LINK?HREF='/path/to/common.js'?/>
- ?'TAGLIB_PRE_LOAD'?=>?'html'?,
- <html:select?options='name'?selected='value'?/>
假設你確信Html標簽庫無論在現在還是將來都不會和系統內置的標簽庫存在相同的標簽,那么可以配置TAGLIB_BUILD_IN的值把Html標簽庫作為內置標簽庫引入,例如:
- 'TAGLIB_BUILD_IN'?=>?'cx,html'?,
- <select?options='name'?selected='value'?/>
27. 修改定界符
要更改普遍模板的起始標簽和結束標簽,請使用下面的配置參數:
- TMPL_L_DELIM??//模板引擎普通標簽開始標記?
- TMPL_R_DELIM????//模板引擎普通標簽結束標記
例如在項目配置文件中增加下面的配置:
- 'TMPL_L_DELIM'=>'',
普通模板標簽主要用于模板變量輸出和模板注釋。如果要使用其它功能,請使用XML模板標簽。XML模板標簽可以用于模板變量輸出、文件包含、條件控制、循環輸出等功能,而且完全可以自己擴展功能。如果你覺得XML標簽無法在正在使用的編輯器里面無法編輯,還可以更改XML標簽庫的起始和結束標簽,請修改下面的配置參數:
- TAGLIB_BEGIN????//標簽庫標簽開始標簽?
- TAGLIB_END????//標簽庫標簽結束標記
例如在項目配置文件中增加下面的配置:
- 'TAGLIB_BEGIN'=>'[',
- ?'TAGLIB_END'=>']',
28. 避免JS混淆
如果使用內置的模板引擎,而且采用默認的標簽設置的話,在某些情況下,如果不注意,{$('name').value} 這樣的JS代碼很容易被內置模板引擎誤解析。有三個方法可以解決類似的混淆問題:
1、{$('name').value}改成{ $('name').value}
因為內置模板引擎的解析規則是"{"后面緊跟"$"符號才會解析變量 因此只要在"{" 和"$"之間添加空格就不會被誤解析了
2、使用內置的literal標簽包含JS代碼
<literal>JS代碼</literal> 包含在literal標簽中的代碼將會直接輸出,不進行任何解析
3、定制模板引擎標簽的定界符
例如:'TMPL_L_DELIM'=>'<{','TMPL_R_DELIM'=>'}>'這樣就和JS代碼區別開來了。
日志:
日志的處理工作是由系統自動進行的,在開啟日志記錄的情況下,會記錄下允許的日志級別的所有日志信息。其中,為了性能考慮,SQL日志級別必須在調試模式開啟下有效,否則就不會記錄。系統的日志記錄由核心的Log類完成,提供了多種方式記錄了不同的級別的日志信息。
1. 日志級別
EMERG | 嚴重錯誤,導致系統崩潰無法使用 |
ALERT | 警戒性錯誤, 必須被立即修改的錯誤 |
CRIT | 臨界值錯誤, 超過臨界值的錯誤 |
ERR | 一般性錯誤 |
WARN | 警告性錯誤, 需要發出警告的錯誤 |
NOTICE | 通知,程序可以運行但是還不夠完美的錯誤 |
INFO | 信息,程序輸出信息 |
DEBUG | 調試,用于調試信息 |
SQL | SQL語句,該級別只在調試模式開啟時有效 |
要開啟日志記錄,必須在配置中開啟LOG_RECORD參數,以及可以在項目配置文件中配置需要記錄的日志級別,例如:
- 'LOG_RECORD'?=>?true,?//?開啟日志記錄
- ?'LOG_LEVEL'??=>'EMERG,ALERT,CRIT,ERR',?//?只記錄EMERG?ALERT?CRIT?ERR?錯誤
2. 記錄方式
記錄方式 | 說明 | 常量標識 |
---|---|---|
SYSTEM | 日志發送到PHP的系統日志記錄 | 0 |
日志通過郵件方式發送 | 1 | |
FILE | 日志通過文件方式記錄(默認方式) | 3 |
SAPI | 日志通過SAPI方式記錄 | 4 |
其中的時間顯示可以動態配置,默認是采用 [ c ],例如我們可以改成:
- Log::$format?=?'[?Y-m-d?H:i:s?]';
- [2012-01-15T18:09:22+08:00]?/Index/index|NOTIC:?[8]?Undefined?variable:?verify?PublicAction.class.php?第?162?行.
- ?[2012-01-15T18:09:22+08:00]?/Index/index?|?SQL:??RunTime:0.214238s?SQL?=?SHOW?COLUMNS?FROM?think_user
- ?[2012-01-15T18:09:22+08:00]?/Index/index?|?SQL:??RunTime:0.039159s?SQL?=?SELECT?*?FROM?`think_user`?WHERE?(?`account`?=?'admin'?)?AND?(?`status`?>?0?)?LIMIT?1
默認采用文件方式記錄日志信息,日志文件的命名格式是:年(簡寫)_月_日.log,例如:09_10_01.log 表示2009年10月1日的日志文件
可以設置LOG_FILE_SIZE參數來限制日志文件的大小,超過大小的日志會形成備份文件。備份文件的格式是在當前文件名前面加上備份的時間戳,例如:
1189571417-07_09_12.log 備份的日志文件
- 'LOG_TYPE'?=>1,?//??采用郵件方式記錄日志
- ?'LOG_DEST'?=>'admin@domain.com',?//?要發送日志的郵箱
- ?'LOG_EXTRA'?=>'From:?webmaster@example.com',?//?郵件的發件人設置
3. 手動記錄
http://doc.thinkphp.cn/manual/log_record.html錯誤:
1. 異常處理
和PHP默認的異常處理不同,ThinkPHP拋出的不是單純的錯誤信息,而是一個人性化的錯誤頁面。調試模式下面一旦系統發生嚴重錯誤會自動拋出異常,也可以用ThinkPHP定義的throw_exception方法手動拋出異常。
- throw_exception('新增失敗');
- throw_exception('信息錄入錯誤','InfoException');
同樣也可以使用throw 關鍵字來拋出異常,下面的寫法是等效的:
- throw?new?ThinkException('新增失敗');
- ?throw?new?InfoException('信息錄入錯誤');
如果需要,我們建議在項目的類庫目錄下面增加Exception目錄用于專門存放異常類庫,以更加精確地定位異常。2. 異常模板
系統內置的異常模板在系統目錄的Tpl/think_exception.tpl,可以通過修改系統模板來修改異常頁面的顯示。也通過設置TMPL_EXCEPTION_FILE 配置參數來修改系統默認的異常模板文件, 例如:
- 'TMPL_EXCEPTION_FILE'?=>?APP_PATH.'/Public/exception.tpl'
異常模板中可以使用的異常變量有:$e['file']異常文件名
$e['line'] 異常發生的文件行數
$e['message'] 異常信息
$e['trace'] 異常的詳細Trace信息
因為異常模板使用的是原生PHP代碼,所以還可以支持任何的PHP方法和系統變量使用。
3. 異常顯示
拋出異常后通常會顯示具體的錯誤信息,如果不想讓用戶看到具體的錯誤信息,可以設置關閉錯誤信息的顯示并設置統一的錯誤提示信息,例如:
- 'SHOW_ERROR_MSG'?=>false,
- ?'ERROR_MESSAGE'?=>'發生錯誤!'
設置之后,所有的異常頁面只會顯示“發生錯誤!”這樣的提示信息,但是日志文件中仍然可以查看具體的錯誤信息。新版如果關閉調試模式的話,為了安全起見,默認就是關閉異常信息提示。另外一種方式是配置ERROR_PAGE參數,把所有異常和錯誤都指向一個統一頁面,從而避免讓用戶看到異常信息,通常在部署模式下面使用。ERROR_PAGE參數必須是一個完整的URL地址,例如:
- 'ERROR_PAGE'?=>'/Public/error.html'
如果不在當前域名,還可以指定域名:
- 'ERROR_PAGE'?=>'http://www.myDomain.com/Public/error.html'
注意ERROR_PAGE所指向的頁面不能再使用異常的模板變量了。?