Skip to content

Commit 951a23d

Browse files
committed
feature: handle errors with custom pages
1 parent e5be7aa commit 951a23d

16 files changed

+326
-113
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"phpgt/domvalidation": "^1.0",
3030
"phpgt/fetch": "^1.2",
3131
"phpgt/filecache": "^1.2",
32-
"phpgt/http": "^1.2",
32+
"phpgt/http": "^1.3.5",
3333
"phpgt/input": "^1.3",
3434
"phpgt/logger": "^1.0",
3535
"phpgt/promise": "^2.4",

composer.lock

Lines changed: 24 additions & 24 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config.default.ini

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
[app]
22
namespace=App
3+
production=false
34
class_dir=class
45
service_loader=ServiceLoader
56
slow_delta=0.25
@@ -12,6 +13,7 @@ globals_whitelist_post=
1213
globals_whitelist_files=
1314
globals_whitelist_cookies=
1415
force_trailing_slash=true
16+
error_page_dir=page/_error
1517

1618
[router]
1719
router_file=router.php
@@ -24,6 +26,8 @@ component_directory=page/_component
2426
partial_directory=page/_partial
2527

2628
[logger]
29+
log_all_requests=true
30+
debug_to_javascript=true
2731
type=stdout
2832
level=debug
2933
path=
@@ -35,7 +39,7 @@ newline=\n
3539
[session]
3640
handler=Gt\Session\FileHandler
3741
path=phpgt/session
38-
name=WebEngineSession
42+
name=GT
3943

4044
[database]
4145
driver=sqlite

go.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* lifecycle in the documentation:
88
* https://github.com/PhpGt/WebEngine/wiki/From-request-to-response
99
*/
10-
use Gt\WebEngine\Application;
10+
use GT\WebEngine\Application;
1111

1212
chdir(dirname($_SERVER["DOCUMENT_ROOT"]));
1313
ini_set("display_errors", "on");
@@ -67,5 +67,3 @@
6767
if(file_exists("teardown.php")) {
6868
require("teardown.php");
6969
}
70-
71-
die("go complete!");

router.default.php

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?php
2-
namespace Gt\WebEngine;
2+
namespace GT\WebEngine;
33

44
use Gt\Http\Request;
55
use Gt\Routing\BaseRouter;
@@ -84,8 +84,15 @@ public function page(Request $request):void {
8484
? -1
8585
: 0);
8686

87+
if($this->errorStatus) {
88+
$uriPath = "_error/$this->errorStatus";
89+
}
90+
else {
91+
$uriPath = $request->getUri()->getPath();
92+
}
93+
8794
$matchingLogics = $pathMatcher->findForUriPath(
88-
$request->getUri()->getPath(),
95+
$uriPath,
8996
"page",
9097
"php"
9198
);
@@ -95,7 +102,7 @@ public function page(Request $request):void {
95102
}
96103

97104
$matchingViews = $pathMatcher->findForUriPath(
98-
$request->getUri()->getPath(),
105+
$uriPath,
99106
"page",
100107
"html"
101108
);

src/Application.php

Lines changed: 75 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
namespace GT\WebEngine;
33

44
use Closure;
5+
use Gt\Http\ResponseStatusException\ClientError\ClientErrorException;
6+
use Gt\Http\ResponseStatusException\ResponseStatusException;
7+
use Gt\Logger\Log;
58
use Throwable;
69
use ErrorException;
710
use GT\WebEngine\Debug\OutputBuffer;
@@ -32,6 +35,7 @@ class Application {
3235
private Timer $timer;
3336
private OutputBuffer $outputBuffer;
3437
private RequestFactory $requestFactory;
38+
private ServerRequest $request;
3539
/** @var array<string, array<string, string|array<string, string>>> */
3640
private array $globals;
3741
private Protection $globalProtection;
@@ -62,7 +66,9 @@ public function __construct(
6266
$this->config->getFloat("app.slow_delta"),
6367
$this->config->getFloat("app.very_slow_delta"),
6468
);
65-
$this->outputBuffer = $outputBuffer ?? new OutputBuffer();
69+
$this->outputBuffer = $outputBuffer ?? new OutputBuffer(
70+
$this->config->getBool("logger.debug_to_javascript")
71+
);
6672
$this->requestFactory = $requestFactory ?? new RequestFactory();
6773
$this->dispatcherFactory = $dispatcherFactory ?? new DispatcherFactory();
6874
$this->globals = array_merge([
@@ -103,8 +109,7 @@ public function start():void {
103109
// $_GET contains query parameters from the URL, and $_POST contains form data.
104110
// These arrays are optional and will default to empty arrays if not provided,
105111
// ensuring the ServerRequest can always be constructed safely.
106-
/** @var ServerRequest $request */
107-
$request = $this->requestFactory->createServerRequestFromGlobalState(
112+
$this->request = $this->requestFactory->createServerRequestFromGlobalState(
108113
$this->globals["_SERVER"] ?? [],
109114
$this->globals["_FILES"] ?? [],
110115
$this->globals["_GET"] ?? [],
@@ -125,7 +130,7 @@ public function start():void {
125130
// the application's error templates and logging mechanisms.
126131
$this->dispatcher = $this->dispatcherFactory->create(
127132
$this->config,
128-
$request,
133+
$this->request,
129134
$this->globals,
130135
$this->finish(...),
131136
);
@@ -134,10 +139,27 @@ public function start():void {
134139
$response = $this->dispatcher->generateResponse();
135140
}
136141
catch(Throwable $throwable) {
137-
var_dump($throwable);
138-
die("ERRRRRRRRRRRRRRRRRRR");
139142
$this->logError($throwable);
140-
$response = $this->dispatcher->generateErrorResponse($throwable);
143+
$errorStatus = 500;
144+
145+
if($throwable instanceof ResponseStatusException) {
146+
$errorStatus = $throwable->getHttpCode();
147+
}
148+
149+
$this->dispatcher = $this->dispatcherFactory->create(
150+
$this->config,
151+
$this->request,
152+
$this->globals,
153+
$this->finish(...),
154+
$errorStatus,
155+
);
156+
157+
try {
158+
$response = $this->dispatcher->generateErrorResponse($throwable);
159+
}
160+
catch(Throwable $innerThrowable) {
161+
$response = $this->dispatcher->generateBasicErrorResponse($throwable, $innerThrowable);
162+
}
141163
}
142164

143165
$this->finish($response);
@@ -156,12 +178,19 @@ private function finish(
156178
$response->getHeaders(),
157179
);
158180

159-
// If there's any content in the output buffer, render it to the developer console/log instead of the page.
160-
$this->outputBuffer->debugOutput();
181+
if($this->config->getBool("logger.log_all_requests")) {
182+
Log::info(
183+
"HTTP " . $response->getStatusCode(),
184+
$this->getLogContext(),
185+
);
186+
}
161187

162188
/** @var Stream $responseBody */
163189
$responseBody = $response->getBody();
164-
$this->outputResponseBody($responseBody);
190+
$this->outputResponseBody(
191+
$responseBody,
192+
$this->outputBuffer->debugOutput(),
193+
);
165194

166195
$this->timer->stop();
167196
$this->timer->logDelta();
@@ -274,7 +303,32 @@ private function handleShutdown():void {
274303
}
275304

276305
private function logError(Throwable $throwable):void {
277-
// TODO: implement
306+
if($throwable instanceof ClientErrorException) {
307+
return;
308+
}
309+
310+
Log::error($throwable);
311+
}
312+
313+
/** @return array<string, string> */
314+
private function getLogContext():array {
315+
$uri = $this->request->getUri();
316+
$uriPath = $uri->getPath();
317+
$uriQuery = $this->request->getQueryParams();
318+
$postBody = $this->request->getParsedBody();
319+
320+
$context = [
321+
"id" => $this->request->getServerParams()["REMOTE_ADDR"] . ":" . substr(session_id(), 0, 4),
322+
"uri" => $uriPath,
323+
];
324+
if($uriQuery) {
325+
$context["query"] = $uriQuery;
326+
}
327+
if($postBody) {
328+
$context["post"] = $postBody;
329+
}
330+
331+
return $context;
278332
}
279333

280334
/** @param array<string, array<string>> $headers */
@@ -301,13 +355,21 @@ private function outputHeaders(int $statusCode, array $headers):void {
301355
* `ob_*` functions are used here to ensure that the response body is
302356
* flushed and doesn't get rendered into another open buffer.
303357
*/
304-
private function outputResponseBody(Stream $responseBody):void {
358+
private function outputResponseBody(Stream $responseBody, ?string $debugScript = null):void {
305359
$length = $this->config->getInt("app.render_buffer_size");
306360

307361
$responseBody->rewind();
308362
ob_start();
309363
while(!$responseBody->eof()) {
310-
echo $responseBody->read($length);
364+
$content = $responseBody->read($length);
365+
if($debugScript) {
366+
$closingBody = strpos($content, "</body>");
367+
if(false !== $closingBody) {
368+
$content = substr_replace($content, $debugScript, $closingBody, 0);
369+
}
370+
}
371+
echo $content;
372+
311373
ob_flush();
312374
flush();
313375
}

0 commit comments

Comments
 (0)