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

miaoxing / plugin / 9662803987

25 Jun 2024 12:48PM UTC coverage: 42.997% (+0.1%) from 42.882%
9662803987

push

github

twinh
refactor(plugin): 移除内置的 `CORS` 功能,改为通过中间件实现

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

1 existing line in 1 file now uncovered.

1231 of 2863 relevant lines covered (43.0%)

7.61 hits per line

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

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

3
namespace Miaoxing\Plugin\Service;
4

5
use Wei\BaseController;
6
use Wei\BaseController as WeiBaseController;
7
use Wei\Res;
8
use Wei\Ret\RetException;
9

10
/**
11
 * 应用
12
 *
13
 * @mixin \EventMixin
14
 * @mixin \StrMixin
15
 * @mixin \AppModelMixin
16
 * @mixin \CacheMixin
17
 * @mixin \PageRouterMixin
18
 * @mixin \ConfigMixin
19
 */
20
class App extends \Wei\App
21
{
22
    protected const NOT_FOUND = 404;
23

24
    protected const METHOD_NOT_ALLOWED = 405;
25

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

33
    /**
34
     * {@inheritdoc}
35
     */
36
    protected $actionMethodFormat = '%action%';
37

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

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

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

59
    /**
60
     * @var string|null
61
     */
62
    protected $fallbackPathInfo;
63

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

71
    /**
72
     * The id of the current application
73
     *
74
     * @var string
75
     */
76
    protected $id;
77

78
    /**
79
     * 应用模型缓存
80
     *
81
     * @var AppModel[]
82
     */
83
    protected $models = [];
84

85
    /**
86
     * @var array
87
     * @internal
88
     */
89
    protected $pathMap = [
90
        '/admin-api/' => '/api/admin/',
91
        '/api/admin/' => '/admin-api/',
92
        '/m-api/' => '/api/',
93
        '/api/' => '/m-api/',
94
    ];
95

96
    /**
97
     * @var WeiBaseController|null
98
     */
99
    private $curControllerInstance;
100

101
    /**
102
     * {@inheritdoc}
103
     */
104
    public function __invoke(array $options = [])
105
    {
106
        // Load global config
UNCOV
107
        $this->config->preloadGlobal();
×
108

109
        $this->event->trigger('appInit');
×
110

111
        return $this->invokeApp($options);
×
112
    }
113

114
    /**
115
     * {@inheritdoc}
116
     */
117
    public function getDefaultTemplate($controller = null, $action = null)
118
    {
119
        $file = $controller ?: $this->controller;
1✔
120
        $file = dirname($file) . '/_' . basename($file);
1✔
121

122
        $plugin = $this->getPlugin();
1✔
123

124
        return $plugin ? '@' . $plugin . '/' . $file : $file;
1✔
125
    }
126

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

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

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

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

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

196
    /**
197
     * Return the id of the current application
198
     *
199
     * @return string
200
     */
201
    public function getId(): string
202
    {
203
        if (!$this->id) {
97✔
204
            $this->id = $this->detectId();
1✔
205
        }
206
        return $this->id;
97✔
207
    }
208

209
    /**
210
     * 重写handleResponse,支持Ret结构
211
     *
212
     * @param mixed $response
213
     * @return Res
214
     * @throws \Exception
215
     */
216
    public function handleResponse($response)
217
    {
218
        if ($response instanceof Ret) {
8✔
219
            return $response->toRes($this->req, $this->res);
3✔
220
        } elseif ($response instanceof \JsonSerializable) {
5✔
221
            return $this->res->json($response);
×
222
        } elseif (is_array($response)) {
5✔
223
            $template = $this->getDefaultTemplate();
1✔
224
            $file = $this->view->resolveFile($template) ? $template : $this->defaultViewFile;
1✔
225
            $content = $this->view->render($file, $response);
1✔
226
            return $this->res->setContent($content);
1✔
227
        } else {
228
            return parent::handleResponse($response);
4✔
229
        }
230
    }
231

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

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

255
    /**
256
     * @return WeiBaseController
257
     * @experimental
258
     */
259
    public function getCurControllerInstance(): WeiBaseController
260
    {
261
        if (!$this->curControllerInstance) {
1✔
262
            $this->curControllerInstance = require $this->controller;
1✔
263
        }
264
        return $this->curControllerInstance;
1✔
265
    }
266

267
    /**
268
     * Returns whether the application is in demo mode
269
     *
270
     * @return bool
271
     * @svc
272
     */
273
    protected function isDemo(): bool
274
    {
275
        return $this->isDemo;
×
276
    }
277

278
    protected function invokeApp(array $options = [])
279
    {
280
        $options && $this->setOption($options);
×
281

282
        $pathInfo = $this->req->getRouterPathInfo();
×
283

284
        $result = $this->matchPathInfo($pathInfo);
×
285
        if (!$result) {
×
286
            throw new \Exception('Not Found', static::NOT_FOUND);
×
287
        }
288

289
        $action = strtolower($this->req->getMethod());
×
290
        return $this->dispatch($result['file'], $action, $result['params']);
×
291
    }
292

293
    /**
294
     * {@inheritdoc}
295
     */
296
    public function dispatch($controller, $action = null, array $params = [], $throwException = true /* ignored */)
297
    {
298
        $this->setController($controller);
1✔
299
        $this->setAction($action);
1✔
300
        $this->req->set($params);
1✔
301

302
        $page = $this->getCurControllerInstance();
1✔
303

304
        // TODO allow execute middleware before action
305
        if ($action !== 'options' && !method_exists($page, $action)) {
1✔
306
            $this->res->setStatusCode(static::METHOD_NOT_ALLOWED);
×
307
            throw new \Exception('Method Not Allowed', static::METHOD_NOT_ALLOWED);
×
308
        }
309

310
        return $this->execute($page, $action);
1✔
311
    }
312

313
    /**
314
     * @param BaseController $instance
315
     * @param string $action
316
     * @return Res
317
     * @throws \Exception
318
     */
319
    protected function execute($instance, $action)
320
    {
321
        $wei = $this->wei;
8✔
322

323
        $instance->init();
8✔
324
        $middleware = $this->getMiddleware($instance, $action);
8✔
325

326
        $callback = function () use ($instance, $action) {
8✔
327
            $instance->before($this->req, $this->res);
5✔
328

329
            $method = $this->getActionMethod($action);
5✔
330
            // TODO 和 forward 异常合并一起处理
331
            try {
332
                $response = $instance->{$method}($this->req, $this->res);
5✔
333
            } catch (RetException $e) {
×
334
                return $e->getRet();
×
335
            }
336

337
            $instance->after($this->req, $response);
5✔
338

339
            return $response;
5✔
340
        };
8✔
341

342
        $next = static function () use (&$middleware, &$next, $callback, $wei, $instance) {
8✔
343
            $config = array_splice($middleware, 0, 1);
8✔
344
            if ($config) {
8✔
345
                $class = key($config);
3✔
346
                $service = new $class(['wei' => $wei] + $config[$class]);
3✔
347
                $result = $service($next, $instance);
3✔
348
            } else {
349
                $result = $callback();
5✔
350
            }
351

352
            return $result;
8✔
353
        };
8✔
354

355
        return $this->handleResponse($next())->send();
8✔
356
    }
357

358
    /**
359
     * Detect the id of application
360
     *
361
     * @return string
362
     */
363
    protected function detectId(): string
364
    {
365
        // 1. Domain
366
        if ($id = $this->getIdByDomain()) {
1✔
367
            return $id;
1✔
368
        }
369

370
        // 2. Request parameter
371
        if ($id = $this->req->get('appId')) {
1✔
372
            return $id;
×
373
        }
374

375
        // 3. First id from database
376
        return $this->cache->remember('app:firstId', 86400, static function () {
1✔
377
            return AppModel::select('id')->asc('id')->fetchColumn();
×
378
        });
1✔
379
    }
380

381
    /**
382
     * 根据域名查找应用编号
383
     *
384
     * @return string|null
385
     */
386
    protected function getIdByDomain(): ?string
387
    {
388
        $domain = $this->req->getHost();
1✔
389
        if (!$domain) {
1✔
390
            // CLI 下默认没有域名,直接返回
391
            return null;
×
392
        }
393

394
        if (in_array($domain, $this->domains, true)) {
1✔
395
            return null;
×
396
        }
397

398
        return $this->cache->remember('appDomain:' . $domain, 86400, static function () use ($domain) {
1✔
399
            $app = AppModel::select('id')->fetch('domain', $domain);
1✔
400
            return $app ? $app['id'] : null;
1✔
401
        });
1✔
402
    }
403

404
    /**
405
     * @internal
406
     */
407
    protected function matchPathInfo(string $pathInfo): ?array
408
    {
409
        $result = $this->pageRouter->match($pathInfo);
×
410
        if ($result) {
×
411
            return $result;
×
412
        }
413

414
        foreach ($this->pathMap as $search => $replace) {
×
415
            if (str_contains($pathInfo, $search)) {
×
416
                $pathInfo = str_replace($search, $replace, $pathInfo);
×
417
                $result = $this->pageRouter->match($pathInfo);
×
418
                if ($result) {
×
419
                    return $result;
×
420
                }
421
                break;
×
422
            }
423
        }
424

425
        if ($this->fallbackPathInfo) {
×
426
            return $this->pageRouter->match($this->fallbackPathInfo);
×
427
        }
428

429
        return null;
×
430
    }
431
}
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