Passed
Push — 5.2 ( af64fa...8b63b2 )
by
unknown
02:49
created

App::autoMulti()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 1
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
// +----------------------------------------------------------------------
1 ignored issue
show
Coding Style introduced by
You must use "/**" style comments for a file comment
Loading history...
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 Opis\Closure\SerializableClosure;
16
use think\exception\ClassNotFoundException;
17
use think\exception\HttpException;
18
19
/**
20
 * App 基础类
21
 * @property Route                 $route
22
 * @property Config                $config
23
 * @property Cache                 $cache
24
 * @property Request               $request
25
 * @property Env                   $env
26
 * @property Debug                 $debug
27
 * @property Event                 $event
28
 * @property Middleware            $middleware
29
 * @property Log                   $log
30
 * @property Lang                  $lang
31
 * @property Db                    $db
32
 * @property Cookie                $cookie
33
 * @property Session               $session
34
 * @property Url                   $url
35
 * @property Validate              $validate
36
 * @property Build                 $build
37
 * @property \think\route\RuleName $rule_name
38
 */
5 ignored issues
show
Coding Style introduced by
Missing @category tag in class comment
Loading history...
Coding Style introduced by
Missing @package tag in class comment
Loading history...
Coding Style introduced by
Missing @author tag in class comment
Loading history...
Coding Style introduced by
Missing @license tag in class comment
Loading history...
Coding Style introduced by
Missing @link tag in class comment
Loading history...
39
class App extends Container
40
{
41
    const VERSION = '5.2.0RC1';
42
43
    /**
44
     * URL
45
     * @var string
46
     */
47
    protected $urlPath = '';
48
49
    /**
50
     * 是否多应用模式
51
     * @var bool
52
     */
53
    protected $multi = false;
54
55
    /**
56
     * 是否自动多应用
57
     * @var bool
58
     */
59
    protected $auto = false;
60
61
    /**
62
     * 默认应用名(多应用模式)
63
     * @var string
64
     */
65
    protected $defaultApp = 'index';
66
67
    /**
68
     * 应用名称
69
     * @var string
70
     */
71
    protected $name;
72
73
    /**
74
     * 应用调试模式
75
     * @var bool
76
     */
77
    protected $appDebug = true;
78
79
    /**
80
     * 应用映射
81
     * @var array
82
     */
83
    protected $map = [];
84
85
    /**
86
     * 应用开始时间
87
     * @var float
88
     */
89
    protected $beginTime;
90
91
    /**
92
     * 应用内存初始占用
93
     * @var integer
94
     */
95
    protected $beginMem;
96
97
    /**
98
     * 应用类库顶级命名空间
99
     * @var string
100
     */
101
    protected $rootNamespace = 'app';
102
103
    /**
104
     * 当前应用类库命名空间
105
     * @var string
106
     */
107
    protected $namespace = '';
108
109
    /**
110
     * 应用根目录
111
     * @var string
112
     */
113
    protected $rootPath = '';
114
115
    /**
116
     * 框架目录
117
     * @var string
118
     */
119
    protected $thinkPath = '';
120
121
    /**
122
     * 配置后缀
123
     * @var string
124
     */
125
    protected $configExt = '.php';
126
127
    /**
128
     * 是否需要事件响应
129
     * @var bool
130
     */
131
    protected $withEvent = true;
132
133
    /**
134
     * 注册的系统服务
135
     * @var array
136
     */
137
    protected static $servicer = [
138
        Error::class,
139
    ];
140
141
    /**
142
     * 架构方法
143
     * @access public
144
     * @param  string $rootPath 应用根目录
145
     */
146
    public function __construct(string $rootPath = '')
147
    {
148
        $this->thinkPath = dirname(__DIR__) . DIRECTORY_SEPARATOR;
149
        $this->rootPath  = $rootPath ? realpath($rootPath) . DIRECTORY_SEPARATOR : $this->getDefaultRootPath();
150
        $this->multi     = is_dir($this->getBasePath() . 'controller') ? false : true;
151
152
        static::setInstance($this);
153
154
        $this->instance('app', $this);
155
156
        // 注册系统服务
157
        foreach (self::$servicer as $servicer) {
158
            $this->make($servicer)->register($this);
159
        }
160
    }
161
162
    /**
163
     * 自动多应用访问
164
     * @access public
165
     * @param  array $map 应用路由映射
166
     * @return $this
167
     */
168
    public function autoMulti(array $map = [])
169
    {
170
        $this->multi = true;
171
        $this->auto  = true;
172
        $this->map   = $map;
173
174
        return $this;
175
    }
176
177
    /**
178
     * 是否为自动多应用模式
179
     * @access public
180
     * @return bool
181
     */
182
    public function isAutoMulti(): bool
183
    {
184
        return $this->auto;
185
    }
186
187
    /**
188
     * 设置应用模式
189
     * @access public
190
     * @param  bool $multi
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
191
     * @return $this
192
     */
193
    public function multi(bool $multi)
194
    {
195
        $this->multi = $multi;
196
        return $this;
197
    }
198
199
    /**
200
     * 是否为多应用模式
201
     * @access public
202
     * @return bool
203
     */
204
    public function isMulti(): bool
205
    {
206
        return $this->multi;
207
    }
208
209
    /**
210
     * 设置默认应用(对多应用有效)
211
     * @access public
212
     * @param  string $name 应用名
213
     * @return $this
214
     */
215
    public function defaultApp(string $name)
216
    {
217
        $this->defaultApp = $name;
218
        return $this;
219
    }
220
221
    /**
222
     * 注册一个系统服务
223
     * @access public
224
     * @param  string $servicer
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
225
     * @return void
226
     */
227
    public static function service(string $servicer): void
228
    {
229
        self::$servicer[] = $servicer;
230
    }
231
232
    /**
233
     * 设置是否使用事件机制
234
     * @access public
235
     * @param  bool $event
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
236
     * @return $this
237
     */
238
    public function withEvent(bool $event)
239
    {
240
        $this->withEvent = $event;
241
        return $this;
242
    }
243
244
    /**
245
     * 开启应用调试模式
246
     * @access public
247
     * @param  bool $debug 开启应用调试模式
248
     * @return $this
249
     */
250
    public function debug(bool $debug = true)
251
    {
252
        $this->appDebug = $debug;
253
        return $this;
254
    }
255
256
    /**
257
     * 是否为调试模式
258
     * @access public
259
     * @return bool
260
     */
261
    public function isDebug(): bool
262
    {
263
        return $this->appDebug;
264
    }
265
266
    /**
267
     * 设置应用名称
268
     * @access public
269
     * @param  string $name 应用名称
270
     * @return $this
271
     */
272
    public function name(string $name)
273
    {
274
        $this->name = $name;
275
        return $this;
276
    }
277
278
    /**
279
     * 设置应用命名空间
280
     * @access public
281
     * @param  string $namespace 应用命名空间
282
     * @return $this
283
     */
284
    public function setNamespace(string $namespace)
285
    {
286
        $this->namespace = $namespace;
287
        return $this;
288
    }
289
290
    /**
291
     * 设置应用根命名空间
292
     * @access public
293
     * @param  string $rootNamespace 应用命名空间
294
     * @return $this
295
     */
296
    public function setRootNamespace(string $rootNamespace)
297
    {
298
        $this->rootNamespace = $rootNamespace;
299
        return $this;
300
    }
301
302
    /**
303
     * 获取框架版本
304
     * @access public
305
     * @return string
306
     */
307
    public function version(): string
308
    {
309
        return static::VERSION;
310
    }
311
312
    /**
313
     * 获取应用名称
314
     * @access public
315
     * @return string
316
     */
317
    public function getName(): string
318
    {
319
        return $this->name ?: '';
320
    }
321
322
    /**
323
     * 获取应用根目录
324
     * @access public
325
     * @return string
326
     */
327
    public function getRootPath(): string
328
    {
329
        return $this->rootPath;
330
    }
331
332
    /**
333
     * 获取应用基础目录
334
     * @access public
335
     * @return string
336
     */
337
    public function getBasePath(): string
338
    {
339
        return $this->rootPath . 'app' . DIRECTORY_SEPARATOR;
340
    }
341
342
    /**
343
     * 获取当前应用目录
344
     * @access public
345
     * @return string
346
     */
347
    public function getAppPath(): string
348
    {
349
        if ($this->multi) {
350
            return $this->getBasePath() . $this->name . DIRECTORY_SEPARATOR;
351
        }
352
        return $this->getBasePath();
353
    }
354
355
    /**
356
     * 获取应用运行时目录
357
     * @access public
358
     * @return string
359
     */
360
    public function getRuntimePath(): string
361
    {
362
        if ($this->multi) {
363
            return $this->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . $this->name . DIRECTORY_SEPARATOR;
364
        }
365
        return $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR;
366
    }
367
368
    /**
369
     * 获取核心框架目录
370
     * @access public
371
     * @return string
372
     */
373
    public function getThinkPath(): string
374
    {
375
        return $this->thinkPath;
376
    }
377
378
    /**
379
     * 获取应用配置目录
380
     * @access public
381
     * @return string
382
     */
383
    public function getConfigPath(): string
384
    {
385
        return $this->rootPath . 'config' . DIRECTORY_SEPARATOR;
386
    }
387
388
    /**
389
     * 获取配置后缀
390
     * @access public
391
     * @return string
392
     */
393
    public function getConfigExt(): string
394
    {
395
        return $this->configExt;
396
    }
397
398
    /**
399
     * 获取应用类基础命名空间
400
     * @access public
401
     * @return string
402
     */
403
    public function getRootNamespace(): string
404
    {
405
        return $this->rootNamespace;
406
    }
407
408
    /**
409
     * 获取应用类库命名空间
410
     * @access public
411
     * @return string
412
     */
413
    public function getNamespace(): string
414
    {
415
        return $this->namespace;
416
    }
417
418
    /**
419
     * 获取应用开启时间
420
     * @access public
421
     * @return float
422
     */
423
    public function getBeginTime(): float
424
    {
425
        return $this->beginTime;
426
    }
427
428
    /**
429
     * 获取应用初始内存占用
430
     * @access public
431
     * @return integer
432
     */
433
    public function getBeginMem(): int
434
    {
435
        return $this->beginMem;
436
    }
437
438
    /**
439
     * 初始化应用
440
     * @access public
441
     * @return $this
442
     */
443
    public function initialize()
444
    {
445
        $this->beginTime = microtime(true);
446
        $this->beginMem  = memory_get_usage();
447
448
        //加载环境变量
449
        if (is_file($this->rootPath . '.env')) {
450
            $this->env->load($this->rootPath . '.env');
451
        }
452
453
        $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...
454
455
        $this->init();
456
457
        return $this;
458
    }
459
460
461
    /**
462
     * 初始化应用
463
     * @access protected
464
     * @return void
465
     */
466
    protected function init(): void
467
    {
468
        $this->parseAppName();
469
470
        if (!$this->namespace) {
471
            $this->namespace = $this->multi ? $this->rootNamespace . '\\' . $this->name : $this->rootNamespace;
472
        }
473
474
        // 加载初始化文件
475
        if (is_file($this->getRuntimePath() . 'init.php')) {
476
            include $this->getRuntimePath() . 'init.php';
477
        } else {
478
            $this->load();
479
        }
480
481
        // 设置开启事件机制
482
        $this->event->withEvent($this->withEvent);
483
484
        // 监听AppInit
485
        $this->event->trigger('AppInit');
486
487
        $this->debugModeInit();
488
489
        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 and 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

489
        date_default_timezone_set(/** @scrutinizer ignore-type */ $this->config->get('app.default_timezone', 'Asia/Shanghai'));
Loading history...
490
    }
491
492
    /**
493
     * 加载应用文件和配置
494
     * @access protected
495
     * @return void
496
     */
497
    protected function load(): void
498
    {
499
        if ($this->multi && is_file($this->getBasePath() . 'event.php')) {
500
            $this->loadEvent(include $this->getBasePath() . 'event.php');
501
        }
502
503
        if (is_file($this->getAppPath() . 'event.php')) {
504
            $this->loadEvent(include $this->getAppPath() . 'event.php');
505
        }
506
507
        if ($this->multi && is_file($this->getBasePath() . 'common.php')) {
508
            include_once $this->getBasePath() . 'common.php';
509
        }
510
511
        if (is_file($this->getAppPath() . 'common.php')) {
512
            include_once $this->getAppPath() . 'common.php';
513
        }
514
515
        include $this->getThinkPath() . 'helper.php';
516
517
        if ($this->multi && is_file($this->getBasePath() . 'middleware.php')) {
518
            $this->middleware->import(include $this->getBasePath() . 'middleware.php');
519
        }
520
521
        if (is_file($this->getAppPath() . 'middleware.php')) {
522
            $this->middleware->import(include $this->getAppPath() . 'middleware.php');
523
        }
524
525
        if ($this->multi && is_file($this->getBasePath() . 'provider.php')) {
526
            $this->bind(include $this->getBasePath() . 'provider.php');
527
        }
528
529
        if (is_file($this->getAppPath() . 'provider.php')) {
530
            $this->bind(include $this->getAppPath() . 'provider.php');
531
        }
532
533
        $files = [];
534
535
        if (is_dir($this->getConfigPath())) {
536
            $files = glob($this->getConfigPath() . '*' . $this->getConfigExt());
537
        }
538
539
        if ($this->multi) {
540
            if (is_dir($this->getAppPath() . 'config')) {
541
                $files = array_merge($files, glob($this->getAppPath() . 'config' . DIRECTORY_SEPARATOR . '*' . $this->getConfigExt()));
542
            } elseif (is_dir($this->getConfigPath() . $this->name)) {
543
                $files = array_merge($files, glob($this->getConfigPath() . $this->name . DIRECTORY_SEPARATOR . '*' . $this->getConfigExt()));
544
            }
545
        }
546
547
        foreach ($files as $file) {
548
            $this->config->load($file, pathinfo($file, PATHINFO_FILENAME));
549
        }
550
    }
551
552
    /**
553
     * 调试模式设置
554
     * @access protected
555
     * @return void
556
     */
557
    protected function debugModeInit(): void
558
    {
559
        // 应用调试模式
560
        if (!$this->appDebug) {
561
            $this->appDebug = $this->env->get('app_debug', false);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->env->get('app_debug', false) can also be of type string. However, the property $appDebug is declared as type boolean. 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...
562
        }
563
564
        if (!$this->appDebug) {
565
            ini_set('display_errors', 'Off');
566
        } elseif (!$this->runningInConsole()) {
567
            //重新申请一块比较大的buffer
568
            if (ob_get_level() > 0) {
569
                $output = ob_get_clean();
570
            }
571
            ob_start();
572
            if (!empty($output)) {
573
                echo $output;
574
            }
575
        }
576
    }
577
578
    /**
579
     * 注册应用事件
580
     * @access protected
581
     * @param array $event
1 ignored issue
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value indented incorrectly; expected 2 spaces but found 1
Loading history...
582
     * @return void
583
     */
584
    protected function loadEvent(array $event): void
585
    {
586
        if (isset($event['bind'])) {
587
            $this->event->bind($event['bind']);
588
        }
589
590
        if (isset($event['listen'])) {
591
            $this->event->listenEvents($event['listen']);
592
        }
593
594
        if (isset($event['subscribe'])) {
595
            $this->event->subscribe($event['subscribe']);
596
        }
597
    }
598
599
    /**
600
     * 解析应用类的类名
601
     * @access public
602
     * @param  string $layer 层名 controller model ...
603
     * @param  string $name  类名
604
     * @return string
605
     */
606
    public function parseClass(string $layer, string $name): string
607
    {
608
        $name  = str_replace(['/', '.'], '\\', $name);
609
        $array = explode('\\', $name);
610
        $class = self::parseName(array_pop($array), 1);
611
        $path  = $array ? implode('\\', $array) . '\\' : '';
612
613
        return $this->namespace . '\\' . $layer . '\\' . $path . $class;
614
    }
615
616
    /**
617
     * 是否运行在命令行下
618
     * @return bool
619
     */
620
    public function runningInConsole()
621
    {
622
        return php_sapi_name() === 'cli' || php_sapi_name() === 'phpdbg';
623
    }
624
625
    /**
626
     * 分析当前请求的应用名
627
     * @access protected
628
     * @return void
629
     */
630
    protected function parseAppName(): void
631
    {
632
        if (!$this->runningInConsole()) {
633
            $path = $this->request->path();
634
635
            if ($this->auto && $path) {
636
                // 自动多应用识别
637
                $name = current(explode('/', $path));
638
639
                if (isset($this->map[$name])) {
640
                    if ($this->map[$name] instanceof \Closure) {
641
                        call_user_func_array($this->map[$name], [$this]);
642
                    } else {
643
                        $this->name = $this->map[$name];
644
                    }
645
                } elseif ($name && false !== array_search($name, $this->map)) {
646
                    throw new HttpException(404, 'app not exists:' . $name);
647
                } else {
648
                    $this->name = $name ?: $this->defaultApp;
649
                }
650
            } elseif ($this->multi) {
651
                $this->name = $this->name ?: $this->getScriptName();
652
            }
653
654
            $this->request->setApp($this->name ?: '');
655
        }
656
    }
657
658
    /**
659
     * 获取当前运行入口名称
660
     * @access protected
661
     * @return string
662
     */
663
    protected function getScriptName(): string
664
    {
665
        if (isset($_SERVER['SCRIPT_FILENAME'])) {
666
            $file = $_SERVER['SCRIPT_FILENAME'];
667
        } elseif (isset($_SERVER['argv'][0])) {
668
            $file = realpath($_SERVER['argv'][0]);
669
        }
670
671
        return isset($file) ? pathinfo($file, PATHINFO_FILENAME) : $this->defaultApp;
672
    }
673
674
    /**
675
     * 获取应用根目录
676
     * @access protected
677
     * @return string
678
     */
679
    protected function getDefaultRootPath(): string
680
    {
681
        $path = dirname(dirname(dirname(dirname($this->thinkPath))));
682
683
        return $path . DIRECTORY_SEPARATOR;
684
    }
685
686
    /**
687
     * 字符串命名风格转换
688
     * type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格
689
     * @access public
690
     * @param  string  $name    字符串
691
     * @param  integer $type    转换类型
692
     * @param  bool    $ucfirst 首字母是否大写(驼峰规则)
693
     * @return string
694
     */
695
    public static function parseName(string $name = null, int $type = 0, bool $ucfirst = true): string
696
    {
697
        if ($type) {
698
            $name = preg_replace_callback('/_([a-zA-Z])/', function ($match) {
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...
699
                return strtoupper($match[1]);
700
            }, $name);
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...
701
            return $ucfirst ? ucfirst($name) : lcfirst($name);
702
        }
703
704
        return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_"));
705
    }
706
707
    /**
708
     * 获取类名(不包含命名空间)
709
     * @access public
710
     * @param  string|object $class
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
711
     * @return string
712
     */
713
    public static function classBaseName($class): string
714
    {
715
        $class = is_object($class) ? get_class($class) : $class;
716
        return basename(str_replace('\\', '/', $class));
717
    }
718
719
    /**
0 ignored issues
show
Coding Style introduced by
Parameter ...$args should have a doc-comment as per coding-style.
Loading history...
720
     * 创建工厂对象实例
721
     * @access public
722
     * @param  string $name      工厂类名
723
     * @param  string $namespace 默认命名空间
724
     * @return mixed
725
     */
726
    public static function factory(string $name, string $namespace = '', ...$args)
727
    {
728
        $class = false !== strpos($name, '\\') ? $name : $namespace . ucwords($name);
729
730
        if (class_exists($class)) {
731
            return Container::getInstance()->invokeClass($class, $args);
732
        }
733
734
        throw new ClassNotFoundException('class not exists:' . $class, $class);
735
    }
736
737
    public static function serialize($data): string
0 ignored issues
show
Coding Style introduced by
Missing function doc comment
Loading history...
738
    {
739
        SerializableClosure::enterContext();
740
        SerializableClosure::wrapClosures($data);
741
        $data = \serialize($data);
742
        SerializableClosure::exitContext();
743
        return $data;
744
    }
745
746
    public static function unserialize(string $data)
0 ignored issues
show
Coding Style introduced by
Missing function doc comment
Loading history...
747
    {
748
        SerializableClosure::enterContext();
749
        $data = \unserialize($data);
750
        SerializableClosure::unwrapClosures($data);
751
        SerializableClosure::exitContext();
752
        return $data;
753
    }
754
}
755