Completed
Push — 6.0 ( 09da7f...677f21 )
by liu
06:22
created

App::path()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 8
ccs 2
cts 2
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 17
        'http'                    => Http::class,
158
        'lang'                    => Lang::class,
159 17
        'log'                     => Log::class,
160 17
        'middleware'              => Middleware::class,
161 17
        'request'                 => Request::class,
162 17
        'response'                => Response::class,
163
        'route'                   => Route::class,
164 17
        'session'                 => Session::class,
165 1
        'validate'                => Validate::class,
166
        'view'                    => View::class,
167
        'filesystem'              => Filesystem::class,
168 17
        'think\DbManager'         => Db::class,
169
        'think\LogManager'        => Log::class,
170 17
        'think\CacheManager'      => Cache::class,
171 17
172 17
        // 接口依赖注入
173
        'Psr\Log\LoggerInterface' => Log::class,
174
    ];
175
176
    /**
177
     * 架构方法
178
     * @access public
179
     * @param string $rootPath 应用根目录
180
     */
181 1
    public function __construct(string $rootPath = '')
182
    {
183 1
        $this->thinkPath   = dirname(__DIR__) . DIRECTORY_SEPARATOR;
184
        $this->rootPath    = $rootPath ? rtrim($rootPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $this->getDefaultRootPath();
185 1
        $this->appPath     = $this->rootPath . 'app' . DIRECTORY_SEPARATOR;
186 1
        $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR;
187
        $this->routePath   = $this->rootPath . 'route' . DIRECTORY_SEPARATOR;
188
189 1
        if (is_file($this->appPath . 'provider.php')) {
190 1
            $this->bind(include $this->appPath . 'provider.php');
191
        }
192
193 1
        static::setInstance($this);
194 1
195
        $this->instance('app', $this);
196
        $this->instance('think\Container', $this);
197 1
    }
198 1
199
    /**
200
     * 注册服务
201 1
     * @access public
202 1
     * @param Service|string $service 服务
203
     * @param bool           $force   强制重新注册
204
     * @return Service|null
205
     */
206
    public function register($service, bool $force = false)
207
    {
208
        $registered = $this->getService($service);
209
210 1
        if ($registered && !$force) {
211
            return $registered;
212 1
        }
213 1
214
        if (is_string($service)) {
215 1
            $service = new $service($this);
216
        }
217
218
        if (method_exists($service, 'register')) {
219
            $service->register();
220
        }
221
222 1
        if (property_exists($service, 'bind')) {
223
            $this->bind($service->bind);
224 1
        }
225
226 1
        $this->services[] = $service;
227 1
    }
228
229
    /**
230
     * 执行服务
231
     * @access public
232
     * @param Service $service 服务
233
     * @return mixed
234
     */
235
    public function bootService($service)
236 2
    {
237
        if (method_exists($service, 'boot')) {
238 2
            return $this->invoke([$service, 'boot']);
239 2
        }
240
    }
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
            return $value instanceof $name;
252
        }, 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 7
     * @param bool $debug 开启应用调试模式
259
     * @return $this
260 7
     */
261 7
    public function debug(bool $debug = true)
262
    {
263
        $this->appDebug = $debug;
264
        return $this;
265
    }
266
267
    /**
268
     * 是否为调试模式
269 1
     * @access public
270
     * @return bool
271 1
     */
272
    public function isDebug(): bool
273
    {
274
        return $this->appDebug;
275
    }
276
277
    /**
278
     * 设置应用名称
279 1
     * @access public
280
     * @param string $name 应用名称
281 1
     * @return $this
282
     */
283
    public function name(string $name)
284
    {
285
        $this->name = $name;
286
        return $this;
287
    }
288
289 1
    /**
290
     * 获取应用名称
291 1
     * @access public
292
     * @return string
293
     */
294
    public function getName(): string
295
    {
296
        return $this->name ?: '';
297
    }
298
299 10
    /**
300
     * 设置应用目录
301 10
     * @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 7
        }
310
311 7
        $this->path = $path;
312
        return $this;
313
    }
314
315
    /**
316
     * 设置应用命名空间
317
     * @access public
318 6
     * @param string $namespace 应用命名空间
319
     * @return $this
320 6
     */
321 6
    public function setNamespace(string $namespace)
322
    {
323
        $this->namespace = $namespace;
324
        return $this;
325
    }
326
327
    /**
328 3
     * 获取应用类库命名空间
329
     * @access public
330 3
     * @return string
331
     */
332
    public function getNamespace(): string
333
    {
334
        return $this->namespace;
335
    }
336
337 6
    /**
338
     * 设置应用绑定域名
339 6
     * @access public
340 6
     * @param bool $bind 是否绑定域名
341
     * @return $this
342
     */
343
    public function setBindDomain(bool $bind = true)
344
    {
345
        $this->bindDomain = $bind;
346
        return $this;
347 1
    }
348
349 1
    /**
350
     * 是否域名绑定应用
351
     * @access public
352
     * @return bool
353
     */
354
    public function isBindDomain(): bool
355
    {
356
        return $this->bindDomain;
357 10
    }
358
359 10
    /**
360
     * 获取框架版本
361
     * @access public
362
     * @return string
363
     */
364
    public function version(): string
365
    {
366
        return static::VERSION;
367 9
    }
368
369 9
    /**
370
     * 获取路由目录
371
     * @access public
372
     * @return string
373
     */
374
    public function getRoutePath(): string
375
    {
376
        return $this->routePath;
377 1
    }
378
379 1
    /**
380
     * 设置路由目录
381
     * @access public
382
     * @param string $path 路由定义目录
383
     * @return string
384
     */
385
    public function setRoutePath(string $path): void
386
    {
387 1
        $this->routePath = $path;
388
    }
389 1
390
    /**
391
     * 获取应用根目录
392
     * @access public
393
     * @return string
394
     */
395
    public function getRootPath(): string
396
    {
397 1
        return $this->rootPath;
398
    }
399 1
400
    /**
401 1
     * 获取应用基础目录
402 1
     * @access public
403
     * @return string
404
     */
405 1
    public function getBasePath(): string
406 1
    {
407
        return $this->rootPath . 'app' . DIRECTORY_SEPARATOR;
408
    }
409 1
410
    /**
411 1
     * 获取当前应用目录
412
     * @access public
413
     * @return string
414 1
     */
415
    public function getAppPath(): string
416
    {
417 1
        return $this->path ?: $this->appPath;
418
    }
419 1
420
    /**
421
     * 设置应用目录
422 1
     * @param string $path 应用目录
423
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
424
    public function setAppPath(string $path)
425 1
    {
426
        $this->appPath = $path;
427 1
    }
428
429
    /**
430 1
     * 获取应用运行时目录
431 1
     * @access public
432
     * @return string
433
     */
434 1
    public function getRuntimePath(): string
435
    {
436
        return $this->runtimePath;
437
    }
438
439
    /**
440
     * 设置runtime目录
441 1
     * @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
        $this->runtimePath = $path;
446
    }
447
448
    /**
449
     * 获取核心框架目录
450
     * @access public
451 6
     * @return string
452
     */
453 6
    public function getThinkPath(): string
454
    {
455
        return $this->thinkPath;
456
    }
457
458 6
    /**
459 6
     * 获取应用配置目录
460
     * @access public
461
     * @return string
462 6
     */
463
    public function getConfigPath(): string
464 6
    {
465
        return $this->rootPath . 'config' . DIRECTORY_SEPARATOR;
466
    }
467 6
468
    /**
469
     * 获取配置后缀
470
     * @access public
471
     * @return string
472
     */
473
    public function getConfigExt(): string
474 1
    {
475
        return $this->configExt;
476
    }
477 1
478 1
    /**
479 1
     * 获取应用开启时间
480
     * @access public
481
     * @return float
482
     */
483
    public function getBeginTime(): float
484
    {
485
        return $this->beginTime;
486 1
    }
487
488 1
    /**
489
     * 获取应用初始内存占用
490 1
     * @access public
491 1
     * @return integer
492
     */
493
    public function getBeginMem(): int
494 1
    {
495
        return $this->beginMem;
496 1
    }
497
498 1
    /**
499
     * 初始化应用
500 1
     * @access public
501 1
     * @return $this
502
     */
503
    public function initialize()
504 1
    {
505
        $this->initialized = true;
506
507
        $this->beginTime = microtime(true);
508 1
        $this->beginMem  = memory_get_usage();
509 1
510
        // 加载环境变量
511
        if (is_file($this->rootPath . '.env')) {
512 1
            $this->env->load($this->rootPath . '.env');
513
        }
514
515
        $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
        $this->debugModeInit();
518 1
519
        // 加载全局初始化文件
520
        $this->load();
521
522
        // 加载框架默认语言包
523
        $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 1
530 1
        // 监听AppInit
531
        $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
        foreach ($this->initializers as $initializer) {
537
            $this->make($initializer)->init($this);
538
        }
539
540
        return $this;
541
    }
542
543 1
    /**
544
     * 是否初始化过
545
     * @return bool
546
     */
547
    public function initialized()
548
    {
549
        return $this->initialized;
550
    }
551 2
552
    /**
553 2
     * 加载语言包
554 2
     * @param string $langset 语言
555
     * @return void
556
     */
557 2
    public function loadLangPack($langset)
558 2
    {
559
        if (empty($langset)) {
560
            return;
561 2
        }
562 2
563
        // 加载系统语言包
564 2
        $files = glob($this->appPath . 'lang' . DIRECTORY_SEPARATOR . $langset . '.*');
565
        $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
        $list = $this->config->get('lang.extend_list', []);
569
570
        if (isset($list[$langset])) {
571
            $this->lang->load($list[$langset]);
572
        }
573 1
    }
574
575 1
    /**
576 1
     * 引导应用
577 1
     * @access public
578 1
     * @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
            $this->bootService($service);
584
        });
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
    }
586
587 1
    /**
588
     * 加载应用文件和配置
589 1
     * @access protected
590
     * @return void
591
     */
592
    protected function load(): void
593
    {
594
        $appPath = $this->getAppPath();
595
596
        if (is_file($appPath . 'common.php')) {
597 17
            include_once $appPath . 'common.php';
598
        }
599 17
600
        include_once $this->thinkPath . 'helper.php';
601 17
602
        $configPath = $this->getConfigPath();
603
604
        $files = [];
605
606
        if (is_dir($configPath)) {
607
            $files = glob($configPath . '*' . $this->configExt);
608
        }
609
610
        foreach ($files as $file) {
611
            $this->config->load($file, pathinfo($file, PATHINFO_FILENAME));
612
        }
613
614
        if (is_file($appPath . 'event.php')) {
615
            $this->loadEvent(include $appPath . 'event.php');
616
        }
617
618
        if (is_file($appPath . 'service.php')) {
619
            $services = include $appPath . 'service.php';
620
            foreach ($services as $service) {
621
                $this->register($service);
622
            }
623
        }
624
    }
625
626
    /**
627
     * 调试模式设置
628
     * @access protected
629
     * @return void
630
     */
631
    protected function debugModeInit(): void
632
    {
633
        // 应用调试模式
634
        if (!$this->appDebug) {
635
            $this->appDebug = $this->env->get('app_debug') ? true : false;
636
            ini_set('display_errors', 'Off');
637
        }
638
639
        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
    }
650
651
    /**
652
     * 注册应用事件
653
     * @access protected
654
     * @param array $event 事件数据
655
     * @return void
656
     */
657
    public function loadEvent(array $event): void
658
    {
659
        if (isset($event['bind'])) {
660
            $this->event->bind($event['bind']);
661
        }
662
663
        if (isset($event['listen'])) {
664
            $this->event->listenEvents($event['listen']);
665
        }
666
667
        if (isset($event['subscribe'])) {
668
            $this->event->subscribe($event['subscribe']);
669
        }
670
    }
671
672
    /**
673
     * 解析应用类的类名
674
     * @access public
675
     * @param string $layer 层名 controller model ...
676
     * @param string $name  类名
677
     * @return string
678
     */
679
    public function parseClass(string $layer, string $name): string
680
    {
681
        $name  = str_replace(['/', '.'], '\\', $name);
682
        $array = explode('\\', $name);
683
        $class = Str::studly(array_pop($array));
684
        $path  = $array ? implode('\\', $array) . '\\' : '';
685
686
        return $this->namespace . '\\' . $layer . '\\' . $path . $class;
687
    }
688
689
    /**
690
     * 是否运行在命令行下
691
     * @return bool
692
     */
693
    public function runningInConsole()
694
    {
695
        return php_sapi_name() === 'cli' || php_sapi_name() === 'phpdbg';
696
    }
697
698
    /**
699
     * 获取应用根目录
700
     * @access protected
701
     * @return string
702
     */
703
    protected function getDefaultRootPath(): string
704
    {
705
        $path = dirname(dirname(dirname(dirname($this->thinkPath))));
706
707
        return $path . DIRECTORY_SEPARATOR;
708
    }
709
710
}
711