// +---------------------------------------------------------------------- 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; } }