Passed
Push — dev ( 8b4395...7454ac )
by 世昌
02:34
created

Application::createResponse()   B

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