Merge pull request #545 from flightphp/output-buffering-correction

Output buffering correction
pull/551/head v3.5.0
fadrian06 11 months ago committed by GitHub
commit 5073758cfa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -50,11 +50,15 @@
"config": {
"allow-plugins": {
"phpstan/extension-installer": true
}
},
"process-timeout": 0,
"sort-packages": true
},
"scripts": {
"test": "phpunit",
"test-coverage": "rm clover.xml && XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html=coverage --coverage-clover=clover.xml && vendor/bin/coverage-check clover.xml 100",
"test-server": "echo \"Running Test Server\" && php -S localhost:8000 -t tests/server/",
"test-server-v2": "echo \"Running Test Server\" && php -S localhost:8000 -t tests/server-v2/",
"test-coverage:win": "del clover.xml && phpunit --coverage-html=coverage --coverage-clover=clover.xml && coverage-check clover.xml 100",
"lint": "phpstan --no-progress -cphpstan.neon",
"beautify": "phpcbf --standard=phpcs.xml",

@ -27,7 +27,7 @@ use flight\net\Route;
* # Core methods
* @method void start() Starts engine
* @method void stop() Stops framework and outputs current response
* @method void halt(int $code = 200, string $message = '') Stops processing and returns a given response.
* @method void halt(int $code = 200, string $message = '', bool $actuallyExit = true) Stops processing and returns a given response.
*
* # Routing
* @method Route route(string $pattern, callable $callback, bool $pass_route = false, string $alias = '')
@ -66,6 +66,15 @@ use flight\net\Route;
*/
class Engine
{
/**
* @var array<string> List of methods that can be extended in the Engine class.
*/
private const MAPPABLE_METHODS = [
'start', 'stop', 'route', 'halt', 'error', 'notFound',
'render', 'redirect', 'etag', 'lastModified', 'json', 'jsonp',
'post', 'put', 'patch', 'delete', 'group', 'getUrl'
];
/** @var array<string, mixed> Stored variables. */
protected array $vars = [];
@ -137,14 +146,7 @@ class Engine
$view->extension = $self->get('flight.views.extension');
});
// Register framework methods
$methods = [
'start', 'stop', 'route', 'halt', 'error', 'notFound',
'render', 'redirect', 'etag', 'lastModified', 'json', 'jsonp',
'post', 'put', 'patch', 'delete', 'group', 'getUrl',
];
foreach ($methods as $name) {
foreach (self::MAPPABLE_METHODS as $name) {
$this->dispatcher->set($name, [$this, "_$name"]);
}
@ -156,6 +158,7 @@ class Engine
$this->set('flight.views.path', './views');
$this->set('flight.views.extension', '.php');
$this->set('flight.content_length', true);
$this->set('flight.v2.output_buffering', false);
// Startup configuration
$this->before('start', function () use ($self) {
@ -169,6 +172,10 @@ class Engine
$self->router()->case_sensitive = $self->get('flight.case_sensitive');
// Set Content-Length
$self->response()->content_length = $self->get('flight.content_length');
// This is to maintain legacy handling of output buffering
// which causes a lot of problems. This will be removed
// in v4
$self->response()->v2_output_buffering = $this->get('flight.v2.output_buffering');
});
$this->initialized = true;
@ -354,8 +361,67 @@ class Engine
$this->loader->addDirectory($dir);
}
// Extensible Methods
/**
* Processes each routes middleware.
*
* @param array<int, callable> $middleware Middleware attached to the route.
* @param array<mixed> $params `$route->params`.
* @param string $event_name If this is the before or after method.
*/
protected function processMiddleware(array $middleware, array $params, string $event_name): bool
{
$at_least_one_middleware_failed = false;
foreach ($middleware as $middleware) {
$middleware_object = false;
if ($event_name === 'before') {
// can be a callable or a class
$middleware_object = (is_callable($middleware) === true
? $middleware
: (method_exists($middleware, 'before') === true
? [$middleware, 'before']
: false
)
);
} elseif ($event_name === 'after') {
// must be an object. No functions allowed here
if (
is_object($middleware) === true
&& !($middleware instanceof Closure)
&& method_exists($middleware, 'after') === true
) {
$middleware_object = [$middleware, 'after'];
}
}
if ($middleware_object === false) {
continue;
}
if ($this->response()->v2_output_buffering === false) {
ob_start();
}
// It's assumed if you don't declare before, that it will be assumed as the before method
$middleware_result = $middleware_object($params);
if ($this->response()->v2_output_buffering === false) {
$this->response()->write(ob_get_clean());
}
if ($middleware_result === false) {
$at_least_one_middleware_failed = true;
break;
}
}
return $at_least_one_middleware_failed;
}
////////////////////////
// Extensible Methods //
////////////////////////
/**
* Starts the framework.
*
@ -374,16 +440,20 @@ class Engine
$self->stop();
});
// Flush any existing output
if (ob_get_length() > 0) {
$response->write(ob_get_clean()); // @codeCoverageIgnore
}
if ($response->v2_output_buffering === true) {
// Flush any existing output
if (ob_get_length() > 0) {
$response->write(ob_get_clean()); // @codeCoverageIgnore
}
// Enable output buffering
ob_start();
// Enable output buffering
// This is closed in the Engine->_stop() method
ob_start();
}
// Route the request
$failed_middleware_check = false;
while ($route = $router->route($request)) {
$params = array_values($route->params);
@ -394,60 +464,39 @@ class Engine
// Run any before middlewares
if (count($route->middleware) > 0) {
foreach ($route->middleware as $middleware) {
$middleware_object = (is_callable($middleware) === true
? $middleware
: (method_exists($middleware, 'before') === true
? [$middleware, 'before']
: false));
if ($middleware_object === false) {
continue;
}
// It's assumed if you don't declare before, that it will be assumed as the before method
$middleware_result = $middleware_object($route->params);
if ($middleware_result === false) {
$failed_middleware_check = true;
break 2;
}
$at_least_one_middleware_failed = $this->processMiddleware($route->middleware, $route->params, 'before');
if ($at_least_one_middleware_failed === true) {
$failed_middleware_check = true;
break;
}
}
if ($response->v2_output_buffering === false) {
ob_start();
}
// Call route handler
$continue = $this->dispatcher->execute(
$route->callback,
$params
);
if ($response->v2_output_buffering === false) {
$response->write(ob_get_clean());
}
// Run any before middlewares
if (count($route->middleware) > 0) {
// process the middleware in reverse order now
foreach (array_reverse($route->middleware) as $middleware) {
// must be an object. No functions allowed here
$middleware_object = false;
if (
is_object($middleware) === true
&& !($middleware instanceof Closure)
&& method_exists($middleware, 'after') === true
) {
$middleware_object = [$middleware, 'after'];
}
// has to have the after method, otherwise just skip it
if ($middleware_object === false) {
continue;
}
$middleware_result = $middleware_object($route->params);
if ($middleware_result === false) {
$failed_middleware_check = true;
break 2;
}
$at_least_one_middleware_failed = $this->processMiddleware(
array_reverse($route->middleware),
$route->params,
'after'
);
if ($at_least_one_middleware_failed === true) {
$failed_middleware_check = true;
break;
}
}
@ -463,7 +512,7 @@ class Engine
}
if ($failed_middleware_check === true) {
$this->halt(403, 'Forbidden');
$this->halt(403, 'Forbidden', empty(getenv('PHPUNIT_TEST')));
} elseif ($dispatched === false) {
$this->notFound();
}
@ -514,8 +563,9 @@ class Engine
$response->status($code);
}
$content = ob_get_clean();
$response->write($content ?: '');
if ($response->v2_output_buffering === true && ob_get_length() > 0) {
$response->write(ob_get_clean());
}
$response->send();
}
@ -599,16 +649,16 @@ class Engine
*
* @param int $code HTTP status code
* @param string $message Response message
* @param bool $actuallyExit Whether to actually exit the script or just send response
*/
public function _halt(int $code = 200, string $message = ''): void
public function _halt(int $code = 200, string $message = '', bool $actuallyExit = true): void
{
$this->response()
->clear()
->status($code)
->write($message)
->send();
// apologies for the crappy hack here...
if ($message !== 'skip---exit') {
if ($actuallyExit === true) {
exit(); // @codeCoverageIgnore
}
}
@ -742,7 +792,7 @@ class Engine
isset($_SERVER['HTTP_IF_NONE_MATCH']) &&
$_SERVER['HTTP_IF_NONE_MATCH'] === $id
) {
$this->halt(304);
$this->halt(304, '', empty(getenv('PHPUNIT_TEST')));
}
}
@ -759,7 +809,7 @@ class Engine
isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) &&
strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) === $time
) {
$this->halt(304);
$this->halt(304, '', empty(getenv('PHPUNIT_TEST')));
}
}

@ -22,7 +22,7 @@ require_once __DIR__ . '/autoload.php';
* @method static void start() Starts the framework.
* @method static void path(string $path) Adds a path for autoloading classes.
* @method static void stop(?int $code = null) Stops the framework and sends a response.
* @method static void halt(int $code = 200, string $message = '')
* @method static void halt(int $code = 200, string $message = '', bool $actuallyExit = true)
* Stop the framework with an optional status code and message.
*
* # Routing

@ -136,6 +136,6 @@ class PdoWrapper extends PDO
$current_index += strlen($question_marks) + 4;
}
return [ 'sql' => $sql, 'params' => $params ];
return ['sql' => $sql, 'params' => $params];
}
}

@ -331,6 +331,49 @@ class Request
return $headers;
}
/**
* Alias of Request->getHeader(). Gets a single header.
*
* @param string $header Header name. Can be caps, lowercase, or mixed.
* @param string $default Default value if the header does not exist
*
* @return string
*/
public static function header(string $header, $default = '')
{
return self::getHeader($header, $default);
}
/**
* Alias of Request->getHeaders(). Gets all the request headers
*
* @return array<string, string|int>
*/
public static function headers(): array
{
return self::getHeaders();
}
/**
* Gets the full request URL.
*
* @return string URL
*/
public function getFullUrl(): string
{
return $this->scheme . '://' . $this->host . $this->url;
}
/**
* Grabs the scheme and host. Does not end with a /
*
* @return string
*/
public function getBaseUrl(): string
{
return $this->scheme . '://' . $this->host;
}
/**
* Parse query parameters from a URL.
*

@ -21,6 +21,15 @@ class Response
*/
public bool $content_length = true;
/**
* This is to maintain legacy handling of output buffering
* which causes a lot of problems. This will be removed
* in v4
*
* @var boolean
*/
public bool $v2_output_buffering = false;
/**
* HTTP status codes
*
@ -96,6 +105,7 @@ class Response
510 => 'Not Extended',
511 => 'Network Authentication Required',
];
/**
* HTTP status
*/
@ -163,6 +173,34 @@ class Response
return $this;
}
/**
* Gets a single header from the response.
*
* @param string $name the name of the header
*
* @return string|null
*/
public function getHeader(string $name): ?string
{
$headers = $this->headers;
// lowercase all the header keys
$headers = array_change_key_case($headers, CASE_LOWER);
return $headers[strtolower($name)] ?? null;
}
/**
* Alias of Response->header(). Adds a header to the response.
*
* @param array<string, int|string>|string $name Header name or array of names and values
* @param ?string $value Header value
*
* @return $this
*/
public function setHeader($name, ?string $value): self
{
return $this->header($name, $value);
}
/**
* Returns the headers from the response.
*
@ -173,6 +211,16 @@ class Response
return $this->headers;
}
/**
* Alias for Response->headers(). Returns the headers from the response.
*
* @return array<string, int|string|array<int, string>>
*/
public function getHeaders(): array
{
return $this->headers();
}
/**
* Writes content to the response body.
*
@ -198,6 +246,11 @@ class Response
$this->headers = [];
$this->body = '';
// This needs to clear the output buffer if it's on
if ($this->v2_output_buffering === false && ob_get_length() > 0) {
ob_clean();
}
return $this;
}
@ -338,8 +391,11 @@ class Response
*/
public function send(): void
{
if (ob_get_length() > 0) {
ob_end_clean(); // @codeCoverageIgnore
// legacy way of handling this
if ($this->v2_output_buffering === true) {
if (ob_get_length() > 0) {
ob_end_clean(); // @codeCoverageIgnore
}
}
if (!headers_sent()) {

@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
// This file is only here so that the PHP8 attribute for doesn't throw an error in files

@ -22,4 +22,7 @@
</testsuite>
</testsuites>
<logging />
<php>
<env name="PHPUNIT_TEST" value="true" force="true" />
</php>
</phpunit>

@ -39,6 +39,25 @@ class DocExamplesTest extends TestCase
echo 'hello world!';
});
Flight::start();
$this->expectOutputString('[]');
$this->assertEquals(404, Flight::response()->status());
$this->assertEquals('[]', Flight::response()->getBody());
}
public function testMapNotFoundMethodV2OutputBuffering()
{
Flight::map('notFound', function () {
Flight::json([], 404);
});
Flight::request()->url = '/not-found';
Flight::route('/', function () {
echo 'hello world!';
});
Flight::set('flight.v2.output_buffering', true);
Flight::start();
ob_get_clean();
$this->assertEquals(404, Flight::response()->status());

@ -30,11 +30,31 @@ class EngineTest extends TestCase
return $this->initialized;
}
};
$this->assertTrue($engine->getInitializedVar());
// we need to setup a dummy route
$engine->route('/someRoute', function () { });
$engine->request()->url = '/someRoute';
$engine->start();
$this->assertFalse($engine->router()->case_sensitive);
$this->assertTrue($engine->response()->content_length);
}
public function testInitBeforeStartV2OutputBuffering()
{
$engine = new class extends Engine {
public function getInitializedVar()
{
return $this->initialized;
}
};
$engine->set('flight.v2.output_buffering', true);
$this->assertTrue($engine->getInitializedVar());
$engine->start();
// this is necessary cause it doesn't actually send the response correctly
ob_end_clean();
// This is a necessary evil because of how the v2 output buffer works.
ob_end_clean();
$this->assertFalse($engine->router()->case_sensitive);
$this->assertTrue($engine->response()->content_length);
@ -126,6 +146,26 @@ class EngineTest extends TestCase
$this->expectOutputString('<h1>404 Not Found</h1><h3>The page you have requested could not be found.</h3>');
$engine->start();
}
public function testStartWithRouteButReturnedValueThrows404V2OutputBuffering()
{
$_SERVER['REQUEST_METHOD'] = 'GET';
$_SERVER['REQUEST_URI'] = '/someRoute';
$engine = new class extends Engine {
public function getInitializedVar()
{
return $this->initialized;
}
};
$engine->set('flight.v2.output_buffering', true);
$engine->route('/someRoute', function () {
echo 'i ran';
return true;
}, true);
$this->expectOutputString('<h1>404 Not Found</h1><h3>The page you have requested could not be found.</h3>');
$engine->start();
}
public function testStopWithCode()
{
@ -144,14 +184,40 @@ class EngineTest extends TestCase
}
};
});
// need to add another one of these because _stop() stops and gets clean, but $response->send() does too.....
ob_start();
$engine->response()->write('I am a teapot');
$this->expectOutputString('I am a teapot');
$engine->stop(500);
$this->assertEquals(500, $engine->response()->status());
}
public function testStopWithCodeV2OutputBuffering()
{
$engine = new class extends Engine {
public function getLoader()
{
return $this->loader;
}
};
// doing this so we can overwrite some parts of the response
$engine->getLoader()->register('response', function () {
return new class extends Response {
public function setRealHeader(string $header_string, bool $replace = true, int $response_code = 0): self
{
return $this;
}
};
});
$engine->set('flight.v2.output_buffering', true);
$engine->route('/testRoute', function () use ($engine) {
echo 'I am a teapot';
$engine->stop(500);
});
$engine->request()->url = '/testRoute';
$engine->start();
$this->expectOutputString('I am a teapot');
$this->assertEquals(500, $engine->response()->status());
}
public function testPostRoute()
{
$engine = new Engine();
@ -207,10 +273,6 @@ class EngineTest extends TestCase
// doing this so we can overwrite some parts of the response
$engine->getLoader()->register('response', function () {
return new class extends Response {
public function __construct()
{
}
public function setRealHeader(
string $header_string,
bool $replace = true,
@ -220,9 +282,7 @@ class EngineTest extends TestCase
}
};
});
$this->expectOutputString('skip---exit');
$engine->halt(500, 'skip---exit');
$engine->halt(500, '', false);
$this->assertEquals(500, $engine->response()->status());
}
@ -280,17 +340,10 @@ class EngineTest extends TestCase
public function testEtagWithHttpIfNoneMatch()
{
// just need this not to exit...
$engine = new class extends Engine {
public function _halt(int $code = 200, string $message = ''): void
{
$this->response()->status($code);
$this->response()->write($message);
}
};
$engine = new Engine;
$_SERVER['HTTP_IF_NONE_MATCH'] = 'etag';
$engine->etag('etag');
$this->assertEquals('"etag"', $engine->response()->headers()['ETag']);
$this->assertTrue(empty($engine->response()->headers()['ETag']));
$this->assertEquals(304, $engine->response()->status());
}
@ -303,17 +356,10 @@ class EngineTest extends TestCase
public function testLastModifiedWithHttpIfModifiedSince()
{
// just need this not to exit...
$engine = new class extends Engine {
public function _halt(int $code = 200, string $message = ''): void
{
$this->response()->status($code);
$this->response()->write($message);
}
};
$engine = new Engine;
$_SERVER['HTTP_IF_MODIFIED_SINCE'] = 'Fri, 13 Feb 2009 23:31:30 GMT';
$engine->lastModified(1234567890);
$this->assertEquals('Fri, 13 Feb 2009 23:31:30 GMT', $engine->response()->headers()['Last-Modified']);
$this->assertTrue(empty($engine->response()->headers()['Last-Modified']));
$this->assertEquals(304, $engine->response()->status());
}
@ -344,11 +390,6 @@ class EngineTest extends TestCase
public function testMiddlewareCallableFunctionReturnFalse()
{
$engine = new class extends Engine {
public function _halt(int $code = 200, string $message = ''): void
{
$this->response()->status($code);
$this->response()->write($message);
}
};
$engine->route('/path1/@id', function ($id) {
echo 'OK' . $id;
@ -359,7 +400,7 @@ class EngineTest extends TestCase
});
$engine->request()->url = '/path1/123';
$engine->start();
$this->expectOutputString('Forbiddenbefore123');
$this->expectOutputString('Forbidden');
$this->assertEquals(403, $engine->response()->status());
}
@ -410,7 +451,6 @@ class EngineTest extends TestCase
$middleware = new class {
public function after($params)
{
echo 'after' . $params['id'];
}
};
@ -435,11 +475,6 @@ class EngineTest extends TestCase
}
};
$engine = new class extends Engine {
public function _halt(int $code = 200, string $message = ''): void
{
$this->response()->status($code);
$this->response()->write($message);
}
};
$engine->route('/path1/@id', function ($id) {
@ -449,7 +484,7 @@ class EngineTest extends TestCase
$engine->request()->url = '/path1/123';
$engine->start();
$this->assertEquals(403, $engine->response()->status());
$this->expectOutputString('ForbiddenOK123after123');
$this->expectOutputString('Forbidden');
}
public function testMiddlewareCallableFunctionMultiple()

@ -242,4 +242,40 @@ class FlightTest extends TestCase
$this->assertEquals('/user/all_users/check_user/check_one/normalpath', $url);
}
public function testHookOutputBuffering()
{
Flight::route('/test', function () {
echo 'test';
});
Flight::before('start', function ($output) {
echo 'hooked before start';
});
Flight::request()->url = '/test';
$this->expectOutputString('hooked before starttest');
Flight::start();
$this->assertEquals('test', Flight::response()->getBody());
}
public function testHookOutputBufferingV2OutputBuffering()
{
Flight::route('/test', function () {
echo 'test';
});
Flight::before('start', function ($output) {
echo 'hooked before start';
});
Flight::set('flight.v2.output_buffering', true);
Flight::request()->url = '/test';
$this->expectOutputString('hooked before starttest');
ob_start();
Flight::start();
$this->assertEquals('hooked before starttest', Flight::response()->getBody());
}
}

@ -205,10 +205,10 @@ class RequestTest extends TestCase
// or the headers that are already in $_SERVER
$this->assertEquals('XMLHttpRequest', $request->getHeader('X-REqUesTed-WiTH'));
$this->assertEquals('32.32.32.32', $request->getHeader('X-Forwarded-For'));
$this->assertEquals('32.32.32.32', $request->header('X-Forwarded-For'));
// default values
$this->assertEquals('default value', $request->getHeader('X-Non-Existent-Header', 'default value'));
$this->assertEquals('default value', $request->header('X-Non-Existent-Header', 'default value'));
}
public function testGetHeaders()
@ -231,7 +231,7 @@ class RequestTest extends TestCase
$_SERVER = [];
$_SERVER['HTTP_X_CUSTOM_HEADER'] = '';
$request = new Request();
$this->assertEquals(['X-Custom-Header' => ''], $request->getHeaders());
$this->assertEquals(['X-Custom-Header' => ''], $request->headers());
}
public function testGetHeadersWithMultipleHeaders()
@ -245,4 +245,38 @@ class RequestTest extends TestCase
'X-Custom-Header2' => 'custom header value 2'
], $request->getHeaders());
}
public function testGetFullUrlNoHttps()
{
$_SERVER['HTTP_HOST'] = 'example.com';
$_SERVER['REQUEST_URI'] = '/page?id=1';
$request = new Request();
$this->assertEquals('http://example.com/page?id=1', $request->getFullUrl());
}
public function testGetFullUrlWithHttps()
{
$_SERVER['HTTP_HOST'] = 'localhost:8000';
$_SERVER['REQUEST_URI'] = '/page?id=1';
$_SERVER['HTTPS'] = 'on';
$request = new Request();
$this->assertEquals('https://localhost:8000/page?id=1', $request->getFullUrl());
}
public function testGetBaseUrlNoHttps()
{
$_SERVER['HTTP_HOST'] = 'example.com';
$_SERVER['REQUEST_URI'] = '/page?id=1';
$request = new Request();
$this->assertEquals('http://example.com', $request->getBaseUrl());
}
public function testGetBaseUrlWithHttps()
{
$_SERVER['HTTP_HOST'] = 'localhost:8000';
$_SERVER['REQUEST_URI'] = '/page?id=1';
$_SERVER['HTTPS'] = 'on';
$request = new Request();
$this->assertEquals('https://localhost:8000', $request->getBaseUrl());
}
}

@ -65,7 +65,7 @@ class ResponseTest extends TestCase
$response = new Response();
$response->header('content-type', 'text/html');
$response->header('x-test', 'test');
$this->assertEquals(['content-type' => 'text/html', 'x-test' => 'test'], $response->headers());
$this->assertEquals(['content-type' => 'text/html', 'x-test' => 'test'], $response->getHeaders());
}
public function testHeaderArray()
@ -81,6 +81,13 @@ class ResponseTest extends TestCase
$this->assertEquals($response, $response->header('Content-Type', 'text/html'));
}
public function testGetHeaderCrazyCase()
{
$response = new Response();
$response->setHeader('CoNtEnT-tYpE', 'text/html');
$this->assertEquals('text/html', $response->getHeader('content-type'));
}
public function testWrite()
{
$response = new Response();
@ -104,6 +111,9 @@ class ResponseTest extends TestCase
public function testClear()
{
$response = new Response();
// Should clear this echo out
echo 'hi';
$response->write('test');
$response->status(404);
$response->header('Content-Type', 'text/html');
@ -111,6 +121,7 @@ class ResponseTest extends TestCase
$this->assertEquals('', $response->getBody());
$this->assertEquals(200, $response->status());
$this->assertEquals([], $response->headers());
$this->assertEquals(0, ob_get_length());
}
public function testCacheSimple()
@ -219,8 +230,8 @@ class ResponseTest extends TestCase
return $this;
}
};
$response->header('Content-Type', 'text/html');
$response->header('X-Test', 'test');
$response->setHeader('Content-Type', 'text/html');
$response->setHeader('X-Test', 'test');
$response->write('Something');
$this->expectOutputString('Something');

@ -0,0 +1,179 @@
<?php
declare(strict_types=1);
/*
* This is the test file where we can open up a quick test server and make
* sure that the UI is really working the way we would expect it to.
*
* @author Kristaps Muižnieks https://github.com/krmu
*/
require file_exists(__DIR__ . '/../../vendor/autoload.php') ? __DIR__ . '/../../vendor/autoload.php' : __DIR__ . '/../../flight/autoload.php';
Flight::set('flight.content_length', false);
Flight::set('flight.views.path', './');
Flight::set('flight.views.extension', '.phtml');
// This enables the old functionality of Flight output buffering
Flight::set('flight.v2.output_buffering', true);
// Test 1: Root route
Flight::route('/', function () {
echo '<span id="infotext">Route text:</span> Root route works!';
});
Flight::route('/querytestpath', function () {
echo '<span id="infotext">Route text:</span> This ir query route<br>';
echo "I got such query parameters:<pre>";
print_r(Flight::request()->query);
echo "</pre>";
}, false, "querytestpath");
// Test 2: Simple route
Flight::route('/test', function () {
echo '<span id="infotext">Route text:</span> Test route works!';
});
// Test 3: Route with parameter
Flight::route('/user/@name', function ($name) {
echo "<span id='infotext'>Route text:</span> Hello, $name!";
});
Flight::route('POST /postpage', function () {
echo '<span id="infotext">Route text:</span> THIS IS POST METHOD PAGE';
}, false, "postpage");
// Test 4: Grouped routes
Flight::group('/group', function () {
Flight::route('/test', function () {
echo '<span id="infotext">Route text:</span> Group test route works!';
});
Flight::route('/user/@name', function ($name) {
echo "<span id='infotext'>Route text:</span> There is variable called name and it is $name";
});
Flight::group('/group1', function () {
Flight::group('/group2', function () {
Flight::group('/group3', function () {
Flight::group('/group4', function () {
Flight::group('/group5', function () {
Flight::group('/group6', function () {
Flight::group('/group7', function () {
Flight::group('/group8', function () {
Flight::route('/final_group', function () {
echo 'Mega Group test route works!';
}, false, "final_group");
});
});
});
});
});
});
});
});
});
// Test 5: Route alias
Flight::route('/alias', function () {
echo '<span id="infotext">Route text:</span> Alias route works!';
}, false, 'aliasroute');
class AuthCheck
{
public function before()
{
if (!isset($_COOKIE['user'])) {
echo '<span id="infotext">Middleware text:</span> You are not authorized to access this route!';
}
}
}
$middle = new AuthCheck();
// Test 6: Route with middleware
Flight::route('/protected', function () {
echo '<span id="infotext">Route text:</span> Protected route works!';
})->addMiddleware([$middle]);
// Test 7: Route with template
Flight::route('/template/@name', function ($name) {
Flight::render('template.phtml', ['name' => $name]);
});
Flight::set('flight.views.path', './');
Flight::map('error', function (Throwable $error) {
echo "<h1> An error occurred, mapped error method worked, error bellow </h1>";
echo '<pre style="border: 2px solid red; padding: 21px; background: lightgray; font-weight: bold;">';
echo str_replace(getenv('PWD'), "***CLASSIFIED*****", $error->getTraceAsString());
echo "</pre>";
echo "<a href='/'>Go back</a>";
});
Flight::map('notFound', function () {
echo '<span id="infotext">Route text:</span> The requested URL was not found';
echo "<a href='/'>Go back</a>";
});
echo '
<style>
ul {
list-style-type: none;
margin: 0;
padding: 0;
overflow: hidden;
background-color: #333;
}
li {
float: left;
}
#infotext {
font-weight: bold;
color: blueviolet;
}
li a {
display: block;
color: white;
text-align: center;
padding: 14px 16px;
text-decoration: none;
}
li a:hover {
background-color: #111;
}
#container {
color: #333;
font-size: 16px;
line-height: 1.5;
margin: 20px 0;
padding: 10px;
border: 1px solid #ddd;
background-color: #f9f9f9;
}
#debugrequest {
color: #333;
font-size: 16px;
line-height: 1.5;
margin: 20px 0;
padding: 10px;
border: 1px solid #ddd;
background-color: #f9f9f9;
}
</style>
<ul>
<li><a href="/">Root Route</a></li>
<li><a href="/test">Test Route</a></li>
<li><a href="/user/John">User Route with Parameter (John)</a></li>
<li><a href="/group/test">Grouped Test Route</a></li>
<li><a href="/group/user/Jane">Grouped User Route with Parameter (Jane)</a></li>
<li><a href="/alias">Alias Route</a></li>
<li><a href="/protected">Protected path</a></li>
<li><a href="/template/templatevariable">Template path</a></li>
<li><a href="/querytestpath?test=1&variable2=uuid&variable3=tester">Query path</a></li>
<li><a href="/postpage">Post method test page - should be 404</a></li>
<li><a href="' . Flight::getUrl('final_group') . '">Mega group</a></li>
</ul>';
Flight::before('start', function ($params) {
echo '<div id="container">';
});
Flight::after('start', function ($params) {
echo '</div>';
echo '<div id="debugrequest">';
echo "Request information<pre>";
print_r(Flight::request());
echo "</pre>";
echo "</div>";
});
Flight::start();

@ -0,0 +1 @@
<span id="infotext">Route text:</span> Template <?=$name?> works!

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
class AuthCheck
{
/**
* Before
*
* @return void
*/
public function before()
{
if (!isset($_COOKIE['user'])) {
echo '<span id="infotext">Middleware text:</span> You are not authorized to access this route!';
}
}
}

@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
class LayoutMiddleware
{
/**
* Before
*
* @return void
*/
public function before()
{
$final_route = Flight::getUrl('final_group');
echo <<<HTML
<style>
ul {
list-style-type: none;
margin: 0;
padding: 0;
overflow: hidden;
background-color: #333;
}
li {
float: left;
}
#infotext {
font-weight: bold;
color: blueviolet;
}
li a {
display: block;
color: white;
text-align: center;
padding: 14px 16px;
text-decoration: none;
}
li a:hover {
background-color: #111;
}
#container {
color: #333;
font-size: 16px;
line-height: 1.5;
margin: 20px 0;
padding: 10px;
border: 1px solid #ddd;
background-color: #f9f9f9;
}
#debugrequest {
color: #333;
font-size: 16px;
line-height: 1.5;
margin: 20px 0;
padding: 10px;
border: 1px solid #ddd;
background-color: #f9f9f9;
}
</style>
<ul>
<li><a href="/">Root Route</a></li>
<li><a href="/test">Test Route</a></li>
<li><a href="/user/John">User Route with Parameter (John)</a></li>
<li><a href="/group/test">Grouped Test Route</a></li>
<li><a href="/group/user/Jane">Grouped User Route with Parameter (Jane)</a></li>
<li><a href="/alias">Alias Route</a></li>
<li><a href="/protected">Protected path</a></li>
<li><a href="/template/templatevariable">Template path</a></li>
<li><a href="/querytestpath?test=1&variable2=uuid&variable3=tester">Query path</a></li>
<li><a href="/postpage">404 Not Found</a></li>
<li><a href="{$final_route}">Mega group</a></li>
<li><a href="/error">Error</a></li>
</ul>
HTML;
echo '<div id="container">';
}
public function after()
{
echo '</div>';
echo '<div id="debugrequest">';
echo "<h2>Request Information</h2><pre>";
print_r(Flight::request());
echo '<h3>Raw Request Information</h3>';
print_r($_SERVER);
echo "</pre><h2>Response Information</h2><pre>";
print_r(Flight::response());
echo "</pre>";
echo "</div>";
}
}

@ -0,0 +1,116 @@
<?php
declare(strict_types=1);
/*
* This is the test file where we can open up a quick test server and make
* sure that the UI is really working the way we would expect it to.
*
* @author Kristaps Muižnieks https://github.com/krmu
*/
require file_exists(__DIR__ . '/../../vendor/autoload.php') ? __DIR__ . '/../../vendor/autoload.php' : __DIR__ . '/../../flight/autoload.php';
Flight::set('flight.content_length', false);
Flight::set('flight.views.path', './');
Flight::set('flight.views.extension', '.phtml');
//Flight::set('flight.v2.output_buffering', true);
require_once 'LayoutMiddleware.php';
Flight::group('', function () {
// Test 1: Root route
Flight::route('/', function () {
echo '<span id="infotext">Route text:</span> Root route works!';
});
Flight::route('/querytestpath', function () {
echo '<span id="infotext">Route text:</span> This ir query route<br>';
echo "I got such query parameters:<pre>";
print_r(Flight::request()->query);
echo "</pre>";
}, false, "querytestpath");
// Test 2: Simple route
Flight::route('/test', function () {
echo '<span id="infotext">Route text:</span> Test route works!';
});
// Test 3: Route with parameter
Flight::route('/user/@name', function ($name) {
echo "<span id='infotext'>Route text:</span> Hello, $name!";
});
Flight::route('POST /postpage', function () {
echo '<span id="infotext">Route text:</span> THIS IS POST METHOD PAGE';
}, false, "postpage");
// Test 4: Grouped routes
Flight::group('/group', function () {
Flight::route('/test', function () {
echo '<span id="infotext">Route text:</span> Group test route works!';
});
Flight::route('/user/@name', function ($name) {
echo "<span id='infotext'>Route text:</span> There is variable called name and it is $name";
});
Flight::group('/group1', function () {
Flight::group('/group2', function () {
Flight::group('/group3', function () {
Flight::group('/group4', function () {
Flight::group('/group5', function () {
Flight::group('/group6', function () {
Flight::group('/group7', function () {
Flight::group('/group8', function () {
Flight::route('/final_group', function () {
echo 'Mega Group test route works!';
}, false, "final_group");
});
});
});
});
});
});
});
});
});
// Test 5: Route alias
Flight::route('/alias', function () {
echo '<span id="infotext">Route text:</span> Alias route works!';
}, false, 'aliasroute');
/** Middleware test */
include_once 'AuthCheck.php';
$middle = new AuthCheck();
// Test 6: Route with middleware
Flight::route('/protected', function () {
echo '<span id="infotext">Route text:</span> Protected route works!';
})->addMiddleware([$middle]);
// Test 7: Route with template
Flight::route('/template/@name', function ($name) {
Flight::render('template.phtml', ['name' => $name]);
});
// Test 8: Throw an error
Flight::route('/error', function () {
trigger_error('This is a successful error');
});
}, [ new LayoutMiddleware() ]);
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>',
$e->getMessage(),
$e->getCode(),
str_replace(getenv('PWD'), '***CONFIDENTIAL***', $e->getTraceAsString())
);
echo "<br><a href='/'>Go back</a>";
});
Flight::map('notFound', function () {
echo '<span id="infotext">Route text:</span> The requested URL was not found<br>';
echo "<a href='/'>Go back</a>";
});
Flight::start();

@ -0,0 +1 @@
<span id="infotext">Route text:</span> Template <?=$name?> works!
Loading…
Cancel
Save