Passed
Push — dev ( 1930e5...4afce3 )
by 世昌
02:32
created

Application::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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