Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
54.35% |
50 / 92 |
|
27.27% |
3 / 11 |
CRAP | |
0.00% |
0 / 1 |
Routing | |
54.35% |
50 / 92 |
|
27.27% |
3 / 11 |
129.43 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
1 | |||
getOrCreateMiddleware | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
getOrCreateShutdownMiddleware | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
handleBeforeMiddlewareCore | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
handleBeforeMiddleware | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
handleAfterMiddleware | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
20 | |||
executeAction | |
85.19% |
23 / 27 |
|
0.00% |
0 / 1 |
5.08 | |||
executeCore | |
81.25% |
13 / 16 |
|
0.00% |
0 / 1 |
8.42 | |||
handleShutdownMiddleware | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
shutdown | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
execute | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | declare(strict_types=1); |
4 | |
5 | namespace PeServer\Core\Mvc; |
6 | |
7 | use PeServer\Core\Collection\Arr; |
8 | use PeServer\Core\DI\IDiRegisterContainer; |
9 | use PeServer\Core\Environment; |
10 | use PeServer\Core\Http\HttpHeader; |
11 | use PeServer\Core\Http\HttpMethod; |
12 | use PeServer\Core\Http\HttpRequest; |
13 | use PeServer\Core\Http\HttpResponse; |
14 | use PeServer\Core\Http\HttpStatus; |
15 | use PeServer\Core\Http\IResponsePrinterFactory; |
16 | use PeServer\Core\Http\RequestPath; |
17 | use PeServer\Core\Http\ResponsePrinter; |
18 | use PeServer\Core\Log\ILogger; |
19 | use PeServer\Core\Log\ILoggerFactory; |
20 | use PeServer\Core\Log\Logging; |
21 | use PeServer\Core\Mvc\ActionSetting; |
22 | use PeServer\Core\Mvc\ControllerArgument; |
23 | use PeServer\Core\Mvc\ControllerBase; |
24 | use PeServer\Core\Mvc\Middleware\IMiddleware; |
25 | use PeServer\Core\Mvc\Middleware\IShutdownMiddleware; |
26 | use PeServer\Core\Mvc\Middleware\MiddlewareArgument; |
27 | use PeServer\Core\Mvc\Middleware\MiddlewareResult; |
28 | use PeServer\Core\Mvc\Result\IActionResult; |
29 | use PeServer\Core\Mvc\RouteAction; |
30 | use PeServer\Core\Mvc\RouteRequest; |
31 | use PeServer\Core\Mvc\RouteSetting; |
32 | use PeServer\Core\OutputBuffer; |
33 | use PeServer\Core\ReflectionUtility; |
34 | use PeServer\Core\Store\Stores; |
35 | use PeServer\Core\Text; |
36 | |
37 | /** |
38 | * ルーティング。 |
39 | * |
40 | * 名前の割に完全に心臓部だけどWebアプリならこれが心臓でいいのか・・・? ふとももなのか。 |
41 | */ |
42 | class Routing |
43 | { |
44 | #region variable |
45 | |
46 | /** |
47 | * ルーティング設定。 |
48 | */ |
49 | protected readonly RouteSetting $setting; |
50 | |
51 | protected readonly Stores $stores; |
52 | protected readonly Environment $environment; |
53 | |
54 | protected readonly ILoggerFactory $loggerFactory; |
55 | |
56 | /** |
57 | * このルーティング内で使いまわされるDI。 |
58 | */ |
59 | protected readonly IDiRegisterContainer $serviceLocator; |
60 | |
61 | /** |
62 | * 前処理済みミドルウェア一覧。 |
63 | * |
64 | * @var IMiddleware[] |
65 | */ |
66 | private array $processedMiddleware = []; |
67 | |
68 | /** |
69 | * 終了時ミドルウェア。 |
70 | * |
71 | * 登録の逆順に実行される。 |
72 | * |
73 | * @var array<IShutdownMiddleware|class-string<IShutdownMiddleware>> |
74 | */ |
75 | private array $shutdownMiddleware = []; |
76 | |
77 | /** |
78 | * 終了処理時に使用する要求データ。 |
79 | * |
80 | * 要求受付前はダミー値が入っており、要求受付後はその要求値が格納される。 |
81 | * |
82 | * @var HttpRequest |
83 | */ |
84 | private HttpRequest $shutdownRequest; |
85 | |
86 | protected readonly HttpMethod $requestMethod; |
87 | protected readonly RequestPath $requestPath; |
88 | protected readonly HttpHeader $requestHeader; |
89 | |
90 | protected readonly IResponsePrinterFactory $responsePrinterFactory; |
91 | |
92 | #endregion |
93 | |
94 | /** |
95 | * 生成。 |
96 | * |
97 | * @param RouteRequest $routeRequest |
98 | * @param RouteSetting $routeSetting |
99 | * @param Stores $stores |
100 | */ |
101 | public function __construct(RouteRequest $routeRequest, RouteSetting $routeSetting, Stores $stores, Environment $environment, IResponsePrinterFactory $responsePrinterFactory, ILoggerFactory $loggerFactory, IDiRegisterContainer $serviceLocator) |
102 | { |
103 | $this->requestMethod = $routeRequest->method; |
104 | $this->requestPath = $routeRequest->path; |
105 | $this->setting = $routeSetting; |
106 | $this->stores = $stores; |
107 | $this->environment = $environment; |
108 | $this->responsePrinterFactory = $responsePrinterFactory; |
109 | $this->loggerFactory = $loggerFactory; |
110 | $this->serviceLocator = $serviceLocator; |
111 | |
112 | $this->requestHeader = $this->stores->special->getRequestHeader(); |
113 | $this->shutdownRequest = new HttpRequest($this->stores->special, $this->requestMethod, $this->requestHeader, []); |
114 | $this->serviceLocator->registerValue($this->shutdownRequest); |
115 | } |
116 | |
117 | #region function |
118 | |
119 | /** |
120 | * ミドルウェア取得。 |
121 | * |
122 | * @param IMiddleware|class-string<IMiddleware> $middleware |
123 | * @return IMiddleware |
124 | */ |
125 | protected function getOrCreateMiddleware(IMiddleware|string $middleware): IMiddleware |
126 | { |
127 | if (is_string($middleware)) { |
128 | /** @var IMiddleware */ |
129 | $middleware = $this->serviceLocator->new($middleware); |
130 | } |
131 | |
132 | return $middleware; |
133 | } |
134 | |
135 | /** |
136 | * 応答完了ミドルウェア取得。 |
137 | * |
138 | * @param IShutdownMiddleware|class-string<IShutdownMiddleware> $middleware |
139 | * @return IShutdownMiddleware |
140 | */ |
141 | protected function getOrCreateShutdownMiddleware(IShutdownMiddleware|string $middleware): IShutdownMiddleware |
142 | { |
143 | if (is_string($middleware)) { |
144 | /** @var IShutdownMiddleware */ |
145 | $middleware = $this->serviceLocator->new($middleware); |
146 | } |
147 | |
148 | return $middleware; |
149 | } |
150 | |
151 | /** |
152 | * ミドルウェア単独処理。 |
153 | * |
154 | * @param RequestPath $requestPath |
155 | * @param HttpRequest $request |
156 | * @param IMiddleware|class-string<IMiddleware> $middleware |
157 | * @return bool 次のミドルウェアを実行してよいか |
158 | */ |
159 | private function handleBeforeMiddlewareCore(RequestPath $requestPath, HttpRequest $request, IMiddleware|string $middleware): bool |
160 | { |
161 | $middlewareArgument = new MiddlewareArgument($requestPath, $this->stores, $this->environment, $request); |
162 | $middleware = self::getOrCreateMiddleware($middleware); |
163 | |
164 | $middlewareResult = $middleware->handleBefore($middlewareArgument); |
165 | |
166 | if ($middlewareResult->canNext()) { |
167 | $this->processedMiddleware[] = $middleware; |
168 | return true; |
169 | } |
170 | |
171 | $middlewareResult->apply(); |
172 | return false; |
173 | } |
174 | |
175 | /** |
176 | * ミドルウェア(事前)をグワーッと処理。 |
177 | * |
178 | * @param array<IMiddleware|class-string<IMiddleware>> $middleware |
179 | * @param HttpRequest $request |
180 | * @return bool 後続処理は可能か |
181 | */ |
182 | protected function handleBeforeMiddleware(array $middleware, HttpRequest $request): bool |
183 | { |
184 | foreach ($middleware as $middlewareItem) { |
185 | $canNext = $this->handleBeforeMiddlewareCore($this->requestPath, $request, $middlewareItem); |
186 | if (!$canNext) { |
187 | return false; |
188 | } |
189 | } |
190 | |
191 | return true; |
192 | } |
193 | |
194 | /** |
195 | * ミドルウェア(事後)をグワーッと処理。 |
196 | * |
197 | * @param HttpRequest $request |
198 | * @param HttpResponse $response |
199 | * @return bool |
200 | */ |
201 | protected function handleAfterMiddleware(HttpRequest $request, HttpResponse $response): bool |
202 | { |
203 | if (!Arr::getCount($this->processedMiddleware)) { |
204 | return true; |
205 | } |
206 | |
207 | $middlewareArgument = new MiddlewareArgument($this->requestPath, $this->stores, $this->environment, $request); |
208 | |
209 | $middleware = Arr::reverse($this->processedMiddleware); |
210 | foreach ($middleware as $middlewareItem) { |
211 | $middlewareResult = $middlewareItem->handleAfter($middlewareArgument, $response); |
212 | |
213 | if (!$middlewareResult->canNext()) { |
214 | $middlewareResult->apply(); |
215 | return false; |
216 | } |
217 | } |
218 | |
219 | return true; |
220 | } |
221 | |
222 | /** |
223 | * アクション実行。 |
224 | * |
225 | * @param string $rawControllerName |
226 | * @param ActionSetting $actionSetting |
227 | * @param array<non-empty-string,string> $urlParameters |
228 | * @return void |
229 | */ |
230 | private function executeAction(string $rawControllerName, ActionSetting $actionSetting, array $urlParameters): void |
231 | { |
232 | $splitNames = Text::split($rawControllerName, '/'); |
233 | /** @phpstan-var class-string<ControllerBase> */ |
234 | $controllerName = $splitNames[Arr::getCount($splitNames) - 1]; |
235 | |
236 | // HTTPリクエストデータをDI再登録 |
237 | $request = new HttpRequest($this->stores->special, $this->requestMethod, $this->requestHeader, $urlParameters); |
238 | $this->serviceLocator->registerValue($request); |
239 | $this->shutdownRequest = $request; |
240 | |
241 | // アクション共通ミドルウェア処理 |
242 | $this->shutdownMiddleware += $this->setting->actionShutdownMiddleware; |
243 | if (!$this->handleBeforeMiddleware($this->setting->actionMiddleware, $request)) { |
244 | return; |
245 | } |
246 | |
247 | // アクションに紐づくミドルウェア処理 |
248 | $this->shutdownMiddleware += $actionSetting->shutdownMiddleware; |
249 | if (!$this->handleBeforeMiddleware($actionSetting->actionMiddleware, $request)) { |
250 | return; |
251 | } |
252 | |
253 | $logger = $this->loggerFactory->createLogger($controllerName); |
254 | $controllerArgument = $this->serviceLocator->new(ControllerArgument::class, [Stores::class => $this->stores, ILogger::class => $logger]); |
255 | |
256 | /** @var IActionResult|null */ |
257 | $actionResult = null; |
258 | $output = OutputBuffer::get(function () use ($controllerArgument, $controllerName, $actionSetting, &$actionResult) { |
259 | /** @var ControllerBase */ |
260 | $controller = $this->serviceLocator->new($controllerName, [ControllerArgument::class => $controllerArgument]); |
261 | $methodName = $actionSetting->controllerMethod; |
262 | /** @var IActionResult */ |
263 | $actionResult = $this->serviceLocator->call([$controller, $methodName]); //@phpstan-ignore-line callable |
264 | }); |
265 | // 標準出力は闇に葬る |
266 | if ($output->count()) { |
267 | $logger->warn('{0}', $output->raw); |
268 | } |
269 | |
270 | $this->stores->apply(); |
271 | |
272 | // 最終出力 |
273 | /** @var IActionResult $actionResult */ |
274 | $response = $actionResult->createResponse(); |
275 | if (!$this->handleAfterMiddleware($request, $response)) { |
276 | return; |
277 | } |
278 | |
279 | $printer = $this->serviceLocator->new(ResponsePrinter::class, [$request, $response]); |
280 | $printer->execute(); |
281 | } |
282 | |
283 | /** |
284 | * メソッド・パスから登録されている処理を実行。 |
285 | */ |
286 | private function executeCore(): void |
287 | { |
288 | $this->shutdownMiddleware += $this->setting->globalShutdownMiddleware; |
289 | |
290 | // グローバルミドルウェアの適用 |
291 | if (Arr::getCount($this->setting->globalMiddleware)) { |
292 | if (!$this->handleBeforeMiddleware($this->setting->globalMiddleware, $this->shutdownRequest)) { |
293 | return; |
294 | } |
295 | } |
296 | |
297 | /** @var RouteAction|null */ |
298 | $errorAction = null; |
299 | foreach ($this->setting->routes as $route) { |
300 | $action = $route->getAction($this->requestMethod, $this->requestPath); |
301 | if ($action !== null) { |
302 | if ($action->status === HttpStatus::None) { |
303 | $this->executeAction($action->className, $action->actionSetting, $action->params); |
304 | return; |
305 | } elseif ($errorAction === null) { |
306 | $errorAction = $action; |
307 | } |
308 | } |
309 | } |
310 | |
311 | if ($errorAction === null) { |
312 | MiddlewareResult::error(HttpStatus::InternalServerError)->apply(); |
313 | } else { |
314 | MiddlewareResult::error($errorAction->status)->apply(); |
315 | } |
316 | } |
317 | |
318 | protected function handleShutdownMiddleware(): void |
319 | { |
320 | if (Arr::getCount($this->shutdownMiddleware)) { |
321 | $middlewareArgument = new MiddlewareArgument($this->requestPath, $this->stores, $this->environment, $this->shutdownRequest); |
322 | |
323 | $shutdownMiddleware = array_reverse($this->shutdownMiddleware); |
324 | foreach ($shutdownMiddleware as $middleware) { |
325 | $middleware = self::getOrCreateShutdownMiddleware($middleware); |
326 | $middleware->handleShutdown($middlewareArgument); |
327 | } |
328 | } |
329 | } |
330 | |
331 | /** |
332 | * 終了処理。 |
333 | */ |
334 | private function shutdown(): void |
335 | { |
336 | $this->handleShutdownMiddleware(); |
337 | } |
338 | |
339 | /** |
340 | * メソッド・パスから登録されている処理を実行。 |
341 | */ |
342 | public function execute(): void |
343 | { |
344 | try { |
345 | $this->executeCore(); |
346 | } finally { |
347 | $this->shutdown(); |
348 | } |
349 | } |
350 | |
351 | #endregion |
352 | } |