• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

miaoxing / plugin / 3812753794

pending completion
3812753794

push

github

twinh
refactor: `wei()->user()` 改为 `UserModel`

1 of 1 new or added line in 1 file covered. (100.0%)

36 existing lines in 2 files now uncovered.

901 of 2262 relevant lines covered (39.83%)

19.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.45
/src/Service/App.php
1
<?php
2

3
namespace Miaoxing\Plugin\Service;
4

5
use Exception;
6
use JsonSerializable;
7
use Miaoxing\Plugin\BaseController;
8
use ReflectionException;
9
use ReflectionMethod;
10
use ReflectionParameter;
11
use Wei\BaseModel;
12
use Wei\Res;
13
use Wei\Ret\RetException;
14

15
/**
16
 * 应用
17
 *
18
 * @mixin \EventMixin
19
 * @mixin \StrMixin
20
 * @mixin \AppModelMixin
21
 * @mixin \CacheMixin
22
 * @mixin \PageRouterMixin
23
 * @mixin \ConfigMixin
24
 */
25
class App extends \Wei\App
26
{
27
    protected const METHOD_NOT_ALLOWED = 405;
28

29
    /**
30
     * 插件控制器不使用该格式,留空可减少类查找
31
     *
32
     * {@inheritdoc}
33
     */
34
    protected $controllerFormat = '';
35

36
    /**
37
     * 当前运行的插件名称
38
     *
39
     * @var false|string
40
     */
41
    protected $plugin = false;
42

43
    /**
44
     * 默认域名
45
     *
46
     * 如果请求的默认域名,就不到数据库查找域名
47
     *
48
     * @var array
49
     */
50
    protected $domains = [];
51

52
    /**
53
     * @var string
54
     */
55
    protected $defaultViewFile = '@plugin/_default.php';
56

57
    /**
58
     * @var string
59
     */
60
    protected $fallbackPathInfo = 'app';
61

62
    /**
63
     * Whether the application is in demo mode
64
     *
65
     * @var bool
66
     */
67
    protected $isDemo = false;
68

69
    /**
70
     * @var string
71
     * @internal
72
     */
73
    protected $accessControlAllowOrigin = '*';
74

75
    /**
76
     * The id of the current application
77
     *
78
     * @var string
79
     */
80
    protected $id;
81

82
    /**
83
     * 应用模型缓存
84
     *
85
     * @var AppModel[]
86
     */
87
    protected $models = [];
88

89
    /**
90
     * @var array
91
     */
92
    private $page = [
93
        'file' => '',
94
    ];
95

96
    /**
97
     * {@inheritdoc}
98
     */
99
    public function __invoke(array $options = [])
100
    {
101
        $this->prepareHeaders();
×
102

103
        // Load global config
104
        $this->config->preloadGlobal();
×
105

106
        $this->event->trigger('appInit');
×
107

108
        return $this->invokeApp($options);
×
109
    }
110

111
    /**
112
     * {@inheritdoc}
113
     */
114
    public function getDefaultTemplate($controller = null, $action = null)
115
    {
116
        $file = $controller ?: $this->page['file'];
9✔
117
        $file = dirname($file) . '/_' . basename($file);
9✔
118

119
        $plugin = $this->getPlugin();
9✔
120

121
        return $plugin ? '@' . $plugin . '/' . $file : $file;
9✔
122
    }
123

124
    /**
125
     * 获取当前插件下的视图文件,即可省略当前插件名称不写
126
     *
127
     * @param string $name
128
     * @return string
129
     */
130
    public function getPluginFile($name)
131
    {
132
        return $this->view->getFile('@' . $this->getPlugin() . '/' . $name);
×
133
    }
134

135
    /**
136
     * 获取当前运行的插件名称
137
     *
138
     * @return string
139
     */
140
    public function getPlugin()
141
    {
142
        if (!$this->plugin && $this->page['file']) {
9✔
143
            // 认为第二部分是插件名称
144
            list(, $plugin) = explode('/', $this->page['file'], 3);
×
UNCOV
145
            $this->plugin = $plugin;
×
146
        }
147
        return $this->plugin;
9✔
148
    }
149

150
    /**
151
     * Return the current application model object
152
     *
153
     * @return AppModel
154
     * @throws Exception When the application not found
155
     */
156
    public function getModel(): AppModel
157
    {
158
        $id = $this->getId();
3✔
159
        if (!isset($this->models[$id])) {
3✔
160
            $model = AppModel::new();
3✔
161
            $this->models[$id] = $model
3✔
162
                ->setCacheKey($model->getModelCacheKey($id))
3✔
163
                ->setCacheTime(86400)
3✔
164
                ->findOrFail($id);
3✔
165
        }
166
        return $this->models[$id];
3✔
167
    }
168

169
    /**
170
     * Set the current application model object
171
     *
172
     * @param AppModel|null $model
173
     * @return $this
174
     */
175
    public function setModel(?AppModel $model): self
176
    {
177
        $this->models[$this->getId()] = $model;
3✔
178
        return $this;
3✔
179
    }
180

181
    /**
182
     * Set the id of the current application
183
     *
184
     * @param string|null $id
185
     * @return $this
186
     */
187
    public function setId(?string $id): self
188
    {
189
        $this->id = $id;
3✔
190
        return $this;
3✔
191
    }
192

193
    /**
194
     * Return the id of the current application
195
     *
196
     * @return string
197
     */
198
    public function getId(): string
199
    {
200
        if (!$this->id) {
240✔
201
            $this->id = $this->detectId();
3✔
202
        }
203
        return $this->id;
240✔
204
    }
205

206
    /**
207
     * 重写handleResponse,支持Ret结构
208
     *
209
     * @param mixed $response
210
     * @return Res
211
     * @throws Exception
212
     */
213
    public function handleResponse($response)
214
    {
215
        if ($response instanceof Ret || $this->isRet($response)) {
45✔
216
            return $this->handleRet($response);
12✔
217
        } elseif ($response instanceof JsonSerializable) {
33✔
UNCOV
218
            return $this->res->json($response);
×
219
        } elseif (is_array($response)) {
33✔
220
            $template = $this->getDefaultTemplate();
9✔
221
            $file = $this->view->resolveFile($template) ? $template : $this->defaultViewFile;
9✔
222
            $content = $this->view->render($file, $response);
9✔
223
            return $this->res->setContent($content);
9✔
224
        } else {
225
            return parent::handleResponse($response);
24✔
226
        }
227
    }
228

229
    /**
230
     * 判断是否请求到后台页面
231
     *
232
     * @return bool
233
     */
234
    public function isAdmin()
235
    {
236
        // NOTE: 控制器不存在时,回退的控制器不带有 admin
UNCOV
237
        return 0 === strpos($this->req->getRouterPathInfo(), '/admin');
×
238
    }
239

240
    /**
241
     * 设置默认视图文件
242
     *
243
     * @param string $defaultViewFile
244
     * @return $this
245
     */
246
    public function setDefaultViewFile($defaultViewFile)
247
    {
248
        $this->defaultViewFile = $defaultViewFile;
9✔
249
        return $this;
9✔
250
    }
251

252
    /**
253
     * Returns the method name of specified acion
254
     *
255
     * @param string $action
256
     * @return string
257
     */
258
    public function getActionMethod($action)
259
    {
260
        return $action;
39✔
261
    }
262

263
    /**
264
     * Returns whether the application is in demo mode
265
     *
266
     * @return bool
267
     * @svc
268
     */
269
    protected function isDemo(): bool
270
    {
UNCOV
271
        return $this->isDemo;
×
272
    }
273

274
    protected function invokeApp(array $options = [])
275
    {
UNCOV
276
        $options && $this->setOption($options);
×
277

UNCOV
278
        $pathInfo = $this->req->getRouterPathInfo();
×
UNCOV
279
        $result = $this->pageRouter->match($pathInfo);
×
UNCOV
280
        if (!$result) {
×
UNCOV
281
            $result = $this->pageRouter->match($this->fallbackPathInfo);
×
282
        }
283

UNCOV
284
        $this->req->set($result['params']);
×
UNCOV
285
        $page = require $result['file'];
×
286

UNCOV
287
        $this->page = [
×
UNCOV
288
            'file' => $result['file'],
×
UNCOV
289
            'page' => $page,
×
290
        ];
291

UNCOV
292
        if ($this->req->isPreflight()) {
×
UNCOV
293
            return $this->res->send();
×
294
        }
295

UNCOV
296
        $method = $this->req->getMethod();
×
UNCOV
297
        if (!method_exists($page, $method)) {
×
UNCOV
298
            $this->res->setStatusCode(static::METHOD_NOT_ALLOWED);
×
UNCOV
299
            throw new \Exception('Method Not Allowed', static::METHOD_NOT_ALLOWED);
×
300
        }
301

302
        return $this->execute($page, $method);
×
303
    }
304

305
    /**
306
     * @param BaseController $instance
307
     * @param string $action
308
     * @return Res
309
     * @throws Exception
310
     */
311
    protected function execute($instance, $action)
312
    {
313
        $wei = $this->wei;
48✔
314

315
        $instance->init();
48✔
316
        $middleware = $this->getMiddleware($instance, $action);
48✔
317

318
        $callback = function () use ($instance, $action) {
32✔
319
            $instance->before($this->req, $this->res);
39✔
320

321
            $method = $this->getActionMethod($action);
39✔
322
            // TODO 和 forward 异常合并一起处理
323
            try {
324
                $args = $this->buildActionArgs($instance, $method);
39✔
325
                $response = $instance->{$method}(...$args);
36✔
326
            } catch (RetException $e) {
3✔
UNCOV
327
                return $e->getRet();
×
328
            }
329

330
            $instance->after($this->req, $response);
36✔
331

332
            return $response;
36✔
333
        };
48✔
334

335
        $next = function () use (&$middleware, &$next, $callback, $wei, $instance) {
32✔
336
            $config = array_splice($middleware, 0, 1);
48✔
337
            if ($config) {
48✔
338
                $class = key($config);
9✔
339
                $service = new $class(['wei' => $wei] + $config[$class]);
9✔
340
                $result = $service($next, $instance);
9✔
341
            } else {
342
                $result = $callback();
39✔
343
            }
344

345
            return $result;
45✔
346
        };
48✔
347

348
        return $this->handleResponse($next())->send();
48✔
349
    }
350

351
    /**
352
     * @param object $instance
353
     * @param string $method
354
     * @return array
355
     * @throws ReflectionException
356
     */
357
    protected function buildActionArgs($instance, string $method)
358
    {
359
        $ref = new ReflectionMethod($instance, $method);
39✔
360
        $params = $ref->getParameters();
39✔
361
        if (!$params || 'req' === $params[0]->getName()) {
39✔
362
            return [$this->req, $this->res];
21✔
363
        }
364

365
        $args = [];
18✔
366
        foreach ($params as $param) {
18✔
367
            $args[] = $this->buildActionArg($param);
18✔
368
        }
369
        return $args;
15✔
370
    }
371

372
    /**
373
     * @param ReflectionParameter $param
374
     * @return mixed
375
     * @throws ReflectionException
376
     */
377
    protected function buildActionArg(ReflectionParameter $param)
378
    {
379
        /** @link https://github.com/phpstan/phpstan/issues/1133 */
380
        /** @var \ReflectionNamedType|null $type */
381
        $type = $param->getType();
18✔
382

383
        // Handle Model class
384
        if (
385
            $type
18✔
386
            && !$type->isBuiltin()
18✔
387
            && is_a($type->getName(), BaseModel::class, true)
18✔
388
        ) {
389
            return $type->getName()::findOrFail($this->req['id']);
3✔
390
        }
391

392
        // Handle other class
393
        if ($type && !$type->isBuiltin()) {
15✔
UNCOV
394
            throw new Exception('Unsupported action parameter type: ' . $type);
×
395
        }
396

397
        // TODO Throw exception for unsupported builtin type
398
        // Handle builtin type
399
        $arg = $this->req[$param->getName()];
15✔
400
        if (null === $arg) {
15✔
401
            if ($param->isDefaultValueAvailable()) {
9✔
402
                $arg = $param->getDefaultValue();
6✔
403
            } else {
404
                throw new Exception('Missing required parameter: ' . $param->getName(), 400);
9✔
405
            }
406
        } elseif ($type) {
9✔
407
            settype($arg, $type->getName());
6✔
408
        }
409

410
        return $arg;
12✔
411
    }
412

413
    /**
414
     * 转换Ret结构为response
415
     *
416
     * @param array|Ret $ret
417
     * @return Res
418
     * @throws Exception
419
     */
420
    protected function handleRet($ret)
421
    {
422
        if (is_array($ret)) {
12✔
423
            if (1 === $ret['code']) {
3✔
424
                $ret = Ret::suc($ret);
3✔
425
            } else {
UNCOV
426
                $ret = Ret::err($ret);
×
427
            }
428
        }
429
        return $ret->toRes($this->req, $this->res);
12✔
430
    }
431

432
    /**
433
     * 检查是否返回了Ret结构
434
     *
435
     * @param mixed $response
436
     * @return bool
437
     */
438
    protected function isRet($response)
439
    {
440
        return is_array($response)
36✔
441
            && array_key_exists('code', $response)
36✔
442
            && array_key_exists('message', $response);
36✔
443
    }
444

445
    /**
446
     * Detect the id of application
447
     *
448
     * @return string
449
     */
450
    protected function detectId(): string
451
    {
452
        // 1. Domain
453
        if ($id = $this->getIdByDomain()) {
3✔
454
            return $id;
3✔
455
        }
456

457
        // 2. Request parameter
458
        if ($id = $this->req->get('appId')) {
3✔
UNCOV
459
            return $id;
×
460
        }
461

462
        // 3. First id from database
463
        return $this->cache->remember('app:firstId', 86400, static function () {
2✔
UNCOV
464
            return AppModel::select('id')->asc('id')->fetchColumn();
×
465
        });
3✔
466
    }
467

468
    /**
469
     * 根据域名查找应用编号
470
     *
471
     * @return string|null
472
     */
473
    protected function getIdByDomain(): ?string
474
    {
475
        $domain = $this->req->getHost();
3✔
476
        if (!$domain) {
3✔
477
            // CLI 下默认没有域名,直接返回
478
            return null;
3✔
479
        }
480

481
        if (in_array($domain, $this->domains, true)) {
3✔
UNCOV
482
            return null;
×
483
        }
484

485
        return $this->cache->remember('appDomain:' . $domain, 86400, static function () use ($domain) {
2✔
486
            $app = AppModel::select('id')->fetch('domain', $domain);
3✔
487
            return $app ? $app['id'] : null;
3✔
488
        });
3✔
489
    }
490

491
    /**
492
     * 根据请求设置跨域标头信息
493
     *
494
     * @return void
495
     * @internal
496
     */
497
    public function prepareHeaders()
498
    {
UNCOV
499
        $this->res->setHeader('Access-Control-Allow-Origin', $this->accessControlAllowOrigin);
×
UNCOV
500
        if ($this->req->isPreflight()) {
×
UNCOV
501
            $this->res
×
UNCOV
502
                ->setHeader('Access-Control-Allow-Methods', 'GET, POST, PATCH, PUT, DELETE, OPTIONS')
×
503
                // NOTE: antd upload 组件上传会加上 XMLHttpRequest 头
UNCOV
504
                ->setHeader('Access-Control-Allow-Headers', 'Origin, Content-Type, Authorization, X-Requested-With')
×
UNCOV
505
                ->setHeader('Access-Control-Max-Age', 0);
×
506
        }
507
    }
508
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc