# 第一講:認(rèn)識(shí)控制器
本講主要是了解`ThinkPHP5.0`的控制器的基本概念和使用方法,主要包括:
[TOC=2,2]
## 什么是控制器
控制器就是`MVC`設(shè)計(jì)模式中的C(`Controller`),通常用于讀取視圖V(`View`)、完成用戶輸入以及處理模型數(shù)據(jù)M(`Model`)。
按照ThinkPHP的架構(gòu)設(shè)計(jì),所有的URL請(qǐng)求(無論是否采用了路由),最終都會(huì)定位到控制器(也許實(shí)際的類不一定是控制器類,但也屬于廣義范疇的控制器)??刂破鞯膶涌赡苡泻芏啵瑸榱吮阌趨^(qū)分就把通過URL訪問的控制器稱之為訪問控制器(通常意義上我們所說的控制器就是指訪問控制器)。
例如我們?cè)L問一個(gè)URL地址:
~~~
http://tp5.com/index/index/hello
~~~
> 本文檔的所有示例都以`tp5.com`為應(yīng)用測(cè)試域名,請(qǐng)首先配置`vhost`指向tp5的`public`目錄(如不清楚請(qǐng)參考快速入門教程)。
實(shí)際上訪問的是`index`模塊下的`Index`控制器類的`hello`方法(在沒有定義任何路由的情況下),`Index`控制器對(duì)應(yīng)的類就是`app\index\controller\Index`(為什么控制器類名需要這樣命名后面命名空間部分會(huì)詳細(xì)描述),完成上面的URL訪問,只需要定義如下的控制器類,看起來非常簡(jiǎn)單:
~~~
<?php
namespace app\index\controller;
class Index
{
public function hello()
{
return 'hello,world';
}
}
~~~
然后保存到:
~~~
application/index/controller/Index.php
~~~
現(xiàn)在你可以正式測(cè)試前面提到的URL地址了。
ThinkPHP5的命名規(guī)范遵循`PSR-2`規(guī)范,并且約定了以下規(guī)則:
* 目錄名統(tǒng)一使用小寫+下劃線;
* 類名使用駝峰(首字母大寫)命名;
* 類文件名和類名規(guī)范一致,并使用`.php`文件后綴;
* 類的方法使用駝峰(首字母小寫)命名;
* 一個(gè)文件中只對(duì)應(yīng)一個(gè)類;
>[danger] 特別強(qiáng)調(diào):模塊名作為目錄作用強(qiáng)制使用小寫和下劃線規(guī)范
遵循命名規(guī)范的目的是為了讓框架可以根據(jù)類的命名空間快速定位類文件的位置,從而實(shí)現(xiàn)自動(dòng)加載,這也是`PSR-4`規(guī)范的要求。
## 命名空間
現(xiàn)在來分析下控制器的類名為什么是`app\index\controller\Index`而不是`Index`,首先就是要明白命名空間的概念。PHP從5.3版本開始引入命名空間的概念,其主要作用是確保類名不會(huì)沖突,因?yàn)樵谝粋€(gè)應(yīng)用中,出現(xiàn)相同的類名的幾率非常之大,并且你很難保證引入的第三方類庫不沖突,而有了命名空間后,相當(dāng)于給自己的類加了一個(gè)門牌號(hào)一樣,一個(gè)類的組成包括:
>[info]### 類的組成 = 根命名空間+子命名空間(可選)+類名
這樣即使是相同的類名,只要在不同的命名空間下面就屬于完全不同的類,所以下面的類都是不同的類庫:
~~~
app\index\controller\Index
app\admin\controller\Index
app\controller\Index
~~~
而當(dāng)使用下面的代碼實(shí)例化一個(gè)`Index`類的時(shí)候,
~~~
$controller = new Index();
~~~
系統(tǒng)其實(shí)并不知道你要實(shí)例化的是哪個(gè)類庫,所以首先就會(huì)從當(dāng)前文件所在的命名空間去實(shí)例化`Index`類,但這樣經(jīng)常會(huì)產(chǎn)生混淆,所以合理的辦法是明確告訴系統(tǒng)你實(shí)例化的是哪個(gè)具體的類,通常我們會(huì)使用`use`來引入一個(gè)命名空間類庫,例如:
~~~
use app\admin\controller\Index;
$controller = new Index();
~~~
這個(gè)時(shí)候就會(huì)明確當(dāng)前實(shí)例化的是`app\admin\controller\Index`類,而不會(huì)是`app\index\controller\Index`類或者`app\controller\Index`類。
在不使用`use`引入的情況下,可以直接使用完整的命名空間來實(shí)例化(但并不建議,完全不必?fù)?dān)心`use`過多的類庫會(huì)帶來性能問題)
~~~
$controller = new \app\admin\controller\Index();
~~~
>[danger] 完整命名空間實(shí)例化的時(shí)候必須加上開頭的`\`表示從根命名空間開始。
命名空間的根命名是可以設(shè)置起始路徑的(嚴(yán)格來說,不僅是根命名可以設(shè)置,比如有些擴(kuò)展就可以單獨(dú)設(shè)置自己的命名空間的對(duì)應(yīng)路徑,`composer`通常是這么設(shè)計(jì)的),系統(tǒng)默認(rèn)設(shè)置了下面三個(gè)根命名:
|根命名|描述|類庫起始目錄|
|---|---|---|
|think|系統(tǒng)核心類庫|thinkphp/library/think|
|traits|系統(tǒng)Trait類庫|thinkphp/library/traits|
|app|應(yīng)用類庫|application|
按照PSR-4的規(guī)范,子命名空間和目錄**必須**是一一對(duì)應(yīng)的,而且**大小寫一致**。最后的類名部分實(shí)際對(duì)應(yīng)的是一個(gè)和類名一致(包括大小寫)的文件,保持一致規(guī)范約束的目的是為了實(shí)現(xiàn)類的自動(dòng)加載(`ThinkPHP`開發(fā)過程中一定要明白大小寫是嚴(yán)格區(qū)分的,即使是在`windows`下面)。
綜上分析,前面的類庫對(duì)應(yīng)的類文件分別是:
~~~
application/index/controller/Index.php
application/admin/controller/Index.php
application/controller/Index.php
~~~
現(xiàn)在我們來回答為什么控制器類的名稱是`app\index\controller\Index`,這是ThinkPHP框架制定的規(guī)范,`app`是應(yīng)用類庫的根命名空間,也就是所有的應(yīng)用類庫都應(yīng)該用`app`作為根命名空間定義。`index`是表示模塊目錄,`controller`表示的是控制器(確切的說是訪問控制器)目錄,`Index`是實(shí)際的控制器類名,所以要表示`index`模塊的`Index`控制器類,使用的就是`app\index\controller\Index`,如果是`admin`模塊的`Index`控制器類,使用的就是`app\admin\controller\Index`類,如果使用的是單一模塊的話,那么`Index`控制器類就變成了`app\controller\Index`,現(xiàn)在明白了么?
核心類庫都是以`think`開頭的命名空間,應(yīng)用類庫都是以`app`開頭的命名空間,核心類庫一般也不需要更改命名空間,但應(yīng)用類庫是可以單獨(dú)定義命名空間的,有些新手總有困惑按照目錄一致的規(guī)范為什么應(yīng)用類庫的根命名空間不是`application`而是`app`(我能說是框架的好意么),下面的配置可以治療這種糾結(jié),將應(yīng)用的命名空間改為`application`:
~~~
// 應(yīng)用命名空間
'app_namespace' => 'application',
~~~
> 不要問我配置文件在哪里修改^_^ 說好的學(xué)好基礎(chǔ)再來呢
修改后,前面對(duì)應(yīng)類的命名空間需要調(diào)整為
~~~
application\index\controller\Index
application\admin\controller\Index
application\controller\Index
~~~
但對(duì)應(yīng)的類文件實(shí)際位置仍然保持不變。
> 在后面的教程內(nèi)容里面不會(huì)每次都說明一個(gè)類文件的實(shí)際位置,大家看到一個(gè)類的命名空間后就應(yīng)該可以定位類文件的位置,否則說明你對(duì)命名空間還不夠理解,請(qǐng)?jiān)俅伍喿x下前面的內(nèi)容。
## 控制器繼承
前面是一個(gè)很簡(jiǎn)單的例子,沒有繼承任何的類(這樣并沒有任何不對(duì),5.0的控制器設(shè)計(jì)如此,事實(shí)上也非常高效),控制器可以繼承系統(tǒng)內(nèi)置的控制器基類`think\Controller`或者應(yīng)用自己的控制器基類,來擴(kuò)展更多的功能和方法。
繼承系統(tǒng)控制器基類:
~~~
<?php
namespace app\index\controller;
use think\Controller;
class Index extends Controller
{
public function hello()
{
return 'hello,world';
}
}
~~~
> 系統(tǒng)控制器基類提供了一些額外的方法,我們會(huì)在后面陸續(xù)講解。
或者自定義一個(gè)基礎(chǔ)控制器類`Base`:
~~~
<?php
namespace app\index\controller;
use think\Controller;
class Base extends Controller
{
}
~~~
可以在`Base`控制器類中定義一些公共方法(如果對(duì)類的基本知識(shí)不夠熟悉的話,參考PHP的類與對(duì)象部分說的非常清楚,在此不做深入了)。
然后應(yīng)用下面的所有控制器類都繼承`Base`:
~~~
<?php
namespace app\index\controller;
use app\index\controller\Base;
class Index extends Base
{
public function hello()
{
return 'hello,world';
}
}
~~~
建議給應(yīng)用統(tǒng)一定義一個(gè)自己的控制器基類,方便后期擴(kuò)展。
> PHP不支持多繼承,如果需要繼承多個(gè)類,可以通過引入`trait`。
## 操作方法
控制器類的每一個(gè)`public`類型方法(包括繼承的)都是一個(gè)可訪問的操作,也是URL訪問的最小單元,`private`和`protected`類型的方法都不能被訪問(只能在控制器內(nèi)部被調(diào)用)。
下面舉個(gè)簡(jiǎn)單的例子:
~~~
<?php
namespace app\index\controller;
use think\Controller;
class Base extends Controller
{
public function base()
{
return 'base';
}
}
~~~
Index控制器的測(cè)試代碼如下:
~~~
<?php
namespace app\index\controller;
use app\index\controller\Base;
class Index extends Base
{
public function hello()
{
return 'hello,world';
}
private function far()
{
return 'far';
}
protected function boo()
{
return 'boo';
}
public function test()
{
return 'test';
}
}
~~~
下面的URL訪問可以正常訪問
~~~
http://tp5.com/index/index/hello
http://tp5.com/index/index/test
http://tp5.com/index/index/base
~~~
下面的URL訪問會(huì)報(bào)錯(cuò)
~~~
http://tp5.com/index/index/far
http://tp5.com/index/index/boo
~~~
雖然使用`echo`方法也能正常輸出,但`ThinkPHP5`的操作方法建議統(tǒng)一使用`return`返回值的方式進(jìn)行響應(yīng)輸出(除非你使用`echo`或者`dump`進(jìn)行調(diào)試輸出),優(yōu)勢(shì)是系統(tǒng)可以自動(dòng)判斷當(dāng)前的響應(yīng)輸出類型進(jìn)行自動(dòng)轉(zhuǎn)換處理,以及可以享受請(qǐng)求緩存的便利。
## 駝峰命名
控制器類名的規(guī)范是駝峰法(并且首字母大寫),不過URL的訪問地址并非如此,假設(shè)定義了一個(gè)`HelloWorld`控制器如下:
~~~
<?php
namespace app\index\controller;
class HelloWorld
{
public function index()
{
return 'hello,world';
}
}
~~~
實(shí)際的URL訪問并非是下面的
~~~
http://tp5.com/index/HelloWorld/index
~~~
實(shí)際會(huì)被系統(tǒng)解析成`Helloworld`控制器類而不是`HelloWorld`控制器類(雖然只是大小寫的區(qū)別但按照`PSR-4`自動(dòng)加載規(guī)范無法自動(dòng)加載,因此會(huì)報(bào)`Helloworld`控制器類不存在的錯(cuò)誤)。
正確的URL訪問應(yīng)該是
~~~
http://tp5.com/index/hello_world/index
~~~
> 注意`hello_world`并不會(huì)自動(dòng)對(duì)應(yīng)`hello_world`控制器(因?yàn)椴环峡刂破黝惖拿?guī)范),仍然會(huì)自動(dòng)對(duì)應(yīng)`HelloWorld`控制器類。
這一切因果緣由就是框架的URL自動(dòng)轉(zhuǎn)換功能,由于系統(tǒng)的URL自動(dòng)轉(zhuǎn)換功能,ThinkPHP5的URL地址默認(rèn)是不區(qū)分大小寫的(也就是說都會(huì)強(qiáng)制轉(zhuǎn)換成小寫)。但事情沒有絕對(duì),我們可以設(shè)置關(guān)閉URL自動(dòng)轉(zhuǎn)換:
~~~
'url_convert' => false,
~~~
一旦關(guān)閉`url_convert`自動(dòng)轉(zhuǎn)換,就意味著URL地址中的控制器名不會(huì)自動(dòng)轉(zhuǎn)換,必須嚴(yán)格使用實(shí)際的控制器名(區(qū)分大小寫)。
這個(gè)時(shí)候,你就可以通過
~~~
http://tp5.com/index/HelloWorld/index
~~~
正常訪問`HelloWorld`控制器了^_^
## 控制器后綴
為什么會(huì)有控制器后綴的概念呢?有兩個(gè)原因,首先是如果控制器類不帶后綴,容易產(chǎn)生和關(guān)鍵字沖突的情況,例如無法使用`public`控制器,其次,控制器類和模型類容易產(chǎn)生混淆,例如`User`控制器類和`User`模型類,默認(rèn)不使用控制器后綴,要使用的話開啟下面的參數(shù):
~~~
// 控制器類后綴
'controller_suffix' => true,
~~~
`controller_suffix`參數(shù)配置的是布爾值,而不是具體的控制器后綴,開啟后,會(huì)自動(dòng)使用`url_controller_layer`配置值作為訪問控制器后綴,這個(gè)參數(shù)默認(rèn)值是`controller`,所以再次訪問
~~~
http://tp5.com/index/index/hello
~~~
的時(shí)候,指向的訪問控制器為:
~~~
application/index/controller/IndexController.php
~~~
控制器類定義修改如下:
~~~
<?php
namespace app\index\controller;
class IndexController
{
public function hello()
{
return 'hello,world';
}
}
~~~
> 注意:開啟了控制器類后綴的話,類名和類文件名依然要保持大小寫一致。
開啟了控制器類后綴的好處是控制器類的命名不受任何關(guān)鍵字約束,例如我們可以定義一個(gè)`public`控制器類用于繼承,
~~~
<?php
namespace app\index\controller;
class PublicController
{
public function base()
{
return 'base';
}
}
~~~
開啟了控制器類后綴,并不會(huì)影響當(dāng)前的控制器名稱的獲取,當(dāng)前訪問的控制器名稱還是`Public`而不是`PublicController`,要注意`控制器名`和`控制器類名`的區(qū)別。
## 方法后綴
同樣的,為了避免操作方法名和關(guān)鍵字混淆,我們也可以給操作方法統(tǒng)一添加方法后綴,例如設(shè)置操作方法后綴為`Action`:
~~~
// 設(shè)置操作方法后綴
'action_suffix' => 'Action',
~~~
接下來,所有的操作方法都必須帶上`Action`后綴才能正常訪問:
~~~
<?php
namespace app\index\controller;
class Index
{
public function helloAction()
{
return 'hello';
}
public function publicAction()
{
return 'public';
}
public function test()
{
return 'test';
}
}
~~~
當(dāng)我們?cè)L問下面的URL地址
~~~
http://tp5.com/index/index/hello
http://tp5.com/index/index/public
~~~
都可以正常訪問,而
~~~
http://tp5.com/index/index/test
~~~
則會(huì)報(bào)錯(cuò):

## 總結(jié)
現(xiàn)在我們已經(jīng)了解了控制器的基本概念和定義方法,下面一講會(huì)深入了解一些高級(jí)的控制器用法。