thinkphp-wechat/thinkphp/library/think/App.php
Wenbin.Wang d49c9fde59 代码
2021-12-24 16:40:05 +08:00

994 lines
27 KiB
PHP
Raw Permalink 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~2018 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\HttpResponseException;
use think\route\Dispatch;
/**
* App 应用管理
*/
class App extends Container
{
const VERSION = '5.1.39 LTS';
/**
* 当前模块路径
* @var string
*/
protected $modulePath;
/**
* 应用调试模式
* @var bool
*/
protected $appDebug = true;
/**
* 应用开始时间
* @var float
*/
protected $beginTime;
/**
* 应用内存初始占用
* @var integer
*/
protected $beginMem;
/**
* 应用类库命名空间
* @var string
*/
protected $namespace = 'app';
/**
* 应用类库后缀
* @var bool
*/
protected $suffix = false;
/**
* 严格路由检测
* @var bool
*/
protected $routeMust;
/**
* 应用类库目录
* @var string
*/
protected $appPath;
/**
* 框架目录
* @var string
*/
protected $thinkPath;
/**
* 应用根目录
* @var string
*/
protected $rootPath;
/**
* 运行时目录
* @var string
*/
protected $runtimePath;
/**
* 配置目录
* @var string
*/
protected $configPath;
/**
* 路由目录
* @var string
*/
protected $routePath;
/**
* 配置后缀
* @var string
*/
protected $configExt;
/**
* 应用调度实例
* @var Dispatch
*/
protected $dispatch;
/**
* 绑定模块(控制器)
* @var string
*/
protected $bindModule;
/**
* 初始化
* @var bool
*/
protected $initialized = false;
public function __construct($appPath = '')
{
$this->thinkPath = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR;
$this->path($appPath);
}
/**
* 绑定模块或者控制器
* @access public
* @param string $bind
* @return $this
*/
public function bind($bind)
{
$this->bindModule = $bind;
return $this;
}
/**
* 设置应用类库目录
* @access public
* @param string $path 路径
* @return $this
*/
public function path($path)
{
$this->appPath = $path ? realpath($path) . DIRECTORY_SEPARATOR : $this->getAppPath();
return $this;
}
/**
* 初始化应用
* @access public
* @return void
*/
public function initialize()
{
if ($this->initialized) {
return;
}
$this->initialized = true;
$this->beginTime = microtime(true);
$this->beginMem = memory_get_usage();
$this->rootPath = dirname($this->appPath) . DIRECTORY_SEPARATOR;
$this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR;
$this->routePath = $this->rootPath . 'route' . DIRECTORY_SEPARATOR;
$this->configPath = $this->rootPath . 'config' . DIRECTORY_SEPARATOR;
static::setInstance($this);
$this->instance('app', $this);
// 加载环境变量配置文件
if (is_file($this->rootPath . '.env')) {
$this->env->load($this->rootPath . '.env');
}
$this->configExt = $this->env->get('config_ext', '.php');
// 加载惯例配置文件
$this->config->set(include $this->thinkPath . 'convention.php');
// 设置路径环境变量
$this->env->set([
'think_path' => $this->thinkPath,
'root_path' => $this->rootPath,
'app_path' => $this->appPath,
'config_path' => $this->configPath,
'route_path' => $this->routePath,
'runtime_path' => $this->runtimePath,
'extend_path' => $this->rootPath . 'extend' . DIRECTORY_SEPARATOR,
'vendor_path' => $this->rootPath . 'vendor' . DIRECTORY_SEPARATOR,
]);
$this->namespace = $this->env->get('app_namespace', $this->namespace);
$this->env->set('app_namespace', $this->namespace);
// 注册应用命名空间
Loader::addNamespace($this->namespace, $this->appPath);
// 初始化应用
$this->init();
// 开启类名后缀
$this->suffix = $this->config('app.class_suffix');
// 应用调试模式
$this->appDebug = $this->env->get('app_debug', $this->config('app.app_debug'));
$this->env->set('app_debug', $this->appDebug);
if (!$this->appDebug) {
ini_set('display_errors', 'Off');
} elseif (PHP_SAPI != 'cli') {
//重新申请一块比较大的buffer
if (ob_get_level() > 0) {
$output = ob_get_clean();
}
ob_start();
if (!empty($output)) {
echo $output;
}
}
// 注册异常处理类
if ($this->config('app.exception_handle')) {
Error::setExceptionHandler($this->config('app.exception_handle'));
}
// 注册根命名空间
if (!empty($this->config('app.root_namespace'))) {
Loader::addNamespace($this->config('app.root_namespace'));
}
// 加载composer autofile文件
Loader::loadComposerAutoloadFiles();
// 注册类库别名
Loader::addClassAlias($this->config->pull('alias'));
// 数据库配置初始化
Db::init($this->config->pull('database'));
// 设置系统时区
date_default_timezone_set($this->config('app.default_timezone'));
// 读取语言包
$this->loadLangPack();
// 路由初始化
$this->routeInit();
}
/**
* 初始化应用或模块
* @access public
* @param string $module 模块名
* @return void
*/
public function init($module = '')
{
// 定位模块目录
$module = $module ? $module . DIRECTORY_SEPARATOR : '';
$path = $this->appPath . $module;
// 加载初始化文件
if (is_file($path . 'init.php')) {
include $path . 'init.php';
} elseif (is_file($this->runtimePath . $module . 'init.php')) {
include $this->runtimePath . $module . 'init.php';
} else {
// 加载行为扩展文件
if (is_file($path . 'tags.php')) {
$tags = include $path . 'tags.php';
if (is_array($tags)) {
$this->hook->import($tags);
}
}
// 加载公共文件
if (is_file($path . 'common.php')) {
include_once $path . 'common.php';
}
if ('' == $module) {
// 加载系统助手函数
include $this->thinkPath . 'helper.php';
}
// 加载中间件
if (is_file($path . 'middleware.php')) {
$middleware = include $path . 'middleware.php';
if (is_array($middleware)) {
$this->middleware->import($middleware);
}
}
// 注册服务的容器对象实例
if (is_file($path . 'provider.php')) {
$provider = include $path . 'provider.php';
if (is_array($provider)) {
$this->bindTo($provider);
}
}
// 自动读取配置文件
if (is_dir($path . 'config')) {
$dir = $path . 'config' . DIRECTORY_SEPARATOR;
} elseif (is_dir($this->configPath . $module)) {
$dir = $this->configPath . $module;
}
$files = isset($dir) ? scandir($dir) : [];
foreach ($files as $file) {
if ('.' . pathinfo($file, PATHINFO_EXTENSION) === $this->configExt) {
$this->config->load($dir . $file, pathinfo($file, PATHINFO_FILENAME));
}
}
}
$this->setModulePath($path);
if ($module) {
// 对容器中的对象实例进行配置更新
$this->containerConfigUpdate($module);
}
}
protected function containerConfigUpdate($module)
{
$config = $this->config->get();
// 注册异常处理类
if ($config['app']['exception_handle']) {
Error::setExceptionHandler($config['app']['exception_handle']);
}
Db::init($config['database']);
$this->middleware->setConfig($config['middleware']);
$this->route->setConfig($config['app']);
$this->request->init($config['app']);
$this->cookie->init($config['cookie']);
$this->view->init($config['template']);
$this->log->init($config['log']);
$this->session->setConfig($config['session']);
$this->debug->setConfig($config['trace']);
$this->cache->init($config['cache'], true);
// 加载当前模块语言包
$this->lang->load($this->appPath . $module . DIRECTORY_SEPARATOR . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php');
// 模块请求缓存检查
$this->checkRequestCache(
$config['app']['request_cache'],
$config['app']['request_cache_expire'],
$config['app']['request_cache_except']
);
}
/**
* 执行应用程序
* @access public
* @return Response
* @throws Exception
*/
public function run()
{
try {
// 初始化应用
$this->initialize();
// 监听app_init
$this->hook->listen('app_init');
if ($this->bindModule) {
// 模块/控制器绑定
$this->route->bind($this->bindModule);
} elseif ($this->config('app.auto_bind_module')) {
// 入口自动绑定
$name = pathinfo($this->request->baseFile(), PATHINFO_FILENAME);
if ($name && 'index' != $name && is_dir($this->appPath . $name)) {
$this->route->bind($name);
}
}
// 监听app_dispatch
$this->hook->listen('app_dispatch');
$dispatch = $this->dispatch;
if (empty($dispatch)) {
// 路由检测
$dispatch = $this->routeCheck()->init();
}
// 记录当前调度信息
$this->request->dispatch($dispatch);
// 记录路由和请求信息
if ($this->appDebug) {
$this->log('[ ROUTE ] ' . var_export($this->request->routeInfo(), true));
$this->log('[ HEADER ] ' . var_export($this->request->header(), true));
$this->log('[ PARAM ] ' . var_export($this->request->param(), true));
}
// 监听app_begin
$this->hook->listen('app_begin');
// 请求缓存检查
$this->checkRequestCache(
$this->config('request_cache'),
$this->config('request_cache_expire'),
$this->config('request_cache_except')
);
$data = null;
} catch (HttpResponseException $exception) {
$dispatch = null;
$data = $exception->getResponse();
}
$this->middleware->add(function (Request $request, $next) use ($dispatch, $data) {
return is_null($data) ? $dispatch->run() : $data;
});
$response = $this->middleware->dispatch($this->request);
// 监听app_end
$this->hook->listen('app_end', $response);
return $response;
}
protected function getRouteCacheKey()
{
if ($this->config->get('route_check_cache_key')) {
$closure = $this->config->get('route_check_cache_key');
$routeKey = $closure($this->request);
} else {
$routeKey = md5($this->request->baseUrl(true) . ':' . $this->request->method());
}
return $routeKey;
}
protected function loadLangPack()
{
// 读取默认语言
$this->lang->range($this->config('app.default_lang'));
if ($this->config('app.lang_switch_on')) {
// 开启多语言机制 检测当前语言
$this->lang->detect();
}
$this->request->setLangset($this->lang->range());
// 加载系统语言包
$this->lang->load([
$this->thinkPath . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php',
$this->appPath . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php',
]);
}
/**
* 设置当前地址的请求缓存
* @access public
* @param string $key 缓存标识,支持变量规则 ,例如 item/:name/:id
* @param mixed $expire 缓存有效期
* @param array $except 缓存排除
* @param string $tag 缓存标签
* @return void
*/
public function checkRequestCache($key, $expire = null, $except = [], $tag = null)
{
$cache = $this->request->cache($key, $expire, $except, $tag);
if ($cache) {
$this->setResponseCache($cache);
}
}
public function setResponseCache($cache)
{
list($key, $expire, $tag) = $cache;
if (strtotime($this->request->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $this->request->server('REQUEST_TIME')) {
// 读取缓存
$response = Response::create()->code(304);
throw new HttpResponseException($response);
} elseif ($this->cache->has($key)) {
list($content, $header) = $this->cache->get($key);
$response = Response::create($content)->header($header);
throw new HttpResponseException($response);
}
}
/**
* 设置当前请求的调度信息
* @access public
* @param Dispatch $dispatch 调度信息
* @return $this
*/
public function dispatch(Dispatch $dispatch)
{
$this->dispatch = $dispatch;
return $this;
}
/**
* 记录调试信息
* @access public
* @param mixed $msg 调试信息
* @param string $type 信息类型
* @return void
*/
public function log($msg, $type = 'info')
{
$this->appDebug && $this->log->record($msg, $type);
}
/**
* 获取配置参数 为空则获取所有配置
* @access public
* @param string $name 配置参数名(支持二级配置 .号分割)
* @return mixed
*/
public function config($name = '')
{
return $this->config->get($name);
}
/**
* 路由初始化 导入路由定义规则
* @access public
* @return void
*/
public function routeInit()
{
// 路由检测
$files = scandir($this->routePath);
foreach ($files as $file) {
if (strpos($file, '.php')) {
$filename = $this->routePath . $file;
// 导入路由配置
$rules = include $filename;
if (is_array($rules)) {
$this->route->import($rules);
}
}
}
if ($this->route->config('route_annotation')) {
// 自动生成路由定义
if ($this->appDebug) {
$suffix = $this->route->config('controller_suffix') || $this->route->config('class_suffix');
$this->build->buildRoute($suffix);
}
$filename = $this->runtimePath . 'build_route.php';
if (is_file($filename)) {
include $filename;
}
}
}
/**
* URL路由检测根据PATH_INFO)
* @access public
* @return Dispatch
*/
public function routeCheck()
{
// 检测路由缓存
if (!$this->appDebug && $this->config->get('route_check_cache')) {
$routeKey = $this->getRouteCacheKey();
$option = $this->config->get('route_cache_option');
if ($option && $this->cache->connect($option)->has($routeKey)) {
return $this->cache->connect($option)->get($routeKey);
} elseif ($this->cache->has($routeKey)) {
return $this->cache->get($routeKey);
}
}
// 获取应用调度信息
$path = $this->request->path();
// 针对swoole如果worker_num大于1则后面的进程会覆盖掉第一次的path数据则会加载到默认的index控制器
// 这里使用缓存的逻辑如果为空则加载第一次的path数据有则覆盖之
// 该方案不是最佳的,待优化
/*if (preg_match("/cli/i", php_sapi_name())) {
if (!empty($path)) {
$this->cache->tag('path_cache')->set('path', $path, 2);
} else {
$path = $this->cache->tag('path_cache')->get('path') ?: '';
$this->cache->clear('path_cache');
}
}*/
// 是否强制路由模式
$must = !is_null($this->routeMust) ? $this->routeMust : $this->route->config('url_route_must');
// 路由检测 返回一个Dispatch对象
$dispatch = $this->route->check($path, $must);
if (!empty($routeKey)) {
try {
if ($option) {
$this->cache->connect($option)->tag('route_cache')->set($routeKey, $dispatch);
} else {
$this->cache->tag('route_cache')->set($routeKey, $dispatch);
}
} catch (\Exception $e) {
// 存在闭包的时候缓存无效
}
}
return $dispatch;
}
/**
* 设置应用的路由检测机制
* @access public
* @param bool $must 是否强制检测路由
* @return $this
*/
public function routeMust($must = false)
{
$this->routeMust = $must;
return $this;
}
/**
* 解析模块和类名
* @access protected
* @param string $name 资源地址
* @param string $layer 验证层名称
* @param bool $appendSuffix 是否添加类名后缀
* @return array
*/
protected function parseModuleAndClass($name, $layer, $appendSuffix)
{
if (false !== strpos($name, '\\')) {
$class = $name;
$module = $this->request->module();
} else {
if (strpos($name, '/')) {
list($module, $name) = explode('/', $name, 2);
} else {
$module = $this->request->module();
}
$class = $this->parseClass($module, $layer, $name, $appendSuffix);
}
return [$module, $class];
}
/**
* 实例化应用类库
* @access public
* @param string $name 类名称
* @param string $layer 业务层名称
* @param bool $appendSuffix 是否添加类名后缀
* @param string $common 公共模块名
* @return object
* @throws ClassNotFoundException
*/
public function create($name, $layer, $appendSuffix = false, $common = 'common')
{
$guid = $name . $layer;
if ($this->__isset($guid)) {
return $this->__get($guid);
}
list($module, $class) = $this->parseModuleAndClass($name, $layer, $appendSuffix);
if (class_exists($class)) {
$object = $this->__get($class);
} else {
$class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class);
if (class_exists($class)) {
$object = $this->__get($class);
} else {
throw new ClassNotFoundException('class not exists:' . $class, $class);
}
}
$this->__set($guid, $class);
return $object;
}
/**
* 实例化(分层)模型
* @access public
* @param string $name Model名称
* @param string $layer 业务层名称
* @param bool $appendSuffix 是否添加类名后缀
* @param string $common 公共模块名
* @return Model
* @throws ClassNotFoundException
*/
public function model($name = '', $layer = 'model', $appendSuffix = false, $common = 'common')
{
return $this->create($name, $layer, $appendSuffix, $common);
}
/**
* 实例化(分层)控制器 格式:[模块名/]控制器名
* @access public
* @param string $name 资源地址
* @param string $layer 控制层名称
* @param bool $appendSuffix 是否添加类名后缀
* @param string $empty 空控制器名称
* @return object
* @throws ClassNotFoundException
*/
public function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '')
{
list($module, $class) = $this->parseModuleAndClass($name, $layer, $appendSuffix);
if (class_exists($class)) {
return $this->make($class, true);
} elseif ($empty && class_exists($emptyClass = $this->parseClass($module, $layer, $empty, $appendSuffix))) {
return $this->make($emptyClass, true);
}
throw new ClassNotFoundException('class not exists:' . $class, $class);
}
/**
* 实例化验证类 格式:[模块名/]验证器名
* @access public
* @param string $name 资源地址
* @param string $layer 验证层名称
* @param bool $appendSuffix 是否添加类名后缀
* @param string $common 公共模块名
* @return Validate
* @throws ClassNotFoundException
*/
public function validate($name = '', $layer = 'validate', $appendSuffix = false, $common = 'common')
{
$name = $name ?: $this->config('default_validate');
if (empty($name)) {
return new Validate;
}
return $this->create($name, $layer, $appendSuffix, $common);
}
/**
* 数据库初始化
* @access public
* @param mixed $config 数据库配置
* @param bool|string $name 连接标识 true 强制重新连接
* @return \think\db\Query
*/
public function db($config = [], $name = false)
{
return Db::connect($config, $name);
}
/**
* 远程调用模块的操作方法 参数格式 [模块/控制器/]操作
* @access public
* @param string $url 调用地址
* @param string|array $vars 调用参数 支持字符串和数组
* @param string $layer 要调用的控制层名称
* @param bool $appendSuffix 是否添加类名后缀
* @return mixed
* @throws ClassNotFoundException
*/
public function action($url, $vars = [], $layer = 'controller', $appendSuffix = false)
{
$info = pathinfo($url);
$action = $info['basename'];
$module = '.' != $info['dirname'] ? $info['dirname'] : $this->request->controller();
$class = $this->controller($module, $layer, $appendSuffix);
if (is_scalar($vars)) {
if (strpos($vars, '=')) {
parse_str($vars, $vars);
} else {
$vars = [$vars];
}
}
return $this->invokeMethod([$class, $action . $this->config('action_suffix')], $vars);
}
/**
* 解析应用类的类名
* @access public
* @param string $module 模块名
* @param string $layer 层名 controller model ...
* @param string $name 类名
* @param bool $appendSuffix
* @return string
*/
public function parseClass($module, $layer, $name, $appendSuffix = false)
{
$name = str_replace(['/', '.'], '\\', $name);
$array = explode('\\', $name);
$class = Loader::parseName(array_pop($array), 1) . ($this->suffix || $appendSuffix ? ucfirst($layer) : '');
$path = $array ? implode('\\', $array) . '\\' : '';
return $this->namespace . '\\' . ($module ? $module . '\\' : '') . $layer . '\\' . $path . $class;
}
/**
* 获取框架版本
* @access public
* @return string
*/
public function version()
{
return static::VERSION;
}
/**
* 是否为调试模式
* @access public
* @return bool
*/
public function isDebug()
{
return $this->appDebug;
}
/**
* 获取模块路径
* @access public
* @return string
*/
public function getModulePath()
{
return $this->modulePath;
}
/**
* 设置模块路径
* @access public
* @param string $path 路径
* @return void
*/
public function setModulePath($path)
{
$this->modulePath = $path;
$this->env->set('module_path', $path);
}
/**
* 获取应用根目录
* @access public
* @return string
*/
public function getRootPath()
{
return $this->rootPath;
}
/**
* 获取应用类库目录
* @access public
* @return string
*/
public function getAppPath()
{
if (is_null($this->appPath)) {
$this->appPath = Loader::getRootPath() . 'application' . DIRECTORY_SEPARATOR;
}
return $this->appPath;
}
/**
* 获取应用运行时目录
* @access public
* @return string
*/
public function getRuntimePath()
{
return $this->runtimePath;
}
/**
* 获取核心框架目录
* @access public
* @return string
*/
public function getThinkPath()
{
return $this->thinkPath;
}
/**
* 获取路由目录
* @access public
* @return string
*/
public function getRoutePath()
{
return $this->routePath;
}
/**
* 获取应用配置目录
* @access public
* @return string
*/
public function getConfigPath()
{
return $this->configPath;
}
/**
* 获取配置后缀
* @access public
* @return string
*/
public function getConfigExt()
{
return $this->configExt;
}
/**
* 获取应用类库命名空间
* @access public
* @return string
*/
public function getNamespace()
{
return $this->namespace;
}
/**
* 设置应用类库命名空间
* @access public
* @param string $namespace 命名空间名称
* @return $this
*/
public function setNamespace($namespace)
{
$this->namespace = $namespace;
return $this;
}
/**
* 是否启用类库后缀
* @access public
* @return bool
*/
public function getSuffix()
{
return $this->suffix;
}
/**
* 获取应用开启时间
* @access public
* @return float
*/
public function getBeginTime()
{
return $this->beginTime;
}
/**
* 获取应用初始内存占用
* @access public
* @return integer
*/
public function getBeginMem()
{
return $this->beginMem;
}
}