diff --git a/flight/Engine.php b/flight/Engine.php index 62342b6c..2271b39f 100644 --- a/flight/Engine.php +++ b/flight/Engine.php @@ -314,7 +314,7 @@ public function after(string $name, callable $callback): void */ public function get(?string $key = null) { - if (null === $key) { + if ($key === null) { return $this->vars; } @@ -360,7 +360,7 @@ public function has(string $key): bool */ public function clear(?string $key = null): void { - if (null === $key) { + if ($key === null) { $this->vars = []; return; } @@ -570,9 +570,11 @@ public function _start(): void public function _error(Throwable $e): void { $msg = sprintf( - '

500 Internal Server Error

' . - '

%s (%s)

' . - '
%s
', + <<500 Internal Server Error +

%s (%s)

+
%s
+ HTML, $e->getMessage(), $e->getCode(), $e->getTraceAsString() @@ -603,8 +605,8 @@ public function _stop(?int $code = null): void { $response = $this->response(); - if (!$response->sent()) { - if (null !== $code) { + if ($response->sent() === false) { + if ($code !== null) { $response->status($code); } @@ -729,12 +731,12 @@ public function _redirect(string $url, int $code = 303): void { $base = $this->get('flight.base_url'); - if (null === $base) { + if ($base === null) { $base = $this->request()->base; } // Append base url to redirect url - if ('/' !== $base && false === strpos($url, '://')) { + if ($base !== '/' && strpos($url, '://') === false) { $url = $base . preg_replace('#/+#', '/', '/' . $url); } @@ -756,7 +758,7 @@ public function _redirect(string $url, int $code = 303): void */ public function _render(string $file, ?array $data = null, ?string $key = null): void { - if (null !== $key) { + if ($key !== null) { $this->view()->set($key, $this->view()->fetch($file, $data)); return; } @@ -833,7 +835,7 @@ public function _jsonp( */ public function _etag(string $id, string $type = 'strong'): void { - $id = (('weak' === $type) ? 'W/' : '') . $id; + $id = (($type === 'weak') ? 'W/' : '') . $id; $this->response()->header('ETag', '"' . str_replace('"', '\"', $id) . '"'); diff --git a/flight/net/Request.php b/flight/net/Request.php index 569994ef..fd9194b8 100644 --- a/flight/net/Request.php +++ b/flight/net/Request.php @@ -151,7 +151,7 @@ public function __construct(array $config = []) 'method' => self::getMethod(), 'referrer' => self::getVar('HTTP_REFERER'), 'ip' => self::getVar('REMOTE_ADDR'), - 'ajax' => 'XMLHttpRequest' === self::getVar('HTTP_X_REQUESTED_WITH'), + 'ajax' => self::getVar('HTTP_X_REQUESTED_WITH') === 'XMLHttpRequest', 'scheme' => self::getScheme(), 'user_agent' => self::getVar('HTTP_USER_AGENT'), 'type' => self::getVar('CONTENT_TYPE'), @@ -160,7 +160,7 @@ public function __construct(array $config = []) 'data' => new Collection($_POST), 'cookies' => new Collection($_COOKIE), 'files' => new Collection($_FILES), - 'secure' => 'https' === self::getScheme(), + 'secure' => self::getScheme() === 'https', 'accept' => self::getVar('HTTP_ACCEPT'), 'proxy_ip' => self::getProxyIpAddress(), 'host' => self::getVar('HTTP_HOST'), @@ -188,12 +188,12 @@ public function init(array $properties = []): self // This rewrites the url in case the public url and base directories match // (such as installing on a subdirectory in a web server) // @see testInitUrlSameAsBaseDirectory - if ('/' !== $this->base && '' !== $this->base && 0 === strpos($this->url, $this->base)) { + if ($this->base !== '/' && $this->base !== '' && strpos($this->url, $this->base) === 0) { $this->url = substr($this->url, \strlen($this->base)); } // Default url - if (empty($this->url)) { + if (empty($this->url) === true) { $this->url = '/'; } else { // Merge URL query parameters with $_GET @@ -203,11 +203,11 @@ public function init(array $properties = []): self } // Check for JSON input - if (0 === strpos($this->type, 'application/json')) { + if (strpos($this->type, 'application/json') === 0) { $body = $this->getBody(); - if ('' !== $body) { + if ($body !== '') { $data = json_decode($body, true); - if (is_array($data)) { + if (is_array($data) === true) { $this->data->setData($data); } } @@ -225,13 +225,13 @@ public function getBody(): string { $body = $this->body; - if ('' !== $body) { + if ($body !== '') { return $body; } $method = $this->method ?? self::getMethod(); - if ('POST' === $method || 'PUT' === $method || 'DELETE' === $method || 'PATCH' === $method) { + if ($method === 'POST' || $method === 'PUT' || $method === 'DELETE' || $method === 'PATCH') { $body = file_get_contents($this->stream_path); } @@ -247,9 +247,9 @@ public static function getMethod(): string { $method = self::getVar('REQUEST_METHOD', 'GET'); - if (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) { + if (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']) === true) { $method = $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']; - } elseif (isset($_REQUEST['_method'])) { + } elseif (isset($_REQUEST['_method']) === true) { $method = $_REQUEST['_method']; } @@ -275,9 +275,9 @@ public static function getProxyIpAddress(): string $flags = \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE; foreach ($forwarded as $key) { - if (\array_key_exists($key, $_SERVER)) { + if (\array_key_exists($key, $_SERVER) === true) { sscanf($_SERVER[$key], '%[^,]', $ip); - if (false !== filter_var($ip, \FILTER_VALIDATE_IP, $flags)) { + if (filter_var($ip, \FILTER_VALIDATE_IP, $flags) !== false) { return $ip; } } @@ -322,7 +322,7 @@ public static function getHeaders(): array { $headers = []; foreach ($_SERVER as $key => $value) { - if (0 === strpos($key, 'HTTP_')) { + if (strpos($key, 'HTTP_') === 0) { // converts headers like HTTP_CUSTOM_HEADER to Custom-Header $key = str_replace(' ', '-', ucwords(str_replace('_', ' ', strtolower(substr($key, 5))))); $headers[$key] = $value; @@ -386,7 +386,7 @@ public static function parseQuery(string $url): array $params = []; $args = parse_url($url); - if (isset($args['query'])) { + if (isset($args['query']) === true) { parse_str($args['query'], $params); } @@ -401,13 +401,13 @@ public static function parseQuery(string $url): array public static function getScheme(): string { if ( - (isset($_SERVER['HTTPS']) && 'on' === strtolower($_SERVER['HTTPS'])) + (isset($_SERVER['HTTPS']) === true && strtolower($_SERVER['HTTPS']) === 'on') || - (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO']) + (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) === true && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') || - (isset($_SERVER['HTTP_FRONT_END_HTTPS']) && 'on' === $_SERVER['HTTP_FRONT_END_HTTPS']) + (isset($_SERVER['HTTP_FRONT_END_HTTPS']) === true && $_SERVER['HTTP_FRONT_END_HTTPS'] === 'on') || - (isset($_SERVER['REQUEST_SCHEME']) && 'https' === $_SERVER['REQUEST_SCHEME']) + (isset($_SERVER['REQUEST_SCHEME']) === true && $_SERVER['REQUEST_SCHEME'] === 'https') ) { return 'https'; } diff --git a/flight/net/Response.php b/flight/net/Response.php index 2971bd9e..9161f18b 100644 --- a/flight/net/Response.php +++ b/flight/net/Response.php @@ -128,6 +128,13 @@ class Response */ protected bool $sent = false; + /** + * These are callbacks that can process the response body before it's sent + * + * @var array $responseBodyCallbacks + */ + protected array $responseBodyCallbacks = []; + /** * Sets the HTTP status of the response. * @@ -429,8 +436,38 @@ public function send(): void $this->sendHeaders(); // @codeCoverageIgnore } + // Only for the v3 output buffering. + if ($this->v2_output_buffering === false) { + $this->processResponseCallbacks(); + } + echo $this->body; $this->sent = true; } + + /** + * Adds a callback to process the response body before it's sent. These are processed in the order + * they are added + * + * @param callable $callback The callback to process the response body + * + * @return void + */ + public function addResponseBodyCallback(callable $callback): void + { + $this->responseBodyCallbacks[] = $callback; + } + + /** + * Cycles through the response body callbacks and processes them in order + * + * @return void + */ + protected function processResponseCallbacks(): void + { + foreach ($this->responseBodyCallbacks as $callback) { + $this->body = $callback($this->body); + } + } } diff --git a/flight/net/Router.php b/flight/net/Router.php index d494dbb2..88df038e 100644 --- a/flight/net/Router.php +++ b/flight/net/Router.php @@ -101,7 +101,7 @@ public function map(string $pattern, $callback, bool $pass_route = false, string $methods = ['*']; - if (false !== strpos($url, ' ')) { + if (strpos($url, ' ') !== false) { [$method, $url] = explode(' ', $url, 2); $url = trim($url); $methods = explode('|', $method); diff --git a/phpstan.neon b/phpstan.neon index 97e16eb2..9be4ca5f 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -6,6 +6,7 @@ parameters: level: 6 excludePaths: - vendor + - flight/util/ReturnTypeWillChange.php paths: - flight - index.php diff --git a/tests/EngineTest.php b/tests/EngineTest.php index 8da7b0c3..6c1837e1 100644 --- a/tests/EngineTest.php +++ b/tests/EngineTest.php @@ -87,8 +87,7 @@ public function testHandleErrorWithException() public function testHandleException() { $engine = new Engine(); - $regex_message = preg_quote('

500 Internal Server Error

thrown exception message (20)

'); - $this->expectOutputRegex('~' . $regex_message . '~'); + $this->expectOutputRegex('~\500 Internal Server Error\[\s\S]*\thrown exception message \(20\)\~'); $engine->handleException(new Exception('thrown exception message', 20)); } diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php index 42701d4c..3226cc93 100644 --- a/tests/ResponseTest.php +++ b/tests/ResponseTest.php @@ -255,4 +255,37 @@ public function testOverwriteBody() $response->write('new', true); $this->assertEquals('new', $response->getBody()); } + + public function testResponseBodyCallback() + { + $response = new Response(); + $response->write('test'); + $str_rot13 = function ($body) { + return str_rot13($body); + }; + $response->addResponseBodyCallback($str_rot13); + ob_start(); + $response->send(); + $rot13_body = ob_get_clean(); + $this->assertEquals('grfg', $rot13_body); + } + + public function testResponseBodyCallbackMultiple() + { + $response = new Response(); + $response->write('test'); + $str_rot13 = function ($body) { + return str_rot13($body); + }; + $str_replace = function ($body) { + return str_replace('g', 'G', $body); + }; + $response->addResponseBodyCallback($str_rot13); + $response->addResponseBodyCallback($str_replace); + $response->addResponseBodyCallback($str_rot13); + ob_start(); + $response->send(); + $rot13_body = ob_get_clean(); + $this->assertEquals('TesT', $rot13_body); + } } diff --git a/tests/server/index.php b/tests/server/index.php index 57e0aa88..d9d16bce 100644 --- a/tests/server/index.php +++ b/tests/server/index.php @@ -161,9 +161,11 @@ Flight::map('error', function (Throwable $e) { echo sprintf( - '

500 Internal Server Error

' . - '

%s (%s)

' . - '
%s
', + <<500 Internal Server Error +

%s (%s)

+
%s
+ HTML, $e->getMessage(), $e->getCode(), str_replace(getenv('PWD'), '***CONFIDENTIAL***', $e->getTraceAsString())