Passed
Push — 5.2 ( 5de8a0...f2908c )
by liu
02:29
created

Web::parsePath()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 23
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 16
nc 6
nop 0
dl 0
loc 23
rs 9.7333
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 think\exception\ClassNotFoundException;
16
use think\exception\HttpException;
17
use think\exception\HttpResponseException;
18
use think\route\Dispatch;
19
20
/**
21
 * Web应用管理类
22
 * @property Route                 $route
23
 * @property Config                $config
24
 * @property Cache                 $cache
25
 * @property Request               $request
26
 * @property Env                   $env
27
 * @property Debug                 $debug
28
 * @property Event                 $event
29
 * @property Middleware            $middleware
30
 * @property Log                   $log
31
 * @property Lang                  $lang
32
 * @property Db                    $db
33
 * @property Cookie                $cookie
34
 * @property Session               $session
35
 * @property Url                   $url
36
 * @property Validate              $validate
37
 * @property Build                 $build
38
 * @property \think\route\RuleName $rule_name
39
 */
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...
40
class Web extends App
41
{
42
43
    /**
44
     * 是否多应用模式
45
     * @var bool
46
     */
47
    protected $multi = false;
48
49
    /**
50
     * 是否自动多应用
51
     * @var bool
52
     */
53
    protected $auto = false;
54
55
    /**
56
     * 默认应用名(多应用模式)
57
     * @var string
58
     */
59
    protected $defaultApp = 'index';
60
61
    /**
62
     * 路由目录
63
     * @var string
64
     */
65
    protected $routePath = '';
66
67
    /**
68
     * URL
69
     * @var string
70
     */
71
    protected $urlPath = '';
72
73
    /**
74
     * 是否需要使用路由
75
     * @var bool
76
     */
77
    protected $withRoute = true;
78
79
    /**
80
     * 访问控制器层名称
81
     * @var string
82
     */
83
    protected $controllerLayer = 'controller';
84
85
    /**
86
     * 是否使用控制器类库后缀
87
     * @var bool
88
     */
89
    protected $controllerSuffix = false;
90
91
    /**
92
     * 空控制器名称
93
     * @var string
94
     */
95
    protected $emptyController = 'Error';
96
97
    /**
98
     * 加载应用文件和配置
99
     * @access protected
100
     * @return void
101
     */
102
    protected function load(): void
103
    {
104
        if ($this->multi && is_file($this->basePath . 'event.php')) {
105
            $this->loadEvent(include $this->basePath . 'event.php');
106
        }
107
108
        if (is_file($this->appPath . 'event.php')) {
109
            $this->loadEvent(include $this->appPath . 'event.php');
110
        }
111
112
        if ($this->multi && is_file($this->basePath . 'common.php')) {
113
            include_once $this->basePath . 'common.php';
114
        }
115
116
        if (is_file($this->appPath . 'common.php')) {
117
            include_once $this->appPath . 'common.php';
118
        }
119
120
        include $this->thinkPath . 'helper.php';
121
122
        if ($this->multi && is_file($this->basePath . 'middleware.php')) {
123
            $this->middleware->import(include $this->basePath . 'middleware.php');
124
        }
125
126
        if (is_file($this->appPath . 'middleware.php')) {
127
            $this->middleware->import(include $this->appPath . 'middleware.php');
128
        }
129
130
        if ($this->multi && is_file($this->basePath . 'provider.php')) {
131
            $this->bind(include $this->basePath . 'provider.php');
132
        }
133
134
        if (is_file($this->appPath . 'provider.php')) {
135
            $this->bind(include $this->appPath . 'provider.php');
136
        }
137
138
        $files = [];
139
140
        if (is_dir($this->configPath)) {
141
            $files = glob($this->configPath . '*' . $this->configExt);
142
        }
143
144
        if ($this->multi) {
145
            if (is_dir($this->appPath . 'config')) {
146
                $files = array_merge($files, glob($this->appPath . 'config' . DIRECTORY_SEPARATOR . '*' . $this->configExt));
147
            } elseif (is_dir($this->configPath . $this->name)) {
148
                $files = array_merge($files, glob($this->configPath . $this->name . DIRECTORY_SEPARATOR . '*' . $this->configExt));
149
            }
150
        }
151
152
        foreach ($files as $file) {
153
            $this->config->load($file, pathinfo($file, PATHINFO_FILENAME));
154
        }
155
    }
156
157
    /**
158
     * 分析应用(参数)
159
     * @access protected
160
     * @return void
161
     */
162
    protected function parse(): void
163
    {
164
        if (is_file($this->rootPath . '.env')) {
165
            $this->env->load($this->rootPath . '.env');
166
        }
167
168
        $this->parseAppName();
169
170
        $this->parsePath();
171
172
        if (!$this->namespace) {
173
            $this->namespace = $this->multi ? $this->rootNamespace . '\\' . $this->name : $this->rootNamespace;
174
        }
175
176
        $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...
177
    }
178
179
    /**
180
     * 自动多应用访问
181
     * @access public
182
     * @param  array $map 应用路由映射
183
     * @return $this
184
     */
185
    public function autoMulti(array $map = [])
186
    {
187
        $this->multi = true;
188
        $this->auto  = true;
189
        $this->map   = $map;
190
191
        return $this;
192
    }
193
194
    /**
195
     * 是否为自动多应用模式
196
     * @access public
197
     * @return bool
198
     */
199
    public function isAutoMulti(): bool
200
    {
201
        return $this->auto;
202
    }
203
204
    /**
205
     * 设置应用模式
206
     * @access public
207
     * @param  bool $multi
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
208
     * @return $this
209
     */
210
    public function multi(bool $multi)
211
    {
212
        $this->multi = $multi;
213
        return $this;
214
    }
215
216
    /**
217
     * 是否为多应用模式
218
     * @access public
219
     * @return bool
220
     */
221
    public function isMulti(): bool
222
    {
223
        return $this->multi;
224
    }
225
226
    /**
227
     * 设置是否使用路由
228
     * @access public
229
     * @param  bool $route
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
230
     * @return $this
231
     */
232
    public function withRoute(bool $route)
233
    {
234
        $this->withRoute = $route;
235
        return $this;
236
    }
237
238
    /**
239
     * 设置默认应用(对多应用有效)
240
     * @access public
241
     * @param  string $name 应用名
242
     * @return $this
243
     */
244
    public function defaultApp(string $name)
245
    {
246
        $this->defaultApp = $name;
247
        return $this;
248
    }
249
250
    /**
251
     * 设置控制器层名称
252
     * @access public
253
     * @param  string $layer 控制器层名称
254
     * @return $this
255
     */
256
    public function controllerLayer(string $layer)
257
    {
258
        $this->controllerLayer = $layer;
259
        return $this;
260
    }
261
262
    /**
263
     * 设置空控制器名称
264
     * @access public
265
     * @param  string $empty 空控制器名称
266
     * @return $this
267
     */
268
    public function emptyController(string $empty)
269
    {
270
        $this->emptyController = $empty;
271
        return $this;
272
    }
273
274
    /**
275
     * 设置是否启用控制器类库后缀
276
     * @access public
277
     * @param  bool $suffix 启用控制器类库后缀
278
     * @return $this
279
     */
280
    public function controllerSuffix(bool $suffix = true)
281
    {
282
        $this->controllerSuffix = $suffix;
283
        return $this;
284
    }
285
286
    /**
287
     * 是否启用控制器类库后缀
288
     * @access public
289
     * @return bool
290
     */
291
    public function hasControllerSuffix(): bool
292
    {
293
        return $this->controllerSuffix;
294
    }
295
296
    /**
297
     * 获取控制器层名称
298
     * @access public
299
     * @return string
300
     */
301
    public function getControllerLayer(): string
302
    {
303
        return $this->controllerLayer;
304
    }
305
306
    /**
307
     * 执行应用程序
308
     * @access public
309
     * @return Response
310
     * @throws Exception
311
     */
312
    public function run(): Response
313
    {
314
        try {
315
            if ($this->withRoute) {
316
                $dispatch = $this->routeCheck()->init();
317
            } else {
318
                $dispatch = $this->route->url($this->getRealPath())->init();
319
            }
320
321
            // 监听AppBegin
322
            $this->event->trigger('AppBegin');
323
324
            $data = null;
325
        } catch (HttpResponseException $exception) {
326
            $dispatch = null;
327
            $data     = $exception->getResponse();
328
        }
329
330
        $this->middleware->add(function (Request $request, $next) use ($dispatch, $data) {
0 ignored issues
show
Unused Code introduced by
The parameter $next is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

330
        $this->middleware->add(function (Request $request, /** @scrutinizer ignore-unused */ $next) use ($dispatch, $data) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

330
        $this->middleware->add(function (/** @scrutinizer ignore-unused */ Request $request, $next) use ($dispatch, $data) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
331
            return is_null($data) ? $dispatch->run() : $data;
332
        });
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...
333
334
        $response = $this->middleware->dispatch($this->request);
335
336
        // 监听AppEnd
337
        $this->event->trigger('AppEnd', $response);
338
339
        return $response;
340
    }
341
342
    /**
343
     * 实例化访问控制器
344
     * @access public
345
     * @param  string $name 资源地址
346
     * @return object
347
     * @throws ClassNotFoundException
348
     */
349
    public function controller(string $name)
350
    {
351
        $suffix = $this->controllerSuffix ? 'Controller' : '';
352
        $class  = $this->parseClass($this->controllerLayer, $name . $suffix);
353
354
        if (class_exists($class)) {
355
            return $this->make($class, [], true);
356
        } elseif ($this->emptyController && class_exists($emptyClass = $this->parseClass($this->controllerLayer, $this->emptyController . $suffix))) {
357
            return $this->make($emptyClass, [], true);
358
        }
359
360
        throw new ClassNotFoundException('class not exists:' . $class, $class);
361
    }
362
363
    /**
364
     * 路由初始化(路由规则注册)
365
     * @access protected
366
     * @return void
367
     */
368
    protected function routeInit(): void
369
    {
370
        // 加载路由定义
371
        if (is_dir($this->routePath)) {
372
            $files = glob($this->routePath . DIRECTORY_SEPARATOR . '*.php');
373
            foreach ($files as $file) {
374
                include $file;
375
            }
376
        }
377
378
        if ($this->route->config('route_annotation')) {
379
            // 自动生成注解路由定义
380
            if ($this->isDebug()) {
381
                $this->build->buildRoute();
382
            }
383
384
            $filename = $this->runtimePath . 'build_route.php';
385
386
            if (is_file($filename)) {
387
                include $filename;
388
            }
389
        }
390
    }
391
392
    /**
393
     * URL路由检测(根据PATH_INFO)
394
     * @access protected
395
     * @return Dispatch
396
     */
397
    protected function routeCheck(): Dispatch
398
    {
399
        // 检测路由缓存
400
        if (!$this->isDebug() && $this->route->config('route_check_cache')) {
401
            $routeKey = $this->getRouteCacheKey();
402
            $option   = $this->route->config('route_cache_option');
403
404
            if ($option && $this->cache->connect($option)->has($routeKey)) {
405
                return $this->cache->connect($option)->get($routeKey);
406
            } elseif ($this->cache->has($routeKey)) {
407
                return $this->cache->get($routeKey);
408
            }
409
        }
410
411
        $this->routeInit();
412
413
        // 路由检测
414
        $dispatch = $this->route->check($this->getRealPath());
415
416
        if (!empty($routeKey)) {
417
            try {
418
                if (!empty($option)) {
419
                    $this->cache->connect($option)->tag('route_cache')->set($routeKey, $dispatch);
420
                } else {
421
                    $this->cache->tag('route_cache')->set($routeKey, $dispatch);
422
                }
423
            } catch (\Exception $e) {
424
                // 存在闭包的时候缓存无效
425
            }
426
        }
427
428
        return $dispatch;
429
    }
430
431
    /**
432
     * 获取路由缓存Key
433
     * @access protected
434
     * @return string
435
     */
436
    protected function getRouteCacheKey(): string
437
    {
438
        if ($this->route->config('route_check_cache_key')) {
439
            $closure  = $this->route->config('route_check_cache_key');
440
            $routeKey = $closure($this->request);
441
        } else {
442
            $routeKey = md5($this->request->baseUrl(true) . ':' . $this->request->method());
443
        }
444
445
        return $routeKey;
446
    }
447
448
    /**
449
     * 获取自动多应用模式下的实际URL Path
450
     * @access public
451
     * @return string
452
     */
453
    public function getRealPath(): string
454
    {
455
        $path = $this->urlPath;
456
457
        if ($path && $this->auto) {
458
            $path = substr_replace($path, '', 0, strpos($path, '/') ? strpos($path, '/') + 1 : strlen($path));
459
        }
460
461
        return $path;
462
    }
463
464
    /**
465
     * 分析应用路径
466
     * @access protected
467
     * @return void
468
     */
469
    protected function parsePath(): void
470
    {
471
        if ($this->multi) {
472
            $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR . $this->name . DIRECTORY_SEPARATOR;
473
            $this->routePath   = $this->rootPath . 'route' . DIRECTORY_SEPARATOR . $this->name . DIRECTORY_SEPARATOR;
474
        } else {
475
            $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR;
476
            $this->routePath   = $this->rootPath . 'route' . DIRECTORY_SEPARATOR;
477
        }
478
479
        if (!$this->appPath) {
480
            $this->appPath = $this->multi ? $this->basePath . $this->name . DIRECTORY_SEPARATOR : $this->basePath;
481
        }
482
483
        $this->configPath = $this->rootPath . 'config' . DIRECTORY_SEPARATOR;
484
485
        $this->env->set([
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...
486
            'think_path'   => $this->thinkPath,
487
            'root_path'    => $this->rootPath,
488
            'app_path'     => $this->appPath,
489
            'runtime_path' => $this->runtimePath,
490
            'route_path'   => $this->routePath,
491
            'config_path'  => $this->configPath,
492
        ]);
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...
493
    }
494
495
    /**
496
     * 分析当前请求的应用名
497
     * @access protected
498
     * @return void
499
     */
500
    protected function parseAppName(): void
501
    {
502
        $this->urlPath = $this->request->path();
503
504
        if ($this->auto && $this->urlPath) {
505
            // 自动多应用识别
506
            $name = current(explode('/', $this->urlPath));
507
508
            if (isset($this->map[$name])) {
509
                if ($this->map[$name] instanceof \Closure) {
510
                    call_user_func_array($this->map[$name], [$this]);
511
                } else {
512
                    $this->name = $this->map[$name];
513
                }
514
            } elseif ($name && false !== array_search($name, $this->map)) {
515
                throw new HttpException(404, 'app not exists:' . $name);
516
            } else {
517
                $this->name = $name ?: $this->defaultApp;
518
            }
519
        } elseif ($this->multi) {
520
            $this->name = $this->name ?: $this->getScriptName();
521
        }
522
523
        $this->request->setApp($this->name ?: '');
524
    }
525
526
    /**
527
     * 获取当前运行入口名称
528
     * @access protected
529
     * @return string
530
     */
531
    protected function getScriptName(): string
532
    {
533
        if (isset($_SERVER['SCRIPT_FILENAME'])) {
534
            $file = $_SERVER['SCRIPT_FILENAME'];
535
        } elseif (isset($_SERVER['argv'][0])) {
536
            $file = realpath($_SERVER['argv'][0]);
537
        }
538
539
        return isset($file) ? pathinfo($file, PATHINFO_FILENAME) : $this->defaultApp;
540
    }
541
542
    /**
543
     * 获取路由目录
544
     * @access public
545
     * @return string
546
     */
547
    public function getRoutePath(): string
548
    {
549
        return $this->routePath;
550
    }
551
552
}
553