thinkphp-wechat/thinkphp/library/think/route/Dispatch.php

367 lines
9.3 KiB
PHP
Raw Permalink Normal View History

2021-12-24 16:40:05 +08:00
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\route;
use think\App;
use think\Container;
use think\exception\ValidateException;
use think\Request;
use think\Response;
abstract class Dispatch
{
/**
* 应用对象
* @var App
*/
protected $app;
/**
* 请求对象
* @var Request
*/
protected $request;
/**
* 路由规则
* @var Rule
*/
protected $rule;
/**
* 调度信息
* @var mixed
*/
protected $dispatch;
/**
* 调度参数
* @var array
*/
protected $param;
/**
* 状态码
* @var string
*/
protected $code;
/**
* 是否进行大小写转换
* @var bool
*/
protected $convert;
public function __construct(Request $request, Rule $rule, $dispatch, $param = [], $code = null)
{
$this->request = $request;
$this->rule = $rule;
$this->app = Container::get('app');
$this->dispatch = $dispatch;
$this->param = $param;
$this->code = $code;
if (isset($param['convert'])) {
$this->convert = $param['convert'];
}
}
public function init()
{
// 执行路由后置操作
if ($this->rule->doAfter()) {
// 设置请求的路由信息
// 设置当前请求的参数
$this->request->setRouteVars($this->rule->getVars());
$this->request->routeInfo([
'rule' => $this->rule->getRule(),
'route' => $this->rule->getRoute(),
'option' => $this->rule->getOption(),
'var' => $this->rule->getVars(),
]);
$this->doRouteAfter();
}
return $this;
}
/**
* 检查路由后置操作
* @access protected
* @return void
*/
protected function doRouteAfter()
{
// 记录匹配的路由信息
$option = $this->rule->getOption();
$matches = $this->rule->getVars();
// 添加中间件
if (!empty($option['middleware'])) {
$this->app['middleware']->import($option['middleware']);
}
// 绑定模型数据
if (!empty($option['model'])) {
$this->createBindModel($option['model'], $matches);
}
// 指定Header数据
if (!empty($option['header'])) {
$header = $option['header'];
$this->app['hook']->add('response_send', function ($response) use ($header) {
$response->header($header);
});
}
// 指定Response响应数据
if (!empty($option['response'])) {
foreach ($option['response'] as $response) {
$this->app['hook']->add('response_send', $response);
}
}
// 开启请求缓存
if (isset($option['cache']) && $this->request->isGet()) {
$this->parseRequestCache($option['cache']);
}
if (!empty($option['append'])) {
$this->request->setRouteVars($option['append']);
}
}
/**
* 执行路由调度
* @access public
* @return mixed
*/
public function run()
{
$option = $this->rule->getOption();
// 检测路由after行为
if (!empty($option['after'])) {
$dispatch = $this->checkAfter($option['after']);
if ($dispatch instanceof Response) {
return $dispatch;
}
}
// 数据自动验证
if (isset($option['validate'])) {
$this->autoValidate($option['validate']);
}
$data = $this->exec();
return $this->autoResponse($data);
}
protected function autoResponse($data)
{
if ($data instanceof Response) {
$response = $data;
} elseif (!is_null($data)) {
// 默认自动识别响应输出类型
$isAjax = $this->request->isAjax();
$type = $isAjax ? $this->rule->getConfig('default_ajax_return') : $this->rule->getConfig('default_return_type');
$response = Response::create($data, $type);
} else {
$data = ob_get_clean();
$content = false === $data ? '' : $data;
$status = '' === $content && $this->request->isJson() ? 204 : 200;
$response = Response::create($content, '', $status);
}
return $response;
}
/**
* 检查路由后置行为
* @access protected
* @param mixed $after 后置行为
* @return mixed
*/
protected function checkAfter($after)
{
$this->app['log']->notice('路由后置行为建议使用中间件替代!');
$hook = $this->app['hook'];
$result = null;
foreach ((array) $after as $behavior) {
$result = $hook->exec($behavior);
if (!is_null($result)) {
break;
}
}
// 路由规则重定向
if ($result instanceof Response) {
return $result;
}
return false;
}
/**
* 验证数据
* @access protected
* @param array $option
* @return void
* @throws ValidateException
*/
protected function autoValidate($option)
{
list($validate, $scene, $message, $batch) = $option;
if (is_array($validate)) {
// 指定验证规则
$v = $this->app->validate();
$v->rule($validate);
} else {
// 调用验证器
$v = $this->app->validate($validate);
if (!empty($scene)) {
$v->scene($scene);
}
}
if (!empty($message)) {
$v->message($message);
}
// 批量验证
if ($batch) {
$v->batch(true);
}
if (!$v->check($this->request->param())) {
throw new ValidateException($v->getError());
}
}
/**
* 处理路由请求缓存
* @access protected
* @param Request $request 请求对象
* @param string|array $cache 路由缓存
* @return void
*/
protected function parseRequestCache($cache)
{
if (is_array($cache)) {
list($key, $expire, $tag) = array_pad($cache, 3, null);
} else {
$key = str_replace('|', '/', $this->request->url());
$expire = $cache;
$tag = null;
}
$cache = $this->request->cache($key, $expire, $tag);
$this->app->setResponseCache($cache);
}
/**
* 路由绑定模型实例
* @access protected
* @param array|\Clousre $bindModel 绑定模型
* @param array $matches 路由变量
* @return void
*/
protected function createBindModel($bindModel, $matches)
{
foreach ($bindModel as $key => $val) {
if ($val instanceof \Closure) {
$result = $this->app->invokeFunction($val, $matches);
} else {
$fields = explode('&', $key);
if (is_array($val)) {
list($model, $exception) = $val;
} else {
$model = $val;
$exception = true;
}
$where = [];
$match = true;
foreach ($fields as $field) {
if (!isset($matches[$field])) {
$match = false;
break;
} else {
$where[] = [$field, '=', $matches[$field]];
}
}
if ($match) {
$query = strpos($model, '\\') ? $model::where($where) : $this->app->model($model)->where($where);
$result = $query->failException($exception)->find();
}
}
if (!empty($result)) {
// 注入容器
$this->app->instance(get_class($result), $result);
}
}
}
public function convert($convert)
{
$this->convert = $convert;
return $this;
}
public function getDispatch()
{
return $this->dispatch;
}
public function getParam()
{
return $this->param;
}
abstract public function exec();
public function __sleep()
{
return ['rule', 'dispatch', 'convert', 'param', 'code', 'controller', 'actionName'];
}
public function __wakeup()
{
$this->app = Container::get('app');
$this->request = $this->app['request'];
}
public function __debugInfo()
{
$data = get_object_vars($this);
unset($data['app'], $data['request'], $data['rule']);
return $data;
}
}