Application::createResponse()   B
last analyzed

Complexity

Conditions 7
Paths 12

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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