>[danger] 官方已經(jīng)在前不久發(fā)布了ThinkPHP`5.1.7`版本,`5.1`版本相較于`5.0`版本而言,本身更加嚴(yán)謹(jǐn)和規(guī)范,更接近主流設(shè)計(jì)思想。近半年來,`5.1`版本更新頻繁,此次最新版本更是帶來了很多的新特性。正在或者打算使用`5.1`版本的朋友可以關(guān)注下了,因?yàn)榻?jīng)過此次更新后,`5.1`版本也進(jìn)入穩(wěn)定階段了,基本上不太會有大的調(diào)整了。
最新版本(`V5.1.7`)的主要新特性主要包含:
[TOC=2,2]
## 引入中間件支持
關(guān)于中間件想必很多開發(fā)者已經(jīng)在其它的主流框架中了解到了,ThinkPHP`5.1`版本最開始沒有引入中間件的原因是考慮到用戶需要了解新的知識概念和用法,同時(shí)容易和原來的行為用法相混淆。
>[info] 為了更規(guī)范開發(fā)和最大程度的公用某些組件,新版正式引入中間件支持。
原來你可能需要在控制器的初始化方法中添加相類似的代碼,現(xiàn)在你可以把這些代碼從控制器中獨(dú)立出來,方便更多的模塊調(diào)用和重用。如果你原來使用的是行為來進(jìn)行處理的話,那么是否決定也改為中間件完全取決于你自己,因?yàn)槿匀豢梢约嫒葸\(yùn)行(至少在`5.1`版本中不會變化)。
中間件的基本用法我大概介紹下:
### 定義中間件
可以通過命令行指令快速生成中間件類
~~~
php think make:middleware Check
~~~
這個(gè)指令會 `application/http/middleware`目錄下面生成一個(gè)`Check`中間件,也就是說默認(rèn)生成的中間件的命名空間都是`app\http\middleware`,這個(gè)必須注意。
~~~
<?php
namespace app\http\middleware;
class Check
{
public function handle($request, \Closure $next)
{
}
}
~~~
中間件的入口執(zhí)行方法必須是`handle`方法,而且第一個(gè)參數(shù)是`Request`對象,第二個(gè)參數(shù)是一個(gè)閉包。
>[danger] 中間件`handle`方法的返回值必須是一個(gè)`Response`對象。
### 前置/后置中間件
中間件是在請求具體的操作之前還是之后執(zhí)行,完全取決于中間件的定義本身。
下面是一個(gè)前置行為的中間件
~~~
<?php
namespace app\http\middleware;
class Before
{
public function handle($request, \Closure $next)
{
// 添加中間件執(zhí)行代碼
return $next($request);
}
}
~~~
下面是一個(gè)后置行為的中間件
~~~
<?php
namespace app\http\middleware;
class After
{
public function handle($request, \Closure $next)
{
$response = $next($request);
// 添加中間件執(zhí)行代碼
return $response;
}
}
~~~
### 注冊中間件
第一種方式:注冊全局中間件
你可以在應(yīng)用目錄下面定義`middleware.php`文件,使用下面的方式:
~~~
<?php
return [
\app\http\middleware\Auth::class,
'Check',
'Hello',
];
~~~
中間件的注冊應(yīng)該使用完整的類名,如果沒有指定命名空間則使用`app\http\middleware`作為命名空間。
全局中間件的執(zhí)行順序就是定義順序??梢栽诙x全局中間件的時(shí)候傳入中間件參數(shù),支持兩種方式傳入。
~~~
<?php
return [
[\app\http\middleware\Auth::class, 'admin'],
'Check',
'Hello:thinkphp',
];
~~~
上面的定義表示 給`Auth`中間件傳入`admin`參數(shù),給`Hello`中間件傳入`thinkphp`參數(shù)。
### 路由中間件
最常用的中間件注冊方式是注冊路由中間件
~~~
Route::rule('hello/:name', 'hello')
->middleware('Auth');
~~~
或者使用完整的中間件類名
~~~
Route::rule('hello/:name', 'hello')
->middleware(app\http\middleware\Auth::class);
~~~
支持對路由分組注冊中間件
~~~
Route::group('hello', function(){
Route::rule('hello/:name', 'hello');
})->middleware('Auth');
~~~
如果需要傳入額外參數(shù)給中間件,可以使用
~~~
Route::rule('hello/:name', 'hello')
->middleware('Auth:admin');
~~~
如果使用的是常量方式定義,可以在第二個(gè)參數(shù)傳入中間件參數(shù)。
~~~
Route::rule('hello/:name', 'hello')
->middleware(Auth::class, 'admin');
~~~
>[danger] 中間件方法參數(shù)只能有一個(gè),但可以支持任意類型,在`handle`方法的第三個(gè)參數(shù)傳入即可。
如果你不希望每次在路由中注冊完整的類名,還可以在應(yīng)用配置文件`middleware.php`中進(jìn)行中間件的預(yù)定義,指定中間件的別名。
~~~
return [
'auth' => 'app\http\middleware\Auth',
'check' => [
'user' => app\common\middleware\CheckUser::class
],
];
~~~
然后,在路由中使用別名注冊中間件。
~~~
Route::rule('hello/:name', 'hello')
->middleware(['auth', 'check.user']);
~~~
好了,更多的中間件用法還需要你親自實(shí)踐。
## 路由改進(jìn)和提速
新版本的路由改進(jìn)是最多的(幾乎整個(gè)過年都在調(diào)整路由組件^_^),我們知道路由無非是兩大要點(diǎn),路由定義和路由檢測。
路由定義涉及到一個(gè)規(guī)范化的問題,新版路由在保持原來的用法前提下,內(nèi)部做了一些架構(gòu)和細(xì)節(jié)的調(diào)整,使得路由的用法更加對象化。如果你看源碼的話,就會發(fā)現(xiàn)核心類庫的`route`目錄下面新增了幾個(gè)類。
### 路由規(guī)則的變量用法改進(jìn)
路由定義方面主要對域名路由和路由分組的功能進(jìn)行了強(qiáng)化,以及路由規(guī)則的更靈活定義。
之前的路由規(guī)則中變量分兩種,普通變量和組合變量定義,新版把這兩種合二為一了(也就是說兩種變量沒有任何區(qū)別,僅僅是表現(xiàn)方式不同,而且是出于兼容考慮)。你現(xiàn)在完全可以在路由規(guī)則中以任何方式定義路由變量。
例如你可以使用下面的路由規(guī)則而不用管目前的URL分隔符是什么:
~~~
Route::rule('item/:name_:id', 'order/index');
Route::rule('product-<category>:name', 'product/item');
~~~
另外,新版路由定義兩種變量方式`:name`和`<name> `可以混合使用,但建議統(tǒng)一使用`<name>`,在性能上略有優(yōu)勢(實(shí)際上,最終解析的時(shí)候系統(tǒng)會統(tǒng)一解析成后者)。
### 路由匹配算法改進(jìn)
路由檢測其實(shí)是最耗性能的,尤其是路由匹配這塊,因?yàn)榛旧隙际遣捎玫恼齽t匹配。在`5.1.6`版本之前,如果定義了100個(gè)路由,那么最后的那個(gè)路由規(guī)則可能需要遍歷100次才能正確匹配到路由,而且每個(gè)路由規(guī)則中的路由變量都是單獨(dú)匹配的,所以這個(gè)路由匹配的性能開銷是隨著路由定義的數(shù)量指數(shù)上升的。
`5.1.6`版本開始,系統(tǒng)對路由的匹配這塊進(jìn)行了算法優(yōu)化,靈感來自于`fastRoute`?;舅枷胧欠謨蓚€(gè)步驟優(yōu)化(思想其實(shí)很容易懂,但技術(shù)層面比較復(fù)雜,所以想了解怎么實(shí)現(xiàn)的話還是仔細(xì)看代碼吧~本文只闡述思想)。
第一個(gè)步驟是對單個(gè)路由規(guī)則的匹配算法進(jìn)行調(diào)整,不按照變量進(jìn)行多次匹配,而是把路由變量的正則合并到一起,然后整個(gè)路由規(guī)則只匹配一次(如果這個(gè)路由規(guī)則都是靜態(tài)的,那么基本上不需要正則匹配,采用的是更快的非正則檢測方式)。這個(gè)是新版默認(rèn)就開啟的,相比較之前的版本,性能已經(jīng)提升明顯。
但路由的性能提速遠(yuǎn)非如此簡單,第二個(gè)步驟的優(yōu)化策略是如果當(dāng)前匹配的路由分組下面有多個(gè)路由規(guī)則(確切的說是滿足當(dāng)前請求類型的),則把這些滿足條件的路由規(guī)則合并成一個(gè)正則表達(dá)式進(jìn)行路由匹配。如果你的路由分組下面有100個(gè)滿足條件的路由規(guī)則,如果要訪問最后的路由規(guī)則,之前的方式可能需要遍歷100次,而新版只需要進(jìn)行一次匹配。就算加上路由規(guī)則的合并開銷,仍然是值得的。
如果需要使用分組路由規(guī)則的全局合并檢測,需要開啟下面的設(shè)置(另外一個(gè)方式是單獨(dú)對某個(gè)路由分組使用`mergeRuleRegex`方法,這種需求應(yīng)該不多):
~~~
// 合并分組路由規(guī)則
'route_rule_merge' => true,
~~~
那么到這是不是就結(jié)束了呢?不要忘了ThinkPHP`5.1`的路由有一個(gè)很創(chuàng)新的地方就是延遲解析,主要體現(xiàn)在路由分組或者域名路由。通常的路由定義是需要把定義的路由進(jìn)行一次解析處理,然后保存成我們方便檢測的一個(gè)數(shù)據(jù)結(jié)構(gòu)或者對象,但由于WEB請求的特殊性,每次請求都需要重復(fù)這種路由定義的解析過程,為了降低路由解析的開銷,ThinkPHP只會在路由分組匹配之后才會實(shí)際去進(jìn)行該路由分組下面的路由解析過程,然后再進(jìn)行后續(xù)的路由檢測,如果你的分組下面還有子分組,那么繼續(xù)按照這個(gè)方式解析。
不過路由的延遲解析功能默認(rèn)是關(guān)閉的,可以在應(yīng)用配置中開啟:
~~~
// 開啟路由延遲解析
'url_lazy_route' => true,
~~~
通過這三個(gè)步驟的優(yōu)化,我相信現(xiàn)在你應(yīng)該明白如何利用路由規(guī)則的合并以及路由的延遲解析機(jī)制來提升你的路由性能了吧。
如果你繼續(xù)深入路由的使用,還會發(fā)現(xiàn)一些細(xì)節(jié)的用法。
## 查詢安全性改進(jìn)
接下來要講的是新版對于查詢安全性的一個(gè)改進(jìn),引入了一個(gè)新的`Expression`類,對于這種類型的數(shù)據(jù),在查詢和寫入操作的時(shí)候會保持原樣`SQL`(比較適合于使用SQL函數(shù)的情況),并且同時(shí)支持參數(shù)綁定功能。
為了方便,`Query`類增加了`raw`方法用于實(shí)例化一個(gè)`Expression`對象,我們可以這樣使用:
~~~
Db::name('user')
->field(Db::raw('id, name'))
->where(Db::raw('id > :id AND status = 1'), ['id' => 1])
->order(Db::raw('id desc'))
->select();
~~~
系統(tǒng)在解析的時(shí)候,支持對`field/where/order`方法的`Expression`對象的解析。
同時(shí)為了方便使用,提供了一些快捷方法,例如上面的例子可以改為:
~~~
Db::name('user')
->fieldRaw('id, name')
->whereRaw('id > :id AND status = 1', ['id' => 1])
->orderRaw('id desc')
->select();
~~~
> 當(dāng)然,大部分情況下,系統(tǒng)會自動判斷是否需要使用`Expression`表達(dá)式對象,從而避免數(shù)據(jù)出現(xiàn)安全隱患。例如,當(dāng)你使用字符串條件,并且包含空格和函數(shù)用法的話,會自動解析為`Expression`對象執(zhí)行查詢。
在數(shù)據(jù)寫入方面一樣可以利用表達(dá)式方式來杜絕可能的安全隱患,例如:
~~~
Db::name('user')
->save($data);
~~~
當(dāng)你的`data`數(shù)據(jù)直接來自于表單提交數(shù)據(jù)的時(shí)候,會導(dǎo)致SQL注入的可能,新版會檢查數(shù)組數(shù)據(jù)的安全性,對`exp`表達(dá)式寫入進(jìn)行更嚴(yán)格的類型檢查。
原來的`exp`數(shù)組用法將不可用,而必須改為:
~~~
Db::name('user')
->where('id', 1)
->save([
'score' => ['exp', Db::raw('score+1')],
]);
~~~
或者直接使用`exp`方法更新數(shù)據(jù)
~~~
Db::name('user')
->where('id', 1)
->exp('score', 'score+1')
->save();
~~~
這些查詢的安全特性同樣對模型適用,比如你需要使用SQL更新數(shù)據(jù)的時(shí)候,可以使用
~~~
$user = User::get(1);
$user->name = Db::raw('UPPER("thinkphp")');
$user->score = Db::raw('score+1');
$user->save();
~~~
新版的特性暫時(shí)就介紹這么多,還有一些細(xì)節(jié)等待各位去挖掘和分享。
> 本文使用了看云文檔的單頁文檔功能,有興趣的可以多了解下,并且多使用看云進(jìn)行一些技術(shù)的分享和電子出版。
