366 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			366 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?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\Container;
 | |
| use think\exception\ValidateException;
 | |
| use think\App;
 | |
| 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->isAjax() ? 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;
 | |
|     }
 | |
| }
 |