Skip to content

Commit

Permalink
Merge pull request #571 from flightphp/response-body-callback
Browse files Browse the repository at this point in the history
Added ability to convert the response body with callbacks
  • Loading branch information
n0nag0n committed Apr 10, 2024
2 parents 4d79dea + 9be596f commit d92c65f
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 36 deletions.
24 changes: 13 additions & 11 deletions flight/Engine.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -570,9 +570,11 @@ public function _start(): void
public function _error(Throwable $e): void
{
$msg = sprintf(
'<h1>500 Internal Server Error</h1>' .
'<h3>%s (%s)</h3>' .
'<pre>%s</pre>',
<<<HTML
<h1>500 Internal Server Error</h1>
<h3>%s (%s)</h3>
<pre>%s</pre>
HTML,
$e->getMessage(),
$e->getCode(),
$e->getTraceAsString()
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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);
}

Expand All @@ -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;
}
Expand Down Expand Up @@ -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) . '"');

Expand Down
38 changes: 19 additions & 19 deletions flight/net/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand All @@ -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'),
Expand Down Expand Up @@ -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
Expand All @@ -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);
}
}
Expand All @@ -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);
}

Expand All @@ -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'];
}

Expand All @@ -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;
}
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

Expand All @@ -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';
}
Expand Down
37 changes: 37 additions & 0 deletions flight/net/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<int, callable> $responseBodyCallbacks
*/
protected array $responseBodyCallbacks = [];

/**
* Sets the HTTP status of the response.
*
Expand Down Expand Up @@ -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);
}
}
}
2 changes: 1 addition & 1 deletion flight/net/Router.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ parameters:
level: 6
excludePaths:
- vendor
- flight/util/ReturnTypeWillChange.php
paths:
- flight
- index.php
Expand Down
3 changes: 1 addition & 2 deletions tests/EngineTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,7 @@ public function testHandleErrorWithException()
public function testHandleException()
{
$engine = new Engine();
$regex_message = preg_quote('<h1>500 Internal Server Error</h1><h3>thrown exception message (20)</h3>');
$this->expectOutputRegex('~' . $regex_message . '~');
$this->expectOutputRegex('~\<h1\>500 Internal Server Error\</h1\>[\s\S]*\<h3\>thrown exception message \(20\)\</h3\>~');
$engine->handleException(new Exception('thrown exception message', 20));
}

Expand Down
33 changes: 33 additions & 0 deletions tests/ResponseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
8 changes: 5 additions & 3 deletions tests/server/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,11 @@

Flight::map('error', function (Throwable $e) {
echo sprintf(
'<h1>500 Internal Server Error</h1>' .
'<h3>%s (%s)</h3>' .
'<pre style="border: 2px solid red; padding: 21px; background: lightgray; font-weight: bold;">%s</pre>',
<<<HTML
<h1>500 Internal Server Error</h1>
<h3>%s (%s)</h3>
<pre style="border: 2px solid red; padding: 21px; background: lightgray; font-weight: bold;">%s</pre>
HTML,
$e->getMessage(),
$e->getCode(),
str_replace(getenv('PWD'), '***CONFIDENTIAL***', $e->getTraceAsString())
Expand Down

0 comments on commit d92c65f

Please sign in to comment.