Passed
Push — master ( b817d9...04ae87 )
by 世昌
02:23
created

Application::request()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 18
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 15
nc 5
nop 4
dl 0
loc 18
rs 9.4555
c 0
b 0
f 0
1
<?php
2
3
namespace suda\application;
4
5
use Exception;
6
use suda\framework\loader\Loader;
7
use Throwable;
8
use ReflectionException;
9
use suda\framework\Request;
10
use suda\framework\Response;
11
use suda\database\exception\SQLException;
12
use suda\framework\route\MatchResult;
13
use suda\application\loader\ModuleLoader;
14
use suda\application\template\RawTemplate;
15
use suda\application\loader\LanguageLoader;
16
use suda\application\template\ModuleTemplate;
17
use suda\application\wrapper\TemplateWrapper;
18
use suda\application\loader\ApplicationLoader;
19
use suda\application\processor\FileRequestProcessor;
20
use suda\application\wrapper\ExceptionContentWrapper;
21
use suda\application\exception\ConfigurationException;
22
use suda\application\processor\TemplateAssetProccesser;
23
use suda\application\processor\TemplateRequestProcessor;
24
25
/**
26
 * 应用程序
27
 */
28
class Application extends ApplicationSource
29
{
30
    public function __construct(string $path, array $manifest, Loader $loader, ?string $dataPath = null)
31
    {
32
        parent::__construct($path, $manifest, $loader, $dataPath);
33
        $this->event = new Event($this);
34
    }
35
36
    /**
37
     * @var DebugDumper
38
     */
39
    protected $dumper;
40
41
    /**
42
     * 准备运行环境
43
     *
44
     * @return void
45
     * @throws SQLException
46
     * @throws ReflectionException
47
     */
48
    public function load()
49
    {
50
        $appLoader = new ApplicationLoader($this);
51
        $this->debug->info('===============================');
52
        $this->debug->time('loading application');
53
        $appLoader->load();
54
        $this->event->exec('application:load-config', [$this->config, $this]);
55
        $this->debug->timeEnd('loading application');
56
        $this->debug->time('loading data-source');
57
        $appLoader->loadDataSource();
58
        $this->debug->timeEnd('loading data-source');
59
        $this->event->exec('application:load-environment', [$this->config, $this]);
60
        $this->debug->time('loading route');
61
        $appLoader->loadRoute();
62
        $this->event->exec('application:load-route', [$this->route, $this]);
63
        $this->debug->timeEnd('loading route');
64
        $this->debug->info('-------------------------------');
65
    }
66
67
    /**
68
     * 准备环境
69
     *
70
     * @param Request $request
71
     * @param Response $response
72
     * @throws SQLException
73
     * @throws ReflectionException
74
     */
75
    protected function prepare(Request $request, Response $response)
76
    {
77
        $response->setHeader('x-powered-by', 'suda/' . SUDA_VERSION, true);
78
        $response->getWrapper()->register(ExceptionContentWrapper::class, [Throwable::class]);
79
        $response->getWrapper()->register(TemplateWrapper::class, [RawTemplate::class]);
80
81
        $this->dumper = new DebugDumper($this, $request, $response);
82
        $this->dumper->register();
83
84
        $this->debug->info('{request-time} {remote-ip} {request-method} {request-uri} debug={debug}', [
85
            'remote-ip' => $request->getRemoteAddr(),
86
            'debug' => SUDA_DEBUG,
87
            'request-uri' => $request->getUrl(),
88
            'request-method' => $request->getMethod(),
89
            'request-time' => date('Y-m-d H:i:s', constant('SUDA_START_TIME')),
90
        ]);
91
92
        if ($this->isPrepared === false) {
93
            $this->load();
94
            $this->isPrepared = true;
95
        }
96
    }
97
98
    /**
99
     * @param Throwable $throwable
100
     */
101
    public function dumpException($throwable)
102
    {
103
        $this->dumper->dumpThrowable($throwable);
104
    }
105
106
    /**
107
     * 运行程序
108
     *
109
     * @param Request $request
110
     * @param Response $response
111
     */
112
    public function run(Request $request, Response $response)
113
    {
114
        try {
115
            $this->prepare($request, $response);
116
            $this->debug->time('match route');
117
            $result = $this->route->match($request);
118
            $this->debug->timeEnd('match route');
119
            if ($result !== null) {
120
                $this->event->exec('application:route:match::after', [$result, $request]);
121
            }
122
            $this->debug->time('sending response');
123
            $response = $this->createResponse($result, $request, $response);
124
            if (!$response->isSend()) {
125
                $response->end();
126
            }
127
            $this->debug->info('responded with code ' . $response->getStatus());
128
            $this->debug->timeEnd('sending response');
129
        } catch (Throwable $e) {
130
            $this->debug->uncaughtException($e);
131
            $this->dumper->dumpThrowable($e);
132
            $response->sendContent($e);
133
            $response->end();
134
            $this->debug->timeEnd('sending response');
135
        }
136
        $this->debug->info('system shutdown');
137
    }
138
139
    /**
140
     * 添加请求
141
     *
142
     * @param array $method
143
     * @param string $name
144
     * @param string $url
145
     * @param array $attributes
146
     * @return void
147
     */
148
    public function request(array $method, string $name, string $url, array $attributes = [])
149
    {
150
        $route = $attributes['config'] ?? [];
151
        $runnable = null;
152
        if (array_key_exists('class', $route)) {
153
            $runnable = $this->className($route['class']) . '->onRequest';
154
        } elseif (array_key_exists('source', $route)) {
155
            $attributes['source'] = $route['source'];
156
            $runnable = FileRequestProcessor::class . '->onRequest';
157
        } elseif (array_key_exists('template', $route)) {
158
            $attributes['template'] = $route['template'];
159
            $runnable = TemplateRequestProcessor::class . '->onRequest';
160
        } elseif (array_key_exists('runnable', $route)) {
161
            $runnable = $route['runnable'];
162
        } else {
163
            throw new ConfigurationException('request config error', ConfigurationException::ERR_CONFIG_SET);
164
        }
165
        $this->route->request($method, $name, $url, $runnable, $attributes);
166
    }
167
168
    /**
169
     * 运行默认请求
170
     * @param Application $application
171
     * @param Request $request
172
     * @param Response $response
173
     * @return mixed|void
174
     * @throws Exception
175
     */
176
    protected function defaultResponse(Application $application, Request $request, Response $response)
177
    {
178
        if ((new TemplateAssetProccesser)->onRequest($application, $request, $response)) {
179
            return;
180
        }
181
        return $this->route->getDefaultRunnable()->run($request, $response);
182
    }
183
184
    /**
185
     * 运行请求
186
     * @param MatchResult|null $result
187
     * @param Request $request
188
     * @param Response $response
189
     * @return Response
190
     * @throws Exception
191
     */
192
    protected function createResponse(?MatchResult $result, Request $request, Response $response)
193
    {
194
        if (SUDA_DEBUG) {
195
            $response->setHeader('x-route', $result === null ? 'default' : $result->getName());
196
        }
197
        if ($result === null) {
198
            $content = $this->defaultResponse($this, $request, $response);
199
        } else {
200
            $content = $this->runResult($result, $request, $response);
201
        }
202
        if ($content !== null && !$response->isSend()) {
203
            $response->setContent($content);
204
        }
205
        return $response;
206
    }
207
208
    /**
209
     * 运行结果
210
     *
211
     * @param MatchResult $result
212
     * @param Request $request
213
     * @param Response $response
214
     * @return mixed
215
     * @throws ReflectionException
216
     */
217
    protected function runResult(MatchResult $result, Request $request, Response $response)
218
    {
219
        $request->mergeQueries($result->getParameter())->setAttributes($result->getMatcher()->getAttribute());
220
        $request->setAttribute('result', $result);
221
        $module = $request->getAttribute('module');
222
        if ($module && ($running = $this->find($module))) {
223
            $moduleLoader = new ModuleLoader($this, $running);
224
            $moduleLoader->toRunning();
225
        }
226
        LanguageLoader::load($this);
227
        return ($result->getRunnable())($this, $request, $response);
228
    }
229
230
    /**
231
     * 获取模板页面
232
     *
233
     * @param string $name
234
     * @param Request $request
235
     * @param string|null $default
236
     * @return ModuleTemplate
237
     */
238
    public function getTemplate(string $name, Request $request, ?string $default = null): ModuleTemplate
239
    {
240
        if ($default === null && $this->running !== null) {
241
            $default = $this->running->getFullName();
242
        } else {
243
            $default = $default ?? $request->getAttribute('module');
244
        }
245
        return new ModuleTemplate($this->getModuleSourceName($name, $default), $this, $request, $default);
246
    }
247
}
248