App.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | ThinkPHP [ WE CAN DO IT JUST THINK ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
  8. // +----------------------------------------------------------------------
  9. // | Author: liu21st <liu21st@gmail.com>
  10. // +----------------------------------------------------------------------
  11. declare (strict_types=1);
  12. namespace think;
  13. use think\event\AppInit;
  14. use think\helper\Str;
  15. use think\initializer\BootService;
  16. use think\initializer\Error;
  17. use think\initializer\RegisterService;
  18. /**
  19. * App 基础类
  20. * @property Route $route
  21. * @property Config $config
  22. * @property Cache $cache
  23. * @property Request $request
  24. * @property Http $http
  25. * @property Console $console
  26. * @property Env $env
  27. * @property Event $event
  28. * @property Middleware $middleware
  29. * @property Log $log
  30. * @property Lang $lang
  31. * @property Db $db
  32. * @property Cookie $cookie
  33. * @property Session $session
  34. * @property Validate $validate
  35. * @property Filesystem $filesystem
  36. */
  37. class App extends Container
  38. {
  39. const VERSION = '6.0.13LTS';
  40. /**
  41. * 应用调试模式
  42. * @var bool
  43. */
  44. protected $appDebug = false;
  45. /**
  46. * 环境变量标识
  47. * @var string
  48. */
  49. protected $envName = '';
  50. /**
  51. * 应用开始时间
  52. * @var float
  53. */
  54. protected $beginTime;
  55. /**
  56. * 应用内存初始占用
  57. * @var integer
  58. */
  59. protected $beginMem;
  60. /**
  61. * 当前应用类库命名空间
  62. * @var string
  63. */
  64. protected $namespace = 'app';
  65. /**
  66. * 当前项目应用类库根命名空间
  67. * @var string
  68. */
  69. protected $rootNamespace = 'app';
  70. /**
  71. * 应用根目录
  72. * @var string
  73. */
  74. protected $rootPath = '';
  75. /**
  76. * 框架目录
  77. * @var string
  78. */
  79. protected $thinkPath = '';
  80. /**
  81. * 应用目录
  82. * @var string
  83. */
  84. protected $appPath = '';
  85. /**
  86. * Runtime目录
  87. * @var string
  88. */
  89. protected $runtimePath = '';
  90. /**
  91. * 路由定义目录
  92. * @var string
  93. */
  94. protected $routePath = '';
  95. /**
  96. * 配置后缀
  97. * @var string
  98. */
  99. protected $configExt = '.php';
  100. /**
  101. * 应用初始化器
  102. * @var array
  103. */
  104. protected $initializers = [
  105. Error::class,
  106. RegisterService::class,
  107. BootService::class,
  108. ];
  109. /**
  110. * 注册的系统服务
  111. * @var array
  112. */
  113. protected $services = [];
  114. /**
  115. * 初始化
  116. * @var bool
  117. */
  118. protected $initialized = false;
  119. /**
  120. * 容器绑定标识
  121. * @var array
  122. */
  123. protected $bind = [
  124. 'app' => App::class,
  125. 'cache' => Cache::class,
  126. 'config' => Config::class,
  127. 'console' => Console::class,
  128. 'cookie' => Cookie::class,
  129. 'db' => Db::class,
  130. 'env' => Env::class,
  131. 'event' => Event::class,
  132. 'http' => Http::class,
  133. 'lang' => Lang::class,
  134. 'log' => Log::class,
  135. 'middleware' => Middleware::class,
  136. 'request' => Request::class,
  137. 'response' => Response::class,
  138. 'route' => Route::class,
  139. 'session' => Session::class,
  140. 'validate' => Validate::class,
  141. 'view' => View::class,
  142. 'filesystem' => Filesystem::class,
  143. 'think\DbManager' => Db::class,
  144. 'think\LogManager' => Log::class,
  145. 'think\CacheManager' => Cache::class,
  146. // 接口依赖注入
  147. 'Psr\Log\LoggerInterface' => Log::class,
  148. ];
  149. /**
  150. * 架构方法
  151. * @access public
  152. * @param string $rootPath 应用根目录
  153. */
  154. public function __construct(string $rootPath = '')
  155. {
  156. if (defined('APP_NAMESPACE')) {
  157. $this->namespace = APP_NAMESPACE;
  158. $this->rootNamespace = APP_NAMESPACE;
  159. }
  160. $this->thinkPath = dirname(__DIR__, 3) . DIRECTORY_SEPARATOR . 'topthink' . DIRECTORY_SEPARATOR . 'framework' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR;
  161. $this->rootPath = $rootPath ? rtrim($rootPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $this->getDefaultRootPath();
  162. $this->appPath = $this->rootPath . $this->namespace . DIRECTORY_SEPARATOR;
  163. $this->runtimePath = $this->rootPath . 'data' . DIRECTORY_SEPARATOR . 'runtime' . DIRECTORY_SEPARATOR;
  164. if (defined('RUNTIME_PATH')) {
  165. $this->runtimePath = RUNTIME_PATH;
  166. }
  167. if (is_file($this->appPath . 'provider.php')) {
  168. $this->bind(include $this->appPath . 'provider.php');
  169. }
  170. // 加载cmf-app,cmf-api provider
  171. $appRootNamespace = $this->getRootNamespace();
  172. $rootPath = $this->rootPath;
  173. $vendorProviderFile = "{$rootPath}vendor/thinkcmf/cmf-{$appRootNamespace}/src/provider.php";
  174. if (is_file($vendorProviderFile)) {
  175. $this->bind(include $vendorProviderFile);
  176. }
  177. // 加载应用 provider
  178. $apps = cmf_scan_dir($this->appPath . '*', GLOB_ONLYDIR);
  179. foreach ($apps as $appName) {
  180. $appProviderFile = $this->appPath . $appName . DIRECTORY_SEPARATOR . 'provider.php';
  181. if (is_file($appProviderFile)) {
  182. $this->bind(include $appProviderFile);
  183. }
  184. }
  185. static::setInstance($this);
  186. $this->instance('app', $this);
  187. $this->instance('think\Container', $this);
  188. }
  189. /**
  190. * 注册服务
  191. * @access public
  192. * @param Service|string $service 服务
  193. * @param bool $force 强制重新注册
  194. * @return Service|null
  195. */
  196. public function register($service, bool $force = false)
  197. {
  198. $registered = $this->getService($service);
  199. if ($registered && !$force) {
  200. return $registered;
  201. }
  202. if (is_string($service)) {
  203. $service = new $service($this);
  204. }
  205. if (method_exists($service, 'register')) {
  206. $service->register();
  207. }
  208. if (property_exists($service, 'bind')) {
  209. $this->bind($service->bind);
  210. }
  211. $this->services[] = $service;
  212. }
  213. /**
  214. * 执行服务
  215. * @access public
  216. * @param Service $service 服务
  217. * @return mixed
  218. */
  219. public function bootService($service)
  220. {
  221. if (method_exists($service, 'boot')) {
  222. return $this->invoke([$service, 'boot']);
  223. }
  224. }
  225. /**
  226. * 获取服务
  227. * @param string|Service $service
  228. * @return Service|null
  229. */
  230. public function getService($service)
  231. {
  232. $name = is_string($service) ? $service : get_class($service);
  233. return array_values(array_filter($this->services, function ($value) use ($name) {
  234. return $value instanceof $name;
  235. }, ARRAY_FILTER_USE_BOTH))[0] ?? null;
  236. }
  237. /**
  238. * 开启应用调试模式
  239. * @access public
  240. * @param bool $debug 开启应用调试模式
  241. * @return $this
  242. */
  243. public function debug(bool $debug = true)
  244. {
  245. $this->appDebug = $debug;
  246. return $this;
  247. }
  248. /**
  249. * 是否为调试模式
  250. * @access public
  251. * @return bool
  252. */
  253. public function isDebug(): bool
  254. {
  255. return $this->appDebug;
  256. }
  257. /**
  258. * 设置应用命名空间
  259. * @access public
  260. * @param string $namespace 应用命名空间
  261. * @return $this
  262. */
  263. public function setNamespace(string $namespace)
  264. {
  265. $this->namespace = $namespace;
  266. return $this;
  267. }
  268. /**
  269. * 获取应用类库命名空间
  270. * @access public
  271. * @return string
  272. */
  273. public function getNamespace(): string
  274. {
  275. return $this->namespace;
  276. }
  277. /**
  278. * 获取项目应用类库根命名空间
  279. * @access public
  280. * @return string
  281. */
  282. public function getRootNamespace(): string
  283. {
  284. return $this->rootNamespace;
  285. }
  286. /**
  287. * 设置环境变量标识
  288. * @access public
  289. * @param string $name 环境标识
  290. * @return $this
  291. */
  292. public function setEnvName(string $name)
  293. {
  294. $this->envName = $name;
  295. return $this;
  296. }
  297. /**
  298. * 获取框架版本
  299. * @access public
  300. * @return string
  301. */
  302. public function version(): string
  303. {
  304. return static::VERSION;
  305. }
  306. /**
  307. * 获取应用根目录
  308. * @access public
  309. * @return string
  310. */
  311. public function getRootPath(): string
  312. {
  313. return $this->rootPath;
  314. }
  315. /**
  316. * 获取应用基础目录
  317. * @access public
  318. * @return string
  319. */
  320. public function getBasePath(): string
  321. {
  322. $app = 'app';
  323. if (defined('APP_NAMESPACE')) {
  324. $app = APP_NAMESPACE;
  325. }
  326. return $this->rootPath . $app . DIRECTORY_SEPARATOR;
  327. }
  328. /**
  329. * 获取当前应用目录
  330. * @access public
  331. * @return string
  332. */
  333. public function getAppPath(): string
  334. {
  335. return $this->appPath;
  336. }
  337. /**
  338. * 设置应用目录
  339. * @param string $path 应用目录
  340. */
  341. public function setAppPath(string $path)
  342. {
  343. $this->appPath = $path;
  344. }
  345. /**
  346. * 获取应用运行时目录
  347. * @access public
  348. * @return string
  349. */
  350. public function getRuntimePath(): string
  351. {
  352. return $this->runtimePath;
  353. }
  354. /**
  355. * 设置runtime目录
  356. * @param string $path 定义目录
  357. */
  358. public function setRuntimePath(string $path): void
  359. {
  360. $this->runtimePath = $path;
  361. }
  362. /**
  363. * 获取核心框架目录
  364. * @access public
  365. * @return string
  366. */
  367. public function getThinkPath(): string
  368. {
  369. return $this->thinkPath;
  370. }
  371. /**
  372. * 获取应用配置目录
  373. * @access public
  374. * @return string
  375. */
  376. public function getConfigPath(): string
  377. {
  378. return $this->rootPath . 'config' . DIRECTORY_SEPARATOR;
  379. }
  380. /**
  381. * 获取配置后缀
  382. * @access public
  383. * @return string
  384. */
  385. public function getConfigExt(): string
  386. {
  387. return $this->configExt;
  388. }
  389. /**
  390. * 获取应用开启时间
  391. * @access public
  392. * @return float
  393. */
  394. public function getBeginTime(): float
  395. {
  396. return $this->beginTime;
  397. }
  398. /**
  399. * 获取应用初始内存占用
  400. * @access public
  401. * @return integer
  402. */
  403. public function getBeginMem(): int
  404. {
  405. return $this->beginMem;
  406. }
  407. /**
  408. * 加载环境变量定义
  409. * @access public
  410. * @param string $envName 环境标识
  411. * @return void
  412. */
  413. public function loadEnv(string $envName = ''): void
  414. {
  415. // 加载环境变量
  416. $envFile = $envName ? $this->rootPath . '.env.' . $envName : $this->rootPath . '.env';
  417. if (is_file($envFile)) {
  418. $this->env->load($envFile);
  419. }
  420. }
  421. /**
  422. * 初始化应用
  423. * @access public
  424. * @return $this
  425. */
  426. public function initialize()
  427. {
  428. $this->initialized = true;
  429. $this->beginTime = microtime(true);
  430. $this->beginMem = memory_get_usage();
  431. // 加载环境变量
  432. $this->loadEnv($this->envName);
  433. $this->configExt = $this->env->get('config_ext', '.php');
  434. $this->debugModeInit();
  435. // 加载全局初始化文件
  436. $this->load();
  437. // 加载框架默认语言包
  438. $langSet = $this->lang->defaultLangSet();
  439. $this->lang->load($this->thinkPath . 'lang' . DIRECTORY_SEPARATOR . $langSet . '.php');
  440. // 加载应用默认语言包
  441. $this->loadLangPack($langSet);
  442. // 监听AppInit
  443. $this->event->trigger(AppInit::class);
  444. date_default_timezone_set($this->config->get('app.default_timezone', 'Asia/Shanghai'));
  445. // 初始化
  446. foreach ($this->initializers as $initializer) {
  447. $this->make($initializer)->init($this);
  448. }
  449. return $this;
  450. }
  451. /**
  452. * 是否初始化过
  453. * @return bool
  454. */
  455. public function initialized()
  456. {
  457. return $this->initialized;
  458. }
  459. /**
  460. * 加载语言包
  461. * @param string $langset 语言
  462. * @return void
  463. */
  464. public function loadLangPack($langset)
  465. {
  466. if (empty($langset)) {
  467. return;
  468. }
  469. // 加载系统语言包
  470. $files = glob($this->appPath . 'lang' . DIRECTORY_SEPARATOR . $langset . '.*');
  471. $this->lang->load($files);
  472. // 加载扩展(自定义)语言包
  473. $list = $this->config->get('lang.extend_list', []);
  474. if (isset($list[$langset])) {
  475. $this->lang->load($list[$langset]);
  476. }
  477. }
  478. /**
  479. * 引导应用
  480. * @access public
  481. * @return void
  482. */
  483. public function boot(): void
  484. {
  485. array_walk($this->services, function ($service) {
  486. $this->bootService($service);
  487. });
  488. }
  489. /**
  490. * 加载应用文件和配置
  491. * @access protected
  492. * @return void
  493. */
  494. protected function load(): void
  495. {
  496. $appPath = $this->getAppPath();
  497. if (is_file($appPath . 'common.php')) {
  498. include_once $appPath . 'common.php';
  499. }
  500. include_once $this->thinkPath . 'helper.php';
  501. // 加载应用配置
  502. $appConfigFiles = glob(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . '*.php');
  503. foreach ($appConfigFiles as $file) {
  504. $this->config->load($file, pathinfo($file, PATHINFO_FILENAME));
  505. }
  506. // 加载应用配置结束
  507. $configPath = $this->getConfigPath();
  508. $files = [];
  509. if (is_dir($configPath)) {
  510. $files = glob($configPath . '*' . $this->configExt);
  511. }
  512. foreach ($files as $file) {
  513. $this->config->load($file, pathinfo($file, PATHINFO_FILENAME));
  514. }
  515. // 动态配置
  516. $runtimeConfigPath = CMF_DATA . 'config' . DIRECTORY_SEPARATOR;
  517. $files = [];
  518. if (is_dir($runtimeConfigPath)) {
  519. $files = glob($runtimeConfigPath . '*' . $this->configExt);
  520. }
  521. foreach ($files as $file) {
  522. $this->config->load($file, pathinfo($file, PATHINFO_FILENAME));
  523. }
  524. // 动态配置结束
  525. // 加载cmf-app,cmf-api事件配置
  526. $appRootNamespace = $this->getRootNamespace();
  527. $rootPath = root_path();
  528. $vendorEventFile = "{$rootPath}vendor/thinkcmf/cmf-{$appRootNamespace}/src/event.php";
  529. if (is_file($vendorEventFile)) {
  530. $this->loadEvent(include $vendorEventFile);
  531. }
  532. if (is_file($appPath . 'event.php')) {
  533. $this->loadEvent(include $appPath . 'event.php');
  534. }
  535. if (is_file($appPath . 'service.php')) {
  536. $services = include $appPath . 'service.php';
  537. foreach ($services as $service) {
  538. $this->register($service);
  539. }
  540. }
  541. }
  542. /**
  543. * 调试模式设置
  544. * @access protected
  545. * @return void
  546. */
  547. protected function debugModeInit(): void
  548. {
  549. // 应用调试模式
  550. if (!$this->appDebug) {
  551. $this->appDebug = $this->env->get('app_debug') ? true : false;
  552. if (!$this->appDebug) {
  553. ini_set('display_errors', 'Off');
  554. }
  555. }
  556. if (!defined('APP_DEBUG')) {
  557. if ($this->appDebug) {
  558. define('APP_DEBUG', true);
  559. } else {
  560. define('APP_DEBUG', false);
  561. }
  562. }
  563. if (!$this->runningInConsole()) {
  564. //重新申请一块比较大的buffer
  565. if (ob_get_level() > 0) {
  566. $output = ob_get_clean();
  567. }
  568. ob_start();
  569. if (!empty($output)) {
  570. echo $output;
  571. }
  572. }
  573. }
  574. /**
  575. * 注册应用事件
  576. * @access protected
  577. * @param array $event 事件数据
  578. * @return void
  579. */
  580. public function loadEvent(array $event): void
  581. {
  582. if (isset($event['bind'])) {
  583. $this->event->bind($event['bind']);
  584. }
  585. if (isset($event['listen'])) {
  586. $this->event->listenEvents($event['listen']);
  587. }
  588. if (isset($event['subscribe'])) {
  589. $this->event->subscribe($event['subscribe']);
  590. }
  591. }
  592. /**
  593. * 解析应用类的类名
  594. * @access public
  595. * @param string $layer 层名 controller model ...
  596. * @param string $name 类名
  597. * @return string
  598. */
  599. public function parseClass(string $layer, string $name): string
  600. {
  601. $name = str_replace(['/', '.'], '\\', $name);
  602. $array = explode('\\', $name);
  603. $class = Str::studly(array_pop($array));
  604. $path = $array ? implode('\\', $array) . '\\' : '';
  605. return $this->namespace . '\\' . $layer . '\\' . $path . $class;
  606. }
  607. /**
  608. * 是否运行在命令行下
  609. * @return bool
  610. */
  611. public function runningInConsole()
  612. {
  613. return php_sapi_name() === 'cli' || php_sapi_name() === 'phpdbg';
  614. }
  615. /**
  616. * 获取应用根目录
  617. * @access protected
  618. * @return string
  619. */
  620. protected function getDefaultRootPath(): string
  621. {
  622. return dirname($this->thinkPath, 4) . DIRECTORY_SEPARATOR;
  623. }
  624. }