<?php<liu21st@gmail.com>
namespace think;
use think\exception\ClassNotFoundException;
use think\exception\HttpResponseException;
use think\route\Dispatch;
class App implements \ArrayAccess
{
const VERSION = '5.1.11';
protected $modulePath;
protected $debug = true;
protected $beginTime;
protected $beginMem;
protected $namespace = 'app';
protected $suffix = false;
protected $routeMust;
protected $appPath;
protected $thinkPath;
protected $rootPath;
protected $runtimePath;
protected $configPath;
protected $routePath;
protected $configExt;
protected $dispatch;
protected $container;
protected $bind;
public function __construct($appPath = '')
{
$this->appPath = $appPath ? realpath($appPath) . DIRECTORY_SEPARATOR : $this->getAppPath();
$this->container = Container::getInstance();
}
public function bind($bind)
{
$this->bind = $bind;
return $this;
}
public function path($path)
{
$this->appPath = $path;
return $this;
}
public function initialize()
{
$this->beginTime = microtime(true);
$this->beginMem = memory_get_usage();
$this->thinkPath = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR;
$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;
$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,
]);
if (is_file($this->rootPath . '.env')) {
$this->env->load($this->rootPath . '.env');
}
$this->namespace = $this->env->get('app_namespace', $this->namespace);
$this->env->set('app_namespace', $this->namespace);
Loader::addNamespace($this->namespace, $this->appPath);
$this->configExt = $this->env->get('config_ext', '.php');
$this->init();
$this->suffix = $this->config('app.class_suffix');
$this->debug = $this->env->get('app_debug', $this->config('app.app_debug'));
$this->env->set('app_debug', $this->debug);
if (!$this->debug) {
ini_set('display_errors', 'Off');
} elseif (PHP_SAPI != 'cli') {
if (ob_get_level() > 0) {
$output = ob_get_clean();
}
ob_start();
if (!empty($output)) {
echo $output;
}
}
if (!empty($this->config('app.root_namespace'))) {
Loader::addNamespace($this->config('app.root_namespace'));
}
Loader::addClassAlias($this->config->pull('alias'));
date_default_timezone_set($this->config('app.default_timezone'));
$this->loadLangPack();
$this->hook->listen('app_init');
}
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 $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->container->bind($provider);
}
}
if (is_dir($path . 'config')) {
$dir = $path . 'config';
} 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) {
$filename = $dir . DIRECTORY_SEPARATOR . $file;
$this->config->load($filename, pathinfo($file, PATHINFO_FILENAME));
}
}
}
$this->request->filter($this->config('app.default_filter'));
}
public function run()
{
try {
$this->initialize();
if ($this->bind) {
$this->route->bind($this->bind);
} 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);
}
}
$this->hook->listen('app_dispatch');
$dispatch = $this->dispatch;
if (empty($dispatch)) {
$this->route
->lazy($this->config('app.url_lazy_route'))
->autoSearchController($this->config('app.controller_auto_search'))
->mergeRuleRegex($this->config('app.route_rule_merge'));
$dispatch = $this->routeCheck();
}
$this->request->dispatch($dispatch);
if ($this->debug) {
$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));
}
$this->hook->listen('app_begin');
$this->request->cache(
$this->config('app.request_cache'),
$this->config('app.request_cache_expire'),
$this->config('app.request_cache_except')
);
$data = null;
} catch (HttpResponseException $exception) {
$dispatch = null;
$data = $exception->getResponse();
}
$this->middleware->add(function (Request $request, $next) use ($dispatch, $data) {
if (is_null($data)) {
try {
$data = $dispatch->run();
} catch (HttpResponseException $exception) {
$data = $exception->getResponse();
}
}
if ($data instanceof Response) {
$response = $data;
} elseif (!is_null($data)) {
$isAjax = $request->isAjax();
$type = $isAjax ? $this->config('app.default_ajax_return') : $this->config('app.default_return_type');
$response = Response::create($data, $type);
} else {
$response = Response::create();
}
return $response;
});
$response = $this->middleware->dispatch($this->request);
$this->hook->listen('app_end', $response);
return $response;
}
protected function loadLangPack()
{
$this->lang->range($this->config('app.default_lang'));
if ($this->config('app.lang_switch_on')) {
$this->lang->detect();
}
$this->request->langset($this->lang->range());
$this->lang->load([
$this->thinkPath . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php',
$this->appPath . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php',
]);
}
public function dispatch(Dispatch $dispatch)
{
$this->dispatch = $dispatch;
return $this;
}
public function log($msg, $type = 'info')
{
$this->debug && $this->log->record($msg, $type);
}
public function config($name = '')
{
return $this->config->get($name);
}
public function routeCheck()
{
$path = $this->request->path();
$depr = $this->config('app.pathinfo_depr');
$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->config('app.route_annotation')) {
if ($this->debug) {
$this->build->buildRoute($this->config('app.controller_suffix'));
}
$filename = $this->runtimePath . 'build_route.php';
if (is_file($filename)) {
include $filename;
}
}
if (is_file($this->runtimePath . 'rule_regex.php')) {
$this->route->setRuleRegexs(include $this->runtimePath . 'rule_regex.php');
}
$must = !is_null($this->routeMust) ? $this->routeMust : $this->config('app.url_route_must');
return $this->route->check($path, $depr, $must, $this->config('app.route_complete_match'));
}
public function routeMust($must = false)
{
$this->routeMust = $must;
return $this;
}
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];
}
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;
}
public function model($name = '', $layer = 'model', $appendSuffix = false, $common = 'common')
{
return $this->create($name, $layer, $appendSuffix, $common);
}
public function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '')
{
list($module, $class) = $this->parseModuleAndClass($name, $layer, $appendSuffix);
if (class_exists($class)) {
return $this->__get($class);
} elseif ($empty && class_exists($emptyClass = $this->parseClass($module, $layer, $empty, $appendSuffix))) {
return $this->__get($emptyClass);
}
throw new ClassNotFoundException('class not exists:' . $class, $class);
}
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);
}
public function db($config = [], $name = false)
{
return Db::connect($config, $name);
}
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->container->invokeMethod([$class, $action . $this->config('action_suffix')], $vars);
}
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;
}
public function version()
{
return static::VERSION;
}
public function isDebug()
{
return $this->debug;
}
public function getModulePath()
{
return $this->modulePath;
}
public function setModulePath($path)
{
$this->modulePath = $path;
$this->env->set('module_path', $path);
}
public function getRootPath()
{
return $this->rootPath;
}
public function getAppPath()
{
if (is_null($this->appPath)) {
$this->appPath = Loader::getRootPath() . 'application' . DIRECTORY_SEPARATOR;
}
return $this->appPath;
}
public function getRuntimePath()
{
return $this->runtimePath;
}
public function getThinkPath()
{
return $this->thinkPath;
}
public function getRoutePath()
{
return $this->routePath;
}
public function getConfigPath()
{
return $this->configPath;
}
public function getConfigExt()
{
return $this->configExt;
}
public function getNamespace()
{
return $this->namespace;
}
public function setNamespace($namespace)
{
$this->namespace = $namespace;
return $this;
}
public function getSuffix()
{
return $this->suffix;
}
public function getBeginTime()
{
return $this->beginTime;
}
public function getBeginMem()
{
return $this->beginMem;
}
public function container()
{
return $this->container;
}
public function __set($name, $value)
{
$this->container->bind($name, $value);
}
public function __get($name)
{
return $this->container->make($name);
}
public function __isset($name)
{
return $this->container->bound($name);
}
public function __unset($name)
{
$this->container->__unset($name);
}
public function offsetExists($key)
{
return $this->__isset($key);
}
public function offsetGet($key)
{
return $this->__get($key);
}
public function offsetSet($key, $value)
{
$this->__set($key, $value);
}
public function offsetUnset($key)
{
$this->__unset($key);
}
}