MiaoScript/thinkphp/library/think/App.php
2017-10-20 14:00:22 +08:00

592 lines
20 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2017 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think;
use think\exception\ClassNotFoundException;
use think\exception\HttpException;
use think\exception\HttpResponseException;
use think\exception\RouteNotFoundException;
/**
* App 应用管理
* @author liu21st <liu21st@gmail.com>
*/
class App
{
/**
* @var bool 是否初始化过
*/
protected static $init = false;
/**
* @var string 当前模块路径
*/
public static $modulePath;
/**
* @var bool 应用调试模式
*/
public static $debug = true;
/**
* @var string 应用类库命名空间
*/
public static $namespace = 'app';
/**
* @var bool 应用类库后缀
*/
public static $suffix = false;
/**
* @var bool 应用路由检测
*/
protected static $routeCheck;
/**
* @var bool 严格路由检测
*/
protected static $routeMust;
protected static $dispatch;
protected static $file = [];
/**
* 执行应用程序
* @access public
* @param Request $request Request对象
* @return Response
* @throws Exception
*/
public static function run(Request $request = null)
{
is_null($request) && $request = Request::instance();
try {
$config = self::initCommon();
if (defined('BIND_MODULE')) {
// 模块/控制器绑定
BIND_MODULE && Route::bind(BIND_MODULE);
} elseif ($config['auto_bind_module']) {
// 入口自动绑定
$name = pathinfo($request->baseFile(), PATHINFO_FILENAME);
if ($name && 'index' != $name && is_dir(APP_PATH . $name)) {
Route::bind($name);
}
}
$request->filter($config['default_filter']);
// 默认语言
Lang::range($config['default_lang']);
if ($config['lang_switch_on']) {
// 开启多语言机制 检测当前语言
Lang::detect();
}
$request->langset(Lang::range());
// 加载系统语言包
Lang::load([
THINK_PATH . 'lang' . DS . $request->langset() . EXT,
APP_PATH . 'lang' . DS . $request->langset() . EXT,
]);
// 获取应用调度信息
$dispatch = self::$dispatch;
if (empty($dispatch)) {
// 进行URL路由检测
$dispatch = self::routeCheck($request, $config);
}
// 记录当前调度信息
$request->dispatch($dispatch);
// 记录路由和请求信息
if (self::$debug) {
Log::record('[ ROUTE ] ' . var_export($dispatch, true), 'info');
Log::record('[ HEADER ] ' . var_export($request->header(), true), 'info');
Log::record('[ PARAM ] ' . var_export($request->param(), true), 'info');
}
// 监听app_begin
Hook::listen('app_begin', $dispatch);
// 请求缓存检查
$request->cache($config['request_cache'], $config['request_cache_expire'], $config['request_cache_except']);
$data = self::exec($dispatch, $config);
} catch (HttpResponseException $exception) {
$data = $exception->getResponse();
}
// 清空类的实例化
Loader::clearInstance();
// 输出数据到客户端
if ($data instanceof Response) {
$response = $data;
} elseif (!is_null($data)) {
// 默认自动识别响应输出类型
$isAjax = $request->isAjax();
$type = $isAjax ? Config::get('default_ajax_return') : Config::get('default_return_type');
$response = Response::create($data, $type);
} else {
$response = Response::create();
}
// 监听app_end
Hook::listen('app_end', $response);
return $response;
}
/**
* 设置当前请求的调度信息
* @access public
* @param array|string $dispatch 调度信息
* @param string $type 调度类型
* @return void
*/
public static function dispatch($dispatch, $type = 'module')
{
self::$dispatch = ['type' => $type, $type => $dispatch];
}
/**
* 执行函数或者闭包方法 支持参数调用
* @access public
* @param string|array|\Closure $function 函数或者闭包
* @param array $vars 变量
* @return mixed
*/
public static function invokeFunction($function, $vars = [])
{
$reflect = new \ReflectionFunction($function);
$args = self::bindParams($reflect, $vars);
// 记录执行信息
self::$debug && Log::record('[ RUN ] ' . $reflect->__toString(), 'info');
return $reflect->invokeArgs($args);
}
/**
* 调用反射执行类的方法 支持参数绑定
* @access public
* @param string|array $method 方法
* @param array $vars 变量
* @return mixed
*/
public static function invokeMethod($method, $vars = [])
{
if (is_array($method)) {
$class = is_object($method[0]) ? $method[0] : self::invokeClass($method[0]);
$reflect = new \ReflectionMethod($class, $method[1]);
} else {
// 静态方法
$reflect = new \ReflectionMethod($method);
}
$args = self::bindParams($reflect, $vars);
self::$debug && Log::record('[ RUN ] ' . $reflect->class . '->' . $reflect->name . '[ ' . $reflect->getFileName() . ' ]', 'info');
return $reflect->invokeArgs(isset($class) ? $class : null, $args);
}
/**
* 调用反射执行类的实例化 支持依赖注入
* @access public
* @param string $class 类名
* @param array $vars 变量
* @return mixed
*/
public static function invokeClass($class, $vars = [])
{
$reflect = new \ReflectionClass($class);
$constructor = $reflect->getConstructor();
if ($constructor) {
$args = self::bindParams($constructor, $vars);
} else {
$args = [];
}
return $reflect->newInstanceArgs($args);
}
/**
* 绑定参数
* @access private
* @param \ReflectionMethod|\ReflectionFunction $reflect 反射类
* @param array $vars 变量
* @return array
*/
private static function bindParams($reflect, $vars = [])
{
if (empty($vars)) {
// 自动获取请求变量
if (Config::get('url_param_type')) {
$vars = Request::instance()->route();
} else {
$vars = Request::instance()->param();
}
}
$args = [];
if ($reflect->getNumberOfParameters() > 0) {
// 判断数组类型 数字数组时按顺序绑定参数
reset($vars);
$type = key($vars) === 0 ? 1 : 0;
$params = $reflect->getParameters();
foreach ($params as $param) {
$args[] = self::getParamValue($param, $vars, $type);
}
}
return $args;
}
/**
* 获取参数值
* @access private
* @param \ReflectionParameter $param
* @param array $vars 变量
* @param string $type
* @return array
*/
private static function getParamValue($param, &$vars, $type)
{
$name = $param->getName();
$class = $param->getClass();
if ($class) {
$className = $class->getName();
$bind = Request::instance()->$name;
if ($bind instanceof $className) {
$result = $bind;
} else {
if (method_exists($className, 'invoke')) {
$method = new \ReflectionMethod($className, 'invoke');
if ($method->isPublic() && $method->isStatic()) {
return $className::invoke(Request::instance());
}
}
$result = method_exists($className, 'instance') ? $className::instance() : new $className;
}
} elseif (1 == $type && !empty($vars)) {
$result = array_shift($vars);
} elseif (0 == $type && isset($vars[$name])) {
$result = $vars[$name];
} elseif ($param->isDefaultValueAvailable()) {
$result = $param->getDefaultValue();
} else {
throw new \InvalidArgumentException('method param miss:' . $name);
}
return $result;
}
protected static function exec($dispatch, $config)
{
switch ($dispatch['type']) {
case 'redirect':
// 执行重定向跳转
$data = Response::create($dispatch['url'], 'redirect')->code($dispatch['status']);
break;
case 'module':
// 模块/控制器/操作
$data = self::module($dispatch['module'], $config, isset($dispatch['convert']) ? $dispatch['convert'] : null);
break;
case 'controller':
// 执行控制器操作
$vars = array_merge(Request::instance()->param(), $dispatch['var']);
$data = Loader::action($dispatch['controller'], $vars, $config['url_controller_layer'], $config['controller_suffix']);
break;
case 'method':
// 执行回调方法
$vars = array_merge(Request::instance()->param(), $dispatch['var']);
$data = self::invokeMethod($dispatch['method'], $vars);
break;
case 'function':
// 执行闭包
$data = self::invokeFunction($dispatch['function']);
break;
case 'response':
$data = $dispatch['response'];
break;
default:
throw new \InvalidArgumentException('dispatch type not support');
}
return $data;
}
/**
* 执行模块
* @access public
* @param array $result 模块/控制器/操作
* @param array $config 配置参数
* @param bool $convert 是否自动转换控制器和操作名
* @return mixed
*/
public static function module($result, $config, $convert = null)
{
if (is_string($result)) {
$result = explode('/', $result);
}
$request = Request::instance();
if ($config['app_multi_module']) {
// 多模块部署
$module = strip_tags(strtolower($result[0] ?: $config['default_module']));
$bind = Route::getBind('module');
$available = false;
if ($bind) {
// 绑定模块
list($bindModule) = explode('/', $bind);
if (empty($result[0])) {
$module = $bindModule;
$available = true;
} elseif ($module == $bindModule) {
$available = true;
}
} elseif (!in_array($module, $config['deny_module_list']) && is_dir(APP_PATH . $module)) {
$available = true;
}
// 模块初始化
if ($module && $available) {
// 初始化模块
$request->module($module);
$config = self::init($module);
// 模块请求缓存检查
$request->cache($config['request_cache'], $config['request_cache_expire'], $config['request_cache_except']);
} else {
throw new HttpException(404, 'module not exists:' . $module);
}
} else {
// 单一模块部署
$module = '';
$request->module($module);
}
// 当前模块路径
App::$modulePath = APP_PATH . ($module ? $module . DS : '');
// 是否自动转换控制器和操作名
$convert = is_bool($convert) ? $convert : $config['url_convert'];
// 获取控制器名
$controller = strip_tags($result[1] ?: $config['default_controller']);
$controller = $convert ? strtolower($controller) : $controller;
// 获取操作名
$actionName = strip_tags($result[2] ?: $config['default_action']);
$actionName = $convert ? strtolower($actionName) : $actionName;
// 设置当前请求的控制器、操作
$request->controller(Loader::parseName($controller, 1))->action($actionName);
// 监听module_init
Hook::listen('module_init', $request);
try {
$instance = Loader::controller($controller, $config['url_controller_layer'], $config['controller_suffix'], $config['empty_controller']);
} catch (ClassNotFoundException $e) {
throw new HttpException(404, 'controller not exists:' . $e->getClass());
}
// 获取当前操作名
$action = $actionName . $config['action_suffix'];
$vars = [];
if (is_callable([$instance, $action])) {
// 执行操作方法
$call = [$instance, $action];
} elseif (is_callable([$instance, '_empty'])) {
// 空操作
$call = [$instance, '_empty'];
$vars = [$actionName];
} else {
// 操作不存在
throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()');
}
Hook::listen('action_begin', $call);
return self::invokeMethod($call, $vars);
}
/**
* 初始化应用
*/
public static function initCommon()
{
if (empty(self::$init)) {
if (defined('APP_NAMESPACE')) {
self::$namespace = APP_NAMESPACE;
}
Loader::addNamespace(self::$namespace, APP_PATH);
// 初始化应用
$config = self::init();
self::$suffix = $config['class_suffix'];
// 应用调试模式
self::$debug = Env::get('app_debug', Config::get('app_debug'));
if (!self::$debug) {
ini_set('display_errors', 'Off');
} elseif (!IS_CLI) {
//重新申请一块比较大的buffer
if (ob_get_level() > 0) {
$output = ob_get_clean();
}
ob_start();
if (!empty($output)) {
echo $output;
}
}
if (!empty($config['root_namespace'])) {
Loader::addNamespace($config['root_namespace']);
}
// 加载额外文件
if (!empty($config['extra_file_list'])) {
foreach ($config['extra_file_list'] as $file) {
$file = strpos($file, '.') ? $file : APP_PATH . $file . EXT;
if (is_file($file) && !isset(self::$file[$file])) {
include $file;
self::$file[$file] = true;
}
}
}
// 设置系统时区
date_default_timezone_set($config['default_timezone']);
// 监听app_init
Hook::listen('app_init');
self::$init = true;
}
return Config::get();
}
/**
* 初始化应用或模块
* @access public
* @param string $module 模块名
* @return array
*/
private static function init($module = '')
{
// 定位模块目录
$module = $module ? $module . DS : '';
// 加载初始化文件
if (is_file(APP_PATH . $module . 'init' . EXT)) {
include APP_PATH . $module . 'init' . EXT;
} elseif (is_file(RUNTIME_PATH . $module . 'init' . EXT)) {
include RUNTIME_PATH . $module . 'init' . EXT;
} else {
$path = APP_PATH . $module;
// 加载模块配置
$config = Config::load(CONF_PATH . $module . 'config' . CONF_EXT);
// 读取数据库配置文件
$filename = CONF_PATH . $module . 'database' . CONF_EXT;
Config::load($filename, 'database');
// 读取扩展配置文件
if (is_dir(CONF_PATH . $module . 'extra')) {
$dir = CONF_PATH . $module . 'extra';
$files = scandir($dir);
foreach ($files as $file) {
if ('.' . pathinfo($file, PATHINFO_EXTENSION) === CONF_EXT) {
$filename = $dir . DS . $file;
Config::load($filename, pathinfo($file, PATHINFO_FILENAME));
}
}
}
// 加载应用状态配置
if ($config['app_status']) {
$config = Config::load(CONF_PATH . $module . $config['app_status'] . CONF_EXT);
}
// 加载行为扩展文件
if (is_file(CONF_PATH . $module . 'tags' . EXT)) {
Hook::import(include CONF_PATH . $module . 'tags' . EXT);
}
// 加载公共文件
if (is_file($path . 'common' . EXT)) {
include $path . 'common' . EXT;
}
// 加载当前模块语言包
if ($module) {
Lang::load($path . 'lang' . DS . Request::instance()->langset() . EXT);
}
}
return Config::get();
}
/**
* URL路由检测根据PATH_INFO)
* @access public
* @param \think\Request $request
* @param array $config
* @return array
* @throws \think\Exception
*/
public static function routeCheck($request, array $config)
{
$path = $request->path();
$depr = $config['pathinfo_depr'];
$result = false;
// 路由检测
$check = !is_null(self::$routeCheck) ? self::$routeCheck : $config['url_route_on'];
if ($check) {
// 开启路由
if (is_file(RUNTIME_PATH . 'route.php')) {
// 读取路由缓存
$rules = include RUNTIME_PATH . 'route.php';
if (is_array($rules)) {
Route::rules($rules);
}
} else {
$files = $config['route_config_file'];
foreach ($files as $file) {
if (is_file(CONF_PATH . $file . CONF_EXT)) {
// 导入路由配置
$rules = include CONF_PATH . $file . CONF_EXT;
if (is_array($rules)) {
Route::import($rules);
}
}
}
}
// 路由检测根据路由定义返回不同的URL调度
$result = Route::check($request, $path, $depr, $config['url_domain_deploy']);
$must = !is_null(self::$routeMust) ? self::$routeMust : $config['url_route_must'];
if ($must && false === $result) {
// 路由无效
throw new RouteNotFoundException();
}
}
if (false === $result) {
// 路由无效 解析模块/控制器/操作/参数... 支持控制器自动搜索
$result = Route::parseUrl($path, $depr, $config['controller_auto_search']);
}
return $result;
}
/**
* 设置应用的路由检测机制
* @access public
* @param bool $route 是否需要检测路由
* @param bool $must 是否强制检测路由
* @return void
*/
public static function route($route, $must = false)
{
self::$routeCheck = $route;
self::$routeMust = $must;
}
}