From feee086e2bb49e363304165488892cad8b574655 Mon Sep 17 00:00:00 2001 From: n0nag0n Date: Wed, 14 Feb 2024 13:22:38 -0700 Subject: [PATCH 01/11] initial commit to overhaul output buffering --- flight/Engine.php | 163 +++++++++++++++++++++++++------------- flight/Flight.php | 2 +- flight/net/Response.php | 22 ++++- phpunit.xml | 3 + tests/DocExamplesTest.php | 21 ++++- tests/EngineTest.php | 119 ++++++++++++++++++---------- 6 files changed, 227 insertions(+), 103 deletions(-) diff --git a/flight/Engine.php b/flight/Engine.php index 58b0859..638c6b8 100644 --- a/flight/Engine.php +++ b/flight/Engine.php @@ -15,6 +15,7 @@ use flight\net\Router; use flight\template\View; use Throwable; use flight\net\Route; +use PHPUnit\Framework\TestCase; /** * The Engine class contains the core functionality of the framework. @@ -27,7 +28,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 $actually_exit = true) Stops processing and returns a given response. * * # Routing * @method Route route(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') @@ -156,6 +157,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 +171,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,7 +360,67 @@ class Engine $this->loader->addDirectory($dir); } - // Extensible Methods + /** + * Processes each routes middleware + * + * @param array $middleware middleware attached to the route + * @param array $params route->params + * @param string $event_name if this is the before or after method + * + * @return boolean + */ + 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,13 +440,16 @@ 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; @@ -394,60 +463,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 +511,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 +562,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 +648,16 @@ class Engine * * @param int $code HTTP status code * @param string $message Response message + * @param bool $actually_exit 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 $actually_exit = true): void { $this->response() ->clear() ->status($code) ->write($message) ->send(); - // apologies for the crappy hack here... - if ($message !== 'skip---exit') { + if ($actually_exit === true) { exit(); // @codeCoverageIgnore } } @@ -742,7 +791,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 +808,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'))); } } diff --git a/flight/Flight.php b/flight/Flight.php index c53b979..955ee87 100644 --- a/flight/Flight.php +++ b/flight/Flight.php @@ -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 $actually_exit = true) * Stop the framework with an optional status code and message. * * # Routing diff --git a/flight/net/Response.php b/flight/net/Response.php index 55e2605..61df314 100644 --- a/flight/net/Response.php +++ b/flight/net/Response.php @@ -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 */ @@ -198,6 +208,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 +353,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()) { diff --git a/phpunit.xml b/phpunit.xml index 2fedaf9..9805e7b 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -22,4 +22,7 @@ + + + diff --git a/tests/DocExamplesTest.php b/tests/DocExamplesTest.php index 76bbb21..dd5d6c6 100644 --- a/tests/DocExamplesTest.php +++ b/tests/DocExamplesTest.php @@ -40,7 +40,26 @@ class DocExamplesTest extends TestCase }); Flight::start(); - ob_get_clean(); + $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()); $this->assertEquals('[]', Flight::response()->getBody()); } diff --git a/tests/EngineTest.php b/tests/EngineTest.php index 0a20b53..1f9d456 100644 --- a/tests/EngineTest.php +++ b/tests/EngineTest.php @@ -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('

404 Not Found

The page you have requested could not be found.

'); $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('

404 Not Found

The page you have requested could not be found.

'); + $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() From e696bd91352fa89a6f1f50e42149e3292ed421d9 Mon Sep 17 00:00:00 2001 From: n0nag0n Date: Wed, 14 Feb 2024 13:24:09 -0700 Subject: [PATCH 02/11] removed unnecessary code --- flight/Engine.php | 1 - 1 file changed, 1 deletion(-) diff --git a/flight/Engine.php b/flight/Engine.php index 638c6b8..fdbdfa6 100644 --- a/flight/Engine.php +++ b/flight/Engine.php @@ -15,7 +15,6 @@ use flight\net\Router; use flight\template\View; use Throwable; use flight\net\Route; -use PHPUnit\Framework\TestCase; /** * The Engine class contains the core functionality of the framework. From 7d1987f73a2696fcb98cb0ee01dd672f73d5f0c4 Mon Sep 17 00:00:00 2001 From: n0nag0n Date: Wed, 14 Feb 2024 16:41:59 -0700 Subject: [PATCH 03/11] Added UI Test script from @krmu --- composer.json | 1 + tests/server/index.php | 199 ++++++++++++++++++++++++++++++++++++++ tests/server/template.php | 1 + 3 files changed, 201 insertions(+) create mode 100644 tests/server/index.php create mode 100644 tests/server/template.php diff --git a/composer.json b/composer.json index 536aa44..8332e94 100644 --- a/composer.json +++ b/composer.json @@ -55,6 +55,7 @@ "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\" && COMPOSER_PROCESS_TIMEOUT=60000 php -S localhost:8000 -t tests/server/", "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", diff --git a/tests/server/index.php b/tests/server/index.php new file mode 100644 index 0000000..675c278 --- /dev/null +++ b/tests/server/index.php @@ -0,0 +1,199 @@ + + 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; + } + + + HTML; + echo '
'; + } + + public function after() { + echo '
'; + echo '
'; + echo "

Request Information

";
+		print_r(Flight::request());
+		echo '

Raw Request Information

'; + print_r($_SERVER); + echo "

Response Information

";
+		print_r(Flight::response());
+		echo "
"; + echo "
"; + } +} + +Flight::group('', function() { + + // Test 1: Root route + Flight::route('/', function(){ + echo 'Route text: Root route works!'; + }); + Flight::route('/querytestpath', function(){ + echo 'Route text: This ir query route
'; + echo "I got such query parameters:
";
+		print_r(Flight::request()->query);
+		echo "
"; + },false,"querytestpath"); + + // Test 2: Simple route + Flight::route('/test', function(){ + echo 'Route text: Test route works!'; + }); + + // Test 3: Route with parameter + Flight::route('/user/@name', function($name){ + echo "Route text: Hello, $name!"; + }); + Flight::route('POST /postpage', function () { + echo 'Route text: THIS IS POST METHOD PAGE'; + },false,"postpage"); + + // Test 4: Grouped routes + Flight::group('/group', function(){ + Flight::route('/test', function(){ + echo 'Route text: Group test route works!'; + }); + Flight::route('/user/@name', function($name){ + echo "Route text: 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 'Route text: Alias route works!'; + }, false, 'aliasroute'); + + /** middleware test */ + class authCheck { + public function before(){ + if(!isset($_COOKIE['user'])){ + echo 'Middleware text: You are not authorized to access this route!'; + } + } + } + $middle = new authCheck(); + // Test 6: Route with middleware + Flight::route('/protected', function(){ + echo 'Route text: Protected route works!'; + })->addMiddleware([$middle]); + + // Test 7: Route with template + Flight::route('/template/@name', function($name){ + Flight::render('template.php', ['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( + '

500 Internal Server Error

' . + '

%s (%s)

' . + '
%s
', + $e->getMessage(), + $e->getCode(), + str_replace(getenv('PWD'), '***CONFIDENTIAL***', $e->getTraceAsString()) + ); + echo "
Go back"; +}); +Flight::map('notFound', function() { + echo 'Route text: The requested URL was not found
'; + echo "Go back"; +}); + +Flight::start(); diff --git a/tests/server/template.php b/tests/server/template.php new file mode 100644 index 0000000..7cb97b4 --- /dev/null +++ b/tests/server/template.php @@ -0,0 +1 @@ +Route text: Template works! \ No newline at end of file From d5a5a1560751ec3590f947cd4db9b69cbcc9b727 Mon Sep 17 00:00:00 2001 From: n0nag0n Date: Wed, 14 Feb 2024 16:52:06 -0700 Subject: [PATCH 04/11] added v2 ui testing --- composer.json | 3 +- tests/server-v2/index.php | 175 +++++++++++++++++++++++++++++++++++ tests/server-v2/template.php | 1 + tests/server/index.php | 4 +- 4 files changed, 180 insertions(+), 3 deletions(-) create mode 100644 tests/server-v2/index.php create mode 100644 tests/server-v2/template.php diff --git a/composer.json b/composer.json index 8332e94..5a98697 100644 --- a/composer.json +++ b/composer.json @@ -55,7 +55,8 @@ "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\" && COMPOSER_PROCESS_TIMEOUT=60000 php -S localhost:8000 -t tests/server/", + "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", diff --git a/tests/server-v2/index.php b/tests/server-v2/index.php new file mode 100644 index 0000000..547a1f9 --- /dev/null +++ b/tests/server-v2/index.php @@ -0,0 +1,175 @@ +Route text: Root route works!'; +}); +Flight::route('/querytestpath', function(){ + echo 'Route text: This ir query route
'; + echo "I got such query parameters:
";
+    print_r(Flight::request()->query);
+    echo "
"; +},false,"querytestpath"); + +// Test 2: Simple route +Flight::route('/test', function(){ + echo 'Route text: Test route works!'; +}); + +// Test 3: Route with parameter +Flight::route('/user/@name', function($name){ + echo "Route text: Hello, $name!"; +}); +Flight::route('POST /postpage', function () { + echo 'Route text: THIS IS POST METHOD PAGE'; +},false,"postpage"); + +// Test 4: Grouped routes +Flight::group('/group', function(){ + Flight::route('/test', function(){ + echo 'Route text: Group test route works!'; + }); + Flight::route('/user/@name', function($name){ + echo "Route text: 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 'Route text: Alias route works!'; +}, false, 'aliasroute'); +class authCheck { + public function before(){ + if(!isset($_COOKIE['user'])){ + echo 'Middleware text: You are not authorized to access this route!'; + } + } +} +$middle = new authCheck(); +// Test 6: Route with middleware +Flight::route('/protected', function(){ + echo 'Route text: Protected route works!'; +})->addMiddleware([$middle]); + +// Test 7: Route with template +Flight::route('/template/@name', function($name){ + Flight::render('template.php', ['name' => $name]); +}); +Flight::set('flight.views.path', './'); +Flight::map('error', function (Throwable $error) { + echo "

An error occurred, mapped error method worked, error bellow

"; + echo '
';
+    echo str_replace(getenv('PWD'),"***CLASSIFIED*****",$error->getTraceAsString());
+    echo "
"; + echo "Go back"; +}); +Flight::map('notFound', function() { + echo 'Route text: The requested URL was not found'; + echo "Go back"; +}); +echo ' + +'; +Flight::before('start', function ($params) { + echo '
'; +}); +Flight::after('start', function ($params) { + echo '
'; + echo '
'; + echo "Request information
";
+    print_r(Flight::request());
+    echo "
"; + echo "
"; +}); +Flight::start(); + + \ No newline at end of file diff --git a/tests/server-v2/template.php b/tests/server-v2/template.php new file mode 100644 index 0000000..7cb97b4 --- /dev/null +++ b/tests/server-v2/template.php @@ -0,0 +1 @@ +Route text: Template works! \ No newline at end of file diff --git a/tests/server/index.php b/tests/server/index.php index 675c278..15de575 100644 --- a/tests/server/index.php +++ b/tests/server/index.php @@ -7,8 +7,8 @@ * @author Kristaps Muižnieks https://github.com/krmu */ -require __DIR__ . '/../../vendor/autoload.php'; - + 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.v2.output_buffering', true); From e29f3b55336e1a3ca53140d6f441c43128386a28 Mon Sep 17 00:00:00 2001 From: n0nag0n Date: Thu, 15 Feb 2024 07:41:58 -0700 Subject: [PATCH 05/11] snakeCased parameter, and added v2 unit testing behaviors --- flight/Engine.php | 8 ++++---- flight/Flight.php | 2 +- tests/FlightTest.php | 34 ++++++++++++++++++++++++++++++++++ tests/server-v2/index.php | 1 + 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/flight/Engine.php b/flight/Engine.php index fdbdfa6..52f0c98 100644 --- a/flight/Engine.php +++ b/flight/Engine.php @@ -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 = '', bool $actually_exit = true) 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 = '') @@ -647,16 +647,16 @@ class Engine * * @param int $code HTTP status code * @param string $message Response message - * @param bool $actually_exit Whether to actually exit the script or just send response + * @param bool $actuallyExit Whether to actually exit the script or just send response */ - public function _halt(int $code = 200, string $message = '', bool $actually_exit = true): void + public function _halt(int $code = 200, string $message = '', bool $actuallyExit = true): void { $this->response() ->clear() ->status($code) ->write($message) ->send(); - if ($actually_exit === true) { + if ($actuallyExit === true) { exit(); // @codeCoverageIgnore } } diff --git a/flight/Flight.php b/flight/Flight.php index 955ee87..6e29781 100644 --- a/flight/Flight.php +++ b/flight/Flight.php @@ -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 = '', bool $actually_exit = true) + * @method static void halt(int $code = 200, string $message = '', bool $actuallyExit = true) * Stop the framework with an optional status code and message. * * # Routing diff --git a/tests/FlightTest.php b/tests/FlightTest.php index c37a8fa..c82caff 100644 --- a/tests/FlightTest.php +++ b/tests/FlightTest.php @@ -242,4 +242,38 @@ 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()); + } } diff --git a/tests/server-v2/index.php b/tests/server-v2/index.php index 547a1f9..240313e 100644 --- a/tests/server-v2/index.php +++ b/tests/server-v2/index.php @@ -11,6 +11,7 @@ require file_exists(__DIR__ . '/../../vendor/autoload.php') ? __DIR__ . '/../../ Flight::set('flight.content_length', false); Flight::set('flight.views.path', './'); +// This enables the old functionality of Flight output buffering Flight::set('flight.v2.output_buffering', true); // Test 1: Root route From a60a31c4bd6d2dc07db776126f80d8f0a0b6a80d Mon Sep 17 00:00:00 2001 From: n0nag0n Date: Thu, 15 Feb 2024 22:43:11 -0700 Subject: [PATCH 06/11] added constant --- flight/Engine.php | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/flight/Engine.php b/flight/Engine.php index 52f0c98..d6e5490 100644 --- a/flight/Engine.php +++ b/flight/Engine.php @@ -66,6 +66,16 @@ use flight\net\Route; */ class Engine { + + /** + * @var array 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 Stored variables. */ protected array $vars = []; @@ -137,14 +147,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"]); } From 4d4c0d5420988a09a55dc0ee7d301741ba364e55 Mon Sep 17 00:00:00 2001 From: n0nag0n Date: Thu, 15 Feb 2024 23:20:34 -0700 Subject: [PATCH 07/11] added helpers to req/resp and cleaned up phpcs/stan errors --- flight/Engine.php | 17 +- flight/net/Request.php | 43 +++ flight/net/Response.php | 38 +++ tests/DocExamplesTest.php | 6 +- tests/FlightTest.php | 68 ++--- tests/RequestTest.php | 40 ++- tests/ResponseTest.php | 17 +- tests/server-v2/index.php | 72 ++--- .../{template.php => template.phtml} | 2 +- tests/server/AuthCheck.php | 18 ++ tests/server/LayoutMiddleware.php | 93 ++++++ tests/server/index.php | 278 ++++++------------ tests/server/{template.php => template.phtml} | 2 +- 13 files changed, 425 insertions(+), 269 deletions(-) rename tests/server-v2/{template.php => template.phtml} (96%) create mode 100644 tests/server/AuthCheck.php create mode 100644 tests/server/LayoutMiddleware.php rename tests/server/{template.php => template.phtml} (96%) diff --git a/flight/Engine.php b/flight/Engine.php index d6e5490..1826445 100644 --- a/flight/Engine.php +++ b/flight/Engine.php @@ -66,15 +66,14 @@ use flight\net\Route; */ class Engine { - - /** - * @var array 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 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 Stored variables. */ protected array $vars = []; diff --git a/flight/net/Request.php b/flight/net/Request.php index 7fffd75..eb932c0 100644 --- a/flight/net/Request.php +++ b/flight/net/Request.php @@ -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 + */ + 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. * diff --git a/flight/net/Response.php b/flight/net/Response.php index 61df314..761d1a6 100644 --- a/flight/net/Response.php +++ b/flight/net/Response.php @@ -173,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 $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. * @@ -183,6 +211,16 @@ class Response return $this->headers; } + /** + * Alias for Response->headers(). Returns the headers from the response. + * + * @return array> + */ + public function getHeaders(): array + { + return $this->headers(); + } + /** * Writes content to the response body. * diff --git a/tests/DocExamplesTest.php b/tests/DocExamplesTest.php index dd5d6c6..1518363 100644 --- a/tests/DocExamplesTest.php +++ b/tests/DocExamplesTest.php @@ -45,7 +45,7 @@ class DocExamplesTest extends TestCase $this->assertEquals('[]', Flight::response()->getBody()); } - public function testMapNotFoundMethodV2OutputBuffering() + public function testMapNotFoundMethodV2OutputBuffering() { Flight::map('notFound', function () { Flight::json([], 404); @@ -57,9 +57,9 @@ class DocExamplesTest extends TestCase echo 'hello world!'; }); - Flight::set('flight.v2.output_buffering', true); + Flight::set('flight.v2.output_buffering', true); Flight::start(); - ob_get_clean(); + ob_get_clean(); $this->assertEquals(404, Flight::response()->status()); $this->assertEquals('[]', Flight::response()->getBody()); } diff --git a/tests/FlightTest.php b/tests/FlightTest.php index c82caff..c068dbe 100644 --- a/tests/FlightTest.php +++ b/tests/FlightTest.php @@ -243,37 +243,39 @@ 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()); - } + 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()); + } } diff --git a/tests/RequestTest.php b/tests/RequestTest.php index e3cdfb5..a8b4310 100644 --- a/tests/RequestTest.php +++ b/tests/RequestTest.php @@ -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()); + } } diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php index 1b9c1da..d7155fc 100644 --- a/tests/ResponseTest.php +++ b/tests/ResponseTest.php @@ -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'); diff --git a/tests/server-v2/index.php b/tests/server-v2/index.php index 240313e..2c6d129 100644 --- a/tests/server-v2/index.php +++ b/tests/server-v2/index.php @@ -1,9 +1,11 @@ Route text: Root route works!'; }); -Flight::route('/querytestpath', function(){ +Flight::route('/querytestpath', function () { echo 'Route text: This ir query route
'; echo "I got such query parameters:
";
     print_r(Flight::request()->query);
     echo "
"; -},false,"querytestpath"); +}, false, "querytestpath"); // Test 2: Simple route -Flight::route('/test', function(){ +Flight::route('/test', function () { echo 'Route text: Test route works!'; }); // Test 3: Route with parameter -Flight::route('/user/@name', function($name){ +Flight::route('/user/@name', function ($name) { echo "Route text: Hello, $name!"; }); Flight::route('POST /postpage', function () { echo 'Route text: THIS IS POST METHOD PAGE'; -},false,"postpage"); +}, false, "postpage"); // Test 4: Grouped routes -Flight::group('/group', function(){ - Flight::route('/test', function(){ +Flight::group('/group', function () { + Flight::route('/test', function () { echo 'Route text: Group test route works!'; }); - Flight::route('/user/@name', function($name){ + Flight::route('/user/@name', function ($name) { echo "Route text: 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(){ + 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"); + }, false, "final_group"); }); }); }); @@ -64,39 +66,41 @@ Flight::group('/group', function(){ }); }); }); - }); + }); }); // Test 5: Route alias -Flight::route('/alias', function(){ +Flight::route('/alias', function () { echo 'Route text: Alias route works!'; }, false, 'aliasroute'); -class authCheck { - public function before(){ - if(!isset($_COOKIE['user'])){ +class AuthCheck +{ + public function before() + { + if (!isset($_COOKIE['user'])) { echo 'Middleware text: You are not authorized to access this route!'; } } } -$middle = new authCheck(); +$middle = new AuthCheck(); // Test 6: Route with middleware -Flight::route('/protected', function(){ +Flight::route('/protected', function () { echo 'Route text: Protected route works!'; })->addMiddleware([$middle]); // Test 7: Route with template -Flight::route('/template/@name', function($name){ - Flight::render('template.php', ['name' => $name]); +Flight::route('/template/@name', function ($name) { + Flight::render('template.phtml', ['name' => $name]); }); Flight::set('flight.views.path', './'); Flight::map('error', function (Throwable $error) { echo "

An error occurred, mapped error method worked, error bellow

"; echo '
';
-    echo str_replace(getenv('PWD'),"***CLASSIFIED*****",$error->getTraceAsString());
+    echo str_replace(getenv('PWD'), "***CLASSIFIED*****", $error->getTraceAsString());
     echo "
"; echo "Go back"; }); -Flight::map('notFound', function() { +Flight::map('notFound', function () { echo 'Route text: The requested URL was not found'; echo "Go back"; }); @@ -158,7 +162,7 @@ echo '
  • Template path
  • Query path
  • Post method test page - should be 404
  • -
  • Mega group
  • +
  • Mega group
  • '; Flight::before('start', function ($params) { echo '
    '; @@ -172,5 +176,3 @@ Flight::after('start', function ($params) { echo "
    "; }); Flight::start(); - - \ No newline at end of file diff --git a/tests/server-v2/template.php b/tests/server-v2/template.phtml similarity index 96% rename from tests/server-v2/template.php rename to tests/server-v2/template.phtml index 7cb97b4..d2ab1fa 100644 --- a/tests/server-v2/template.php +++ b/tests/server-v2/template.phtml @@ -1 +1 @@ -Route text: Template works! \ No newline at end of file +Route text: Template works! diff --git a/tests/server/AuthCheck.php b/tests/server/AuthCheck.php new file mode 100644 index 0000000..58526b2 --- /dev/null +++ b/tests/server/AuthCheck.php @@ -0,0 +1,18 @@ +Middleware text: You are not authorized to access this route!'; + } + } +} diff --git a/tests/server/LayoutMiddleware.php b/tests/server/LayoutMiddleware.php new file mode 100644 index 0000000..bb30bda --- /dev/null +++ b/tests/server/LayoutMiddleware.php @@ -0,0 +1,93 @@ + + 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; + } + + +HTML; + echo '
    '; + } + + public function after() + { + echo '
    '; + echo '
    '; + echo "

    Request Information

    ";
    +        print_r(Flight::request());
    +        echo '

    Raw Request Information

    '; + print_r($_SERVER); + echo "

    Response Information

    ";
    +        print_r(Flight::response());
    +        echo "
    "; + echo "
    "; + } +} diff --git a/tests/server/index.php b/tests/server/index.php index 15de575..d70eabf 100644 --- a/tests/server/index.php +++ b/tests/server/index.php @@ -1,199 +1,115 @@ - 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; - } - - - HTML; - echo '
    '; - } - - public function after() { - echo '
    '; - echo '
    '; - echo "

    Request Information

    ";
    -		print_r(Flight::request());
    -		echo '

    Raw Request Information

    '; - print_r($_SERVER); - echo "

    Response Information

    ";
    -		print_r(Flight::response());
    -		echo "
    "; - echo "
    "; - } -} - -Flight::group('', function() { - - // Test 1: Root route - Flight::route('/', function(){ - echo 'Route text: Root route works!'; - }); - Flight::route('/querytestpath', function(){ - echo 'Route text: This ir query route
    '; - echo "I got such query parameters:
    ";
    -		print_r(Flight::request()->query);
    -		echo "
    "; - },false,"querytestpath"); - - // Test 2: Simple route - Flight::route('/test', function(){ - echo 'Route text: Test route works!'; - }); - - // Test 3: Route with parameter - Flight::route('/user/@name', function($name){ - echo "Route text: Hello, $name!"; - }); - Flight::route('POST /postpage', function () { - echo 'Route text: THIS IS POST METHOD PAGE'; - },false,"postpage"); - - // Test 4: Grouped routes - Flight::group('/group', function(){ - Flight::route('/test', function(){ - echo 'Route text: Group test route works!'; - }); - Flight::route('/user/@name', function($name){ - echo "Route text: 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 'Route text: Alias route works!'; - }, false, 'aliasroute'); - - /** middleware test */ - class authCheck { - public function before(){ - if(!isset($_COOKIE['user'])){ - echo 'Middleware text: You are not authorized to access this route!'; - } - } - } - $middle = new authCheck(); - // Test 6: Route with middleware - Flight::route('/protected', function(){ - echo 'Route text: Protected route works!'; - })->addMiddleware([$middle]); - - // Test 7: Route with template - Flight::route('/template/@name', function($name){ - Flight::render('template.php', ['name' => $name]); - }); - - // Test 8: Throw an error - Flight::route('/error', function() { - trigger_error('This is a successful error'); - }); -}, [ new LayoutMiddleware ]); +require_once 'LayoutMiddleware.php'; + +Flight::group('', function () { + + // Test 1: Root route + Flight::route('/', function () { + echo 'Route text: Root route works!'; + }); + Flight::route('/querytestpath', function () { + echo 'Route text: This ir query route
    '; + echo "I got such query parameters:
    ";
    +        print_r(Flight::request()->query);
    +        echo "
    "; + }, false, "querytestpath"); + + // Test 2: Simple route + Flight::route('/test', function () { + echo 'Route text: Test route works!'; + }); + + // Test 3: Route with parameter + Flight::route('/user/@name', function ($name) { + echo "Route text: Hello, $name!"; + }); + Flight::route('POST /postpage', function () { + echo 'Route text: THIS IS POST METHOD PAGE'; + }, false, "postpage"); + + // Test 4: Grouped routes + Flight::group('/group', function () { + Flight::route('/test', function () { + echo 'Route text: Group test route works!'; + }); + Flight::route('/user/@name', function ($name) { + echo "Route text: 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 'Route text: Alias route works!'; + }, false, 'aliasroute'); + + /** Middleware test */ + include_once 'AuthCheck.php'; + $middle = new AuthCheck(); + // Test 6: Route with middleware + Flight::route('/protected', function () { + echo 'Route text: 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( - '

    500 Internal Server Error

    ' . - '

    %s (%s)

    ' . - '
    %s
    ', - $e->getMessage(), - $e->getCode(), - str_replace(getenv('PWD'), '***CONFIDENTIAL***', $e->getTraceAsString()) - ); - echo "
    Go back"; + echo sprintf( + '

    500 Internal Server Error

    ' . + '

    %s (%s)

    ' . + '
    %s
    ', + $e->getMessage(), + $e->getCode(), + str_replace(getenv('PWD'), '***CONFIDENTIAL***', $e->getTraceAsString()) + ); + echo "
    Go back"; }); -Flight::map('notFound', function() { - echo 'Route text: The requested URL was not found
    '; - echo "Go back"; +Flight::map('notFound', function () { + echo 'Route text: The requested URL was not found
    '; + echo "Go back"; }); Flight::start(); diff --git a/tests/server/template.php b/tests/server/template.phtml similarity index 96% rename from tests/server/template.php rename to tests/server/template.phtml index 7cb97b4..d2ab1fa 100644 --- a/tests/server/template.php +++ b/tests/server/template.phtml @@ -1 +1 @@ -Route text: Template works! \ No newline at end of file +Route text: Template works! From a212d736d5b0d6b62d456271bc0803a4f91a2acc Mon Sep 17 00:00:00 2001 From: n0nag0n Date: Thu, 15 Feb 2024 23:22:36 -0700 Subject: [PATCH 08/11] corrected template path in test servers --- tests/server-v2/index.php | 1 + tests/server/index.php | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/server-v2/index.php b/tests/server-v2/index.php index 2c6d129..1ebdd13 100644 --- a/tests/server-v2/index.php +++ b/tests/server-v2/index.php @@ -13,6 +13,7 @@ require file_exists(__DIR__ . '/../../vendor/autoload.php') ? __DIR__ . '/../../ 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); diff --git a/tests/server/index.php b/tests/server/index.php index d70eabf..b3d5230 100644 --- a/tests/server/index.php +++ b/tests/server/index.php @@ -13,6 +13,7 @@ declare(strict_types=1); 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'; From 2b2b5a1ff6d21f1affc849ca16aed00cc91fc39a Mon Sep 17 00:00:00 2001 From: n0nag0n Date: Sat, 17 Feb 2024 08:24:02 -0700 Subject: [PATCH 09/11] tweak to composer config --- composer.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 5a98697..c79b3b0 100644 --- a/composer.json +++ b/composer.json @@ -50,7 +50,9 @@ "config": { "allow-plugins": { "phpstan/extension-installer": true - } + }, + "process-timeout": 0, + "sort-packages": true }, "scripts": { "test": "phpunit", From d9e5185087b856fd2235b1f9b9a6321e1c3b7f19 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Sun, 18 Feb 2024 03:32:58 -0400 Subject: [PATCH 10/11] =?UTF-8?q?tabs=20again,=20config=20your=20editor=20?= =?UTF-8?q?=F0=9F=98=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- composer.json | 8 ++++---- phpunit.xml | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index c79b3b0..b45d101 100644 --- a/composer.json +++ b/composer.json @@ -51,14 +51,14 @@ "allow-plugins": { "phpstan/extension-installer": true }, - "process-timeout": 0, - "sort-packages": 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-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", diff --git a/phpunit.xml b/phpunit.xml index 9805e7b..c97f669 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -22,7 +22,7 @@ - - - + + + From 4d064cbb6df16e927423a5a58592cdd6a183596d Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Sun, 18 Feb 2024 03:42:25 -0400 Subject: [PATCH 11/11] Applied phpcbf --- flight/Engine.php | 24 ++++++++++++------------ flight/database/PdoWrapper.php | 2 +- flight/util/ReturnTypeWillChange.php | 1 + 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/flight/Engine.php b/flight/Engine.php index 1826445..ffd51e4 100644 --- a/flight/Engine.php +++ b/flight/Engine.php @@ -362,17 +362,16 @@ class Engine } /** - * Processes each routes middleware + * Processes each routes middleware. * - * @param array $middleware middleware attached to the route - * @param array $params route->params - * @param string $event_name if this is the before or after method - * - * @return boolean + * @param array $middleware Middleware attached to the route. + * @param array $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; @@ -382,7 +381,9 @@ class Engine ? $middleware : (method_exists($middleware, 'before') === true ? [$middleware, 'before'] - : false)); + : false + ) + ); } elseif ($event_name === 'after') { // must be an object. No functions allowed here if ( @@ -418,11 +419,9 @@ class Engine return $at_least_one_middleware_failed; } - /********************************* - * - * Extensible Methods - *********************************/ - + //////////////////////// + // Extensible Methods // + //////////////////////// /** * Starts the framework. * @@ -454,6 +453,7 @@ class Engine // Route the request $failed_middleware_check = false; + while ($route = $router->route($request)) { $params = array_values($route->params); diff --git a/flight/database/PdoWrapper.php b/flight/database/PdoWrapper.php index ee6d4bf..842038c 100644 --- a/flight/database/PdoWrapper.php +++ b/flight/database/PdoWrapper.php @@ -136,6 +136,6 @@ class PdoWrapper extends PDO $current_index += strlen($question_marks) + 4; } - return [ 'sql' => $sql, 'params' => $params ]; + return ['sql' => $sql, 'params' => $params]; } } diff --git a/flight/util/ReturnTypeWillChange.php b/flight/util/ReturnTypeWillChange.php index df7c49f..31a929b 100644 --- a/flight/util/ReturnTypeWillChange.php +++ b/flight/util/ReturnTypeWillChange.php @@ -1,4 +1,5 @@