请求对象和信息

一.请求对象

当控制器继承了控制器基类时,自动会被注入 Request 请求对象的功能;

  1. class Rely extends Controller
  2. {
  3. public function index()
  4. {
  5. return $this->request->param('name');
  6. }
  7. }

Request 请求对象拥有一个 param 方法,传入参数 name,可以得到相应的值;
如果我们不继承控制器基类,可是自行注入 Request 对象,依赖注入后面会讲;

  1. use think\Request;
  2. class Rely
  3. {
  4. public function index(Request $request)
  5. {
  6. return $request->param('name');
  7. }
  8. }

还可以通过构造方法进行注入,通过构造注入,就不需要每个方法都注入一遍;

  1. use think\Request;
  2. class Rely
  3. {
  4. protected $request;
  5. public function __construct(Request $request)
  6. {
  7. $this->request = $request;
  8. }
  9. public function index()
  10. {
  11. return $this->request->param('name');
  12. }
  13. }

使用 Facade 方式应用于没有进行依赖注入时使用 Request 对象的场合;

  1. use think\facade\Request;
  2. class Rely
  3. {
  4. public function index()
  5. {
  6. return Request::param('name');
  7. }
  8. }

使用助手函数 request()方法也可以应用在没有依赖注入的场合;

  1. class Rely
  2. {
  3. public function index()
  4. {
  5. return request()->param('name');
  6. }
  7. }

二.请求信息

Request 对象除了 param 方法外,还有一些请求的固定信息,如表:
image.png
上表的调用方法,直接调用即可,无须传入值,只有极个别如果传入 true 获取完 整 URL 的功能;
Request::url();
// 获取完整 URL 地址 包含域名
Request::url(true);
// 获取当前 URL(不含 QUERY_STRING) 不带域名
Request::baseFile();
// 获取当前 URL(不含 QUERY_STRING) 包含域名
Request::baseFile(true);
// 获取 URL 访问根地址 不带域名
Request::root();
// 获取 URL 访问根地址 包含域名
Request::root(true);

请求变量

一.请求变量

Request 对象支持全局变量的检测、获取和安全过滤,支持$_GET、$_POST…等;
使用 has()方法,可以检测全局变量是否已经设置:
dump(Request::has(‘id’, ‘get’));
dump(Request::has(‘username’, ‘post’));
Request 支持的所有变量类型方法,如下表:
image.png
param()变量方法是自动识别 GET、POST 等的当前请求,推荐使用;
//获取请求为 name 的值,过滤
dump(Request::param(‘name’));
//获取所有请求的变量,以数组形式,过滤
dump(Request::param());
//获取所有请求的变量(原始变量),不包含上传变量,不过滤
dump(Request::param(false));
//获取所有请求的变量,包含上传变量,过滤
dump(Request::param(true));
如果使用依赖注入的方式,可以将变量作为对象的属性进行调用;

  1. public function read(\think\Request $request)
  2. {
  3. return $request->name;
  4. }

如果采用的是路由 URL,也可以获取到变量,但 param::get()不支持路由变量;

  1. public function edit($id)
  2. {
  3. dump(Request::param());
  4. dump(Request::route()); // 路由请求不支持 get 变量
  5. dump(Request::get()); // get 变量不支持路由请求
  6. }

注意:除了::server()和::env()方法外,其它方法传递的变量名区分大小写;
因为::server()和::env()属于系统变量,会强制转换为大写后获取值;
如果获取不到值,支持请求的变量设置一个默认值;
dump(Request::param(‘name’, ‘nodata’));
对于变量的过滤,在全局设置一个过滤函数,也可以单独对某个变量过滤;
‘default_filter’ => ‘htmlspecialchars’,
Request::param(‘name’, ‘’, ‘htmlspecialchars’);
Request::param(‘name’, ‘’, ‘strtoupper’);
使用 only()方法,可以获取指定的变量,也可以设置默认值;
dump(Request::only(‘id,name’));
dump(Request::only([‘id’,’name’]));
dump(Request::only([‘id’=>1,’name’=>’nodata’]));
使用 only()方法,默认是 param 变量,可以在第二参数设置 GET、POST 等;
dump(Request::only([‘id’,’name’], ‘post’));
相反的 except()方法,就是排除指定的变量;
dump(Request::except(‘id,name’));
dump(Request::except([‘id’,’name’]));
dump(Request::except([‘id’=>1,’name’=>’nodata’]));
dump(Request::except([‘id’,’name’], ‘post’));
使用变量修饰符,可以将参数强制转换成指定的类型;
/s(字符串)、/d(整型)、/b(布尔)、/a(数组)、/f(浮点); Request::param(‘id/d’);

二.助手函数

为了简化操作,Request 对象提供了助手函数;
dump(input(‘?get.id’)); //判断 get 下的 id 是否存在
dump(input(‘?post.name’)); //判断 post 下的 name 是否存在
dump(input(‘param.name’)); //获取 param 下的 name 值
dump(input(‘param.name’, ‘nodata’)); //默认值
dump(input(‘param.name’, ‘’, ‘htmlspecialchars’)); //过滤器
dump(input(‘param.id/d’)); //设置强制转换

请求类型和Http头信息

一.请求类型

有时,我们需要判断 Request 的请求类型,比如 GET、POST 等等;
可以使用 method()方法来判断当前的请求类型,当然,还有很多专用的请求判断;
image.png
使用普通表单提交,通过 method()方法获取类型;

  1. <form action="http://localhost/tp5.1/public/rely" method="post">
  2. <input type="text" name="name" value="Lee">
  3. <input type="submit" value="提交">
  4. </form>
  5. return Request::method();

在表单提交时,我们也可以设置请求类型伪装,设置隐藏字段_method;
而在判断请求,使用 method(true)可以获取原始请求,否则获取伪装请求;

Request::method(true);
如果想更改请求伪装变量类型的名称,可以在 app.php 中更改;
‘var_method’ => ‘_method’,
AJAX/PJAX 伪装,使用?_ajax=1 和?_pjax=1,并使用 isAjax()和 isPjax();
…/rely?_ajax=1
dump(Request::isAjax());
这里需要用 isAjax()和 isPjax()来判断,用 method 无法判断是否为 a(p)jax;
在 app.php 也可以更改 ajax 和 pjax 的名称;
‘var_ajax’ => ‘_ajax’,
‘var_pjax’ => ‘_pjax’,

二.HTTP 头信息

1使用 header()方法可以输出 HTTP 头信息,返回是数组类型,也可单信息获取;
dump(Request::header());
dump(Request::header(‘host’));

伪静态,参数绑定,请求缓存

一.伪静态

先使用 Url::build()方法获取当前的 url 路径,得到默认的后缀为.html;
return Url::build();
可以通过 app.php 修改伪静态的后缀,比如修改成 shtml、xml 等;
‘url_html_suffix’ => ‘xml’,
如果地址栏用后缀访问成功后,可以使用 Request::ext()方法得到当前伪静态;
return Request::ext();
配置文件伪静态后缀,可以支持多个,用竖线隔开,访问时不在区间内则报错;
‘url_html_suffix’ => ‘shtml|xml|pdf’,
直接将伪静态配置文件设置为 false,则关闭伪静态功能;
‘url_html_suffix’ => false,

二.参数绑定

参数绑定功能:即 URL 地址栏的数据传参,我们一直在使用的功能;

  1. public function get($id)
  2. {
  3. return 'get:'.$id;
  4. }

操作方法 URL:/get,而带上 id 参数后,则为:/get/id/5;
如果缺少了/5 或者缺少了/id/5,则都会报错方法参数错误:id;
那么解决方案,就是给$id = 0 一个默认值,防止 URL 参数错误;
如果设置了两个参数,那么参数传递的执行顺序可以设置,比如;

  1. public function get($id, $name)
  2. {
  3. return 'get:'.$id.','.$name;
  4. }

不管是:/id/5/name/lee,还是:/name/lee/id/5,都不会产生错误;
但如果你在 app.php 中设置了,必须按照顺序去传递参数,则需要严格;
// URL 参数方式 0 按名称成对解析 1 按顺序解析
‘url_param_type’ => 1,
/get/5/lee //不需要再传递 id 和 name,直接按顺序传值即可

三.请求缓存

请求缓存仅对 GET 请求有效,并设置有效期;
可以设置全局请求缓存,在 app.php 中设置;
‘request_cache’ => true,
‘request_cache_expire’ => 3600,
当第二次访问时,会自动获取请求缓存的数据响应输出,并发送 304 状态码;
如果要对路由设置一条缓存,直接使用 cache(3600)方法;
Route::get(‘edit/:id’, ‘Rely/edit’)->cache(3600);

响应重定向和文件下载

一.响应操作

响应输出,之前已经都掌握了,包括 return、json()和 view();
return 默认会输出 html 格式,配置文件默认设定的 default_return_type;
而背后是 response 对象,可以用 response()输出达到相同的效果; return response($data);
使用 response()方法可以设置第二参数,状态码,或调用 code()方法;
return response($data, 201);
return response($data)->code(202);
使用 json()、view()方法和 response()返回的数据类型不同,效果一样;
return json($data, 201);
return json($data)->code(202);
不但可以设置状态码,还可以设置 header()头文件信息;
return json($data)->code(202)
->header([‘Cache-control’ => ‘no-cache,must-revalidate’]);

二.重定向

使用 redirect()方法可以实现页面重定向,需要 return 执行;
return redirect(‘http://www.baidu.com‘);
站内重定向,直接输入路由地址或相对地址即可,需要用顶级域名,二级会错误;
return redirect(‘edit/5’);
return redirect(‘/address/details/id/5’);
也可以通过 params()方法传递数组参数键值对的方式,进行跳转;
return redirect(‘/address/details’)->params([‘id’=>5]);
return redirect(‘/address/details’, [‘id’=>5]);【十天精品课堂系列】 主讲:李炎恢

三.文件下载

文字下载和图片下载都使用 download()方法即可,路径为实际路径;
return \download(‘image.jpg’, ‘my’);
$data = ‘这是一个测试文件’;
return \download($data, ‘test.txt’, true);

容器和依赖注入

一.依赖注入

手册对依赖注入比较严谨的说明,具体如下:
依赖注入其实本质上是指对类的依赖通过构造器完成自动注入,例如在控制器架构方法和操作
方法中一旦对参数进行对象类型约束则会自动触发依赖注入,由于访问控制器的参数都来自于 URL
请求,普通变量就是通过参数绑定自动获取,对象变量则是通过依赖注入生成。
先看一个小例子,了解一下依赖注入的写法,创建一个模型;

  1. namespace app\model;
  2. use think\Model;
  3. class One extends Model
  4. {
  5. public $name = 'Mr.Lee';
  6. }

创建一个控制器 Inject,通过依赖注入将模型 One 对象引入其内;

  1. namespace app\controller;
  2. use app\model\One;
  3. class Inject
  4. {
  5. protected $one;
  6. public function __construct(One $one)
  7. {
  8. $this->one = $one;
  9. }
  10. public function index()
  11. {
  12. return $this->one->name;
  13. }
  14. }

依赖注入:即允许通过类的方法传递对象的能力,并且限制了对象的类型(约束);
而传递的对象背后的那个类被自动绑定并且实例化了,这就是依赖注入;

二.容器

依赖注入的类统一由容器管理的,大多数情况下是自动绑定和自动实例化的;
如果想手动来完成绑定和实例化,可以使用 bind()和 app()助手函数来实现;

  1. class Inject
  2. {
  3. public function index()
  4. {
  5. bind('one', 'app\model\One');
  6. return app('one')->name;
  7. }
  8. }

bind(‘one’,’…’)绑定类库标识,这个标识具有唯一性,以便快速调用;
app(‘one’)快速调用,并自动实例化对象,标识严格保持一致包括大小写;
自动实例化对象的方式,是采用单例模式实现,如果想重新实例化一个对象,则:
//每次调用总是会重新实例化
$one = app(‘one’, true);
return $one->name;
当然,你也可以直接通过 app()绑定一个类到容器中并自动实例化;
return app(‘app\model\One’)->name;
使用 bind([])可以实现批量绑定,只不过系统有专门提供批量绑定的文件;
bind([
‘one’ => ‘app\model\One’,
‘user’ => ‘app\model\User’
]);
return app(‘one’)->name;
bind([
‘one’ => \app\model\One::class,
‘user’ => \app\model\User::class
]);
return app(‘user’)->name;
::class 模式,不需要单引号,而且需要在最前面加上\,之前的加不加都行;
系统提供了 provider.php 文件,用于批量绑定类到容器中,这里不加不报错;
return [
‘one’ => app\model\One::class,
‘user’ => app\model\User::class
];
系统内置了很多常用的类库,以便开发者快速调用,具体如下:
image.png
return app(‘request’)->param(‘name’);
我们之前学过的 Request::param()么?对!
也就是说,实现同一个效果可以由容器的 bind()和 app()实现,
也可以使用依赖 注入实现,还有 Facade(下节课重点探讨)实现,以及助手函数实现;

Facade

一.创建静态调用

Facade,即门面设计模式,为容器的类提供了一种静态的调用方式;
在之前的很多课程中,我们大量的引入 Facade 类库,并且通过静态调用;
比如请求 Request::?,路由 Route::?,数据库 Db::?等等,均来自 Facade;
下面我们手工来创建一个自己的静态调用类库,来了解一下流程;
首先,在应用目录下创建 common 公共类库文件夹,并创建 Test.php;

  1. namespace app\common;
  2. class Test
  3. {
  4. public function hello($name)
  5. {
  6. return 'Hello, '.$name;
  7. }
  8. }

再在同一个目录下创建 facade 文件夹,并创建 Test.php,用于生成静态调用;

  1. namespace app\Facade;
  2. use think\Facade;
  3. class Test extends Facade
  4. {
  5. protected static function getFacadeClass()
  6. {
  7. return 'app\common\Test';
  8. }
  9. }

然后在控制器端,就可以和之前系统提供的静态调用一样调用了;
return Test::hello(‘Mr.Lee!’);
除了在 getFacadeClass()方法显示绑定,也可以在应用公共函数文件进行绑定;
这里的绑定后,就不需要 getFacadeClass()方法了,还可以进行批量统一绑定;
// 应用公共文件
use think\Facade;
Facade::bind(‘app\facade\Test’, ‘app\common\Test’);【十天精品课堂系列】 主讲:李炎恢
Facade::bind([
‘app\facade\Test’ => ‘app\common\Test’,
]);

二.核心类库

以下是系统提供的常用 Facade 核心类库表;
image.png
在真正使用 Facade 核心类库时,直接使用提供的别名即可,具体别名如下:
image.png

钩子与行为

一.概念理解

首先,钩子和行为在 6.0 的版本被废弃了,用事件来取代;
虽说用事件来取代,不过意思的一样的,我们还是有必要理解一下;
什么是行为,就是在系统执行的流程中执行的一个动作;
比如,当执行到路由时,对路由的设置进行一系列的检测,这种就叫行为;
而钩子又是什么呢?可以理解为行为执行的那个位置点,触发点;
系统架构里用了很多这种方式实现框架程序,我们自己手工来创建一个试试;

二.小实例

在应用目录下创建一个 behavior 文件夹,用于存放行为类,比如 Test.php

  1. namespace app\behavior;
  2. class Test
  3. {
  4. public function run($params)
  5. {
  6. echo $params.',只要触发,我就执行!';
  7. }
  8. }

行为类创建好之后,设置一个入口方法 run(),run()方法只要钩子被触发就执行;
比如,我们将行为注册到 tags.php 中应用初始化的数组里(app_init);
// 应用初始化
‘app_init’ => [
‘app\behavior\Test’,
],
我们也可以自定义一个钩子,然后注册到 tags.php 中,执行后触发;

  1. public function bhv()
  2. {
  3. //钩子
  4. Hook::listen('eat', '吃饭');
  5. }【十天精品课堂系列】 主讲:李炎恢
  6. //自定义
  7. 'eat' => [
  8. 'app\behavior\Test',
  9. ],

那么,我们可不可以让初始化对应的是初始化的行为,自定义对应自定义的行为呢;
app_init 对应的方法是 appInit(有下划线的大写),而自定义 eat 就是 eat;

  1. public function appInit($params)
  2. {
  3. echo '初始化的行为被触发!';
  4. }
  5. public function eat($params)
  6. {
  7. echo $params.'的行为被触发!';
  8. }

系统除了 app_init 钩子,还提供了一系列的钩子供使用;
image.png

中间件

一.定义中间件

中间件和钩子有点类似,它主要用于拦截和过滤 HTTP 请求,并进行相应处理;
这些请求的功能可以是 URL 重定向、权限验证等等;
为了进一步了解中间件的用法,我们首先定义一个基础的中间件;
可以通过命令行模式,在应用目录下生成一个中间件文件和文件夹;
php think make:middleware Check
具体路径为:application\http\middleware\Check.php;
namespace app\http\middleware;
use think\Request;
class Check
{
public function handle(Request $request, \Closure $next)
{
if ($request->param(‘name’) == ‘index’) {
return redirect(‘/‘);
}
return $next($request);
}
}
然后将这个中间件进行注册,在应用目录下创建 middleware.php 中间件配置;
return [
app\http\middleware\Check::class
];
中间件的入口执行方法必须是:handle()方法,第一参数请求,第二参数是闭包;
业务代码判断请求的 name 如果等于 index,就拦截住,不再执行,跳转到首页;
但如果请求的 name 是 lee,那需要继续往下执行才行,不能被拦死;
那么就需要$next($request)把这个请求去调用回调函数;
中间件 handle()方法规定需要返回 response 对象,才能正常使用;
而$next($request),研读源码追踪发现,它就是返回的 response 对象;
为了测试拦截后,无法继续执行,可以 return response()助手函数测试;

二.前/后置中间件

上节课,我们创建了一个简单的中间件,它拦截 HTTP 验证请求匹配后跳转;
这种将$next($request)放在方法底部的方式,属于前置中间件;
前置中间件就是请求阶段来进行拦截验证,比如登录判断、跳转、权限等;
而后置中间件就是请求完毕之后再进行验证,比如写入日志等等;

  1. public function handle(Request $request, \Closure $next)
  2. {
  3. //中间件代码,前置
  4. return $next($request);
  5. }
  6. public function handle(Request $request, \Closure $next)
  7. {
  8. $response = $next($request);
  9. //中间件代码,后置
  10. return $response;
  11. }

三.路由中间件

创建一个给路由使用的中间件,判断路由的 ID 值实现相应的验证;

  1. class Auth
  2. {
  3. public function handle(Request $request, \Closure $next)
  4. {
  5. if ($request->param('id') == 10)
  6. {
  7. echo '是管理员,提供后台权限并跳转操作';
  8. }
  9. return $next($request);
  10. }
  11. }

如果将 Auth 中间件注册到 middleware.php 中,就变成公有中间件了;
路由方法提供了一个 middleware()方法,让指定的路由采用指定的中间件;【十天精品课堂系列】 主讲:李炎恢
Route::rule(‘read/:id’, ‘Inject/read’)->middleware(‘Auth’);
middleware()方法,除了传类名,还可以是命名空间的两种形式,均支持;
->middleware(‘app\http\middleware\Auth’)
->middleware(app\http\middleware\Auth::class)
一个路由规则,如果要注册多个中间件,可以用数组的绑定;
Route::rule(‘read/:id’, ‘Inject/read’)
->middleware([‘Auth’, ‘Check’]);
也支持分组路由,闭包路由等;
Route::group(‘read’, function () {
Route::rule(‘:id’, ‘Inject/read’);
})->middleware(‘Auth’);
Route::rule(‘read/:id’, ‘Inject/read’)
->middleware(function ($request, Closure $next) {
if ($request->param(‘id’) == 10) {
echo ‘是管理员!’;
}
return $next($request);
});
中间件 handler()方法的第三参数,可以路由进行设置;
Route::rule(‘read/:id’, ‘Inject/read’)
->middleware(‘Auth:abc’);
public function handle(Request $request, \Closure $next, $name)
{
echo $name;
}
11. 在定义全局中间件绑定的时候,如果想传入参数,可以设置为数组模式;
[app\http\middleware\Auth::class,’hello’]
‘Auth’,
‘Auth:hello’

四.控制器中间件

可以让中间件在控制器里注册,控制器必须继承 Controller 基类;
class Inject extends Controller
{
protected $middleware = [‘Auth’];
}
默认情况下,控制器中间件对所有操作方法有效,支持做限制;
protected $middleware = [
‘Auth’ => [‘only’ =>[‘index’, ‘test’]],
‘Check’ => [‘except’ =>[‘bhv’, ‘read’]],
];
中间件给控制器传递参数,通过 Request 对象实现; $request->name = ‘Mr.Lee’;

异常处理

一.异常处理

系统输出的异常信息比 PHP 原生的要人性化的多,但需要开启调试模式;
如果你想更改异常页面的样式、布局之类的,可以修改这个页面:
thinkphp/tpl/think_exception.tpl
如果你想要直接替换掉异常页面,换成别的,可以在 app.php 中进行设置:
// 异常页面的模板文件
‘exception_tmpl’ => Env::get(‘think_path’) .
‘tpl/think_exception.tpl’,
默认情况下,对所有 PHP 的错误都会抛出异常信息,可以用错误级别关闭;
error_reporting(0);
系统的异常抛出是自动进行的,并不需要你手动抛出,但也支持手动;
throw new Exception(‘异常消息’, 10086);
我们可以使用 try…catch 对可能发生的异常进行手动捕获或抛出;
try {
echo 0/0;
} catch (ErrorException $e)
{
echo ‘发生错误:’.$e->getMessage();
}
我们可以抛出 HTTP 异常,所谓 HTTP 异常比如 404 错误,500 错误之类;
throw new HttpException(404, ‘页面不存在’);
系统提供了一个助手函数 abort()方法简化 HTTP 异常抛出;
abort(404, ‘页面不存在’);
如果系统关闭了调试模式,进入部署环境下,可以设置 HTTP 错误页面,比如 404;
‘http_exception_template’ => [
// 定义 404 错误的模板文件地址
404 => Env::get(‘app_path’) . ‘404.html’,
]

日志处理

一.日志处理

日志处理的操作由 Log 类完成,它记录着所有程序中运行的错误记录;
在 config 目录下的 log.php 配置文件,用于设置日志信息;
我们在 runtime 目录下后一个 log 文件夹,里面按照日期排好了每月的日志;
使用 record()方法,记录一条测试日志; Log::record(‘测试日志!’);
我们在 log 日志文件夹里找到最新生成的日志,可以看到生成的日志信息;
系统提供了不同日志级别,默认 info 级别,从低到高排列如下:
debug/info/notice/warning/error/critical/alert/emergency;
一般记录就是 info 信息,我们也可以指定我们的信息级别; Log::record(‘测试日志!’, ‘error’);
系统还提供了关闭写入的功能,在配置文件中关闭,或者使用::close()方法;
Log::close();
‘close’ => false,
系统发生异常后,会自动写入 error 日志,如果你想手动也可以;
try {
echo 0/0;
} catch (ErrorException $e)
{
echo ‘发生错误:’.$e->getMessage();
Log::record(‘被除数不得为零’, ‘error’);
}
对于各种日志级别,系统提供了一些快捷方式和助手函数,比如:
Log::error(‘错误日志!’); //Log::record(‘错误日志!’, ‘error’)
Log::info(‘信息日志!’); //Log::record(‘信息日志!’, ‘info’)
trace(‘错误日志!’, ‘error’);
trace(‘信息日志!’, ‘info’);
如果你开启的是调试模式,那每次加载都会写入内存的运行数据,关闭就不写入;
系统默认并不记录 HTTP 异常,因为这种异常容易遭受攻击不断写入日志;
在配置文件 log.php 中,可以设置路径,来更改日志文件的存储位置;
Env::get(‘app_path’) .’../runtime/logs/‘,【十天精品课堂系列】 主讲:李炎恢
在配置文件 log.php 中,可以设置限定日志文件的级别,不属于的无法写入;
‘level’ => [‘info’],
在配置文件 log.php 中,添加转换为 json 格式,部署模式不知;
‘json’ => true
使用::getLog()方法,可以获取写入到内存中的日志;
$logs = Log::getLog();
dump($logs);
使用::clear()方法,可以清理掉内存中的日志;
Log::clear();
在配置文件 log.php 中,可以设置以单独文件存储的方式;
‘single’ => true,
在配置文件 log.php 中,设置 max_files 最大文件数量,会按日期文件存储;
一旦超过指定的数量,早期的会被清理掉,验证时,更改服务器时间,设置如下: ‘max_files’ => 2,