Completed
Push — 6.0 ( 677f21...00c571 )
by liu
05:27
created

App::parseClass()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 2
dl 0
loc 8
ccs 6
cts 6
cp 1
crap 2
rs 10
c 0
b 0
f 0
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 <[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.0RC4';
43
44
    /**
45
     * 应用名称
46
     * @var string
47
     */
48
    protected $name;
49
50
    /**
51
     * 应用路径
52
     * @var string
53
     */
54
    protected $path;
55
56
    /**
57
     * 是否域名绑定应用
58
     * @var bool
59
     */
60
    protected $bindDomain = false;
61
62
    /**
63
     * 应用调试模式
64
     * @var bool
65
     */
66
    protected $appDebug = false;
67
68
    /**
69
     * 应用开始时间
70
     * @var float
71
     */
72
    protected $beginTime;
73
74
    /**
75
     * 应用内存初始占用
76
     * @var integer
77
     */
78
    protected $beginMem;
79
80
    /**
81
     * 当前应用类库命名空间
82
     * @var string
83
     */
84
    protected $namespace = 'app';
85
86
    /**
87
     * 应用根目录
88
     * @var string
89
     */
90
    protected $rootPath = '';
91
92
    /**
93
     * 框架目录
94
     * @var string
95
     */
96
    protected $thinkPath = '';
97
98
    /**
99
     * 应用目录
100
     * @var string
101
     */
102
    protected $appPath = '';
103
104
    /**
105
     * Runtime目录
106
     * @var string
107
     */
108
    protected $runtimePath = '';
109
110
    /**
111
     * 路由定义目录
112
     * @var string
113
     */
114
    protected $routePath = '';
115
116
    /**
117
     * 配置后缀
118
     * @var string
119
     */
120
    protected $configExt = '.php';
121
122
    /**
123
     * 应用初始化器
124
     * @var array
125
     */
126
    protected $initializers = [
127
        Error::class,
128
        RegisterService::class,
129
        BootService::class,
130
    ];
131
132
    /**
133
     * 注册的系统服务
134
     * @var array
135
     */
136
    protected $services = [];
137
138
    /**
139
     * 初始化
140
     * @var bool
141
     */
142
    protected $initialized = false;
143
144
    /**
145
     * 容器绑定标识
146
     * @var array
147
     */
148
    protected $bind = [
149
        'app'                     => App::class,
150
        'cache'                   => Cache::class,
151
        'config'                  => Config::class,
152
        'console'                 => Console::class,
153
        'cookie'                  => Cookie::class,
154
        'db'                      => Db::class,
155
        'env'                     => Env::class,
156
        'event'                   => Event::class,
157
        'http'                    => Http::class,
158
        'lang'                    => Lang::class,
159
        'log'                     => Log::class,
160
        'middleware'              => Middleware::class,
161
        'request'                 => Request::class,
162
        'response'                => Response::class,
163
        'route'                   => Route::class,
164
        'session'                 => Session::class,
165
        'validate'                => Validate::class,
166
        'view'                    => View::class,
167
        'filesystem'              => Filesystem::class,
168
        'think\DbManager'         => Db::class,
169
        'think\LogManager'        => Log::class,
170
        'think\CacheManager'      => Cache::class,
171
172
        // 接口依赖注入
173
        'Psr\Log\LoggerInterface' => Log::class,
174
    ];
175
176
    /**
177
     * 架构方法
178
     * @access public
179
     * @param string $rootPath 应用根目录
180
     */
181 11
    public function __construct(string $rootPath = '')
182
    {
183 11
        $this->thinkPath   = dirname(__DIR__) . DIRECTORY_SEPARATOR;
184 11
        $this->rootPath    = $rootPath ? rtrim($rootPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $this->getDefaultRootPath();
185 11
        $this->appPath     = $this->rootPath . 'app' . DIRECTORY_SEPARATOR;
186 11
        $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR;
187 11
        $this->routePath   = $this->rootPath . 'route' . DIRECTORY_SEPARATOR;
188
189 11
        if (is_file($this->appPath . 'provider.php')) {
190 1
            $this->bind(include $this->appPath . 'provider.php');
191
        }
192
193 11
        static::setInstance($this);
194
195 11
        $this->instance('app', $this);
196 11
        $this->instance('think\Container', $this);
197 11
    }
198
199
    /**
200
     * 注册服务
201
     * @access public
202
     * @param Service|string $service 服务
203
     * @param bool           $force   强制重新注册
204
     * @return Service|null
205
     */
206 1
    public function register($service, bool $force = false)
207
    {
208 1
        $registered = $this->getService($service);
209
210 1
        if ($registered && !$force) {
211 1
            return $registered;
212
        }
213
214 1
        if (is_string($service)) {
215 1
            $service = new $service($this);
216
        }
217
218 1
        if (method_exists($service, 'register')) {
219 1
            $service->register();
220
        }
221
222 1
        if (property_exists($service, 'bind')) {
223 1
            $this->bind($service->bind);
224
        }
225
226 1
        $this->services[] = $service;
227 1
    }
228
229
    /**
230
     * 执行服务
231
     * @access public
232
     * @param Service $service 服务
233
     * @return mixed
234
     */
235 1
    public function bootService($service)
236
    {
237 1
        if (method_exists($service, 'boot')) {
238 1
            return $this->invoke([$service, 'boot']);
239
        }
240 1
    }
241
242
    /**
243
     * 获取服务
244
     * @param string|Service $service
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
245
     * @return Service|null
246
     */
247 1
    public function getService($service)
248
    {
249 1
        $name = is_string($service) ? $service : get_class($service);
250
        return array_values(array_filter($this->services, function ($value) use ($name) {
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
251 1
            return $value instanceof $name;
252 1
        }, ARRAY_FILTER_USE_BOTH))[0] ?? null;
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
253
    }
254
255
    /**
256
     * 开启应用调试模式
257
     * @access public
258
     * @param bool $debug 开启应用调试模式
259
     * @return $this
260
     */
261 2
    public function debug(bool $debug = true)
262
    {
263 2
        $this->appDebug = $debug;
264 2
        return $this;
265
    }
266
267
    /**
268
     * 是否为调试模式
269
     * @access public
270
     * @return bool
271
     */
272 1
    public function isDebug(): bool
273
    {
274 1
        return $this->appDebug;
275
    }
276
277
    /**
278
     * 设置应用名称
279
     * @access public
280
     * @param string $name 应用名称
281
     * @return $this
282
     */
283
    public function name(string $name)
284
    {
285
        $this->name = $name;
286
        return $this;
287
    }
288
289
    /**
290
     * 获取应用名称
291
     * @access public
292
     * @return string
293
     */
294
    public function getName(): string
295
    {
296
        return $this->name ?: '';
297
    }
298
299
    /**
300
     * 设置应用目录
301
     * @access public
302
     * @param string $path 应用目录
303
     * @return $this
304
     */
305
    public function path(string $path)
306
    {
307
        if (substr($path, -1) != DIRECTORY_SEPARATOR) {
308
            $path .= DIRECTORY_SEPARATOR;
309
        }
310
311
        $this->path = $path;
312
        return $this;
313
    }
314
315
    /**
316
     * 设置应用命名空间
317
     * @access public
318
     * @param string $namespace 应用命名空间
319
     * @return $this
320
     */
321 2
    public function setNamespace(string $namespace)
322
    {
323 2
        $this->namespace = $namespace;
324 2
        return $this;
325
    }
326
327
    /**
328
     * 获取应用类库命名空间
329
     * @access public
330
     * @return string
331
     */
332 1
    public function getNamespace(): string
333
    {
334 1
        return $this->namespace;
335
    }
336
337
    /**
338
     * 设置应用绑定域名
339
     * @access public
340
     * @param bool $bind 是否绑定域名
341
     * @return $this
342
     */
343
    public function setBindDomain(bool $bind = true)
344
    {
345
        $this->bindDomain = $bind;
346
        return $this;
347
    }
348
349
    /**
350
     * 是否域名绑定应用
351
     * @access public
352
     * @return bool
353
     */
354
    public function isBindDomain(): bool
355
    {
356
        return $this->bindDomain;
357
    }
358
359
    /**
360
     * 获取框架版本
361
     * @access public
362
     * @return string
363
     */
364 1
    public function version(): string
365
    {
366 1
        return static::VERSION;
367
    }
368
369
    /**
370
     * 获取路由目录
371
     * @access public
372
     * @return string
373
     */
374 1
    public function getRoutePath(): string
375
    {
376 1
        return $this->routePath;
377
    }
378
379
    /**
380
     * 设置路由目录
381
     * @access public
382
     * @param string $path 路由定义目录
383
     * @return string
384
     */
385
    public function setRoutePath(string $path): void
386
    {
387
        $this->routePath = $path;
388
    }
389
390
    /**
391
     * 获取应用根目录
392
     * @access public
393
     * @return string
394
     */
395 1
    public function getRootPath(): string
396
    {
397 1
        return $this->rootPath;
398
    }
399
400
    /**
401
     * 获取应用基础目录
402
     * @access public
403
     * @return string
404
     */
405 1
    public function getBasePath(): string
406
    {
407 1
        return $this->rootPath . 'app' . DIRECTORY_SEPARATOR;
408
    }
409
410
    /**
411
     * 获取当前应用目录
412
     * @access public
413
     * @return string
414
     */
415 2
    public function getAppPath(): string
416
    {
417 2
        return $this->path ?: $this->appPath;
418
    }
419
420
    /**
421
     * 设置应用目录
422
     * @param string $path 应用目录
423
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
424 1
    public function setAppPath(string $path)
425
    {
426 1
        $this->appPath = $path;
427 1
    }
428
429
    /**
430
     * 获取应用运行时目录
431
     * @access public
432
     * @return string
433
     */
434 3
    public function getRuntimePath(): string
435
    {
436 3
        return $this->runtimePath;
437
    }
438
439
    /**
440
     * 设置runtime目录
441
     * @param string $path 定义目录
442
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
443 1
    public function setRuntimePath(string $path): void
444
    {
445 1
        $this->runtimePath = $path;
446 1
    }
447
448
    /**
449
     * 获取核心框架目录
450
     * @access public
451
     * @return string
452
     */
453 1
    public function getThinkPath(): string
454
    {
455 1
        return $this->thinkPath;
456
    }
457
458
    /**
459
     * 获取应用配置目录
460
     * @access public
461
     * @return string
462
     */
463 4
    public function getConfigPath(): string
464
    {
465 4
        return $this->rootPath . 'config' . DIRECTORY_SEPARATOR;
466
    }
467
468
    /**
469
     * 获取配置后缀
470
     * @access public
471
     * @return string
472
     */
473 3
    public function getConfigExt(): string
474
    {
475 3
        return $this->configExt;
476
    }
477
478
    /**
479
     * 获取应用开启时间
480
     * @access public
481
     * @return float
482
     */
483 1
    public function getBeginTime(): float
484
    {
485 1
        return $this->beginTime;
486
    }
487
488
    /**
489
     * 获取应用初始内存占用
490
     * @access public
491
     * @return integer
492
     */
493 1
    public function getBeginMem(): int
494
    {
495 1
        return $this->beginMem;
496
    }
497
498
    /**
499
     * 初始化应用
500
     * @access public
501
     * @return $this
502
     */
503 1
    public function initialize()
504
    {
505 1
        $this->initialized = true;
506
507 1
        $this->beginTime = microtime(true);
508 1
        $this->beginMem  = memory_get_usage();
509
510
        // 加载环境变量
511 1
        if (is_file($this->rootPath . '.env')) {
512 1
            $this->env->load($this->rootPath . '.env');
513
        }
514
515 1
        $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...
516
517 1
        $this->debugModeInit();
518
519
        // 加载全局初始化文件
520 1
        $this->load();
521
522
        // 加载框架默认语言包
523 1
        $langSet = $this->lang->defaultLangSet();
524
525 1
        $this->lang->load($this->thinkPath . 'lang' . DIRECTORY_SEPARATOR . $langSet . '.php');
526
527
        // 加载应用默认语言包
528 1
        $this->loadLangPack($langSet);
529
530
        // 监听AppInit
531 1
        $this->event->trigger(AppInit::class);
532
533 1
        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 $timezone_identifier 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

533
        date_default_timezone_set(/** @scrutinizer ignore-type */ $this->config->get('app.default_timezone', 'Asia/Shanghai'));
Loading history...
534
535
        // 初始化
536 1
        foreach ($this->initializers as $initializer) {
537 1
            $this->make($initializer)->init($this);
538
        }
539
540 1
        return $this;
541
    }
542
543
    /**
544
     * 是否初始化过
545
     * @return bool
546
     */
547 1
    public function initialized()
548
    {
549 1
        return $this->initialized;
550
    }
551
552
    /**
553
     * 加载语言包
554
     * @param string $langset 语言
555
     * @return void
556
     */
557 1
    public function loadLangPack($langset)
558
    {
559 1
        if (empty($langset)) {
560
            return;
561
        }
562
563
        // 加载系统语言包
564 1
        $files = glob($this->appPath . 'lang' . DIRECTORY_SEPARATOR . $langset . '.*');
565 1
        $this->lang->load($files);
0 ignored issues
show
Bug introduced by
It seems like $files can also be of type false; however, parameter $file of think\Lang::load() does only seem to accept array|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

565
        $this->lang->load(/** @scrutinizer ignore-type */ $files);
Loading history...
566
567
        // 加载扩展(自定义)语言包
568 1
        $list = $this->config->get('lang.extend_list', []);
569
570 1
        if (isset($list[$langset])) {
571
            $this->lang->load($list[$langset]);
572
        }
573 1
    }
574
575
    /**
576
     * 引导应用
577
     * @access public
578
     * @return void
579
     */
580 1
    public function boot(): void
581
    {
582
        array_walk($this->services, function ($service) {
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
583 1
            $this->bootService($service);
584 1
        });
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
585 1
    }
586
587
    /**
588
     * 加载应用文件和配置
589
     * @access protected
590
     * @return void
591
     */
592 1
    protected function load(): void
593
    {
594 1
        $appPath = $this->getAppPath();
595
596 1
        if (is_file($appPath . 'common.php')) {
597 1
            include_once $appPath . 'common.php';
598
        }
599
600 1
        include_once $this->thinkPath . 'helper.php';
601
602 1
        $configPath = $this->getConfigPath();
603
604 1
        $files = [];
605
606 1
        if (is_dir($configPath)) {
607 1
            $files = glob($configPath . '*' . $this->configExt);
608
        }
609
610 1
        foreach ($files as $file) {
611
            $this->config->load($file, pathinfo($file, PATHINFO_FILENAME));
612
        }
613
614 1
        if (is_file($appPath . 'event.php')) {
615 1
            $this->loadEvent(include $appPath . 'event.php');
616
        }
617
618 1
        if (is_file($appPath . 'service.php')) {
619
            $services = include $appPath . 'service.php';
620
            foreach ($services as $service) {
621
                $this->register($service);
622
            }
623
        }
624 1
    }
625
626
    /**
627
     * 调试模式设置
628
     * @access protected
629
     * @return void
630
     */
631 1
    protected function debugModeInit(): void
632
    {
633
        // 应用调试模式
634 1
        if (!$this->appDebug) {
635 1
            $this->appDebug = $this->env->get('app_debug') ? true : false;
636 1
            ini_set('display_errors', 'Off');
637
        }
638
639 1
        if (!$this->runningInConsole()) {
640
            //重新申请一块比较大的buffer
641
            if (ob_get_level() > 0) {
642
                $output = ob_get_clean();
643
            }
644
            ob_start();
645
            if (!empty($output)) {
646
                echo $output;
647
            }
648
        }
649 1
    }
650
651
    /**
652
     * 注册应用事件
653
     * @access protected
654
     * @param array $event 事件数据
655
     * @return void
656
     */
657 1
    public function loadEvent(array $event): void
658
    {
659 1
        if (isset($event['bind'])) {
660 1
            $this->event->bind($event['bind']);
661
        }
662
663 1
        if (isset($event['listen'])) {
664 1
            $this->event->listenEvents($event['listen']);
665
        }
666
667 1
        if (isset($event['subscribe'])) {
668 1
            $this->event->subscribe($event['subscribe']);
669
        }
670 1
    }
671
672
    /**
673
     * 解析应用类的类名
674
     * @access public
675
     * @param string $layer 层名 controller model ...
676
     * @param string $name  类名
677
     * @return string
678
     */
679 1
    public function parseClass(string $layer, string $name): string
680
    {
681 1
        $name  = str_replace(['/', '.'], '\\', $name);
682 1
        $array = explode('\\', $name);
683 1
        $class = Str::studly(array_pop($array));
684 1
        $path  = $array ? implode('\\', $array) . '\\' : '';
685
686 1
        return $this->namespace . '\\' . $layer . '\\' . $path . $class;
687
    }
688
689
    /**
690
     * 是否运行在命令行下
691
     * @return bool
692
     */
693 1
    public function runningInConsole()
694
    {
695 1
        return php_sapi_name() === 'cli' || php_sapi_name() === 'phpdbg';
696
    }
697
698
    /**
699
     * 获取应用根目录
700
     * @access protected
701
     * @return string
702
     */
703 11
    protected function getDefaultRootPath(): string
704
    {
705 11
        $path = dirname(dirname(dirname(dirname($this->thinkPath))));
706
707 11
        return $path . DIRECTORY_SEPARATOR;
708
    }
709
710
}
711