本笔记内容是基于ThinkPHP5.0.7进行实践。
需要声明:默认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');
上面提供多种形式的路由编写规则,可根据需要进行选择使用。
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(); } }
上述代码提供了三种的获取参数的方式:静态方法获取、助手函数获取、依赖注入方式获取。
方式之一:独立验证
use think/Validate; public function getBanner($id) { $data = [ 'name' => 'vender', 'email' => 'vender@qq.com', ]; $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' => 'vender@qq.com', ]; //需要引入类 //$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; } }
通过上面方法,代码复用性增强且精简。
默认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即可显示自定义的异常。
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下需要提供文件写入权限。
首先需要简单理解模型的概念。模型不等同于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模型类封装了很多的方法,详细可以自行探讨。