Passed
Pull Request — 8.0 (#3055)
by wj
02:23
created

App::load()   B

Complexity

Conditions 9
Paths 24

Size

Total Lines 30
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 9.4453

Importance

Changes 0
Metric Value
cc 9
eloc 16
c 0
b 0
f 0
nc 24
nop 0
dl 0
loc 30
ccs 14
cts 17
cp 0.8235
crap 9.4453
rs 8.0555
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 $baseEnvName = '';
55
56
    /**
57
     * 环境变量标识
58
     * @var string
59
     */
60
    protected $envName = '';
61
62
    /**
63
     * 应用开始时间
64
     * @var float
65
     */
66
    protected $beginTime;
67
68
    /**
69
     * 应用内存初始占用
70
     * @var integer
71
     */
72
    protected $beginMem;
73
74
    /**
75
     * 当前应用类库命名空间
76
     * @var string
77
     */
78
    protected $namespace = 'app';
79
80
    /**
81
     * 应用根目录
82
     * @var string
83
     */
84
    protected $rootPath = '';
85
86
    /**
87
     * 框架目录
88
     * @var string
89
     */
90
    protected $thinkPath = '';
91
92
    /**
93
     * 应用目录
94
     * @var string
95
     */
96
    protected $appPath = '';
97
98
    /**
99
     * Runtime目录
100
     * @var string
101
     */
102
    protected $runtimePath = '';
103
104
    /**
105
     * 路由定义目录
106
     * @var string
107
     */
108
    protected $routePath = '';
109
110
    /**
111
     * 配置后缀
112
     * @var string
113
     */
114
    protected $configExt = '.php';
115
116
    /**
117
     * 应用初始化器
118
     * @var array
119
     */
120
    protected $initializers = [
121
        Error::class,
122
        RegisterService::class,
123
        BootService::class,
124
    ];
125
126
    /**
127
     * 注册的系统服务
128
     * @var array
129
     */
130
    protected $services = [];
131
132
    /**
133
     * 初始化
134
     * @var bool
135
     */
136
    protected $initialized = false;
137
138
    /**
139
     * 容器绑定标识
140
     * @var array
141
     */
142
    protected $bind = [
143
        'app'                     => App::class,
144
        'cache'                   => Cache::class,
145
        'config'                  => Config::class,
146
        'console'                 => Console::class,
147
        'cookie'                  => Cookie::class,
148
        'db'                      => Db::class,
149
        'env'                     => Env::class,
150
        'event'                   => Event::class,
151
        'http'                    => Http::class,
152
        'lang'                    => Lang::class,
153
        'log'                     => Log::class,
154
        'middleware'              => Middleware::class,
155
        'request'                 => Request::class,
156
        'response'                => Response::class,
157
        'route'                   => Route::class,
158
        'session'                 => Session::class,
159
        'validate'                => Validate::class,
160
        'view'                    => View::class,
161
        'think\DbManager'         => Db::class,
162
        'think\LogManager'        => Log::class,
163
        'think\CacheManager'      => Cache::class,
164
165
        // 接口依赖注入
166
        'Psr\Log\LoggerInterface' => Log::class,
167
    ];
168
169
    /**
170
     * 架构方法
171
     * @access public
172
     * @param string $rootPath 应用根目录
173
     */
174 30
    public function __construct(string $rootPath = '')
175
    {
176 30
        $this->thinkPath   = dirname(__DIR__) . DIRECTORY_SEPARATOR;
177 30
        $this->rootPath    = $rootPath ? rtrim($rootPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $this->getDefaultRootPath();
178 30
        $this->appPath     = $this->rootPath . 'app' . DIRECTORY_SEPARATOR;
179 30
        $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR;
180
181 30
        if (is_file($this->appPath . 'provider.php')) {
182 3
            $this->bind(include $this->appPath . 'provider.php');
183
        }
184
185 30
        static::setInstance($this);
186
187 30
        $this->instance('app', $this);
188 30
        $this->instance('think\Container', $this);
189
    }
190
191
    /**
192
     * 注册服务
193
     * @access public
194
     * @param Service|string $service 服务
195
     * @param bool           $force   强制重新注册
196
     * @return Service|null
197
     */
198 3
    public function register(Service | string $service, bool $force = false)
199
    {
200 3
        $registered = $this->getService($service);
201
202 3
        if ($registered && !$force) {
203 3
            return $registered;
204
        }
205
206 3
        if (is_string($service)) {
207
            $service = new $service($this);
208
        }
209
210 3
        if (method_exists($service, 'register')) {
211 3
            $service->register();
212
        }
213
214 3
        if (property_exists($service, 'bind')) {
215 3
            $this->bind($service->bind);
216
        }
217
218 3
        $this->services[] = $service;
219
    }
220
221
    /**
222
     * 执行服务
223
     * @access public
224
     * @param Service $service 服务
225
     * @return mixed
226
     */
227 3
    public function bootService(Service $service)
228
    {
229 3
        if (method_exists($service, 'boot')) {
230 3
            return $this->invoke([$service, 'boot']);
231
        }
232
    }
233
234
    /**
235
     * 获取服务
236
     * @param string|Service $service
237
     * @return Service|null
238
     */
239 3
    public function getService(Service | string $service): ?Service
240
    {
241 3
        $name = is_string($service) ? $service : $service::class;
242 3
        return array_values(array_filter($this->services, function ($value) use ($name) {
243 3
            return $value instanceof $name;
244 3
        }, ARRAY_FILTER_USE_BOTH))[0] ?? null;
245
    }
246
247
    /**
248
     * 开启应用调试模式
249
     * @access public
250
     * @param bool $debug 开启应用调试模式
251
     * @return $this
252
     */
253 6
    public function debug(bool $debug = true)
254
    {
255 6
        $this->appDebug = $debug;
256 6
        return $this;
257
    }
258
259
    /**
260
     * 是否为调试模式
261
     * @access public
262
     * @return bool
263
     */
264 3
    public function isDebug(): bool
265
    {
266 3
        return $this->appDebug;
267
    }
268
269
    /**
270
     * 设置应用命名空间
271
     * @access public
272
     * @param string $namespace 应用命名空间
273
     * @return $this
274
     */
275 6
    public function setNamespace(string $namespace)
276
    {
277 6
        $this->namespace = $namespace;
278 6
        return $this;
279
    }
280
281
    /**
282
     * 获取应用类库命名空间
283
     * @access public
284
     * @return string
285
     */
286 3
    public function getNamespace(): string
287
    {
288 3
        return $this->namespace;
289
    }
290
291
    /**
292
     * 设置公共环境变量标识
293
     * @access public
294
     * @param string $name 环境标识
295
     * @return $this
296
     */
297
    public function setBaseEnvName(string $name)
298
    {
299
        $this->baseEnvName = $name;
300
        return $this;
301
    }
302
303
    /**
304
     * 设置环境变量标识
305
     * @access public
306
     * @param string $name 环境标识
307
     * @return $this
308
     */
309
    public function setEnvName(string $name)
310
    {
311
        $this->envName = $name;
312
        return $this;
313
    }
314
315
    /**
316
     * 获取框架版本
317
     * @access public
318
     * @return string
319
     */
320
    public function version(): string
321
    {
322
        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

322
        return ltrim(/** @scrutinizer ignore-type */ InstalledVersions::getPrettyVersion('topthink/framework'), 'v');
Loading history...
323
    }
324
325
    /**
326
     * 获取应用根目录
327
     * @access public
328
     * @return string
329
     */
330 12
    public function getRootPath(): string
331
    {
332 12
        return $this->rootPath;
333
    }
334
335
    /**
336
     * 获取应用基础目录
337
     * @access public
338
     * @return string
339
     */
340 3
    public function getBasePath(): string
341
    {
342 3
        return $this->rootPath . 'app' . DIRECTORY_SEPARATOR;
343
    }
344
345
    /**
346
     * 获取当前应用目录
347
     * @access public
348
     * @return string
349
     */
350 6
    public function getAppPath(): string
351
    {
352 6
        return $this->appPath;
353
    }
354
355
    /**
356
     * 设置应用目录
357
     * @param string $path 应用目录
358
     */
359 3
    public function setAppPath(string $path)
360
    {
361 3
        $this->appPath = $path;
362
    }
363
364
    /**
365
     * 获取应用运行时目录
366
     * @access public
367
     * @return string
368
     */
369 36
    public function getRuntimePath(): string
370
    {
371 36
        return $this->runtimePath;
372
    }
373
374
    /**
375
     * 设置runtime目录
376
     * @param string $path 定义目录
377
     */
378 3
    public function setRuntimePath(string $path): void
379
    {
380 3
        $this->runtimePath = $path;
381
    }
382
383
    /**
384
     * 获取核心框架目录
385
     * @access public
386
     * @return string
387
     */
388 6
    public function getThinkPath(): string
389
    {
390 6
        return $this->thinkPath;
391
    }
392
393
    /**
394
     * 获取应用配置目录
395
     * @access public
396
     * @return string
397
     */
398 39
    public function getConfigPath(): string
399
    {
400 39
        return $this->rootPath . 'config' . DIRECTORY_SEPARATOR;
401
    }
402
403
    /**
404
     * 获取配置后缀
405
     * @access public
406
     * @return string
407
     */
408 36
    public function getConfigExt(): string
409
    {
410 36
        return $this->configExt;
411
    }
412
413
    /**
414
     * 获取应用开启时间
415
     * @access public
416
     * @return float
417
     */
418 3
    public function getBeginTime(): float
419
    {
420 3
        return $this->beginTime;
421
    }
422
423
    /**
424
     * 获取应用初始内存占用
425
     * @access public
426
     * @return integer
427
     */
428 3
    public function getBeginMem(): int
429
    {
430 3
        return $this->beginMem;
431
    }
432
433
    /**
434
     * 加载环境变量定义
435
     * @access public
436
     * @param string $envName 环境标识
437
     * @return void
438
     */
439 3
    public function loadEnv(string $envName = ''): void
440
    {
441
        // 加载环境变量
442 3
        $envFile = $envName ? $this->rootPath . '.env.' . $envName : $this->rootPath . '.env';
443
444 3
        if (is_file($envFile)) {
445 3
            $this->env->load($envFile);
446
        }
447
    }
448
449
    /**
450
     * 初始化应用
451
     * @access public
452
     * @return $this
453
     */
454 3
    public function initialize()
455
    {
456 3
        $this->initialized = true;
457
458 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...
459 3
        $this->beginMem  = memory_get_usage();
460
461
        // 加载环境变量
462 3
        if ($this->baseEnvName) {
463
            $this->loadEnv($this->baseEnvName);
464
        }
465
466 3
        $this->envName = $this->envName ?: (string) $this->env->get('env_name', '');
467 3
        $this->loadEnv($this->envName);
468
469 3
        $this->configExt = $this->env->get('config_ext', '.php');
470
471 3
        $this->debugModeInit();
472
473
        // 加载全局初始化文件
474 3
        $this->load();
475
476
        // 加载应用默认语言包
477 3
        $this->loadLangPack();
478
479
        // 监听AppInit
480 3
        $this->event->trigger(AppInit::class);
481
482 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

482
        date_default_timezone_set(/** @scrutinizer ignore-type */ $this->config->get('app.default_timezone', 'Asia/Shanghai'));
Loading history...
483
484
        // 初始化
485 3
        foreach ($this->initializers as $initializer) {
486 3
            $this->make($initializer)->init($this);
487
        }
488
489 3
        return $this;
490
    }
491
492
    /**
493
     * 是否初始化过
494
     * @return bool
495
     */
496 6
    public function initialized()
497
    {
498 6
        return $this->initialized;
499
    }
500
501
    /**
502
     * 加载语言包
503
     * @return void
504
     */
505 3
    public function loadLangPack(): void
506
    {
507
        // 加载默认语言包
508 3
        $langSet = $this->lang->defaultLangSet();
509 3
        $this->lang->switchLangSet($langSet);
510
    }
511
512
    /**
513
     * 引导应用
514
     * @access public
515
     * @return void
516
     */
517 3
    public function boot(): void
518
    {
519 3
        array_walk($this->services, function ($service) {
520 3
            $this->bootService($service);
521 3
        });
522
    }
523
524
    /**
525
     * 加载应用文件和配置
526
     * @access protected
527
     * @return void
528
     */
529 3
    protected function load(): void
530
    {
531 3
        $appPath = $this->getAppPath();
532
533 3
        if (is_file($appPath . 'common.php')) {
534 3
            include_once $appPath . 'common.php';
535
        }
536
537 3
        include_once $this->thinkPath . 'helper.php';
538
539 3
        $configPath = $this->getConfigPath();
540
541 3
        if (is_dir($configPath)) {
542 3
            foreach (scandir($configPath) as $name) {
543 3
                if (!str_ends_with($name, $this->configExt) || !is_file($configPath . $name)) {
544 3
                    continue;
545
                }
546
547 3
                $this->config->load($configPath . $name, pathinfo($name, PATHINFO_FILENAME));
0 ignored issues
show
Bug introduced by
It seems like pathinfo($name, 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

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