本筆記內容是基於ThinkPHP5.0.7進行實踐。
1.路由書寫規則route.php
需要聲明:默認tp採用path_info模式的實現路由,默認是:’http://servername/模塊/控制器/方法’。但可以通過修改route.php使用路由規則來實現url尋址。默認情況下tp採用的是混合路由規則,即上述兩個路由方式共存,但針對不同方法而言,即同一個方法,如果使用兩種不同的路由定義方法,路由規則的優先級大於path_info。也可以通過設置嚴格路由模式,禁止使用path_info使系統較爲統一。接下來講述的是配置路由規則:
默認:以配置形式返回
return [
'__pattern__' => [
'name' => '\w+',
],
'[hello]' => [
':id' => ['index/hello', ['method' => 'get'], ['id' => '\d+']],
':name' => ['index/hello', ['method' => 'post']],
],
];
可以修改爲如下:(把文件內容清空,重新編寫如下)
//先引入route類
use think/Route;
//編寫路由規則
//Route::rule('路由表達式','路由地址','請求類型','路由參數(數組)','變量規則(數
組)');
//Route::rule('hello', 'simple/Test/hello','GET|POST',['https'=>false]);
//Route::get('hello', 'simple/Test/hello');
//Route::post('hello', 'simple/Test/hello');
//Route::any();
//傳遞參數
Route::get('hello/:id', 'simple/Test/hello');
上面提供多種形式的路由編寫規則,可根據需要進行選擇使用。
2.控制器獲取前端傳來參數方式
namespace app/simple/controller;
use think\Request;
class Test
{
public function hello($id, $name)
{
echo $id;
echo "|";
echo $name;
// return "hello,here is test/hello";
}
public function test()
{
$all = Request::instance()->param();
var_dump($all);
// $all = Request::instance()->route();//獲取url參數
// $all = Request::instance()->get();//獲取?後面的參數
// $all = Request::instance()->post();//獲取post參數
// $id = Request::instance()->param('id');
// $name = Request::instance()->param('name');
// $age = Request::instance()->param('age');
//使用助手函數
$all = input('param.');//獲取所有,獲取單個param.name
//$all = input('get.age');
}
//使用依賴注入方式獲取參數變量
public function test2(Request $request)
{
$all = $request->param();
}
}
上述代碼提供了三種的獲取參數的方式:靜態方法獲取、助手函數獲取、依賴注入方式獲取。
3.信息校驗
方式之一:獨立驗證
use think/Validate;
public function getBanner($id) {
$data = [
'name' => 'vender',
'email' => '[email protected]',
];
$validate = new Validate([
'name' => 'require|max:10',
'email' => 'email',
]);
$result = $validate->batch()->check($data);
var_dump($validate->getError());
}
方式二:驗證器
在模塊目錄或者application目錄下創建一個validate的文件夾,在文件夾下創建驗證器類:
namespace app\api\validate;
use think\Validate;
class TestValidate extends Validate
{
protected $rule = [
'name' => 'require|max:10',
'email' => 'email'
];
}
調用方式:
//use app\api\validate\TestValidate;用於直接new對象使用
public function getBanner($id){
$data = [
'name' => 'vender13456879',
'email' => '[email protected]',
];
//需要引入類
//$validate = new TestValidate();
//直接使用助手函數進行創建對象
$validate = validate('TestValidate');
$result = $validate->batch()->check($data);
var_dump($validate->getError());
}
官方也建議使用驗證器進行驗證,條理更加清晰,封裝性更好。
當需要驗證的數據,官方文檔沒有給出校驗規則時,可以自定義校驗規則,創建方法如上面創建,只需添加一個protected校驗方法如下:
//判斷是否爲正整數
protected function isPostiveInteger($value, $rule = '', $data = '', $field = '') {
if(is_numeric($value) && is_int($value + 0) && ($value + 0) > 0) {
return true;
} else {
return $field . '必須爲正整數';
}
}
使用方法:(完整代碼示例)
namespace app\api\validate;
use think\Validate;
class IDMustBePostiveInt extends Validate
{
protected $rule = [
'id' => 'require|isPostiveInteger'
];
protected function isPostiveInteger($value, $rule = '', $data = '', $field = '') {
if(is_numeric($value) && is_int($value + 0) && ($value + 0) > 0) {
return true;
} else {
return $field . '必須爲正整數';
}
}
}
如上述代碼所示,直接在$rule中使用自定義的方法即可。
儘管採用了驗證器,但每次調用驗證器都需要重複上面的調用代碼,會產生很多的代碼冗餘,因此,抽象出一個驗證層很有必要。實現方法如下:在validate類新建一個驗證基類BaseValidate.php,代碼如下:
namespace app\api\validate;
use think\Exception;
use think\Request;
use think\Validate;
class BaseValidate extends Validate
{
public function goCheck()
{
//獲取http傳入的參數變量
//對參數進行驗證
$request = Request::instance();
$params = $request->param();
$result = $this->check($params);
if(!$result) {
$error = $this->getError();
throw new Exception($error);
} else {
return true;
}
}
}
然後,讓各個校驗類繼承該基類:
namespace app\api\validate;
use think\Validate;
class IDMustBePostiveInt extends BaseValidate
{
protected $rule = [
'id' => 'require|isPostiveInteger'
];
protected function isPostiveInteger($value, $rule = '', $data = '', $field = '') {
if(is_numeric($value) && is_int($value + 0) && ($value + 0) > 0) {
return true;
} else {
return $field . '必須爲正整數';
}
}
}
現在的調用代碼精簡如下:
namespace app\api\controller\v1;
use app\api\validate\IDMustBePostiveInt;
class Banner
{
/**
* 獲取banner
* @url /banner/:id
* @param $id banner的id號
*/
public function getBanner($id){
(new IDMustBePostiveInt())->goCheck();
echo 1;
}
}
通過上面方法,代碼複用性增強且精簡。
4、異常處理
默認tp框架提供異常處理,我們只需要try-catch進行異常拋出和捕獲即可,但是,當我們需要根據自己的實際需要給出響應的異常信息時,我們需要自定義自己的異常類,並進行異常拋出並捕獲。
系統異常大可分爲兩類:用戶操作異常和系統內部代碼異常。前者不需要返回用戶具體代碼的錯誤信息,只需返回合適的提示信息即可;而後者信息一般不會直接返回給客戶端,因爲返回給客戶端也沒有用,人家不會給你處理,此時我們只需要返回客戶端,告訴他我們服務端內部出錯即可。因此,自定義異常處理類很有必要。
tp框架自定義異常處理類步驟如下:
首先,自定義的一個異常基類:BaseException.php
namespace app\lib\exception;
use think\Exception;
//讓基類繼承tp的異常類,並定義自己的一些異常編碼及提示信息
class BaseException extends Exception
{
//http狀態碼
public $code = 400;
//錯誤具體信息
public $msg = '參數錯誤';
//自定義的錯誤碼
public $errorCode = 10000;
}
其次,定義一個某個場景下所出現的異常。例如,系統需要通過id找到對應的數據庫記錄,當記錄存在時,我們認爲這是一個異常,並拋出(不考究舉例是否合理,僅作假設)。接下來,我們需要定義這個異常類:BannerMissException.php
<?php
namespace app\lib\exception;
use app\lib\exception\BaseException;
//讓自定義的異常類繼承基類,並重寫父類的異常編碼及提示信息
class BannerMissException extends BaseException
{
public $code = 404;
public $msg = "請求Banner不存在";
public $errorCode = 40000;
}
最後,定義自己的異常處理類ExceptionHandle.php,目的是讓拋出的異常直接經過自己定義的異常處理方法,替代tp默認的的處理方法,從而實現自定義異常處理效果。代碼如下:
<?php
namespace app\lib\exception;
use think\exception\Handle;
use think\Request;
use Exception; //注意這裏使用的不是think\Exception,與繼承的Handle類保持一致
use app\lib\exception\BaseException;
//繼承tp的異常處理基類Handle,並覆蓋render異常處理方法
class ExceptionHandle extends Handle
{
protected $code;
protected $msg;
protected $errorCode;
public function render(Exception $e)
{
//判斷拋出的異常是否爲自定義的異常
if($e instanceof BaseException) {
//如果是自定義的異常
$this->code = $e->code;
$this->msg = $e->msg;
$this->errorCode = $e->errorCode;
} else {
$this->code = 500;
$this->msg = "服務器內部錯誤123";
$this->errorCode = 999;
}
$request = Request::instance();
$result = [
'msg' => $this->msg,
'error_code' => $this->errorCode,
'request_url' => $request->url(),
];
return json($result, $this->code);
}
}
調用方法:直接在出現異常的地方拋出自定義的異常類即可被自定義的處理類捕獲並處理。
<?php
namespace app\api\controller\v1;
use app\api\validate\IDMustBePostiveInt;
use app\api\model\Banner as BannerModel;
use app\lib\exception\BannerMissException;
class Banner
{
/**
* 獲取banner
* @url /banner/:id
* @param $id banner的id號
* @return banner
* @throws BannerMissException
*/
public function getBanner($id){
//校驗id是否爲正整數
(new IDMustBePostiveInt())->goCheck();
$banner = BannerModel::getBannerByID($id);
if(!$banner) {
throw new BannerMissException();
}
// return $banner;
}
}
測試方法,在上述代碼BannerModel::getBannerByID(),讓其返回null即可顯示自定義的異常。
5、寫入日誌
tp框架默認會對所有異常進行自動寫入日誌,但是有很多異常信息我們實際上並不需要記錄,這樣導致空間的浪費,因此我們有必要進行自定義的日誌寫入。
首先,關閉tp的自動寫入日誌功能,編輯config.php文件:把File改成test即可
'log' => [
// 日誌記錄方式,內置 file socket 支持擴展
'type' => 'test',
// 日誌保存目錄
'path' => LOG_PATH,
// 日誌記錄級別
'level' => [],
],
然後再需要寫日誌的地方進行日誌寫入,此處省略日誌文件路徑的配置。我們一般記錄系統內部錯誤異常日誌即可。因此,我們可以在此前的全局異常處理類中進行日誌寫入,代碼如下:
<?php
namespace app\lib\exception;
use think\exception\Handle;
use think\Log;
use think\Request;
use Exception;
use app\lib\exception\BaseException;
class ExceptionHandle extends Handle
{
protected $code;
protected $msg;
protected $errorCode;
public function render(Exception $e)
{
if($e instanceof BaseException) {
//如果是自定義的異常
$this->code = $e->code;
$this->msg = $e->msg;
$this->errorCode = $e->errorCode;
} else {
$this->code = 500;
$this->msg = "服務器內部錯誤";
$this->errorCode = 999;
//調用日誌寫入方法,寫入異常信息
$this->recordErrorLog($e);
}
$request = Request::instance();
$result = [
'msg' => $this->msg,
'error_code' => $this->errorCode,
'request_url' => $request->url(),
];
return json($result, $this->code);
}
//自定義日誌寫入方法,引入think/Log類
private function recordErrorLog(Exception $e) {
//對日誌進行初始化操作,等同於config.php中的配置效果
Log::init([
'type' => 'File',
'path' => LOG_PATH,
'level' => ['error']
]);
//僅僅記錄error及其以上級別的異常
Log::record($e->getMessage(), 'error');
}
}
tp默認日誌保存路徑爲:runtime/log/日期。linux下需要提供文件寫入權限。
6、數據庫操作:
首先需要簡單理解模型的概念。模型不等同於model,而是model(對象)+logic(邏輯)。模型不僅僅用於操作數據庫,而應該包含一些邏輯處理。
tp操作數據庫可以採用三種方法:原生sql、查詢構建器、ORM模型關係映射。這些操作都是在model類中實現和使用。
方式一:使用原生sql
直接編寫sql語句如下:需引入think\Db類
$result = Db::query('select * from banner_item where banner_id = ?',[$id])
方式二:使用查詢構建器
直接編寫sql語句如下:需引入think\Db類
$result = Db::table('banner_item')->where('banner_id', '=', $id)->select();
注意:
1.使用查詢構建器find()返回一維數組,即單條記錄,select()返回二維數組;
2.分爲兩部分:輔助方法(鏈式方法)、執行方法。前者可多個,且無順序關係;
3.update()、delete()、insert()、find()、select()稱爲執行方法,前面的稱爲輔助方法,也叫鏈式方法;
4.輔助方法一般結構:where(‘字段名’,’表達式’,’查詢條件’),且需要執行執行方法才能真正進行數據查詢,否則只能返回拼接的sql語句;
查詢構建器有三種形式:表達式、數組、閉包。其中閉包實現方式如下:
$result = Db::table('banner_item')->where(function($query) use ($id){
$query->where('banner_id','=',$id);
})->select();
如果在鏈式方法添加fetchsql()方法時,sql不會執行,而是返回SQL語句:
$result = Db::table('banner_item')->fetchsql()->where('banner_id', '=', $id)->select();
方式三:使用模型ORM
使用模型,模型不等於model,可以分成model+service,或者更多。模型的實現需要繼承tp的Model類。
//使用模型BannerModel的靜態方法,通過id找到對應的記錄
$banner = BannerModel::find($id);
表示表與表之間的關聯關係:
一對多:Banner <- Banneritem
在一的一方model(Banner)添加描述對應關係的方法如下:
public function items()
{
return $this->hasMany('BannerItem','banner_id','id');
}
使用:
$banner = BannerModel::with('items')->find($id);//建議使用,調用簡潔、邏輯合理
獲取的結果自動包含關聯的多條Banneritem的相關內容。
一對一:Image <-> Banneritem
在BannerItemModel中添加如下方法代碼:
public function img()
{
return $this->belongsTo('Image','img_id', 'id');
}
使用:
$banner = BannerModel::with(['items','items.img'])->find($id);
以上代碼建議寫在模型內部,而不是控制器中,控制器直接調用模型的封裝的方法即可,代碼更清晰合理。
隱藏字段
有時候,我們需要隱藏查詢到的一些記錄的字段信息,例如delete_time之類的,常規方法,我們需要將查詢出來的記錄數據進行重新處理再返回,但實際上,tp的模型爲我們提供了封裝好的方法,調用如下:
$banner = BannerModel::getBannerByID($id);
$banner->hidden(['delete_time','update_time']);
tp模型類封裝了很多的方法,詳細可以自行探討。