Passed
Push — 8.0 ( e7e471...f574a5 )
by liu
02:45
created

App::debugModeInit()   B

Complexity

Conditions 7
Paths 30

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 14.9539

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 10
c 1
b 0
f 0
nc 30
nop 0
dl 0
loc 19
ccs 5
cts 11
cp 0.4545
crap 14.9539
rs 8.8333
1
<?php
2
// +----------------------------------------------------------------------
3
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
4
// +----------------------------------------------------------------------
5
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
6
// +----------------------------------------------------------------------
7
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
// +----------------------------------------------------------------------
9
// | Author: liu21st <[email protected]>
10
// +----------------------------------------------------------------------
11
declare (strict_types = 1);
12
13
namespace think;
14
15
use Composer\InstalledVersions;
16
use think\event\AppInit;
17
use think\helper\Str;
18
use think\initializer\BootService;
19
use think\initializer\Error;
20
use think\initializer\RegisterService;
21
22
/**
23
 * App 基础类
24
 * @property Route      $route
25
 * @property Config     $config
26
 * @property Cache      $cache
27
 * @property Request    $request
28
 * @property Http       $http
29
 * @property Console    $console
30
 * @property Env        $env
31
 * @property Event      $event
32
 * @property Middleware $middleware
33
 * @property Log        $log
34
 * @property Lang       $lang
35
 * @property Db         $db
36
 * @property Cookie     $cookie
37
 * @property Session    $session
38
 * @property Validate   $validate
39
 */
40
class App extends Container
41
{
42
    const VERSION = '8.0.0';
43
44
    /**
45
     * 应用调试模式
46
     * @var bool
47
     */
48
    protected $appDebug = false;
49
50
    /**
51
     * 环境变量标识
52
     * @var string
53
     */
54
    protected $envName = '';
55
56
    /**
57
     * 应用开始时间
58
     * @var float
59
     */
60
    protected $beginTime;
61
62
    /**
63
     * 应用内存初始占用
64
     * @var integer
65
     */
66
    protected $beginMem;
67
68
    /**
69
     * 当前应用类库命名空间
70
     * @var string
71
     */
72
    protected $namespace = 'app';
73
74
    /**
75
     * 应用根目录
76
     * @var string
77
     */
78
    protected $rootPath = '';
79
80
    /**
81
     * 框架目录
82
     * @var string
83
     */
84
    protected $thinkPath = '';
85
86
    /**
87
     * 应用目录
88
     * @var string
89
     */
90
    protected $appPath = '';
91
92
    /**
93
     * Runtime目录
94
     * @var string
95
     */
96
    protected $runtimePath = '';
97
98
    /**
99
     * 路由定义目录
100
     * @var string
101
     */
102
    protected $routePath = '';
103
104
    /**
105
     * 配置后缀
106
     * @var string
107
     */
108
    protected $configExt = '.php';
109
110
    /**
111
     * 应用初始化器
112
     * @var array
113
     */
114
    protected $initializers = [
115
        Error::class,
116
        RegisterService::class,
117
        BootService::class,
118
    ];
119
120
    /**
121
     * 注册的系统服务
122
     * @var array
123
     */
124
    protected $services = [];
125
126
    /**
127
     * 初始化
128
     * @var bool
129
     */
130
    protected $initialized = false;
131
132
    /**
133
     * 容器绑定标识
134
     * @var array
135
     */
136
    protected $bind = [
137
        'app'                     => App::class,
138
        'cache'                   => Cache::class,
139
        'config'                  => Config::class,
140
        'console'                 => Console::class,
141
        'cookie'                  => Cookie::class,
142
        'db'                      => Db::class,
143
        'env'                     => Env::class,
144
        'event'                   => Event::class,
145
        'http'                    => Http::class,
146
        'lang'                    => Lang::class,
147
        'log'                     => Log::class,
148
        'middleware'              => Middleware::class,
149
        'request'                 => Request::class,
150
        'response'                => Response::class,
151
        'route'                   => Route::class,
152
        'session'                 => Session::class,
153
        'validate'                => Validate::class,
154
        'view'                    => View::class,
155
        'think\DbManager'         => Db::class,
156
        'think\LogManager'        => Log::class,
157
        'think\CacheManager'      => Cache::class,
158
159
        // 接口依赖注入
160
        'Psr\Log\LoggerInterface' => Log::class,
161
    ];
162
163
    /**
164
     * 架构方法
165
     * @access public
166
     * @param string $rootPath 应用根目录
167
     */
168 30
    public function __construct(string $rootPath = '')
169
    {
170 30
        $this->thinkPath   = realpath(dirname(__DIR__)) . DIRECTORY_SEPARATOR;
171 30
        $this->rootPath    = $rootPath ? rtrim($rootPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $this->getDefaultRootPath();
172 30
        $this->appPath     = $this->rootPath . 'app' . DIRECTORY_SEPARATOR;
173 30
        $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR;
174
175 30
        if (is_file($this->appPath . 'provider.php')) {
176 3
            $this->bind(include $this->appPath . 'provider.php');
177
        }
178
179 30
        static::setInstance($this);
180
181 30
        $this->instance('app', $this);
182 30
        $this->instance('think\Container', $this);
183
    }
184
185
    /**
186
     * 注册服务
187
     * @access public
188
     * @param Service|string $service 服务
189
     * @param bool           $force   强制重新注册
190
     * @return Service|null
191
     */
192 3
    public function register(Service | string $service, bool $force = false)
193
    {
194 3
        $registered = $this->getService($service);
195
196 3
        if ($registered && !$force) {
197 3
            return $registered;
198
        }
199
200 3
        if (is_string($service)) {
201
            $service = new $service($this);
202
        }
203
204 3
        if (method_exists($service, 'register')) {
205 3
            $service->register();
206
        }
207
208 3
        if (property_exists($service, 'bind')) {
209 3
            $this->bind($service->bind);
210
        }
211
212 3
        $this->services[] = $service;
213
    }
214
215
    /**
216
     * 执行服务
217
     * @access public
218
     * @param Service $service 服务
219
     * @return mixed
220
     */
221 3
    public function bootService(Service $service)
222
    {
223 3
        if (method_exists($service, 'boot')) {
224 3
            return $this->invoke([$service, 'boot']);
225
        }
226
    }
227
228
    /**
229
     * 获取服务
230
     * @param string|Service $service
231
     * @return Service|null
232
     */
233 3
    public function getService(Service | string $service): ?Service
234
    {
235 3
        $name = is_string($service) ? $service : $service::class;
236 3
        return array_values(array_filter($this->services, function ($value) use ($name) {
237 3
            return $value instanceof $name;
238 3
        }, ARRAY_FILTER_USE_BOTH))[0] ?? null;
239
    }
240
241
    /**
242
     * 开启应用调试模式
243
     * @access public
244
     * @param bool $debug 开启应用调试模式
245
     * @return $this
246
     */
247 6
    public function debug(bool $debug = true)
248
    {
249 6
        $this->appDebug = $debug;
250 6
        return $this;
251
    }
252
253
    /**
254
     * 是否为调试模式
255
     * @access public
256
     * @return bool
257
     */
258 3
    public function isDebug(): bool
259
    {
260 3
        return $this->appDebug;
261
    }
262
263
    /**
264
     * 设置应用命名空间
265
     * @access public
266
     * @param string $namespace 应用命名空间
267
     * @return $this
268
     */
269 6
    public function setNamespace(string $namespace)
270
    {
271 6
        $this->namespace = $namespace;
272 6
        return $this;
273
    }
274
275
    /**
276
     * 获取应用类库命名空间
277
     * @access public
278
     * @return string
279
     */
280 3
    public function getNamespace(): string
281
    {
282 3
        return $this->namespace;
283
    }
284
285
    /**
286
     * 设置环境变量标识
287
     * @access public
288
     * @param string $name 环境标识
289
     * @return $this
290
     */
291
    public function setEnvName(string $name)
292
    {
293
        $this->envName = $name;
294
        return $this;
295
    }
296
297
    /**
298
     * 获取框架版本
299
     * @access public
300
     * @return string
301
     */
302
    public function version(): string
303
    {
304
        return ltrim(InstalledVersions::getPrettyVersion('topthink/framework'), 'v');
0 ignored issues
show
Bug introduced by
It seems like Composer\InstalledVersio...n('topthink/framework') can also be of type null; however, parameter $string of ltrim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

304
        return ltrim(/** @scrutinizer ignore-type */ InstalledVersions::getPrettyVersion('topthink/framework'), 'v');
Loading history...
305
    }
306
307
    /**
308
     * 获取应用根目录
309
     * @access public
310
     * @return string
311
     */
312 12
    public function getRootPath(): string
313
    {
314 12
        return $this->rootPath;
315
    }
316
317
    /**
318
     * 获取应用基础目录
319
     * @access public
320
     * @return string
321
     */
322 3
    public function getBasePath(): string
323
    {
324 3
        return $this->rootPath . 'app' . DIRECTORY_SEPARATOR;
325
    }
326
327
    /**
328
     * 获取当前应用目录
329
     * @access public
330
     * @return string
331
     */
332 6
    public function getAppPath(): string
333
    {
334 6
        return $this->appPath;
335
    }
336
337
    /**
338
     * 设置应用目录
339
     * @param string $path 应用目录
340
     */
341 3
    public function setAppPath(string $path)
342
    {
343 3
        $this->appPath = $path;
344
    }
345
346
    /**
347
     * 获取应用运行时目录
348
     * @access public
349
     * @return string
350
     */
351 36
    public function getRuntimePath(): string
352
    {
353 36
        return $this->runtimePath;
354
    }
355
356
    /**
357
     * 设置runtime目录
358
     * @param string $path 定义目录
359
     */
360 3
    public function setRuntimePath(string $path): void
361
    {
362 3
        $this->runtimePath = $path;
363
    }
364
365
    /**
366
     * 获取核心框架目录
367
     * @access public
368
     * @return string
369
     */
370 6
    public function getThinkPath(): string
371
    {
372 6
        return $this->thinkPath;
373
    }
374
375
    /**
376
     * 获取应用配置目录
377
     * @access public
378
     * @return string
379
     */
380 39
    public function getConfigPath(): string
381
    {
382 39
        return $this->rootPath . 'config' . DIRECTORY_SEPARATOR;
383
    }
384
385
    /**
386
     * 获取配置后缀
387
     * @access public
388
     * @return string
389
     */
390 36
    public function getConfigExt(): string
391
    {
392 36
        return $this->configExt;
393
    }
394
395
    /**
396
     * 获取应用开启时间
397
     * @access public
398
     * @return float
399
     */
400 3
    public function getBeginTime(): float
401
    {
402 3
        return $this->beginTime;
403
    }
404
405
    /**
406
     * 获取应用初始内存占用
407
     * @access public
408
     * @return integer
409
     */
410 3
    public function getBeginMem(): int
411
    {
412 3
        return $this->beginMem;
413
    }
414
415
    /**
416
     * 加载环境变量定义
417
     * @access public
418
     * @param string $envName 环境标识
419
     * @return void
420
     */
421 3
    public function loadEnv(string $envName = ''): void
422
    {
423
        // 加载环境变量
424 3
        $envFile = $envName ? $this->rootPath . '.env.' . $envName : $this->rootPath . '.env';
425
426 3
        if (is_file($envFile)) {
427 3
            $this->env->load($envFile);
428
        }
429
    }
430
431
    /**
432
     * 初始化应用
433
     * @access public
434
     * @return $this
435
     */
436 3
    public function initialize()
437
    {
438 3
        $this->initialized = true;
439
440 3
        $this->beginTime = microtime(true);
0 ignored issues
show
Documentation Bug introduced by
It seems like microtime(true) can also be of type string. However, the property $beginTime is declared as type double. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
441 3
        $this->beginMem  = memory_get_usage();
442
443 3
        $this->loadEnv($this->envName);
444
445 3
        $this->configExt = $this->env->get('config_ext', '.php');
446
447 3
        $this->debugModeInit();
448
449
        // 加载全局初始化文件
450 3
        $this->load();
451
452
        // 加载应用默认语言包
453 3
        $this->loadLangPack();
454
455
        // 监听AppInit
456 3
        $this->event->trigger(AppInit::class);
457
458 3
        date_default_timezone_set($this->config->get('app.default_timezone', 'Asia/Shanghai'));
0 ignored issues
show
Bug introduced by
It seems like $this->config->get('app....zone', 'Asia/Shanghai') can also be of type array; however, parameter $timezoneId of date_default_timezone_set() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

458
        date_default_timezone_set(/** @scrutinizer ignore-type */ $this->config->get('app.default_timezone', 'Asia/Shanghai'));
Loading history...
459
460
        // 初始化
461 3
        foreach ($this->initializers as $initializer) {
462 3
            $this->make($initializer)->init($this);
463
        }
464
465 3
        return $this;
466
    }
467
468
    /**
469
     * 是否初始化过
470
     * @return bool
471
     */
472 6
    public function initialized()
473
    {
474 6
        return $this->initialized;
475
    }
476
477
    /**
478
     * 加载语言包
479
     * @return void
480
     */
481 3
    public function loadLangPack(): void
482
    {
483
        // 加载默认语言包
484 3
        $langSet = $this->lang->defaultLangSet();
485 3
        $this->lang->switchLangSet($langSet);
486
    }
487
488
    /**
489
     * 引导应用
490
     * @access public
491
     * @return void
492
     */
493 3
    public function boot(): void
494
    {
495 3
        array_walk($this->services, function ($service) {
496 3
            $this->bootService($service);
497 3
        });
498
    }
499
500
    /**
501
     * 加载应用文件和配置
502
     * @access protected
503
     * @return void
504
     */
505 3
    protected function load(): void
506
    {
507 3
        $appPath = $this->getAppPath();
508
509 3
        if (is_file($appPath . 'common.php')) {
510 3
            include_once $appPath . 'common.php';
511
        }
512
513 3
        include_once $this->thinkPath . 'helper.php';
514
515 3
        $configPath = $this->getConfigPath();
516
517 3
        $files = [];
518
519 3
        if (is_dir($configPath)) {
520 3
            $files = glob($configPath . '*' . $this->configExt);
521
        }
522
523 3
        foreach ($files as $file) {
524
            $this->config->load($file, pathinfo($file, PATHINFO_FILENAME));
0 ignored issues
show
Bug introduced by
It seems like pathinfo($file, think\PATHINFO_FILENAME) can also be of type array; however, parameter $name of think\Config::load() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

524
            $this->config->load($file, /** @scrutinizer ignore-type */ pathinfo($file, PATHINFO_FILENAME));
Loading history...
525
        }
526
527 3
        if (is_file($appPath . 'event.php')) {
528 3
            $this->loadEvent(include $appPath . 'event.php');
529
        }
530
531 3
        if (is_file($appPath . 'service.php')) {
532
            $services = include $appPath . 'service.php';
533
            foreach ($services as $service) {
534
                $this->register($service);
535
            }
536
        }
537
    }
538
539
    /**
540
     * 调试模式设置
541
     * @access protected
542
     * @return void
543
     */
544 3
    protected function debugModeInit(): void
545
    {
546
        // 应用调试模式
547 3
        if (!$this->appDebug) {
548 3
            $this->appDebug = $this->env->get('app_debug') ? true : false;
549
        }
550
551 3
        if (!$this->appDebug) {
552
            ini_set('display_errors', 'Off');
553
        }
554
555 3
        if (!$this->runningInConsole()) {
556
            //重新申请一块比较大的buffer
557
            if (ob_get_level() > 0) {
558
                $output = ob_get_clean();
559
            }
560
            ob_start();
561
            if (!empty($output)) {
562
                echo $output;
563
            }
564
        }
565
    }
566
567
    /**
568
     * 注册应用事件
569
     * @access protected
570
     * @param array $event 事件数据
571
     * @return void
572
     */
573 3
    public function loadEvent(array $event): void
574
    {
575 3
        if (isset($event['bind'])) {
576 3
            $this->event->bind($event['bind']);
577
        }
578
579 3
        if (isset($event['listen'])) {
580 3
            $this->event->listenEvents($event['listen']);
581
        }
582
583 3
        if (isset($event['subscribe'])) {
584 3
            $this->event->subscribe($event['subscribe']);
585
        }
586
    }
587
588
    /**
589
     * 解析应用类的类名
590
     * @access public
591
     * @param string $layer 层名 controller model ...
592
     * @param string $name  类名
593
     * @return string
594
     */
595 6
    public function parseClass(string $layer, string $name): string
596
    {
597 6
        $name  = str_replace(['/', '.'], '\\', $name);
598 6
        $array = explode('\\', $name);
599 6
        $class = Str::studly(array_pop($array));
600 6
        $path  = $array ? implode('\\', $array) . '\\' : '';
601
602 6
        return $this->namespace . '\\' . $layer . '\\' . $path . $class;
603
    }
604
605
    /**
606
     * 是否运行在命令行下
607
     * @return bool
608
     */
609 3
    public function runningInConsole(): bool
610
    {
611 3
        return php_sapi_name() === 'cli' || php_sapi_name() === 'phpdbg';
612
    }
613
614
    /**
615
     * 获取应用根目录
616
     * @access protected
617
     * @return string
618
     */
619 30
    protected function getDefaultRootPath(): string
620
    {
621 30
        return dirname($this->thinkPath, 4) . DIRECTORY_SEPARATOR;
622
    }
623
}
624