Completed
Push — 6.0 ( a6720b...55264d )
by liu
02:26
created

App   F

Complexity

Total Complexity 62

Size/Duplication

Total Lines 597
Duplicated Lines 0 %

Test Coverage

Coverage 90.85%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 154
c 4
b 0
f 0
dl 0
loc 597
ccs 139
cts 153
cp 0.9085
rs 3.44
wmc 62

32 Methods

Rating   Name   Duplication   Size   Complexity  
A debug() 0 4 1
A getBeginMem() 0 3 1
A getConfigExt() 0 3 1
A setRuntimePath() 0 3 1
A getAppPath() 0 3 1
A bootService() 0 4 2
A register() 0 21 6
A isDebug() 0 3 1
A __construct() 0 15 3
A getNamespace() 0 3 1
A getRuntimePath() 0 3 1
A getConfigPath() 0 3 1
A getThinkPath() 0 3 1
A getBasePath() 0 3 1
A setAppPath() 0 3 1
A getBeginTime() 0 3 1
A version() 0 3 1
A getRootPath() 0 3 1
A setNamespace() 0 4 1
A setEnvName() 0 4 1
A getService() 0 6 2
A runningInConsole() 0 3 2
A initialized() 0 3 1
A loadEnv() 0 7 3
A initialize() 0 35 2
A boot() 0 4 1
A parseClass() 0 8 2
B load() 0 30 7
A loadEvent() 0 12 4
A loadLangPack() 0 15 3
A getDefaultRootPath() 0 3 1
A debugModeInit() 0 16 6

How to fix   Complexity   

Complex Class

Complex classes like App often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use App, and based on these observations, apply Extract Interface, too.

1
<?php
2
// +----------------------------------------------------------------------
3
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
4
// +----------------------------------------------------------------------
5
// | Copyright (c) 2006~2021 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 think\event\AppInit;
16
use think\helper\Str;
17
use think\initializer\BootService;
18
use think\initializer\Error;
19
use think\initializer\RegisterService;
20
21
/**
22
 * App 基础类
23
 * @property Route      $route
24
 * @property Config     $config
25
 * @property Cache      $cache
26
 * @property Request    $request
27
 * @property Http       $http
28
 * @property Console    $console
29
 * @property Env        $env
30
 * @property Event      $event
31
 * @property Middleware $middleware
32
 * @property Log        $log
33
 * @property Lang       $lang
34
 * @property Db         $db
35
 * @property Cookie     $cookie
36
 * @property Session    $session
37
 * @property Validate   $validate
38
 * @property Filesystem $filesystem
39
 */
40
class App extends Container
41
{
42
    const VERSION = '6.0.7';
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
        'filesystem'              => Filesystem::class,
156
        'think\DbManager'         => Db::class,
157
        'think\LogManager'        => Log::class,
158
        'think\CacheManager'      => Cache::class,
159
160
        // 接口依赖注入
161
        'Psr\Log\LoggerInterface' => Log::class,
162
    ];
163
164
    /**
165
     * 架构方法
166
     * @access public
167
     * @param string $rootPath 应用根目录
168
     */
169 30
    public function __construct(string $rootPath = '')
170
    {
171 30
        $this->thinkPath   = dirname(__DIR__) . DIRECTORY_SEPARATOR;
172 30
        $this->rootPath    = $rootPath ? rtrim($rootPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $this->getDefaultRootPath();
173 30
        $this->appPath     = $this->rootPath . 'app' . DIRECTORY_SEPARATOR;
174 30
        $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR;
175
176 30
        if (is_file($this->appPath . 'provider.php')) {
177 3
            $this->bind(include $this->appPath . 'provider.php');
178
        }
179
180 30
        static::setInstance($this);
181
182 30
        $this->instance('app', $this);
183 30
        $this->instance('think\Container', $this);
184 30
    }
185
186
    /**
187
     * 注册服务
188
     * @access public
189
     * @param Service|string $service 服务
190
     * @param bool           $force   强制重新注册
191
     * @return Service|null
192
     */
193 3
    public function register($service, bool $force = false)
194
    {
195 3
        $registered = $this->getService($service);
196
197 3
        if ($registered && !$force) {
198 3
            return $registered;
199
        }
200
201 3
        if (is_string($service)) {
202 3
            $service = new $service($this);
203
        }
204
205 3
        if (method_exists($service, 'register')) {
206 3
            $service->register();
207
        }
208
209 3
        if (property_exists($service, 'bind')) {
210 3
            $this->bind($service->bind);
211
        }
212
213 3
        $this->services[] = $service;
214 3
    }
215
216
    /**
217
     * 执行服务
218
     * @access public
219
     * @param Service $service 服务
220
     * @return mixed
221
     */
222 3
    public function bootService($service)
223
    {
224 3
        if (method_exists($service, 'boot')) {
225 3
            return $this->invoke([$service, 'boot']);
226
        }
227 3
    }
228
229
    /**
230
     * 获取服务
231
     * @param string|Service $service
232
     * @return Service|null
233
     */
234 3
    public function getService($service)
235
    {
236 3
        $name = is_string($service) ? $service : get_class($service);
237
        return array_values(array_filter($this->services, function ($value) use ($name) {
238 3
            return $value instanceof $name;
239 3
        }, ARRAY_FILTER_USE_BOTH))[0] ?? null;
240
    }
241
242
    /**
243
     * 开启应用调试模式
244
     * @access public
245
     * @param bool $debug 开启应用调试模式
246
     * @return $this
247
     */
248 6
    public function debug(bool $debug = true)
249
    {
250 6
        $this->appDebug = $debug;
251 6
        return $this;
252
    }
253
254
    /**
255
     * 是否为调试模式
256
     * @access public
257
     * @return bool
258
     */
259 3
    public function isDebug(): bool
260
    {
261 3
        return $this->appDebug;
262
    }
263
264
    /**
265
     * 设置应用命名空间
266
     * @access public
267
     * @param string $namespace 应用命名空间
268
     * @return $this
269
     */
270 6
    public function setNamespace(string $namespace)
271
    {
272 6
        $this->namespace = $namespace;
273 6
        return $this;
274
    }
275
276
    /**
277
     * 获取应用类库命名空间
278
     * @access public
279
     * @return string
280
     */
281 3
    public function getNamespace(): string
282
    {
283 3
        return $this->namespace;
284
    }
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
     * 获取框架版本
300
     * @access public
301
     * @return string
302
     */
303 3
    public function version(): string
304
    {
305 3
        return static::VERSION;
306
    }
307
308
    /**
309
     * 获取应用根目录
310
     * @access public
311
     * @return string
312
     */
313 12
    public function getRootPath(): string
314
    {
315 12
        return $this->rootPath;
316
    }
317
318
    /**
319
     * 获取应用基础目录
320
     * @access public
321
     * @return string
322
     */
323 3
    public function getBasePath(): string
324
    {
325 3
        return $this->rootPath . 'app' . DIRECTORY_SEPARATOR;
326
    }
327
328
    /**
329
     * 获取当前应用目录
330
     * @access public
331
     * @return string
332
     */
333 6
    public function getAppPath(): string
334
    {
335 6
        return $this->appPath;
336
    }
337
338
    /**
339
     * 设置应用目录
340
     * @param string $path 应用目录
341
     */
342 3
    public function setAppPath(string $path)
343
    {
344 3
        $this->appPath = $path;
345 3
    }
346
347
    /**
348
     * 获取应用运行时目录
349
     * @access public
350
     * @return string
351
     */
352 42
    public function getRuntimePath(): string
353
    {
354 42
        return $this->runtimePath;
355
    }
356
357
    /**
358
     * 设置runtime目录
359
     * @param string $path 定义目录
360
     */
361 3
    public function setRuntimePath(string $path): void
362
    {
363 3
        $this->runtimePath = $path;
364 3
    }
365
366
    /**
367
     * 获取核心框架目录
368
     * @access public
369
     * @return string
370
     */
371 3
    public function getThinkPath(): string
372
    {
373 3
        return $this->thinkPath;
374
    }
375
376
    /**
377
     * 获取应用配置目录
378
     * @access public
379
     * @return string
380
     */
381 42
    public function getConfigPath(): string
382
    {
383 42
        return $this->rootPath . 'config' . DIRECTORY_SEPARATOR;
384
    }
385
386
    /**
387
     * 获取配置后缀
388
     * @access public
389
     * @return string
390
     */
391 39
    public function getConfigExt(): string
392
    {
393 39
        return $this->configExt;
394
    }
395
396
    /**
397
     * 获取应用开启时间
398
     * @access public
399
     * @return float
400
     */
401 3
    public function getBeginTime(): float
402
    {
403 3
        return $this->beginTime;
404
    }
405
406
    /**
407
     * 获取应用初始内存占用
408
     * @access public
409
     * @return integer
410
     */
411 3
    public function getBeginMem(): int
412
    {
413 3
        return $this->beginMem;
414
    }
415
416
    /**
417
     * 加载环境变量定义
418
     * @access public
419
     * @param string $envName 环境标识
420
     * @return void
421
     */
422 3
    public function loadEnv(string $envName = ''): void
423
    {
424
        // 加载环境变量
425 3
        $envFile = $envName ? $this->rootPath . '.env.' . $envName : $this->rootPath . '.env';
426
427 3
        if (is_file($envFile)) {
428 3
            $this->env->load($envFile);
429
        }
430 3
    }
431
432
    /**
433
     * 初始化应用
434
     * @access public
435
     * @return $this
436
     */
437 3
    public function initialize()
438
    {
439 3
        $this->initialized = true;
440
441 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...
442 3
        $this->beginMem  = memory_get_usage();
443
444 3
        $this->loadEnv($this->envName);
445
446 3
        $this->configExt = $this->env->get('config_ext', '.php');
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->env->get('config_ext', '.php') can also be of type boolean. However, the property $configExt is declared as type string. 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...
447
448 3
        $this->debugModeInit();
449
450
        // 加载全局初始化文件
451 3
        $this->load();
452
453
        // 加载框架默认语言包
454 3
        $langSet = $this->lang->defaultLangSet();
455
456 3
        $this->lang->load($this->thinkPath . 'lang' . DIRECTORY_SEPARATOR . $langSet . '.php');
457
458
        // 加载应用默认语言包
459 3
        $this->loadLangPack($langSet);
460
461
        // 监听AppInit
462 3
        $this->event->trigger(AppInit::class);
463
464 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

464
        date_default_timezone_set(/** @scrutinizer ignore-type */ $this->config->get('app.default_timezone', 'Asia/Shanghai'));
Loading history...
465
466
        // 初始化
467 3
        foreach ($this->initializers as $initializer) {
468 3
            $this->make($initializer)->init($this);
469
        }
470
471 3
        return $this;
472
    }
473
474
    /**
475
     * 是否初始化过
476
     * @return bool
477
     */
478 6
    public function initialized()
479
    {
480 6
        return $this->initialized;
481
    }
482
483
    /**
484
     * 加载语言包
485
     * @param string $langset 语言
486
     * @return void
487
     */
488 3
    public function loadLangPack($langset)
489
    {
490 3
        if (empty($langset)) {
491
            return;
492
        }
493
494
        // 加载系统语言包
495 3
        $files = glob($this->appPath . 'lang' . DIRECTORY_SEPARATOR . $langset . '.*');
496 3
        $this->lang->load($files);
497
498
        // 加载扩展(自定义)语言包
499 3
        $list = $this->config->get('lang.extend_list', []);
500
501 3
        if (isset($list[$langset])) {
502
            $this->lang->load($list[$langset]);
503
        }
504 3
    }
505
506
    /**
507
     * 引导应用
508
     * @access public
509
     * @return void
510
     */
511 3
    public function boot(): void
512
    {
513
        array_walk($this->services, function ($service) {
514 3
            $this->bootService($service);
515 3
        });
516 3
    }
517
518
    /**
519
     * 加载应用文件和配置
520
     * @access protected
521
     * @return void
522
     */
523 3
    protected function load(): void
524
    {
525 3
        $appPath = $this->getAppPath();
526
527 3
        if (is_file($appPath . 'common.php')) {
528 3
            include_once $appPath . 'common.php';
529
        }
530
531 3
        include_once $this->thinkPath . 'helper.php';
532
533 3
        $configPath = $this->getConfigPath();
534
535 3
        $files = [];
536
537 3
        if (is_dir($configPath)) {
538 3
            $files = glob($configPath . '*' . $this->configExt);
539
        }
540
541 3
        foreach ($files as $file) {
542
            $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

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