From f11ba92fca13c3ceed5c06c0838fa59440567253 Mon Sep 17 00:00:00 2001 From: "Don Wilson (local webserver)" Date: Fri, 3 Nov 2023 14:08:25 -0500 Subject: [PATCH] uniformity in Response and Response-like classes --- src/Magnetar/Helpers/Facades/Response.php | 20 +- src/Magnetar/Helpers/Facades/Router.php | 4 +- src/Magnetar/Http/ExceptionHandler.php | 4 +- src/Magnetar/Http/JsonResponse.php | 39 ++-- src/Magnetar/Http/Kernel.php | 8 +- src/Magnetar/Http/RedirectResponse.php | 82 ++++---- src/Magnetar/Http/Response.php | 221 ++++++++++------------ src/Magnetar/Router/Router.php | 9 +- src/Magnetar/Template/Template.php | 20 +- src/Magnetar/_autoload.php | 17 +- 10 files changed, 198 insertions(+), 226 deletions(-) diff --git a/src/Magnetar/Helpers/Facades/Response.php b/src/Magnetar/Helpers/Facades/Response.php index 39dce76..e94e983 100644 --- a/src/Magnetar/Helpers/Facades/Response.php +++ b/src/Magnetar/Helpers/Facades/Response.php @@ -6,25 +6,23 @@ use Magnetar\Helpers\Facades\Facade; /** - * @method status(int $code=200): self + * @method responseCode(int $response_code=200): self + * @method body(string $body=''): self + * @method sendBody(): self * @method setCookie(string $name, ?string $value=null, int $expires=0, string $path='', string $domain='', bool $secure=false, bool $httponly=false): self - * @method redirect(string $path, int $response_code=302): void - * @method setBody(string $body=''): self - * @method json(mixed $body): self * @method send(): self * @method sendHeaders(): self - * @method sendBody(): self - * @method body(): string - * @method statusCode(): int * @method header(string $header, ?string $value=null, int|bool $replace=true, ?int $response_code=0): self|string - * @method setHeaders(array $headers): self * @method headers(): array - * @method getHeader(string $name): ?string - * @method hasHeader(string $name): bool + * @method setHeaders(array $headers): self * @method removeHeader(string $name): self * @method clearHeaders(): self - * @method headersSent(): bool + * @method getHeader(string $name): ?string + * @method hasHeader(string $name): bool + * @method sentHeaders(): bool * @method sent(): bool + * @method redirect(string $path, int $response_code=302): self + * @method json(mixed $body): self * * @see Magnetar\Http\Response */ diff --git a/src/Magnetar/Helpers/Facades/Router.php b/src/Magnetar/Helpers/Facades/Router.php index 6f0b7b0..c692084 100644 --- a/src/Magnetar/Helpers/Facades/Router.php +++ b/src/Magnetar/Helpers/Facades/Router.php @@ -17,7 +17,9 @@ * @method patch(string $pattern, callable|array|string|null $callback=null): Magnetar\Router\Route * @method delete(string $pattern, callable|array|string|null $callback=null): Magnetar\Router\Route * @method options(string $pattern, callable|array|string|null $callback=null): Magnetar\Router\Route - * @method group(string $pathPrefix, callable $callback): void + * @method group(string $pathPrefix, callable $callback): Magnetar\Router\RouteCollection + * @method redirect(string $pattern, string $redirect_path, int $response_code=302): Magnetar\Router\Route + * @method permanentRedirect(string $pattern, string $redirect_path): Magnetar\Router\Route * * @see Magnetar\Router\Router */ diff --git a/src/Magnetar/Http/ExceptionHandler.php b/src/Magnetar/Http/ExceptionHandler.php index d69ebb8..1fb510c 100644 --- a/src/Magnetar/Http/ExceptionHandler.php +++ b/src/Magnetar/Http/ExceptionHandler.php @@ -49,7 +49,7 @@ public function render(Request $request, Throwable $e): Response { $response_status = 404; } - $response = (new Response())->status($response_status)->setBody( + return (new Response())->responseCode($response_status)->body( $this->app->make('theme')->tpl($theme_file, [ 'request' => $request, 'message' => $e->getMessage(), @@ -58,7 +58,5 @@ public function render(Request $request, Throwable $e): Response { 'trace' => $e->getTraceAsString() ]) ); - - return $response; } } \ No newline at end of file diff --git a/src/Magnetar/Http/JsonResponse.php b/src/Magnetar/Http/JsonResponse.php index eb4f41c..2b5c0ed 100644 --- a/src/Magnetar/Http/JsonResponse.php +++ b/src/Magnetar/Http/JsonResponse.php @@ -6,36 +6,35 @@ use Magnetar\Http\Response; class JsonResponse extends Response { + /** + * Constructor method + * + * @note Sets the default Content-Type header to application/json + */ + public function __construct() { + parent::__construct(); + + // set default content type to JSON + $this->header('Content-Type', 'application/json'); + } + /** * Set the response data. This will be encoded to JSON * @param mixed $body * @return self + * + * @note Intentionally overrides the json method in the Response class */ - public function setData(mixed $data): self { - $this->body = json_encode($data); - - return $this; + public function json(mixed $data): self { + return $this->body( json_encode($data) ); } /** * Set the raw JSON response body. Expects a JSON string - * @param string $json_body + * @param string $json The JSON string to set as the response body * @return self */ - public function setJson(string $json): self { - $this->body = $json; - - return $this; - } - - public function send(): self { - $this->header('Content-Type', 'application/json; charset=UTF-8', true); - - $this->sendHeaders(); - $this->sendBody(); - - $this->sent = true; - - return $this; + public function rawJson(string $json): self { + return $this->body($json); } } \ No newline at end of file diff --git a/src/Magnetar/Http/Kernel.php b/src/Magnetar/Http/Kernel.php index c3d3e6e..4484494 100644 --- a/src/Magnetar/Http/Kernel.php +++ b/src/Magnetar/Http/Kernel.php @@ -192,7 +192,7 @@ protected function parseMiddlewareString(string $middleware): array { */ protected function panic(Exception $e): Response { // send 503 response - $response = (new Response())->status(503)->setBody( + return (new Response())->responseCode(503)->body( $this->app->make('theme')->tpl('errors/503', [ 'message' => $e->getMessage(), 'file' => $e->getFile(), @@ -200,8 +200,6 @@ protected function panic(Exception $e): Response { 'trace' => $e->getTraceAsString() ]) ); - - return $response; } /** @@ -211,13 +209,11 @@ protected function panic(Exception $e): Response { */ public function handle404(string $message=''): Response { // send 404 response - $response = (new Response())->status(404)->setBody( + return (new Response())->responseCode(404)->body( $this->app->make('theme')->tpl('errors/404', [ 'message' => $message ]) ); - - return $response; } /** diff --git a/src/Magnetar/Http/RedirectResponse.php b/src/Magnetar/Http/RedirectResponse.php index 863c8f3..6974663 100644 --- a/src/Magnetar/Http/RedirectResponse.php +++ b/src/Magnetar/Http/RedirectResponse.php @@ -19,25 +19,17 @@ class RedirectResponse extends Response { protected string|null $url = null; /** - * The HTTP Response Code + * How many seconds to delay the redirect, showing a "Redirecting..." page during instead of an instant redirect * @var int */ - protected int $statusCode = 307; + protected int $delay_seconds = 0; /** - * How long to delay the redirect in seconds, showing a "Redirecting..." page during the delay - * @var int - */ - protected int $delaySeconds = 0; - - /** - * Set the redirect URL - * @param string $url The URL to redirect to. If it does not start with http:// or https://, it will be prepended with the current domain + * Simplified alias for setURL() + * @param string $url The URL to redirect to * @return self - * - * @throws InvalidRedirectURLException */ - public function setURL(string $url): self { + public function to(string $url): self { if(!preg_match('/^https?:\/\//', $url)) { $url = URL::to($url); } @@ -52,27 +44,28 @@ public function setURL(string $url): self { } /** - * Simplified alias for setURL() - * @param string $url The URL to redirect to + * Set the redirect URL + * @param string $url The URL to redirect to. If it does not start with http:// or https://, it will be prepended with the current domain * @return self + * + * @throws InvalidRedirectURLException */ - public function to(string $url): self { - return $this->setURL($url); + public function url(string $url): self { + return $this->to($url); } /** - * Set the response code - * @param int $code The HTTP Response Code for the redirect. Only allows 301, 302, and 307. Defaults to 307 - * @return self + * {@inheritDoc} * * @throws InvalidResponseCodeException */ - public function setCode(int $code=307): self { - if(!in_array($code, [301, 302, 307])) { + public function responseCode(int $response_code=307): self { + // sanitize response code + if(!in_array($response_code, [300, 301, 302, 303, 304, 307, 308])) { throw new InvalidResponseCodeException('Invalid redirect code'); } - return $this; + return parent::responseCode($response_code); } /** @@ -80,9 +73,7 @@ public function setCode(int $code=307): self { * @return self */ public function permanent(): self { - $this->statusCode = 301; - - return $this; + return $this->responseCode(301); } /** @@ -90,9 +81,7 @@ public function permanent(): self { * @return self */ public function temporary(): self { - $this->statusCode = 307; - - return $this; + return $this->responseCode(307); } /** @@ -101,7 +90,7 @@ public function temporary(): self { * @return self */ public function delay(int $seconds=0): self { - $this->delaySeconds = abs($seconds); + $this->delay_seconds = abs($seconds); return $this; } @@ -110,37 +99,38 @@ public function delay(int $seconds=0): self { * Resets the delay to the default of 0 seconds * @return self */ - public function nodelay(): self { - $this->delaySeconds = 0; + public function noDelay(): self { + $this->delay_seconds = 0; return $this; } /** * {@inheritDoc} + * + * @throws InvalidRedirectURLException */ public function send(): self { // check for valid URL if(null === $this->url) { - throw new \Exception('No URL set'); + throw new InvalidRedirectURLException('No URL set'); } // send the redirect header if there is no delay - if(0 === $this->delaySeconds) { - $this->header('Location', $this->url, true, $this->statusCode); + if(0 === $this->delay_seconds) { + $this->header( + 'Location', + $this->url, + true, + $this->response_code + ); } // set body to a basic redirect page - $this->setBody( - $this->generateRedirecPage($this->url) - ); + $this->body( $this->generateRedirecHTMLPage($this->url) ); - $this->sendHeaders(); - $this->sendBody(); - - $this->sent = true; - - return $this; + // send the response + return parent::send(); } /** @@ -148,7 +138,7 @@ public function send(): self { * @param string|null $url The URL to redirect to. Defaults to the URL set in the response * @return string */ - protected function generateRedirecPage(string|null $url=null): string { + protected function generateRedirecHTMLPage(string|null $url=null): string { // sanitize url $url ??= $this->url; @@ -161,7 +151,7 @@ protected function generateRedirecPage(string|null $url=null): string { Redirecting... - + diff --git a/src/Magnetar/Http/Response.php b/src/Magnetar/Http/Response.php index ea59c06..c2fa7ad 100644 --- a/src/Magnetar/Http/Response.php +++ b/src/Magnetar/Http/Response.php @@ -6,10 +6,34 @@ use Magnetar\Http\HeaderCollection; class Response { + /** + * The response headers + * @var HeaderCollection + */ protected HeaderCollection $headers; - protected int $statusCode = 200; + + /** + * The HTTP Response Code + * @var int + */ + protected int $response_code = 200; + + /** + * The response body + * @var string + */ protected string $body = ''; - protected bool $headersSent = false; + + /** + * Whether the response headers have been sent + * @var bool + */ + protected bool $sent_headers = false; + + /** + * Whether the response has been sent (headers and body) + * @var bool + */ protected bool $sent = false; /** @@ -24,11 +48,34 @@ public function __construct() { /** * Set the HTTP Response Code - * @param int $code The HTTP Response Code. Defaults to 200 + * @param int $response_code The HTTP Response Code. Defaults to 200 + * @return self + */ + public function responseCode(int $response_code=200): self { + $this->response_code = $response_code; + + return $this; + } + + /** + * Set the response body + * @param string $body The response body * @return self */ - public function status(int $code=200): self { - $this->statusCode = $code; + public function body(string $body=''): self { + $this->body = $body; + + return $this; + } + + /** + * Print the response body to the output buffer + * @return self + */ + public function sendBody(): self { + print $this->body; + + $this->sent = true; return $this; } @@ -64,57 +111,6 @@ public function setCookie( return $this; } - /** - * Redirect to a specific URL - */ - public function redirect(string $path, int $response_code=302): void { - // sanitize response code - if(!in_array($response_code, [300, 301, 302, 303, 304, 307, 308])) { - $response_code = 302; - } - - // sanitize path - if(!preg_match("#^https?\://#si", $path)) { - //$path = ABS_URI . $path; - $path = config('app.url') . $path; - } - - // send location header - $this->header( - 'Location', - $path, - true, - $response_code - ); - } - - /** - * Set the response body - * @param string $body - * @return self - */ - public function setBody(string $body=''): self { - $this->body = $body; - - return $this; - } - - /** - * Set JSON header and prints JSON response - * @param mixed $body The JSON body to print - * @return void - */ - public function json(mixed $body): self { - // @TODO turn into a factory method that clones itself and returns - // a JsonResponse object instead - - $this->header('Content-Type', 'application/json'); - - $this->setBody(json_encode($body)); - - return $this; - } - /** * Send the response to the client * @return self @@ -141,45 +137,17 @@ public function sendHeaders(): self { } // send status code - http_response_code($this->statusCode); + http_response_code($this->response_code); // send headers $this->headers->send(); // mark headers as sent - $this->headersSent = true; - - return $this; - } - - /** - * Print the response body to the output buffer - * @return self - */ - public function sendBody(): self { - print $this->body; - - $this->sent = true; + $this->sent_headers = true; return $this; } - /** - * Get the response body - * @return string - */ - public function body(): string { - return $this->body; - } - - /** - * Get the response status code - * @return int - */ - public function statusCode(): int { - return $this->statusCode; - } - /** * Get or set a single header * @param string $header The header to set @@ -207,13 +175,21 @@ public function header( return $this; } + /** + * Get the response headers + * @return array + */ + public function headers(): array { + return $this->headers->all(); + } + /** * Set multiple headers at once. Note, this will replace any existing headers with the same name * @param array $headers An associative array of headers to set. Key is the header name, value is the header value * @return self */ public function setHeaders(array $headers): self { - if($this->headersSent) { + if($this->sent_headers) { return $this; } @@ -224,39 +200,13 @@ public function setHeaders(array $headers): self { return $this; } - /** - * Get the response headers - * @return array - */ - public function headers(): array { - return $this->headers->all(); - } - - /** - * Get a response header by name - * @param string $name The header name - * @return string|null - */ - public function getHeader(string $name): ?string { - return $this->headers->get($name); - } - - /** - * Check if a response header exists - * @param string $name The header name - * @return bool - */ - public function hasHeader(string $name): bool { - return $this->headers->has($name); - } - /** * Remove a response header by name * @param string $name The header name * @return self */ public function removeHeader(string $name): self { - if($this->headersSent) { + if($this->sent_headers) { return $this; } @@ -270,7 +220,7 @@ public function removeHeader(string $name): self { * @return self */ public function clearHeaders(): self { - if($this->headersSent) { + if($this->sent_headers) { return $this; } @@ -279,12 +229,30 @@ public function clearHeaders(): self { return $this; } + /** + * Get a response header by name + * @param string $name The header name + * @return string|null + */ + public function getHeader(string $name): ?string { + return $this->headers->get($name); + } + + /** + * Check if a response header exists + * @param string $name The header name + * @return bool + */ + public function hasHeader(string $name): bool { + return $this->headers->has($name); + } + /** * Check if the response headers have been sent * @return bool */ - public function headersSent(): bool { - return $this->headersSent; + public function sentHeaders(): bool { + return $this->sent_headers; } /** @@ -294,4 +262,23 @@ public function headersSent(): bool { public function sent(): bool { return $this->sent; } + + /** + * Redirect to a specific URL + * @param string $path The URL to redirect to + * @param int $response_code Optional. HTTP status code. Defaults to 302 + * @return self + */ + public function redirect(string $path, int $response_code=302): self { + return (new RedirectResponse($path, $response_code))->responseCode($response_code); + } + + /** + * Set JSON header and prints JSON response + * @param mixed $body The JSON body to print + * @return self + */ + public function json(mixed $body): self { + return (new JsonResponse)->json($body); + } } \ No newline at end of file diff --git a/src/Magnetar/Router/Router.php b/src/Magnetar/Router/Router.php index 5d913c7..b03019a 100644 --- a/src/Magnetar/Router/Router.php +++ b/src/Magnetar/Router/Router.php @@ -351,14 +351,14 @@ public function group(string $pathPrefix, callable $callback): RouteCollection { * Assign a redirect rule for a given path * @param string $pattern The pattern to match against * @param string $redirect_path The URI to redirect to - * @param int $status The HTTP status code to use. Defaults to 302 + * @param int $response_code The HTTP response code to use. Defaults to 302 * @return Route */ - public function redirect(string $pattern, string $redirect_path, int $status=302): Route { + public function redirect(string $pattern, string $redirect_path, int $response_code=302): Route { return $this->assignRoute( null, $pattern, - fn() => $this->container->instance('response', (new RedirectResponse)->to($redirect_path)->status($status)) + fn() => $this->container->instance('response', (new RedirectResponse)->responseCode($response_code)->to($redirect_path)) ); } @@ -366,14 +366,13 @@ public function redirect(string $pattern, string $redirect_path, int $status=302 * Assign a permanent redirect (301) rule for a given path * @param string $pattern The pattern to match against * @param string $redirect_path The URI to redirect to - * @param int $status The HTTP status code to use * @return Route */ public function permanentRedirect(string $pattern, string $redirect_path): Route { return $this->assignRoute( null, $pattern, - fn() => $this->container->instance('response', (new RedirectResponse)->to($redirect_path)->permanent()) + fn() => $this->container->instance('response', (new RedirectResponse)->permanent()->to($redirect_path)) ); } diff --git a/src/Magnetar/Template/Template.php b/src/Magnetar/Template/Template.php index 5911278..a0de371 100644 --- a/src/Magnetar/Template/Template.php +++ b/src/Magnetar/Template/Template.php @@ -83,23 +83,23 @@ public function render(string $tpl_name, array $view_data=[]): string { } /** - * Return an HTTP response of a rendered template + * Render and template and print results * @param string $tpl_name The template name to render - * @param array $view_data The data to pass to the template - * @return Response + * @return void */ - public function renderResponse(string $tpl_name, array $view_data=[]): Response { - // generate a response by generating a rendered template - return (new Response())->setBody($this->render($tpl_name, $view_data)); + public function display(string $tpl_name): void { + print $this->render($tpl_name); } /** - * Render and template and print results + * Return an HTTP response of a rendered template * @param string $tpl_name The template name to render - * @return void + * @param array $view_data The data to pass to the template + * @return Response */ - public function display(string $tpl_name): void { - print $this->render($tpl_name); + public function renderResponse(string $tpl_name, array $view_data=[]): Response { + // generate a response by generating a rendered template + return (new Response())->body($this->render($tpl_name, $view_data)); } /** diff --git a/src/Magnetar/_autoload.php b/src/Magnetar/_autoload.php index ef3542c..db341be 100644 --- a/src/Magnetar/_autoload.php +++ b/src/Magnetar/_autoload.php @@ -173,10 +173,10 @@ function env(string $key, mixed $default=null): mixed { /** * Make a JSON Response instance with the specified data * @param mixed $data Data to set for the response - * @return \Magnetar\Http\JsonResponse + * @return JsonResponse */ function json(mixed $data): JsonResponse { - return (new JsonResponse())->setData($data); + return (new JsonResponse)->json($data); } } @@ -215,10 +215,13 @@ function public_path(string $rel_path=''): string { /** * Make a Redirect Response instance and tell it to redirect to the specified URL * @param string $url URL to redirect to + * @param int $response_code Optional. HTTP status code. Defaults to 307. Any non-3xx code will throw an exception * @return RedirectResponse + * + * @throws \Magnetar\Http\Exceptions\InvalidRedirectURLException */ - function redirect(string $url): RedirectResponse { - return (new RedirectResponse())->setURL($url); + function redirect(string $url, int $response_code=307): RedirectResponse { + return (new RedirectResponse)->responseCode($response_code)->to($url); } } @@ -226,16 +229,16 @@ function redirect(string $url): RedirectResponse { /** * Make a Response instance and set various properties * @param string $body Response body - * @param int $status_code Optional. HTTP status code. Defaults to 200 + * @param int $response_code Optional. HTTP status code. Defaults to 200 * @param array $headers Optional. HTTP headers to set. Defaults to an empty array * @return Response */ function response( string $body, - int $status_code=200, + int $response_code=200, array $headers=[] ): Response { - return (new Response())->setBody($body)->status($status_code)->setHeaders($headers); + return (new Response)->responseCode($response_code)->setHeaders($headers)->body($body); } }