From f7610f883c12800949529e046056bb5a290f41db Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 26 Mar 2026 02:49:51 -0400 Subject: [PATCH 01/49] add trim_trailing_whitespace and insert_final_newline to .editorconfig --- .editorconfig | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.editorconfig b/.editorconfig index 19ae126..e8d15f4 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,9 +5,8 @@ indent_style = space indent_size = 4 end_of_line = lf charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true [*.md] indent_size = 2 - -[tests/views/*.php] -insert_final_newline = false From b312f611fc3aaa3a55fed38213e81bdecaf5e667 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 26 Mar 2026 02:50:38 -0400 Subject: [PATCH 02/49] set indent_size 4 in phpunit-watcher.yml --- phpunit-watcher.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/phpunit-watcher.yml b/phpunit-watcher.yml index d31e912..6b158df 100644 --- a/phpunit-watcher.yml +++ b/phpunit-watcher.yml @@ -1,13 +1,13 @@ hideManual: true watch: - directories: - - tests - - flight - fileMask: '*.php' + directories: + - tests + - flight + fileMask: '*.php' notifications: - passingTests: false - failingTests: false + passingTests: false + failingTests: false phpunit: - binaryPath: ./vendor/bin/phpunit - arguments: '--stop-on-failure' - timeout: 180 \ No newline at end of file + binaryPath: ./vendor/bin/phpunit + arguments: '--stop-on-failure' + timeout: 180 From f9b7c6b5979399147fd61cf50639e0d9663605b1 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 26 Mar 2026 02:59:09 -0400 Subject: [PATCH 03/49] delete index.php --- .gitattributes | 1 - index.php | 12 ------------ phpcs.xml.dist | 1 - 3 files changed, 14 deletions(-) delete mode 100644 index.php diff --git a/.gitattributes b/.gitattributes index c01e089..cd88ff9 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,7 +5,6 @@ /.gitattributes export-ignore /.gitignore export-ignore /CONTRIBUTING.md export-ignore -/index.php export-ignore /phpcs.xml.dist export-ignore /phpstan-baseline.neon export-ignore /phpstan.dist.neon export-ignore diff --git a/index.php b/index.php deleted file mode 100644 index 0db24be..0000000 --- a/index.php +++ /dev/null @@ -1,12 +0,0 @@ - - index.php flight tests From cc5d162bb754e87816c31a7b3b93dc156eb593c3 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 26 Mar 2026 03:04:16 -0400 Subject: [PATCH 04/49] ignore README.md from composer install --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index cd88ff9..edcce41 100644 --- a/.gitattributes +++ b/.gitattributes @@ -10,3 +10,4 @@ /phpstan.dist.neon export-ignore /phpunit-watcher.yml export-ignore /phpunit.xml.dist export-ignore +/README.md export-ignore From 6ece1edb3810281e489e8edad3e5b93c9a192ccf Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 26 Mar 2026 03:04:55 -0400 Subject: [PATCH 05/49] allow !is_array() in contributions (cleaner and readable code) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4b1af6d..d6a5acc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,7 @@ Flight aims to be simple and fast. Anything that compromises either of those two * **Coding Standards** - We use PSR1 coding standards enforced by PHPCS. Some standards that either need additional configuration or need to be manually done are: * PHPStan is at level 6. - * `===` instead of truthy or falsey statements like `==` or `!is_array()`. + * `===` instead of truthy or falsey statements like `==`. * **PHP 7.4 Focused** - We do not make PHP 8+ focused enhancements on the framework as the focus is maintaining PHP 7.4. From f1a8c8104b235e1764db4da0bfac3126a48b094a Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 26 Mar 2026 03:05:15 -0400 Subject: [PATCH 06/49] reset default phpcs psr12 rules --- phpcs.xml.dist | 1 - 1 file changed, 1 deletion(-) diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 327abfe..ba1c395 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -18,7 +18,6 @@ - flight From 6cd027e620a6db4433fbb292557a875187e8e32d Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 26 Mar 2026 03:08:16 -0400 Subject: [PATCH 07/49] clean .gitignore --- .gitignore | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index cb308cf..a15f603 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,3 @@ -.idea/ -.vscode/ -vendor/ -composer.phar -composer.lock -.phpunit.result.cache -coverage/ *.sublime* -clover.xml -phpcs.xml -phpstan.neon -phpunit.xml -.runway-config.json -.runway-creds.json -.DS_Store +/vendor/ +composer.lock From 7f20704cd228a0e04fa4c135c83ca607436b05e7 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 26 Mar 2026 03:09:25 -0400 Subject: [PATCH 08/49] update broken packagist homepage --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 33d2951..8f521d6 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "flightphp/core", "description": "Flight is a fast, simple, extensible framework for PHP. Flight enables you to quickly and easily build RESTful web applications. This is the maintained fork of mikecao/flight", - "homepage": "http://flightphp.com", + "homepage": "https://docs.flightphp.com/", "license": "MIT", "authors": [ { From eac46dbf395281183a45f80197aaf8c476ad026c Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 26 Mar 2026 03:10:58 -0400 Subject: [PATCH 09/49] update contributors homepages --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 8f521d6..e99527b 100644 --- a/composer.json +++ b/composer.json @@ -7,13 +7,13 @@ { "name": "Mike Cao", "email": "mike@mikecao.com", - "homepage": "http://www.mikecao.com/", + "homepage": "https://mikecao.com/", "role": "Original Developer" }, { "name": "Franyer Sánchez", "email": "franyeradriansanchez@gmail.com", - "homepage": "https://faslatam.42web.io", + "homepage": "https://faslatam.42web.io/", "role": "Maintainer" }, { From 42e8166984472b5cb69f858cfc904e641f88db38 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 26 Mar 2026 03:15:25 -0400 Subject: [PATCH 10/49] ignore .phpunit.result.cache, phpcs.xml, phpstan.neon and phpunit.xml --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index a15f603..2cae83d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ *.sublime* +.phpunit.result.cache /vendor/ composer.lock +phpcs.xml +phpstan.neon +phpunit.xml From c8e7855fa82a4f6eeaf06ccce51fb50c73dfff74 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 26 Mar 2026 03:16:48 -0400 Subject: [PATCH 11/49] rename flight -> src --- {flight => src}/Engine.php | 0 {flight => src}/Flight.php | 0 {flight => src}/autoload.php | 0 {flight => src}/commands/AiGenerateInstructionsCommand.php | 0 {flight => src}/commands/AiInitCommand.php | 0 {flight => src}/commands/ControllerCommand.php | 0 {flight => src}/commands/RouteCommand.php | 0 {flight => src}/core/Dispatcher.php | 0 {flight => src}/core/EventDispatcher.php | 0 {flight => src}/core/Loader.php | 0 {flight => src}/database/PdoWrapper.php | 0 {flight => src}/database/SimplePdo.php | 0 {flight => src}/net/Request.php | 0 {flight => src}/net/Response.php | 0 {flight => src}/net/Route.php | 0 {flight => src}/net/Router.php | 0 {flight => src}/net/UploadedFile.php | 0 {flight => src}/template/View.php | 0 {flight => src}/util/Collection.php | 0 {flight => src}/util/Json.php | 0 {flight => src}/util/ReturnTypeWillChange.php | 0 21 files changed, 0 insertions(+), 0 deletions(-) rename {flight => src}/Engine.php (100%) rename {flight => src}/Flight.php (100%) rename {flight => src}/autoload.php (100%) rename {flight => src}/commands/AiGenerateInstructionsCommand.php (100%) rename {flight => src}/commands/AiInitCommand.php (100%) rename {flight => src}/commands/ControllerCommand.php (100%) rename {flight => src}/commands/RouteCommand.php (100%) rename {flight => src}/core/Dispatcher.php (100%) rename {flight => src}/core/EventDispatcher.php (100%) rename {flight => src}/core/Loader.php (100%) rename {flight => src}/database/PdoWrapper.php (100%) rename {flight => src}/database/SimplePdo.php (100%) rename {flight => src}/net/Request.php (100%) rename {flight => src}/net/Response.php (100%) rename {flight => src}/net/Route.php (100%) rename {flight => src}/net/Router.php (100%) rename {flight => src}/net/UploadedFile.php (100%) rename {flight => src}/template/View.php (100%) rename {flight => src}/util/Collection.php (100%) rename {flight => src}/util/Json.php (100%) rename {flight => src}/util/ReturnTypeWillChange.php (100%) diff --git a/flight/Engine.php b/src/Engine.php similarity index 100% rename from flight/Engine.php rename to src/Engine.php diff --git a/flight/Flight.php b/src/Flight.php similarity index 100% rename from flight/Flight.php rename to src/Flight.php diff --git a/flight/autoload.php b/src/autoload.php similarity index 100% rename from flight/autoload.php rename to src/autoload.php diff --git a/flight/commands/AiGenerateInstructionsCommand.php b/src/commands/AiGenerateInstructionsCommand.php similarity index 100% rename from flight/commands/AiGenerateInstructionsCommand.php rename to src/commands/AiGenerateInstructionsCommand.php diff --git a/flight/commands/AiInitCommand.php b/src/commands/AiInitCommand.php similarity index 100% rename from flight/commands/AiInitCommand.php rename to src/commands/AiInitCommand.php diff --git a/flight/commands/ControllerCommand.php b/src/commands/ControllerCommand.php similarity index 100% rename from flight/commands/ControllerCommand.php rename to src/commands/ControllerCommand.php diff --git a/flight/commands/RouteCommand.php b/src/commands/RouteCommand.php similarity index 100% rename from flight/commands/RouteCommand.php rename to src/commands/RouteCommand.php diff --git a/flight/core/Dispatcher.php b/src/core/Dispatcher.php similarity index 100% rename from flight/core/Dispatcher.php rename to src/core/Dispatcher.php diff --git a/flight/core/EventDispatcher.php b/src/core/EventDispatcher.php similarity index 100% rename from flight/core/EventDispatcher.php rename to src/core/EventDispatcher.php diff --git a/flight/core/Loader.php b/src/core/Loader.php similarity index 100% rename from flight/core/Loader.php rename to src/core/Loader.php diff --git a/flight/database/PdoWrapper.php b/src/database/PdoWrapper.php similarity index 100% rename from flight/database/PdoWrapper.php rename to src/database/PdoWrapper.php diff --git a/flight/database/SimplePdo.php b/src/database/SimplePdo.php similarity index 100% rename from flight/database/SimplePdo.php rename to src/database/SimplePdo.php diff --git a/flight/net/Request.php b/src/net/Request.php similarity index 100% rename from flight/net/Request.php rename to src/net/Request.php diff --git a/flight/net/Response.php b/src/net/Response.php similarity index 100% rename from flight/net/Response.php rename to src/net/Response.php diff --git a/flight/net/Route.php b/src/net/Route.php similarity index 100% rename from flight/net/Route.php rename to src/net/Route.php diff --git a/flight/net/Router.php b/src/net/Router.php similarity index 100% rename from flight/net/Router.php rename to src/net/Router.php diff --git a/flight/net/UploadedFile.php b/src/net/UploadedFile.php similarity index 100% rename from flight/net/UploadedFile.php rename to src/net/UploadedFile.php diff --git a/flight/template/View.php b/src/template/View.php similarity index 100% rename from flight/template/View.php rename to src/template/View.php diff --git a/flight/util/Collection.php b/src/util/Collection.php similarity index 100% rename from flight/util/Collection.php rename to src/util/Collection.php diff --git a/flight/util/Json.php b/src/util/Json.php similarity index 100% rename from flight/util/Json.php rename to src/util/Json.php diff --git a/flight/util/ReturnTypeWillChange.php b/src/util/ReturnTypeWillChange.php similarity index 100% rename from flight/util/ReturnTypeWillChange.php rename to src/util/ReturnTypeWillChange.php From 4f6080f6f4788335c6468c2a866317a4e1e062d1 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 26 Mar 2026 03:16:53 -0400 Subject: [PATCH 12/49] add psr-4 --- composer.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index e99527b..edcb703 100644 --- a/composer.json +++ b/composer.json @@ -28,8 +28,11 @@ }, "autoload": { "files": [ - "flight/autoload.php" - ] + "src/autoload.php" + ], + "psr-4": { + "flight\\": "src" + } }, "autoload-dev": { "classmap": [ From 450eea84eab521080e832a55e7bf9c36ec50c356 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 26 Mar 2026 03:36:25 -0400 Subject: [PATCH 13/49] simplify autoload-dev to a single psr-4 autoload --- composer.json | 12 ++---------- phpunit.xml.dist | 2 +- .../FlightRouteCompactSyntaxTest.php | 5 +---- .../PostsController.php | 2 +- .../TodosController.php | 2 +- .../UsersController.php | 2 +- .../ExampleClass.php | 2 ++ .../FlightTest.php | 3 +-- tests/server/AuthCheck.php | 2 +- tests/server/LayoutMiddleware.php | 2 +- tests/server/OverwriteBodyMiddleware.php | 2 +- tests/server/Pascal_Snake_Case.php | 2 +- tests/server/index.php | 8 ++++---- tests/{server-v2 => server_v2}/index.php | 4 ++-- tests/{server-v2 => server_v2}/template.phtml | 0 15 files changed, 20 insertions(+), 30 deletions(-) rename tests/{groupcompactsyntax => group_compact_syntax}/FlightRouteCompactSyntaxTest.php (96%) rename tests/{groupcompactsyntax => group_compact_syntax}/PostsController.php (93%) rename tests/{groupcompactsyntax => group_compact_syntax}/TodosController.php (83%) rename tests/{groupcompactsyntax => group_compact_syntax}/UsersController.php (84%) rename tests/{named-arguments => named_arguments}/ExampleClass.php (78%) rename tests/{named-arguments => named_arguments}/FlightTest.php (98%) rename tests/{server-v2 => server_v2}/index.php (99%) rename tests/{server-v2 => server_v2}/template.phtml (100%) diff --git a/composer.json b/composer.json index edcb703..5e72dd7 100644 --- a/composer.json +++ b/composer.json @@ -35,16 +35,8 @@ } }, "autoload-dev": { - "classmap": [ - "tests/classes/" - ], "psr-4": { - "Tests\\PHP8\\": [ - "tests/named-arguments" - ], - "Tests\\Server\\": "tests/server", - "Tests\\ServerV2\\": "tests/server-v2", - "tests\\groupcompactsyntax\\": "tests/groupcompactsyntax" + "tests\\": "tests" } }, "require-dev": { @@ -84,7 +76,7 @@ ], "test-server-v2": [ "echo \"Running Test Server\"", - "@php -S localhost:8000 -t tests/server-v2" + "@php -S localhost:8000 -t tests/server_v2" ], "test-coverage:win": [ "del clover.xml", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index b890bb7..b205597 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -22,7 +22,7 @@ tests/ - tests/named-arguments/ + tests/named_arguments/ diff --git a/tests/groupcompactsyntax/FlightRouteCompactSyntaxTest.php b/tests/group_compact_syntax/FlightRouteCompactSyntaxTest.php similarity index 96% rename from tests/groupcompactsyntax/FlightRouteCompactSyntaxTest.php rename to tests/group_compact_syntax/FlightRouteCompactSyntaxTest.php index 84d168f..2c8d3a1 100644 --- a/tests/groupcompactsyntax/FlightRouteCompactSyntaxTest.php +++ b/tests/group_compact_syntax/FlightRouteCompactSyntaxTest.php @@ -2,13 +2,10 @@ declare(strict_types=1); -namespace tests\groupcompactsyntax; +namespace tests\group_compact_syntax; use Flight; use PHPUnit\Framework\TestCase; -use tests\groupcompactsyntax\PostsController; -use tests\groupcompactsyntax\TodosController; -use tests\groupcompactsyntax\UsersController; final class FlightRouteCompactSyntaxTest extends TestCase { diff --git a/tests/groupcompactsyntax/PostsController.php b/tests/group_compact_syntax/PostsController.php similarity index 93% rename from tests/groupcompactsyntax/PostsController.php rename to tests/group_compact_syntax/PostsController.php index f95f60d..5e693fb 100644 --- a/tests/groupcompactsyntax/PostsController.php +++ b/tests/group_compact_syntax/PostsController.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\groupcompactsyntax; +namespace tests\group_compact_syntax; final class PostsController { diff --git a/tests/groupcompactsyntax/TodosController.php b/tests/group_compact_syntax/TodosController.php similarity index 83% rename from tests/groupcompactsyntax/TodosController.php rename to tests/group_compact_syntax/TodosController.php index 91c30cf..744f6b7 100644 --- a/tests/groupcompactsyntax/TodosController.php +++ b/tests/group_compact_syntax/TodosController.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\groupcompactsyntax; +namespace tests\group_compact_syntax; final class TodosController { diff --git a/tests/groupcompactsyntax/UsersController.php b/tests/group_compact_syntax/UsersController.php similarity index 84% rename from tests/groupcompactsyntax/UsersController.php rename to tests/group_compact_syntax/UsersController.php index d6372b5..7323771 100644 --- a/tests/groupcompactsyntax/UsersController.php +++ b/tests/group_compact_syntax/UsersController.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\groupcompactsyntax; +namespace tests\group_compact_syntax; final class UsersController { diff --git a/tests/named-arguments/ExampleClass.php b/tests/named_arguments/ExampleClass.php similarity index 78% rename from tests/named-arguments/ExampleClass.php rename to tests/named_arguments/ExampleClass.php index 508068c..9555047 100644 --- a/tests/named-arguments/ExampleClass.php +++ b/tests/named_arguments/ExampleClass.php @@ -2,6 +2,8 @@ declare(strict_types=1); +namespace tests\named_arguments; + // phpcs:ignore PSR1.Classes.ClassDeclaration.MissingNamespace class ExampleClass { diff --git a/tests/named-arguments/FlightTest.php b/tests/named_arguments/FlightTest.php similarity index 98% rename from tests/named-arguments/FlightTest.php rename to tests/named_arguments/FlightTest.php index 6593450..9053b02 100644 --- a/tests/named-arguments/FlightTest.php +++ b/tests/named_arguments/FlightTest.php @@ -2,10 +2,9 @@ declare(strict_types=1); -namespace Tests\PHP8; +namespace tests\named_arguments; use DateTimeImmutable; -use ExampleClass; use Flight; use flight\Container; use flight\Engine; diff --git a/tests/server/AuthCheck.php b/tests/server/AuthCheck.php index 79e8f67..c042c1b 100644 --- a/tests/server/AuthCheck.php +++ b/tests/server/AuthCheck.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Tests\Server; +namespace tests\server; class AuthCheck { diff --git a/tests/server/LayoutMiddleware.php b/tests/server/LayoutMiddleware.php index 150985b..01058c0 100644 --- a/tests/server/LayoutMiddleware.php +++ b/tests/server/LayoutMiddleware.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Tests\Server; +namespace tests\server; use Flight; diff --git a/tests/server/OverwriteBodyMiddleware.php b/tests/server/OverwriteBodyMiddleware.php index 98e8468..6a3dfa8 100644 --- a/tests/server/OverwriteBodyMiddleware.php +++ b/tests/server/OverwriteBodyMiddleware.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Tests\Server; +namespace tests\server; use Flight; diff --git a/tests/server/Pascal_Snake_Case.php b/tests/server/Pascal_Snake_Case.php index d80ed41..df9a66f 100644 --- a/tests/server/Pascal_Snake_Case.php +++ b/tests/server/Pascal_Snake_Case.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Tests\Server; +namespace tests\server; class Pascal_Snake_Case // phpcs:ignore { diff --git a/tests/server/index.php b/tests/server/index.php index 6bb9dca..f4193e8 100644 --- a/tests/server/index.php +++ b/tests/server/index.php @@ -7,10 +7,10 @@ use flight\core\Loader; use flight\database\PdoWrapper; use tests\classes\Container; use tests\classes\ContainerDefault; -use Tests\Server\AuthCheck; -use Tests\Server\LayoutMiddleware; -use Tests\Server\OverwriteBodyMiddleware; -use Tests\Server\Pascal_Snake_Case; +use tests\server\AuthCheck; +use tests\server\LayoutMiddleware; +use tests\server\OverwriteBodyMiddleware; +use tests\server\Pascal_Snake_Case; /* * This is the test file where we can open up a quick test server and make diff --git a/tests/server-v2/index.php b/tests/server_v2/index.php similarity index 99% rename from tests/server-v2/index.php rename to tests/server_v2/index.php index 4f45d99..1aadcc1 100644 --- a/tests/server-v2/index.php +++ b/tests/server_v2/index.php @@ -9,7 +9,7 @@ declare(strict_types=1); * @author Kristaps Muižnieks https://github.com/krmu */ -namespace Tests\ServerV2 { +namespace tests\server_v2 { class AuthCheck { public function before(): void @@ -23,7 +23,7 @@ namespace Tests\ServerV2 { namespace { - use Tests\ServerV2\AuthCheck; + use tests\server_v2\AuthCheck; require_once __DIR__ . '/../phpunit_autoload.php'; diff --git a/tests/server-v2/template.phtml b/tests/server_v2/template.phtml similarity index 100% rename from tests/server-v2/template.phtml rename to tests/server_v2/template.phtml From a19a627dae58941822b9658aba1b861d005a4b02 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 26 Mar 2026 03:50:53 -0400 Subject: [PATCH 14/49] allow to set phpunit-watcher options per contributor --- .gitignore | 1 + composer.json | 9 ++++----- phpunit-watcher.yml => phpunit-watcher.yml.dist | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) rename phpunit-watcher.yml => phpunit-watcher.yml.dist (93%) diff --git a/.gitignore b/.gitignore index 2cae83d..baf8793 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ composer.lock phpcs.xml phpstan.neon +phpunit-watcher.yml phpunit.xml diff --git a/composer.json b/composer.json index 5e72dd7..4b49279 100644 --- a/composer.json +++ b/composer.json @@ -49,6 +49,7 @@ "phpstan/phpstan": "^2.1", "phpunit/phpunit": "^9.6", "rregeer/phpunit-coverage-check": "^0.3.1", + "spatie/phpunit-watcher": "^1.23", "squizlabs/php_codesniffer": "^4.0" }, "config": { @@ -60,10 +61,7 @@ }, "scripts": { "test": "phpunit", - "test-watcher": [ - "phpunit-watcher || composer global require spatie/phpunit-watcher --dev", - "phpunit-watcher watch" - ], + "test-watcher": "phpunit-watcher watch", "test-coverage": [ "rm -f clover.xml", "@putenv XDEBUG_MODE=coverage", @@ -98,7 +96,8 @@ "post-install-cmd": [ "@php -r \"if (!file_exists('phpcs.xml')) copy('phpcs.xml.dist', 'phpcs.xml');\"", "@php -r \"if (!file_exists('phpstan.neon')) copy('phpstan.dist.neon', 'phpstan.neon');\"", - "@php -r \"if (!file_exists('phpunit.xml')) copy('phpunit.xml.dist', 'phpunit.xml');\"" + "@php -r \"if (!file_exists('phpunit.xml')) copy('phpunit.xml.dist', 'phpunit.xml');\"", + "@php -r \"if (!file_exists('phpunit-watcher.yml')) copy('phpunit-watcher.yml.dist', 'phpunit-watcher.yml');\"" ] }, "suggest": { diff --git a/phpunit-watcher.yml b/phpunit-watcher.yml.dist similarity index 93% rename from phpunit-watcher.yml rename to phpunit-watcher.yml.dist index 6b158df..86c3979 100644 --- a/phpunit-watcher.yml +++ b/phpunit-watcher.yml.dist @@ -2,7 +2,7 @@ hideManual: true watch: directories: - tests - - flight + - src fileMask: '*.php' notifications: passingTests: false From 3046bf770e0f3e6fec0bd8538b8bb4264c14ae48 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 26 Mar 2026 03:58:35 -0400 Subject: [PATCH 15/49] fix coverage generation --- .gitignore | 1 + composer.json | 6 +++--- phpunit.xml.dist | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index baf8793..ac7efc8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *.sublime* .phpunit.result.cache +/coverage/ /vendor/ composer.lock phpcs.xml diff --git a/composer.json b/composer.json index 4b49279..e811cfd 100644 --- a/composer.json +++ b/composer.json @@ -63,10 +63,10 @@ "test": "phpunit", "test-watcher": "phpunit-watcher watch", "test-coverage": [ - "rm -f clover.xml", + "rm -rf coverage", "@putenv XDEBUG_MODE=coverage", - "phpunit --coverage-html=coverage --coverage-clover=clover.xml", - "coverage-check clover.xml 100" + "phpunit --coverage-html coverage --coverage-clover coverage/clover.xml", + "coverage-check coverage/clover.xml 100" ], "test-server": [ "echo \"Running Test Server\"", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index b205597..c1a85f7 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -13,10 +13,10 @@ colors="true"> - flight/ + src/ - flight/autoload.php + src/autoload.php From b0b3e096ca1ea3189ae3948763bed19b15dfa046 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 26 Mar 2026 03:59:15 -0400 Subject: [PATCH 16/49] remove test-coverage:win --- composer.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/composer.json b/composer.json index e811cfd..dce7b8e 100644 --- a/composer.json +++ b/composer.json @@ -76,11 +76,6 @@ "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" - ], "test-performance": [ "echo \"Running Performance Tests...\"", "@php -S localhost:8077 -t tests/performance/ > /dev/null 2>&1 & echo $! > server.pid", From 17e0224a55776a34b82c3786629967bada49917e Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 26 Mar 2026 04:01:41 -0400 Subject: [PATCH 17/49] simplify lint and beautify scripts --- composer.json | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index dce7b8e..426f7f3 100644 --- a/composer.json +++ b/composer.json @@ -85,9 +85,13 @@ "rm server.pid", "echo \"Performance Tests Completed.\"" ], - "lint": "phpstan --no-progress --memory-limit=256M", - "beautify": "phpcbf", - "phpcs": "phpcs", + "lint": [ + "phpstan --no-progress --memory-limit=256M", + "phpcs" + ], + "format": [ + "phpcbf" + ], "post-install-cmd": [ "@php -r \"if (!file_exists('phpcs.xml')) copy('phpcs.xml.dist', 'phpcs.xml');\"", "@php -r \"if (!file_exists('phpstan.neon')) copy('phpstan.dist.neon', 'phpstan.neon');\"", From d2d1d0505de24b35c513adc677748ec125324762 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 26 Mar 2026 04:04:00 -0400 Subject: [PATCH 18/49] update GEMINI.md and copilot-instructions.md --- .gemini/GEMINI.md | 5 ++--- .github/copilot-instructions.md | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.gemini/GEMINI.md b/.gemini/GEMINI.md index 59a33e4..03cc3ba 100644 --- a/.gemini/GEMINI.md +++ b/.gemini/GEMINI.md @@ -16,12 +16,11 @@ This is the main FlightPHP core library for building fast, simple, and extensibl - Run tests: `composer test` (uses phpunit/phpunit and spatie/phpunit-watcher) - Run test server: `composer test-server` or `composer test-server-v2` - Lint code: `composer lint` (uses phpstan/phpstan, level 6) -- Beautify code: `composer beautify` (uses squizlabs/php_codesniffer, PSR1) -- Check code style: `composer phpcs` +- Format code: `composer format` (uses squizlabs/php_codesniffer, PSR12) - Test coverage: `composer test-coverage` ## Coding Standards -- Follow PSR1 coding standards (enforced by PHPCS) +- Follow PSR12 coding standards (enforced by PHPCS) - Use strict comparisons (`===`, `!==`) - PHPStan level 6 compliance - Focus on PHP 7.4 compatibility (avoid PHP 8+ only features) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 59a33e4..03cc3ba 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -16,12 +16,11 @@ This is the main FlightPHP core library for building fast, simple, and extensibl - Run tests: `composer test` (uses phpunit/phpunit and spatie/phpunit-watcher) - Run test server: `composer test-server` or `composer test-server-v2` - Lint code: `composer lint` (uses phpstan/phpstan, level 6) -- Beautify code: `composer beautify` (uses squizlabs/php_codesniffer, PSR1) -- Check code style: `composer phpcs` +- Format code: `composer format` (uses squizlabs/php_codesniffer, PSR12) - Test coverage: `composer test-coverage` ## Coding Standards -- Follow PSR1 coding standards (enforced by PHPCS) +- Follow PSR12 coding standards (enforced by PHPCS) - Use strict comparisons (`===`, `!==`) - PHPStan level 6 compliance - Focus on PHP 7.4 compatibility (avoid PHP 8+ only features) From 26ce749db99d9846e8231051eeeef7b94f6f490f Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 26 Mar 2026 04:05:48 -0400 Subject: [PATCH 19/49] remove ignored required_once __DIR__ . '/core/Loader.php' --- src/autoload.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/autoload.php b/src/autoload.php index 0a31c86..b5f84ab 100644 --- a/src/autoload.php +++ b/src/autoload.php @@ -5,6 +5,5 @@ declare(strict_types=1); use flight\core\Loader; require_once __DIR__ . '/Flight.php'; -require_once __DIR__ . '/core/Loader.php'; Loader::autoload(true, [dirname(__DIR__)]); From 7b1e414717d8238b117dec71acade323578db8e8 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 26 Mar 2026 04:07:50 -0400 Subject: [PATCH 20/49] update Loader docblock --- src/core/Loader.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/core/Loader.php b/src/core/Loader.php index 1824b9c..bed0f0f 100644 --- a/src/core/Loader.php +++ b/src/core/Loader.php @@ -8,12 +8,11 @@ use Closure; use Exception; /** - * The Loader class is responsible for loading objects. It maintains - * a list of reusable class instances and can generate a new class - * instances with custom initialization parameters. It also performs - * class autoloading. + * The Loader class is responsible for loading objects. It maintains a list of + * reusable class instances and can generate a new class instances with custom + * initialization parameters. It also performs class autoloading. * - * @license MIT, http://flightphp.com/license + * @license MIT, https://docs.flightphp.com/license/ * @copyright Copyright (c) 2011, Mike Cao */ class Loader From 6784507bf8cf082750e5eb7f4218887248170251 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 26 Mar 2026 11:11:25 -0400 Subject: [PATCH 21/49] refactor step 1 of Loader --- phpstan.dist.neon | 3 +- src/core/Loader.php | 107 +++++++++++++++----------------------------- 2 files changed, 38 insertions(+), 72 deletions(-) diff --git a/phpstan.dist.neon b/phpstan.dist.neon index 57ab298..d104bd5 100644 --- a/phpstan.dist.neon +++ b/phpstan.dist.neon @@ -5,6 +5,5 @@ includes: parameters: level: 6 paths: - - flight - - index.php + - src treatPhpDocTypesAsCertain: false diff --git a/src/core/Loader.php b/src/core/Loader.php index bed0f0f..491af65 100644 --- a/src/core/Loader.php +++ b/src/core/Loader.php @@ -4,14 +4,12 @@ declare(strict_types=1); namespace flight\core; -use Closure; use Exception; /** * The Loader class is responsible for loading objects. It maintains a list of * reusable class instances and can generate a new class instances with custom * initialization parameters. It also performs class autoloading. - * * @license MIT, https://docs.flightphp.com/license/ * @copyright Copyright (c) 2011, Mike Cao */ @@ -19,50 +17,39 @@ class Loader { /** * Registered classes. - * - * @var array, ?callable}> $classes + * @var array, ?callable(object): void}> */ protected array $classes = []; - /** - * If this is disabled, classes can load with underscores - */ + /** If this is disabled, classes can load with underscores */ protected static bool $v2ClassLoading = true; - /** - * Class instances. - * - * @var array - */ + /** @var array Class instances */ protected array $instances = []; - /** - * Autoload directories. - * - * @var array - */ + /** @var array Autoload directories */ protected static array $dirs = []; /** * Registers a class. - * + * @template T of object * @param string $name Registry name - * @param class-string|Closure(): T $class Class name or function to instantiate class + * @param class-string|callable(): T $class Class name or function to instantiate class * @param array $params Class initialization parameters - * @param ?Closure(T $instance): void $callback $callback Function to call after object instantiation - * - * @template T of object + * @param ?callable(T): void $callback $callback Function to call after object instantiation */ - public function register(string $name, $class, array $params = [], ?callable $callback = null): void - { + public function register( + string $name, + $class, + array $params = [], + ?callable $callback = null + ): void { unset($this->instances[$name]); - $this->classes[$name] = [$class, $params, $callback]; } /** * Unregisters a class. - * * @param string $name Registry name */ public function unregister(string $name): void @@ -72,12 +59,9 @@ class Loader /** * Loads a registered class. - * - * @param string $name Method name - * @param bool $shared Shared instance - * + * @param string $name Method name + * @param bool $shared Shared instance * @throws Exception - * * @return ?object Class instance */ public function load(string $name, bool $shared = true): ?object @@ -85,14 +69,13 @@ class Loader $obj = null; if (isset($this->classes[$name])) { - [0 => $class, 1 => $params, 2 => $callback] = $this->classes[$name]; - + [$class, $params, $callback] = $this->classes[$name]; $exists = isset($this->instances[$name]); if ($shared) { - $obj = ($exists) ? - $this->getInstance($name) : - $this->newInstance($class, $params); + $obj = $exists + ? $this->getInstance($name) + : $this->newInstance($class, $params); if (!$exists) { $this->instances[$name] = $obj; @@ -103,7 +86,7 @@ class Loader if ($callback && (!$shared || !$exists)) { $ref = [&$obj]; - \call_user_func_array($callback, $ref); + call_user_func_array($callback, $ref); } } @@ -112,9 +95,7 @@ class Loader /** * Gets a single instance of a class. - * * @param string $name Instance name - * * @return ?object Class instance */ public function getInstance(string $name): ?object @@ -124,53 +105,43 @@ class Loader /** * Gets a new instance of a class. - * - * @param class-string|Closure(): class-string $class Class name or callback function to instantiate class - * @param array $params Class initialization parameters - * * @template T of object - * + * @param class-string|callable(): T $class Class name or callback function to instantiate class + * @param array $params Class initialization parameters * @throws Exception - * * @return T Class instance */ - public function newInstance($class, array $params = []) + public function newInstance($class, array $params = []): object { - if (\is_callable($class)) { - return \call_user_func_array($class, $params); + if (is_callable($class)) { + return call_user_func_array($class, $params); } return new $class(...$params); } /** - * Gets a registered callable - * + * Gets a registered callable. * @param string $name Registry name - * - * @return mixed Class information or null if not registered + * @return ?array{class-string|callable(): object, array, ?callable(object): void} + * Class information or null if not registered */ - public function get(string $name) + public function get(string $name): ?array { return $this->classes[$name] ?? null; } - /** - * Resets the object to the initial state. - */ + /** Resets the object to the initial state */ public function reset(): void { $this->classes = []; $this->instances = []; } - // Autoloading Functions - /** * Starts/stops autoloader. - * - * @param bool $enabled Enable/disable autoloading - * @param string|iterable $dirs Autoload directories + * @param bool $enabled Enable/disable autoloading + * @param string|iterable $dirs Autoload directories */ public static function autoload(bool $enabled = true, $dirs = []): void { @@ -187,14 +158,13 @@ class Loader /** * Autoloads classes. - * * Classes are not allowed to have underscores in their names. * * @param string $class Class name */ public static function loadClass(string $class): void { - $replace_chars = self::$v2ClassLoading === true ? ['\\', '_'] : ['\\']; + $replace_chars = self::$v2ClassLoading ? ['\\', '_'] : ['\\']; $classFile = str_replace($replace_chars, '/', $class) . '.php'; foreach (self::$dirs as $dir) { @@ -202,6 +172,7 @@ class Loader if (file_exists($filePath)) { require_once $filePath; + return; } } @@ -209,17 +180,16 @@ class Loader /** * Adds a directory for autoloading classes. - * * @param string|iterable $dir Directory path */ public static function addDirectory($dir): void { - if (\is_array($dir) || \is_object($dir)) { + if (is_array($dir) || is_object($dir)) { foreach ($dir as $value) { self::addDirectory($value); } - } elseif (\is_string($dir)) { - if (!\in_array($dir, self::$dirs, true)) { + } elseif (is_string($dir)) { + if (!in_array($dir, self::$dirs, true)) { self::$dirs[] = $dir; } } @@ -228,10 +198,7 @@ class Loader /** * Sets the value for V2 class loading. - * * @param bool $value The value to set for V2 class loading. - * - * @return void */ public static function setV2ClassLoading(bool $value): void { From 886e58442957cc58ef25bec1b022b154068e3060 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 26 Mar 2026 11:30:25 -0400 Subject: [PATCH 22/49] remove v2 loader in favor of psr-4 --- src/core/Loader.php | 20 ++------------------ tests/LoaderTest.php | 15 ++------------- 2 files changed, 4 insertions(+), 31 deletions(-) diff --git a/src/core/Loader.php b/src/core/Loader.php index 491af65..167b6fd 100644 --- a/src/core/Loader.php +++ b/src/core/Loader.php @@ -15,15 +15,9 @@ use Exception; */ class Loader { - /** - * Registered classes. - * @var array, ?callable(object): void}> - */ + /** @var array, ?callable(object): void}> */ protected array $classes = []; - /** If this is disabled, classes can load with underscores */ - protected static bool $v2ClassLoading = true; - /** @var array Class instances */ protected array $instances = []; @@ -164,7 +158,7 @@ class Loader */ public static function loadClass(string $class): void { - $replace_chars = self::$v2ClassLoading ? ['\\', '_'] : ['\\']; + $replace_chars = ['\\']; $classFile = str_replace($replace_chars, '/', $class) . '.php'; foreach (self::$dirs as $dir) { @@ -194,14 +188,4 @@ class Loader } } } - - - /** - * Sets the value for V2 class loading. - * @param bool $value The value to set for V2 class loading. - */ - public static function setV2ClassLoading(bool $value): void - { - self::$v2ClassLoading = $value; - } } diff --git a/tests/LoaderTest.php b/tests/LoaderTest.php index de37efa..2ab19b3 100644 --- a/tests/LoaderTest.php +++ b/tests/LoaderTest.php @@ -146,23 +146,12 @@ class LoaderTest extends TestCase return self::$dirs; } }; + $loader->addDirectory([__DIR__ . '/classes']); + self::assertEquals([ dirname(__DIR__), __DIR__ . '/classes' ], $loader->getDirectories()); } - - public function testV2ClassLoading(): void - { - $loader = new class extends Loader { - public static function getV2ClassLoading() - { - return self::$v2ClassLoading; - } - }; - $this->assertTrue($loader::getV2ClassLoading()); - $loader::setV2ClassLoading(false); - $this->assertFalse($loader::getV2ClassLoading()); - } } From 07d72d24ef2888474da0230a2d316708ac8c8af6 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 26 Mar 2026 11:38:29 -0400 Subject: [PATCH 23/49] remove Loader::autoload in favor of psr-4 --- composer.json | 4 ++-- src/Flight.php | 2 -- src/autoload.php | 9 --------- src/core/Loader.php | 18 ------------------ tests/LoaderTest.php | 22 ++-------------------- 5 files changed, 4 insertions(+), 51 deletions(-) delete mode 100644 src/autoload.php diff --git a/composer.json b/composer.json index 426f7f3..e08f86a 100644 --- a/composer.json +++ b/composer.json @@ -27,8 +27,8 @@ "ext-json": "*" }, "autoload": { - "files": [ - "src/autoload.php" + "classmap": [ + "src/Flight.php" ], "psr-4": { "flight\\": "src" diff --git a/src/Flight.php b/src/Flight.php index c2e9a01..ddb536f 100644 --- a/src/Flight.php +++ b/src/Flight.php @@ -11,8 +11,6 @@ use flight\net\Route; use flight\core\EventDispatcher; use Psr\Container\ContainerInterface; -require_once __DIR__ . '/autoload.php'; - /** * The Flight class is a static representation of the framework. * diff --git a/src/autoload.php b/src/autoload.php deleted file mode 100644 index b5f84ab..0000000 --- a/src/autoload.php +++ /dev/null @@ -1,9 +0,0 @@ -instances = []; } - /** - * Starts/stops autoloader. - * @param bool $enabled Enable/disable autoloading - * @param string|iterable $dirs Autoload directories - */ - public static function autoload(bool $enabled = true, $dirs = []): void - { - if ($enabled) { - spl_autoload_register([__CLASS__, 'loadClass']); - } else { - spl_autoload_unregister([__CLASS__, 'loadClass']); // @codeCoverageIgnore - } - - if (!empty($dirs)) { - self::addDirectory($dirs); - } - } - /** * Autoloads classes. * Classes are not allowed to have underscores in their names. diff --git a/tests/LoaderTest.php b/tests/LoaderTest.php index 2ab19b3..af4a1a8 100644 --- a/tests/LoaderTest.php +++ b/tests/LoaderTest.php @@ -5,9 +5,9 @@ declare(strict_types=1); namespace tests; use flight\core\Loader; -use tests\classes\Factory; -use tests\classes\User; use PHPUnit\Framework\TestCase; +use tests\classes\User; +use tests\classes\Factory; use tests\classes\TesterClass; class LoaderTest extends TestCase @@ -17,7 +17,6 @@ class LoaderTest extends TestCase protected function setUp(): void { $this->loader = new Loader(); - $this->loader->autoload(true, __DIR__ . '/classes'); } // Autoload a class @@ -137,21 +136,4 @@ class LoaderTest extends TestCase $this->assertEquals('Sally', $TesterClass->param5); $this->assertEquals('Suzie', $TesterClass->param6); } - - public function testAddDirectoryAsArray(): void - { - $loader = new class extends Loader { - public function getDirectories() - { - return self::$dirs; - } - }; - - $loader->addDirectory([__DIR__ . '/classes']); - - self::assertEquals([ - dirname(__DIR__), - __DIR__ . '/classes' - ], $loader->getDirectories()); - } } From 78b8c4be60aaba5f1a1daf22613782a35f706a10 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 26 Mar 2026 11:41:16 -0400 Subject: [PATCH 24/49] remove directory loading in favor of psr-4 --- src/core/Loader.php | 42 ------------------------------------------ tests/AutoloadTest.php | 1 - tests/FlightTest.php | 2 -- 3 files changed, 45 deletions(-) diff --git a/src/core/Loader.php b/src/core/Loader.php index c9ae5fc..6d971b8 100644 --- a/src/core/Loader.php +++ b/src/core/Loader.php @@ -21,9 +21,6 @@ class Loader /** @var array Class instances */ protected array $instances = []; - /** @var array Autoload directories */ - protected static array $dirs = []; - /** * Registers a class. * @template T of object @@ -131,43 +128,4 @@ class Loader $this->classes = []; $this->instances = []; } - - /** - * Autoloads classes. - * Classes are not allowed to have underscores in their names. - * - * @param string $class Class name - */ - public static function loadClass(string $class): void - { - $replace_chars = ['\\']; - $classFile = str_replace($replace_chars, '/', $class) . '.php'; - - foreach (self::$dirs as $dir) { - $filePath = "$dir/$classFile"; - - if (file_exists($filePath)) { - require_once $filePath; - - return; - } - } - } - - /** - * Adds a directory for autoloading classes. - * @param string|iterable $dir Directory path - */ - public static function addDirectory($dir): void - { - if (is_array($dir) || is_object($dir)) { - foreach ($dir as $value) { - self::addDirectory($value); - } - } elseif (is_string($dir)) { - if (!in_array($dir, self::$dirs, true)) { - self::$dirs[] = $dir; - } - } - } } diff --git a/tests/AutoloadTest.php b/tests/AutoloadTest.php index 97ee31a..77fb014 100644 --- a/tests/AutoloadTest.php +++ b/tests/AutoloadTest.php @@ -15,7 +15,6 @@ class AutoloadTest extends TestCase protected function setUp(): void { $this->app = new Engine(); - $this->app->path(__DIR__ . '/classes'); } // Autoload a class diff --git a/tests/FlightTest.php b/tests/FlightTest.php index 5b0b516..c74feaa 100644 --- a/tests/FlightTest.php +++ b/tests/FlightTest.php @@ -71,8 +71,6 @@ class FlightTest extends TestCase // Register a class public function testRegister(): void { - Flight::path(__DIR__ . '/classes'); - Flight::register('user', User::class); $user = Flight::user(); From 7a73e0e725ce0c273207b0e5233baeb298658a22 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 26 Mar 2026 11:55:20 -0400 Subject: [PATCH 25/49] simplify ref object --- composer.json | 3 ++- src/core/Loader.php | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index e08f86a..0d4817c 100644 --- a/composer.json +++ b/composer.json @@ -50,7 +50,8 @@ "phpunit/phpunit": "^9.6", "rregeer/phpunit-coverage-check": "^0.3.1", "spatie/phpunit-watcher": "^1.23", - "squizlabs/php_codesniffer": "^4.0" + "squizlabs/php_codesniffer": "^4.0", + "symfony/var-dumper": "^5.4" }, "config": { "allow-plugins": { diff --git a/src/core/Loader.php b/src/core/Loader.php index 6d971b8..4ee1a16 100644 --- a/src/core/Loader.php +++ b/src/core/Loader.php @@ -18,7 +18,7 @@ class Loader /** @var array, ?callable(object): void}> */ protected array $classes = []; - /** @var array Class instances */ + /** @var array */ protected array $instances = []; /** @@ -76,8 +76,7 @@ class Loader } if ($callback && (!$shared || !$exists)) { - $ref = [&$obj]; - call_user_func_array($callback, $ref); + call_user_func_array($callback, [$obj]); } } From a9ce5967b97f49758082634c199d2b46ca2652e4 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 26 Mar 2026 11:55:44 -0400 Subject: [PATCH 26/49] refactor LoaderTest --- tests/LoaderTest.php | 60 ++++++++++------------------------- tests/classes/TesterClass.php | 24 ++++++++------ tests/classes/User.php | 2 +- 3 files changed, 32 insertions(+), 54 deletions(-) diff --git a/tests/LoaderTest.php b/tests/LoaderTest.php index af4a1a8..7016130 100644 --- a/tests/LoaderTest.php +++ b/tests/LoaderTest.php @@ -19,60 +19,39 @@ class LoaderTest extends TestCase $this->loader = new Loader(); } - // Autoload a class - public function testAutoload(): void - { - $this->loader->register('tests', User::class); - - $test = $this->loader->load('tests'); - - self::assertIsObject($test); - self::assertInstanceOf(User::class, $test); - } - - // Register a class public function testRegister(): void { $this->loader->register('a', User::class); - $user = $this->loader->load('a'); - self::assertIsObject($user); self::assertInstanceOf(User::class, $user); - self::assertEquals('', $user->name); + self::assertSame('', $user->name); } - // Register a class with constructor parameters public function testRegisterWithConstructor(): void { $this->loader->register('b', User::class, ['Bob']); - $user = $this->loader->load('b'); - self::assertIsObject($user); self::assertInstanceOf(User::class, $user); - self::assertEquals('Bob', $user->name); + self::assertSame('Bob', $user->name); } - // Register a class with initialization public function testRegisterWithInitialization(): void { - $this->loader->register('c', User::class, ['Bob'], function ($user) { + $this->loader->register('c', User::class, ['Bob'], static function (User $user): void { $user->name = 'Fred'; }); $user = $this->loader->load('c'); - self::assertIsObject($user); self::assertInstanceOf(User::class, $user); self::assertEquals('Fred', $user->name); } - // Get a non-shared instance of a class public function testSharedInstance(): void { $this->loader->register('d', User::class); - $user1 = $this->loader->load('d'); $user2 = $this->loader->load('d'); $user3 = $this->loader->load('d', false); @@ -81,38 +60,24 @@ class LoaderTest extends TestCase self::assertNotSame($user1, $user3); } - // Gets an object from a factory method public function testRegisterUsingCallable(): void { - $this->loader->register('e', ['\tests\classes\Factory', 'create']); - + $this->loader->register('e', [Factory::class, 'create']); $obj = $this->loader->load('e'); - - self::assertIsObject($obj); - self::assertInstanceOf(Factory::class, $obj); - $obj2 = $this->loader->load('e'); + $obj3 = $this->loader->load('e', false); - self::assertIsObject($obj2); - self::assertInstanceOf(Factory::class, $obj2); + self::assertInstanceOf(Factory::class, $obj); self::assertSame($obj, $obj2); - - $obj3 = $this->loader->load('e', false); - self::assertIsObject($obj3); self::assertInstanceOf(Factory::class, $obj3); self::assertNotSame($obj, $obj3); } - // Gets an object from a callback function public function testRegisterUsingCallback(): void { - $this->loader->register('f', function () { - return Factory::create(); - }); - + $this->loader->register('f', static fn(): Factory => Factory::create()); $obj = $this->loader->load('f'); - self::assertIsObject($obj); self::assertInstanceOf(Factory::class, $obj); } @@ -120,15 +85,22 @@ class LoaderTest extends TestCase { $this->loader->register('g', User::class); $current_class = $this->loader->get('g'); - $this->assertEquals([User::class, [], null], $current_class); + + $this->assertSame([User::class, [], null], $current_class); + $this->loader->unregister('g'); $unregistered_class_result = $this->loader->get('g'); + $this->assertNull($unregistered_class_result); } public function testNewInstance6Params(): void { - $TesterClass = $this->loader->newInstance(TesterClass::class, ['Bob', 'Fred', 'Joe', 'Jane', 'Sally', 'Suzie']); + $TesterClass = $this->loader->newInstance( + TesterClass::class, + ['Bob', 'Fred', 'Joe', 'Jane', 'Sally', 'Suzie'] + ); + $this->assertEquals('Bob', $TesterClass->param1); $this->assertEquals('Fred', $TesterClass->param2); $this->assertEquals('Joe', $TesterClass->param3); diff --git a/tests/classes/TesterClass.php b/tests/classes/TesterClass.php index 289ee04..2d9bf12 100644 --- a/tests/classes/TesterClass.php +++ b/tests/classes/TesterClass.php @@ -4,17 +4,23 @@ declare(strict_types=1); namespace tests\classes; -class TesterClass +final class TesterClass { - public $param1; - public $param2; - public $param3; - public $param4; - public $param5; - public $param6; + public string $param1; + public string $param2; + public string $param3; + public string $param4; + public string $param5; + public string $param6; - public function __construct($param1, $param2, $param3, $param4, $param5, $param6) - { + public function __construct( + string $param1, + string $param2, + string $param3, + string $param4, + string $param5, + string $param6 + ) { $this->param1 = $param1; $this->param2 = $param2; $this->param3 = $param3; diff --git a/tests/classes/User.php b/tests/classes/User.php index 0d14102..1a6c30b 100644 --- a/tests/classes/User.php +++ b/tests/classes/User.php @@ -4,7 +4,7 @@ declare(strict_types=1); namespace tests\classes; -class User +final class User { public string $name; From 701e0e73cef8622074214883febeb95e052af1a4 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 26 Mar 2026 11:59:42 -0400 Subject: [PATCH 27/49] fix TypeError --- tests/classes/TesterClass.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/classes/TesterClass.php b/tests/classes/TesterClass.php index 2d9bf12..a0066f4 100644 --- a/tests/classes/TesterClass.php +++ b/tests/classes/TesterClass.php @@ -6,20 +6,20 @@ namespace tests\classes; final class TesterClass { - public string $param1; - public string $param2; - public string $param3; - public string $param4; - public string $param5; - public string $param6; + public ?string $param1; + public ?string $param2; + public ?string $param3; + public ?string $param4; + public ?string $param5; + public ?string $param6; public function __construct( - string $param1, - string $param2, - string $param3, - string $param4, - string $param5, - string $param6 + ?string $param1, + ?string $param2, + ?string $param3, + ?string $param4, + ?string $param5, + ?string $param6 ) { $this->param1 = $param1; $this->param2 = $param2; From dd201d1e8488e58cd3d3d6d7c51fcc4d41cccf49 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 26 Mar 2026 18:12:12 -0400 Subject: [PATCH 28/49] replace Exception for Throwable --- src/core/Loader.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/Loader.php b/src/core/Loader.php index 4ee1a16..c9656f1 100644 --- a/src/core/Loader.php +++ b/src/core/Loader.php @@ -4,7 +4,7 @@ declare(strict_types=1); namespace flight\core; -use Exception; +use Throwable; /** * The Loader class is responsible for loading objects. It maintains a list of @@ -52,7 +52,7 @@ class Loader * Loads a registered class. * @param string $name Method name * @param bool $shared Shared instance - * @throws Exception + * @throws Throwable * @return ?object Class instance */ public function load(string $name, bool $shared = true): ?object @@ -98,7 +98,7 @@ class Loader * @template T of object * @param class-string|callable(): T $class Class name or callback function to instantiate class * @param array $params Class initialization parameters - * @throws Exception + * @throws Throwable * @return T Class instance */ public function newInstance($class, array $params = []): object From 95262bd39ecec905956ba621e33b548b72ab1896 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 26 Mar 2026 18:22:50 -0400 Subject: [PATCH 29/49] refactor EventDispatcher --- src/core/EventDispatcher.php | 63 ++++++++++++------------------------ 1 file changed, 21 insertions(+), 42 deletions(-) diff --git a/src/core/EventDispatcher.php b/src/core/EventDispatcher.php index 8e60d1a..251217c 100644 --- a/src/core/EventDispatcher.php +++ b/src/core/EventDispatcher.php @@ -6,51 +6,44 @@ namespace flight\core; class EventDispatcher { - /** @var self|null Singleton instance of the EventDispatcher */ + /** Singleton instance of the EventDispatcher */ private static ?self $instance = null; - /** @var array> */ + /** @var array> */ protected array $listeners = []; - /** - * Singleton instance of the EventDispatcher. - * - * @return self - */ + /** Singleton instance of the EventDispatcher. */ public static function getInstance(): self { - if (self::$instance === null) { + if (!self::$instance) { self::$instance = new self(); } + return self::$instance; } /** * Register a callback for an event. - * * @param string $event Event name - * @param callable $callback Callback function + * @param callable(): mixed $callback Callback function */ public function on(string $event, callable $callback): void { - if (isset($this->listeners[$event]) === false) { - $this->listeners[$event] = []; - } + $this->listeners[$event] ??= []; $this->listeners[$event][] = $callback; } /** * Trigger an event with optional arguments. - * * @param string $event Event name * @param mixed ...$args Arguments to pass to the callbacks - * * @return mixed */ public function trigger(string $event, ...$args) { $result = null; - if (isset($this->listeners[$event]) === true) { + + if (isset($this->listeners[$event])) { foreach ($this->listeners[$event] as $callback) { $result = call_user_func_array($callback, $args); @@ -60,27 +53,24 @@ class EventDispatcher } } } + return $result; } /** * Check if an event has any registered listeners. - * * @param string $event Event name - * * @return bool True if the event has listeners, false otherwise */ public function hasListeners(string $event): bool { - return isset($this->listeners[$event]) === true && count($this->listeners[$event]) > 0; + return isset($this->listeners[$event]) && count($this->listeners[$event]) > 0; } /** * Get all listeners registered for a specific event. - * * @param string $event Event name - * - * @return array Array of callbacks registered for the event + * @return array Array of callbacks registered for the event */ public function getListeners(string $event): array { @@ -89,7 +79,6 @@ class EventDispatcher /** * Get a list of all events that have registered listeners. - * * @return array Array of event names */ public function getAllRegisteredEvents(): array @@ -99,41 +88,31 @@ class EventDispatcher /** * Remove a specific listener for an event. - * * @param string $event the event name - * @param callable $callback the exact callback to remove - * - * @return void + * @param callable(): mixed $callback the exact callback to remove */ public function removeListener(string $event, callable $callback): void { - if (isset($this->listeners[$event]) === true && count($this->listeners[$event]) > 0) { - $this->listeners[$event] = array_filter($this->listeners[$event], function ($listener) use ($callback) { - return $listener !== $callback; - }); + if ($this->hasListeners($event)) { + $this->listeners[$event] = array_filter( + $this->listeners[$event], + static fn(callable $listener): bool => $listener !== $callback, + ); + $this->listeners[$event] = array_values($this->listeners[$event]); // Re-index the array } } /** * Remove all listeners for a specific event. - * * @param string $event the event name - * - * @return void */ public function removeAllListeners(string $event): void { - if (isset($this->listeners[$event]) === true) { - unset($this->listeners[$event]); - } + unset($this->listeners[$event]); } - /** - * Remove the current singleton instance of the EventDispatcher. - * - * @return void - */ + /** Remove the current singleton instance of the EventDispatcher. */ public static function resetInstance(): void { self::$instance = null; From 9dca6fb8365e4b1cc166ad87f9e1f4b8b1d8c5e5 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 26 Mar 2026 18:49:36 -0400 Subject: [PATCH 30/49] ignore phpunit-watcher.yml.dist from composer install --- .gitattributes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index edcce41..72af06c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8,6 +8,6 @@ /phpcs.xml.dist export-ignore /phpstan-baseline.neon export-ignore /phpstan.dist.neon export-ignore -/phpunit-watcher.yml export-ignore +/phpunit-watcher.yml.dist export-ignore /phpunit.xml.dist export-ignore /README.md export-ignore From eaeb6b4fcc9a4083b019958889e2bf3432d48fe1 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 1 Apr 2026 01:52:36 -0400 Subject: [PATCH 31/49] remove Engine::path --- phpcs.xml.dist | 3 ++- src/Engine.php | 10 ---------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/phpcs.xml.dist b/phpcs.xml.dist index ba1c395..3e038e9 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -18,8 +18,9 @@ + - flight + src tests diff --git a/src/Engine.php b/src/Engine.php index a74a21d..2086b69 100644 --- a/src/Engine.php +++ b/src/Engine.php @@ -413,16 +413,6 @@ class Engine unset($this->vars[$key]); } - /** - * Adds a path for class autoloading. - * - * @param string $dir Directory path - */ - public function path(string $dir): void - { - $this->loader->addDirectory($dir); - } - /** * Processes each routes middleware. * From 9bab4874e0dfc40f931e14e101b35738a768e2ba Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 1 Apr 2026 02:14:36 -0400 Subject: [PATCH 32/49] simplify Flight class --- src/Flight.php | 34 +++++----------------------------- 1 file changed, 5 insertions(+), 29 deletions(-) diff --git a/src/Flight.php b/src/Flight.php index ddb536f..e59f107 100644 --- a/src/Flight.php +++ b/src/Flight.php @@ -89,40 +89,16 @@ class Flight */ private static Engine $engine; - /** - * Don't allow object instantiation - * - * @codeCoverageIgnore - * @return void - */ - private function __construct() - { - // - } - - /** - * Forbid cloning the class - * - * @codeCoverageIgnore - * @return void - */ - private function __clone() - { - // - } - /** * Handles calls to static methods. * - * @param string $name Method name - * @param array $params Method parameters - * + * @param array $arguments Method parameters * @return mixed Callback results - * @throws Exception + * @throws Throwable */ - public static function __callStatic(string $name, array $params) + public static function __callStatic(string $name, array $arguments) { - return self::app()->{$name}(...$params); + return self::app()->{$name}(...$arguments); } /** @return Engine Application instance */ @@ -132,7 +108,7 @@ class Flight } /** - * Set the engine instance + * Set the engine instance. * * @param Engine $engine Vroom vroom! */ From 0913dc65f8fa0bdbf0ca1e4b46a28b0430f61a3c Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 1 Apr 2026 03:02:20 -0400 Subject: [PATCH 33/49] delete static docblock flight methods --- src/Engine.php | 2 +- src/Flight.php | 334 +++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 270 insertions(+), 66 deletions(-) diff --git a/src/Engine.php b/src/Engine.php index 2086b69..93386ed 100644 --- a/src/Engine.php +++ b/src/Engine.php @@ -28,7 +28,7 @@ use Psr\Container\ContainerInterface; * @copyright Copyright (c) 2011-2025, Mike Cao , n0nag0n * * @method void start() - * @method void stop() + * @method void stop(?int $code = null) * @method void halt(int $code = 200, string $message = '', bool $actuallyExit = true) * @method EventDispatcher eventDispatcher() * @method Route route(string $pattern, callable|string|array $callback, bool $pass_route = false, string $alias = '') diff --git a/src/Flight.php b/src/Flight.php index e59f107..9538cb6 100644 --- a/src/Flight.php +++ b/src/Flight.php @@ -15,71 +15,8 @@ use Psr\Container\ContainerInterface; * The Flight class is a static representation of the framework. * * @license MIT, https://docs.flightphp.com/license - * @copyright Copyright (c) 2011-2025, Mike Cao , n0nag0n - * - * @method static void start() - * @method static void path(string $dir) - * @method static void stop(int $code = null) - * @method static void halt(int $code = 200, string $message = '', bool $actuallyExit = true) - * @method static void register(string $name, string $class, array $params = [], callable $callback = null) - * @method static void unregister(string $methodName) - * @method static void registerContainerHandler($containerHandler) - * @method static EventDispatcher eventDispatcher() - * @method static Route route(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') - * @method static void group(string $pattern, callable $callback, array $group_middlewares = []) - * @method static Route post(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') - * @method static Route put(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') - * @method static Route patch(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') - * @method static Route delete(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') - * @method static void resource(string $pattern, string $controllerClass, array $methods = []) - * @method static Router router() - * @method static string getUrl(string $alias, array $params = []) - * @method static void map(string $name, callable $callback) - * @method static void before(string $name, Closure $callback) - * @method static void after(string $name, Closure $callback) - * @method static void set($key, $value) - * @method static mixed get($key = null) - * @method static bool has(string $key) - * @method static void clear($key = null) - * @method static void render(string $file, array $data = null, string $key = null) - * @method static View view() - * @method void onEvent(string $event, callable $callback) - * @method void triggerEvent(string $event, ...$args) - * @method static Request request() - * @method static Response response() - * @method static void redirect(string $url, int $code = 303) - * @method static void json($data, int $code = 200, bool $encode = true, string $charset = "utf8", int $encodeOption = 0, int $encodeDepth = 512) - * @method static void jsonHalt($data, int $code = 200, bool $encode = true, string $charset = 'utf-8', int $option = 0) - * @method static void jsonp($data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = "utf8", int $encodeOption = 0, int $encodeDepth = 512) - * @method static void error(Throwable $exception) - * @method static void notFound() - * @method static void methodNotFound(Route $route) - * @method static void etag(string $id, string $type = 'strong') - * @method static void lastModified(int $time) - * @method static void download(string $filePath) - * - * @phpstan-template FlightTemplate of object - * @phpstan-method static void register(string $name, class-string $class, array $params = [], (callable(class-string $class, array $params): void)|null $callback = null) - * @phpstan-method static void registerContainerHandler(ContainerInterface|callable(class-string $id, array $params): ?FlightTemplate $containerHandler) - * @phpstan-method static Route route(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '') - * @phpstan-method static void group(string $pattern, callable $callback, (class-string|callable|array{0: class-string, 1: string})[] $group_middlewares = []) - * @phpstan-method static Route post(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '') - * @phpstan-method static Route put(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '') - * @phpstan-method static Route patch(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '') - * @phpstan-method static Route delete(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '') - * @phpstan-method static void resource(string $pattern, class-string $controllerClass, array> $methods = []) - * @phpstan-method static string getUrl(string $alias, array $params = []) - * @phpstan-method static void before(string $name, Closure(array &$params, string &$output): (void|false) $callback) - * @phpstan-method static void after(string $name, Closure(array &$params, string &$output): (void|false) $callback) - * @phpstan-method static void set(string|iterable $key, mixed $value) - * @phpstan-method static mixed get(?string $key) - * @phpstan-method static void render(string $file, ?array $data = null, ?string $key = null) - * @phpstan-method static void json(mixed $data, int $code = 200, bool $encode = true, string $charset = "utf8", int $encodeOption = 0, int $encodeDepth = 512) - * @phpstan-method static void jsonHalt(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf-8', int $option = 0) - * @phpstan-method static void jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = "utf8", int $encodeOption = 0, int $encodeDepth = 512) - * - * Note: IDEs will use standard @method tags for autocompletion, - * while PHPStan will use @phpstan-* tags for advanced type checking. + * @copyright Copyright (c) 2011-2026, + * Mike Cao , n0nag0n , fadrian06 */ // phpcs:ignore PSR1.Classes.ClassDeclaration.MissingNamespace class Flight @@ -116,4 +53,271 @@ class Flight { self::$engine = $engine; } + + public static function start(): void + { + self::app()->start(); + } + + public static function stop(?int $code = null): void + { + self::app()->stop($code); + } + + public static function halt(int $code = 200, string $message = '', bool $actuallyExit = true): void + { + self::app()->halt($code, $message, $actuallyExit); + } + + /** + * @template T of object + * @param class-string $class + * @param mixed[] $params + * @param ?callable(T): void $callback + */ + public static function register(string $name, string $class, array $params = [], callable $callback = null): void + { + self::app()->register($name, $class, $params, $callback); + } + + public static function unregister(string $methodName): void + { + self::app()->unregister($methodName); + } + + /** + * @template T of object + * @param ContainerInterface|callable(class-string, mixed[]): ?T $containerHandler + */ + public static function registerContainerHandler($containerHandler): void + { + self::app()->registerContainerHandler($containerHandler); + } + + public static function eventDispatcher(): EventDispatcher + { + return self::app()->eventDispatcher(); + } + + /** @param callable|string|array{0: class-string, 1: string} $callback */ + public static function route( + string $pattern, + $callback, + bool $pass_route = false, + string $alias = '' + ): Route { + return self::app()->route($pattern, $callback, $pass_route, $alias); + } + + /** @param array $group_middlewares */ + public static function group(string $pattern, callable $callback, array $group_middlewares = []): void + { + self::app()->group($pattern, $callback, $group_middlewares); + } + + /** @param callable|string|array{0: class-string, 1: string} $callback */ + public static function post( + string $pattern, + $callback, + bool $pass_route = false, + string $alias = '' + ): Route { + return self::app()->post($pattern, $callback, $pass_route, $alias); + } + + /** @param callable|string|array{0: class-string, 1: string} $callback */ + public static function put( + string $pattern, + $callback, + bool $pass_route = false, + string $alias = '' + ): Route { + return self::app()->put($pattern, $callback, $pass_route, $alias); + } + + /** @param callable|string|array{0: class-string, 1: string} $callback */ + public static function patch( + string $pattern, + $callback, + bool $pass_route = false, + string $alias = '' + ): Route { + return self::app()->patch($pattern, $callback, $pass_route, $alias); + } + + /** @param callable|string|array{0: class-string, 1: string} $callback */ + public static function delete( + string $pattern, + $callback, + bool $pass_route = false, + string $alias = '' + ): Route { + return self::app()->delete($pattern, $callback, $pass_route, $alias); + } + + /** + * @param class-string $controllerClass + * @param array> $methods + */ + public static function resource(string $pattern, string $controllerClass, array $methods = []): void + { + self::app()->resource($pattern, $controllerClass, $methods); + } + + public static function router(): Router + { + return self::app()->router(); + } + + /** @param array $params */ + public static function getUrl(string $alias, array $params = []): string + { + return self::app()->getUrl($alias, $params); + } + + public static function map(string $name, callable $callback): void + { + self::app()->map($name, $callback); + } + + /** @param callable(array &$params, string &$output): (void|false) $callback */ + public static function before(string $name, callable $callback): void + { + self::app()->before($name, $callback); + } + + /** @param callable(array &$params, string &$output): (void|false) $callback */ + public static function after(string $name, callable $callback): void + { + self::app()->after($name, $callback); + } + + /** + * @param string|iterable $key + * @param mixed $value + */ + public static function set($key, $value): void + { + self::app()->set($key, $value); + } + + /** @return mixed */ + public static function get(?string $key = null) + { + return self::app()->get($key); + } + + public static function has(string $key): bool + { + return self::app()->has($key); + } + + public static function clear(?string $key = null): void + { + self::app()->clear($key); + } + + /** @param array $data */ + public static function render(string $file, ?array $data = null, ?string $key = null): void + { + self::app()->render($file, $data, $key); + } + + public static function view(): View + { + return self::app()->view(); + } + + public static function onEvent(string $event, callable $callback): void + { + self::app()->onEvent($event, $callback); + } + + /** @param ...mixed $args */ + public static function triggerEvent(string $event, ...$args): void + { + self::app()->triggerEvent($event, ...$args); + } + + public static function request(): Request + { + return self::app()->request(); + } + + public static function response(): Response + { + return self::app()->response(); + } + + public static function redirect(string $url, int $code = 303): void + { + self::app()->redirect($url, $code); + } + + /** @param mixed $data */ + public static function json( + $data, + int $code = 200, + bool $encode = true, + string $charset = 'utf8', + int $encodeOption = 0, + int $encodeDepth = 512 + ): void { + self::app()->json($data, $code, $encode, $charset, $encodeOption, $encodeDepth); + } + + /** @param mixed $data */ + public static function jsonHalt( + $data, + int $code = 200, + bool $encode = true, + string $charset = 'utf8', + int $encodeOption = 0, + int $encodeDepth = 512 + ): void { + self::app()->jsonHalt($data, $code, $encode, $charset, $encodeOption, $encodeDepth); + } + + /** @param mixed $data */ + public static function jsonp( + $data, + string $param = 'jsonp', + int $code = 200, + bool $encode = true, + string $charset = 'utf8', + int $encodeOption = 0, + int $encodeDepth = 512 + ): void { + self::app()->jsonp($data, $param, $code, $encode, $charset, $encodeOption, $encodeDepth); + } + + public static function error(Throwable $exception): void + { + self::app()->error($exception); + } + + public static function notFound(): void + { + self::app()->notFound(); + } + + public function methodNotFound(Route $route): void + { + self::app()->methodNotFound($route); + } + + public static function etag(string $id, string $type = 'strong'): void + { + self::app()->etag($id, $type); + } + + public static function lastModified(int $time): void + { + self::app()->lastModified($time); + } + + public static function download(string $filePath): void + { + self::app()->download($filePath); + } } From eed1c58f33c1f44e021c7d7c98b13a4e8d58ea37 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 1 Apr 2026 03:05:29 -0400 Subject: [PATCH 34/49] fix nullable deprecated php80 warning --- src/Flight.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Flight.php b/src/Flight.php index 9538cb6..c901e76 100644 --- a/src/Flight.php +++ b/src/Flight.php @@ -75,7 +75,7 @@ class Flight * @param mixed[] $params * @param ?callable(T): void $callback */ - public static function register(string $name, string $class, array $params = [], callable $callback = null): void + public static function register(string $name, string $class, array $params = [], ?callable $callback = null): void { self::app()->register($name, $class, $params, $callback); } From e48eb283b572205ba03d134e2dc245ef34be2db4 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 1 Apr 2026 03:32:33 -0400 Subject: [PATCH 35/49] init refactor in order to delete Engine --- src/Engine.php | 91 ++++++++++++++++---------------------------------- src/Flight.php | 15 ++------- 2 files changed, 30 insertions(+), 76 deletions(-) diff --git a/src/Engine.php b/src/Engine.php index 93386ed..f44da07 100644 --- a/src/Engine.php +++ b/src/Engine.php @@ -25,7 +25,8 @@ use Psr\Container\ContainerInterface; * and generating an HTTP response. * * @license MIT, https://docs.flightphp.com/license - * @copyright Copyright (c) 2011-2025, Mike Cao , n0nag0n + * @copyright Copyright (c) 2011-2026, + * Mike Cao , n0nag0n , fadrian06 * * @method void start() * @method void stop(?int $code = null) @@ -56,35 +57,10 @@ use Psr\Container\ContainerInterface; * @method void etag(string $id, string $type = 'strong') * @method void lastModified(int $time) * @method void download(string $filePath) - * - * @phpstan-template EngineTemplate of object - * @phpstan-method void registerContainerHandler(ContainerInterface|callable(class-string $id, array $params): ?EngineTemplate $containerHandler) - * @phpstan-method Route route(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '') - * @phpstan-method void group(string $pattern, callable $callback, (class-string|callable|array{0: class-string, 1: string})[] $group_middlewares = []) - * @phpstan-method Route post(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '') - * @phpstan-method Route put(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '') - * @phpstan-method Route patch(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '') - * @phpstan-method Route delete(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '') - * @phpstan-method void resource(string $pattern, class-string $controllerClass, array> $methods = []) - * @phpstan-method string getUrl(string $alias, array $params = []) - * @phpstan-method void before(string $name, Closure(array &$params, string &$output): (void|false) $callback) - * @phpstan-method void after(string $name, Closure(array &$params, string &$output): (void|false) $callback) - * @phpstan-method void set(string|iterable $key, ?mixed $value = null) - * @phpstan-method mixed get(?string $key) - * @phpstan-method void render(string $file, ?array $data = null, ?string $key = null) - * @phpstan-method void json(mixed $data, int $code = 200, bool $encode = true, string $charset = "utf8", int $encodeOption = 0, int $encodeDepth = 512) - * @phpstan-method void jsonHalt(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf-8', int $option = 0) - * @phpstan-method void jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = "utf8", int $encodeOption = 0, int $encodeDepth = 512) - * - * Note: IDEs will use standard @method tags for autocompletion, while PHPStan will use @phpstan-* tags for advanced type checking. - * - * phpcs:disable PSR2.Methods.MethodDeclaration.Underscore */ class Engine { - /** - * @var array List of methods that can be extended in the Engine class. - */ + /** @var array List of methods that can be extended in the Engine class */ private const MAPPABLE_METHODS = [ 'start', 'stop', @@ -112,22 +88,17 @@ class Engine 'triggerEvent' ]; - /** @var array Stored variables. */ + /** @var array */ protected array $vars = []; - /** Class loader. */ protected Loader $loader; - - /** @var Dispatcher Method and class dispatcher. */ protected Dispatcher $dispatcher; - - /** Event dispatcher. */ protected EventDispatcher $eventDispatcher; - /** If the framework has been initialized or not. */ + /** If the framework has been initialized or not */ protected bool $initialized = false; - /** If the request has been handled or not. */ + /** If the request has been handled or not */ protected bool $requestHandled = false; public function __construct() @@ -138,27 +109,24 @@ class Engine } /** - * Handles calls to class methods. - * * @param string $name Method name - * @param array $params Method parameters - * - * @throws Exception - * @return mixed Callback results + * @param array $arguments Method parameters + * @throws Throwable + * @return mixed */ - public function __call(string $name, array $params) + public function __call(string $name, array $arguments) { $callback = $this->dispatcher->get($name); - if (\is_callable($callback)) { - return $this->dispatcher->run($name, $params); + if (is_callable($callback)) { + return $this->dispatcher->run($name, $arguments); } if (!$this->loader->get($name)) { throw new Exception("$name must be a mapped method."); } - $shared = empty($params) || $params[0]; + $shared = empty($arguments) || $arguments[0]; return $this->loader->load($name, $shared); } @@ -167,13 +135,10 @@ class Engine // Core Methods // ////////////////// - /** Initializes the framework. */ + /** Initializes the framework */ public function init(): void { - $initialized = $this->initialized; - $self = $this; - - if ($initialized) { + if ($this->initialized) { $this->vars = []; $this->loader->reset(); $this->dispatcher->reset(); @@ -183,16 +148,14 @@ class Engine $this->dispatcher->setEngine($this); // Register default components - $this->map('eventDispatcher', function () { - return EventDispatcher::getInstance(); - }); + $this->map('eventDispatcher', static fn (): EventDispatcher => EventDispatcher::getInstance()); $this->loader->register('request', Request::class); $this->loader->register('response', Response::class); $this->loader->register('router', Router::class); - $this->loader->register('view', View::class, [], function (View $view) use ($self) { - $view->path = $self->get('flight.views.path'); - $view->extension = $self->get('flight.views.extension'); + $this->loader->register('view', View::class, [], function (View $view) { + $view->path = $this->get('flight.views.path'); + $view->extension = $this->get('flight.views.extension'); }); foreach (self::MAPPABLE_METHODS as $name) { @@ -210,21 +173,23 @@ class Engine $this->set('flight.v2.output_buffering', false); // Startup configuration - $this->before('start', function () use ($self) { + $this->before('start', function () { // Enable error handling - if ($self->get('flight.handle_errors')) { - set_error_handler([$self, 'handleError']); - set_exception_handler([$self, 'handleException']); + if ($this->get('flight.handle_errors')) { + set_error_handler([$this, 'handleError']); + set_exception_handler([$this, 'handleException']); } // Set case-sensitivity - $self->router()->caseSensitive = $self->get('flight.case_sensitive'); + $this->router()->caseSensitive = $this->get('flight.case_sensitive'); + // Set Content-Length - $self->response()->content_length = $self->get('flight.content_length'); + $this->response()->content_length = $this->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->response()->v2_output_buffering = $this->get('flight.v2.output_buffering'); }); $this->initialized = true; diff --git a/src/Flight.php b/src/Flight.php index c901e76..65af58f 100644 --- a/src/Flight.php +++ b/src/Flight.php @@ -21,16 +21,11 @@ use Psr\Container\ContainerInterface; // phpcs:ignore PSR1.Classes.ClassDeclaration.MissingNamespace class Flight { - /** - * @var Engine - */ private static Engine $engine; /** - * Handles calls to static methods. - * - * @param array $arguments Method parameters - * @return mixed Callback results + * @param array $arguments + * @return mixed * @throws Throwable */ public static function __callStatic(string $name, array $arguments) @@ -38,17 +33,11 @@ class Flight return self::app()->{$name}(...$arguments); } - /** @return Engine Application instance */ public static function app(): Engine { return self::$engine ?? self::$engine = new Engine(); } - /** - * Set the engine instance. - * - * @param Engine $engine Vroom vroom! - */ public static function setEngine(Engine $engine): void { self::$engine = $engine; From 1ac33a5d27dd4a6f55db68351101a40afa8cc15e Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 1 Apr 2026 03:43:58 -0400 Subject: [PATCH 36/49] minor docblock refactors --- src/Engine.php | 18 +++++++++--------- src/Flight.php | 19 ++++++++----------- src/core/Dispatcher.php | 4 +--- src/core/Loader.php | 2 +- 4 files changed, 19 insertions(+), 24 deletions(-) diff --git a/src/Engine.php b/src/Engine.php index f44da07..3c642be 100644 --- a/src/Engine.php +++ b/src/Engine.php @@ -32,16 +32,16 @@ use Psr\Container\ContainerInterface; * @method void stop(?int $code = null) * @method void halt(int $code = 200, string $message = '', bool $actuallyExit = true) * @method EventDispatcher eventDispatcher() - * @method Route route(string $pattern, callable|string|array $callback, bool $pass_route = false, string $alias = '') - * @method void group(string $pattern, callable $callback, array $group_middlewares = []) - * @method Route post(string $pattern, callable|string|array $callback, bool $pass_route = false, string $alias = '') - * @method Route put(string $pattern, callable|string|array $callback, bool $pass_route = false, string $alias = '') - * @method Route patch(string $pattern, callable|string|array $callback, bool $pass_route = false, string $alias = '') - * @method Route delete(string $pattern, callable|string|array $callback, bool $pass_route = false, string $alias = '') - * @method void resource(string $pattern, string $controllerClass, array $methods = []) + * @method Route route(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '') + * @method void group(string $pattern, callable $callback, array $group_middlewares = []) + * @method Route post(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '') + * @method Route put(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '') + * @method Route patch(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '') + * @method Route delete(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '') + * @method void resource(string $pattern, class-string $controllerClass, array> $methods = []) * @method Router router() - * @method string getUrl(string $alias) - * @method void render(string $file, array $data = null, string $key = null) + * @method string getUrl(string $alias, array $params) + * @method void render(string $file, ?array $data, string $key = null) * @method View view() * @method void onEvent(string $event, callable $callback) * @method void triggerEvent(string $event, ...$args) diff --git a/src/Flight.php b/src/Flight.php index 65af58f..0752ab4 100644 --- a/src/Flight.php +++ b/src/Flight.php @@ -206,7 +206,7 @@ class Flight self::app()->clear($key); } - /** @param array $data */ + /** @param ?array $data */ public static function render(string $file, ?array $data = null, ?string $key = null): void { self::app()->render($file, $data, $key); @@ -222,7 +222,7 @@ class Flight self::app()->onEvent($event, $callback); } - /** @param ...mixed $args */ + /** @param mixed ...$args */ public static function triggerEvent(string $event, ...$args): void { self::app()->triggerEvent($event, ...$args); @@ -249,10 +249,9 @@ class Flight int $code = 200, bool $encode = true, string $charset = 'utf8', - int $encodeOption = 0, - int $encodeDepth = 512 + int $encodeOption = 0 ): void { - self::app()->json($data, $code, $encode, $charset, $encodeOption, $encodeDepth); + self::app()->json($data, $code, $encode, $charset, $encodeOption); } /** @param mixed $data */ @@ -261,10 +260,9 @@ class Flight int $code = 200, bool $encode = true, string $charset = 'utf8', - int $encodeOption = 0, - int $encodeDepth = 512 + int $encodeOption = 0 ): void { - self::app()->jsonHalt($data, $code, $encode, $charset, $encodeOption, $encodeDepth); + self::app()->jsonHalt($data, $code, $encode, $charset, $encodeOption); } /** @param mixed $data */ @@ -274,10 +272,9 @@ class Flight int $code = 200, bool $encode = true, string $charset = 'utf8', - int $encodeOption = 0, - int $encodeDepth = 512 + int $encodeOption = 0 ): void { - self::app()->jsonp($data, $param, $code, $encode, $charset, $encodeOption, $encodeDepth); + self::app()->jsonp($data, $param, $code, $encode, $charset, $encodeOption); } public static function error(Throwable $exception): void diff --git a/src/core/Dispatcher.php b/src/core/Dispatcher.php index 12f3393..1f26588 100644 --- a/src/core/Dispatcher.php +++ b/src/core/Dispatcher.php @@ -18,9 +18,8 @@ use TypeError; * allows you to hook other functions to an event that can modify the * input parameters and/or the output. * - * @license MIT, http://flightphp.com/license + * @license MIT, https://docs.flightphp.com/license/ * @copyright Copyright (c) 2011, Mike Cao - * @phpstan-template EngineTemplate of object */ class Dispatcher { @@ -30,7 +29,6 @@ class Dispatcher /** Exception message if thrown by setting the container as a callable method. */ protected ?Throwable $containerException = null; - /** @var ?Engine $engine Engine instance. */ protected ?Engine $engine = null; /** @var array Mapped events. */ diff --git a/src/core/Loader.php b/src/core/Loader.php index c9656f1..bb0ad41 100644 --- a/src/core/Loader.php +++ b/src/core/Loader.php @@ -9,7 +9,7 @@ use Throwable; /** * The Loader class is responsible for loading objects. It maintains a list of * reusable class instances and can generate a new class instances with custom - * initialization parameters. It also performs class autoloading. + * initialization parameters. * @license MIT, https://docs.flightphp.com/license/ * @copyright Copyright (c) 2011, Mike Cao */ From 74c8e456b611ef410aa04dd3dc0bea6b9032dc93 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 1 Apr 2026 10:50:01 -0400 Subject: [PATCH 37/49] add void return type in Engine::init callables --- src/Engine.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Engine.php b/src/Engine.php index 3c642be..c44cd6a 100644 --- a/src/Engine.php +++ b/src/Engine.php @@ -153,7 +153,7 @@ class Engine $this->loader->register('response', Response::class); $this->loader->register('router', Router::class); - $this->loader->register('view', View::class, [], function (View $view) { + $this->loader->register('view', View::class, [], function (View $view): void { $view->path = $this->get('flight.views.path'); $view->extension = $this->get('flight.views.extension'); }); @@ -173,7 +173,7 @@ class Engine $this->set('flight.v2.output_buffering', false); // Startup configuration - $this->before('start', function () { + $this->before('start', function (): void { // Enable error handling if ($this->get('flight.handle_errors')) { set_error_handler([$this, 'handleError']); From 1246c64d995886b4364672d6e75eeed362d9aa49 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 1 Apr 2026 10:50:46 -0400 Subject: [PATCH 38/49] simplify Dispatcher and EventDispatcher docblocks --- src/core/Dispatcher.php | 30 +++--------------------------- src/core/EventDispatcher.php | 2 +- 2 files changed, 4 insertions(+), 28 deletions(-) diff --git a/src/core/Dispatcher.php b/src/core/Dispatcher.php index 1f26588..a3fe0eb 100644 --- a/src/core/Dispatcher.php +++ b/src/core/Dispatcher.php @@ -74,13 +74,6 @@ class Dispatcher ); } - /** - * Sets the engine instance - * - * @param Engine $engine Flight instance - * - * @return void - */ public function setEngine(Engine $engine): void { $this->engine = $engine; @@ -158,14 +151,7 @@ class Dispatcher return $output; } - /** - * Assigns a callback to an event. - * - * @param string $name Event name. - * @param callable(): (void|mixed) $callback Callback function. - * - * @return $this - */ + /** Assigns a callback to an event */ public function set(string $name, callable $callback): self { $this->events[$name] = $callback; @@ -173,13 +159,7 @@ class Dispatcher return $this; } - /** - * Gets an assigned callback. - * - * @param string $name Event name. - * - * @return null|(callable(): (void|mixed)) $callback Callback function. - */ + /** Gets an assigned callback */ public function get(string $name): ?callable { return $this->events[$name] ?? null; @@ -498,11 +478,7 @@ class Dispatcher } } - /** - * Resets the object to the initial state. - * - * @return $this - */ + /** Resets the object to the initial state */ public function reset(): self { $this->events = []; diff --git a/src/core/EventDispatcher.php b/src/core/EventDispatcher.php index 251217c..823354c 100644 --- a/src/core/EventDispatcher.php +++ b/src/core/EventDispatcher.php @@ -12,7 +12,7 @@ class EventDispatcher /** @var array> */ protected array $listeners = []; - /** Singleton instance of the EventDispatcher. */ + /** Singleton instance of the EventDispatcher */ public static function getInstance(): self { if (!self::$instance) { From bd861748d1803bbf16f21945ec7782094bb40e65 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 1 Apr 2026 10:51:31 -0400 Subject: [PATCH 39/49] allow $loader->reset()->reset() --- src/core/Loader.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/Loader.php b/src/core/Loader.php index bb0ad41..025b90c 100644 --- a/src/core/Loader.php +++ b/src/core/Loader.php @@ -122,9 +122,11 @@ class Loader } /** Resets the object to the initial state */ - public function reset(): void + public function reset(): self { $this->classes = []; $this->instances = []; + + return $this; } } From 50075ab83d28f406b5b469505b164e3051b950d8 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 1 Apr 2026 11:09:40 -0400 Subject: [PATCH 40/49] minor space format --- src/Engine.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Engine.php b/src/Engine.php index c44cd6a..a8639e7 100644 --- a/src/Engine.php +++ b/src/Engine.php @@ -148,7 +148,7 @@ class Engine $this->dispatcher->setEngine($this); // Register default components - $this->map('eventDispatcher', static fn (): EventDispatcher => EventDispatcher::getInstance()); + $this->map('eventDispatcher', static fn(): EventDispatcher => EventDispatcher::getInstance()); $this->loader->register('request', Request::class); $this->loader->register('response', Response::class); $this->loader->register('router', Router::class); From 3fe2ca1d061076b4ab864e668f35cb4c9fbbab15 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 1 Apr 2026 11:12:14 -0400 Subject: [PATCH 41/49] remove v2_output_buffering --- src/Engine.php | 41 ++------------------ src/net/Response.php | 23 +----------- tests/DocExamplesTest.php | 19 ---------- tests/EngineTest.php | 78 --------------------------------------- tests/FlightTest.php | 19 ---------- 5 files changed, 6 insertions(+), 174 deletions(-) diff --git a/src/Engine.php b/src/Engine.php index a8639e7..eefbab5 100644 --- a/src/Engine.php +++ b/src/Engine.php @@ -170,7 +170,6 @@ 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 (): void { @@ -185,11 +184,6 @@ class Engine // Set Content-Length $this->response()->content_length = $this->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 - $this->response()->v2_output_buffering = $this->get('flight.v2.output_buffering'); }); $this->initialized = true; @@ -431,9 +425,7 @@ class Engine } // This is the way that v3 handles output buffering (which captures output correctly) - $useV3OutputBuffering = - $this->response()->v2_output_buffering === false && - $route->is_streamed === false; + $useV3OutputBuffering = !$route->is_streamed; if ($useV3OutputBuffering === true) { ob_start(); @@ -505,17 +497,6 @@ class Engine $response = $this->response(); $router = $this->router(); - if ($response->v2_output_buffering === true) { - // Flush any existing output - if (ob_get_length() > 0) { - $response->write(ob_get_clean()); // @codeCoverageIgnore - } - - // Enable output buffering - // This is closed in the Engine->_stop() method - ob_start(); - } - // Route the request $failedMiddlewareCheck = false; while ($route = $router->route($request)) { @@ -565,9 +546,7 @@ class Engine $this->triggerEvent('flight.middleware.before', $route); } - $useV3OutputBuffering = - $this->response()->v2_output_buffering === false && - $route->is_streamed === false; + $useV3OutputBuffering = !$route->is_streamed; if ($useV3OutputBuffering === true) { ob_start(); @@ -675,10 +654,6 @@ class Engine $response->status($code); } - if ($response->v2_output_buffering === true && ob_get_length() > 0) { - $response->write(ob_get_clean()); - } - $response->send(); } } @@ -903,9 +878,6 @@ class Engine ->status($code) ->header('Content-Type', 'application/json') ->write($json); - if ($this->response()->v2_output_buffering === true) { - $this->response()->send(); - } } /** @@ -928,10 +900,8 @@ class Engine ): void { $this->json($data, $code, $encode, $charset, $option); $jsonBody = $this->response()->getBody(); - if ($this->response()->v2_output_buffering === false) { - $this->response()->clearBody(); - $this->response()->send(); - } + $this->response()->clearBody(); + $this->response()->send(); $this->halt($code, $jsonBody, empty(getenv('PHPUNIT_TEST'))); } @@ -962,9 +932,6 @@ class Engine ->status($code) ->header('Content-Type', 'application/javascript; charset=' . $charset) ->write($callback . '(' . $json . ');'); - if ($this->response()->v2_output_buffering === true) { - $this->response()->send(); - } } /** diff --git a/src/net/Response.php b/src/net/Response.php index 1a738cf..e848640 100644 --- a/src/net/Response.php +++ b/src/net/Response.php @@ -22,15 +22,6 @@ 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 * @@ -271,7 +262,7 @@ class Response $this->clearBody(); // This needs to clear the output buffer if it's on - if ($this->v2_output_buffering === false && ob_get_length() > 0) { + if (ob_get_length() > 0) { ob_clean(); } @@ -421,18 +412,8 @@ class Response */ public function send(): void { - // legacy way of handling this - if ($this->v2_output_buffering === true) { - if (ob_get_length() > 0) { - ob_end_clean(); // @codeCoverageIgnore - } - } - $start = microtime(true); - // Only for the v3 output buffering. - if ($this->v2_output_buffering === false) { - $this->processResponseCallbacks(); - } + $this->processResponseCallbacks(); if ($this->headersSent() === false) { $this->sendHeaders(); diff --git a/tests/DocExamplesTest.php b/tests/DocExamplesTest.php index 96121f3..33f92bc 100644 --- a/tests/DocExamplesTest.php +++ b/tests/DocExamplesTest.php @@ -45,25 +45,6 @@ class DocExamplesTest extends TestCase $this->assertEquals('[]', Flight::response()->getBody()); } - public function testMapNotFoundMethodV2OutputBuffering(): void - { - 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()); - } - public function testMapErrorMethod(): void { Flight::map('error', function (Throwable $error) { diff --git a/tests/EngineTest.php b/tests/EngineTest.php index d89e453..b94bb60 100644 --- a/tests/EngineTest.php +++ b/tests/EngineTest.php @@ -49,25 +49,6 @@ class EngineTest extends TestCase $this->assertTrue($engine->response()->content_length); } - public function testInitBeforeStartV2OutputBuffering(): void - { - $engine = new class extends Engine { - public function getInitializedVar(): bool - { - return $this->initialized; - } - }; - $engine->set('flight.v2.output_buffering', true); - $this->assertTrue($engine->getInitializedVar()); - $engine->start(); - - // This is a necessary evil because of how the v2 output buffer works. - ob_end_clean(); - - $this->assertFalse($engine->router()->caseSensitive); - $this->assertTrue($engine->response()->content_length); - } - public function testHandleErrorNoErrorNumber(): void { $engine = new Engine(); @@ -322,34 +303,6 @@ class EngineTest extends TestCase $this->assertEquals(500, $engine->response()->status()); } - public function testStopWithCodeV2OutputBuffering(): void - { - $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(): void { $engine = new Engine(); @@ -519,16 +472,6 @@ class EngineTest extends TestCase $engine->json(['key1' => 'value1', 'key2' => 'value2', 'utf8_emoji' => "\xB1\x31"]); } - public function testJsonV2OutputBuffering(): void - { - $engine = new Engine(); - $engine->response()->v2_output_buffering = true; - $engine->json(['key1' => 'value1', 'key2' => 'value2']); - $this->expectOutputString('{"key1":"value1","key2":"value2"}'); - $this->assertEquals('application/json', $engine->response()->headers()['Content-Type']); - $this->assertEquals(200, $engine->response()->status()); - } - public function testJsonHalt(): void { $engine = new Engine(); @@ -549,17 +492,6 @@ class EngineTest extends TestCase $this->assertEquals('whatever({"key1":"value1","key2":"value2"});', $engine->response()->getBody()); } - public function testJsonPV2OutputBuffering(): void - { - $engine = new Engine(); - $engine->response()->v2_output_buffering = true; - $engine->request()->query['jsonp'] = 'whatever'; - $engine->jsonp(['key1' => 'value1', 'key2' => 'value2']); - $this->expectOutputString('whatever({"key1":"value1","key2":"value2"});'); - $this->assertEquals('application/javascript; charset=utf-8', $engine->response()->headers()['Content-Type']); - $this->assertEquals(200, $engine->response()->status()); - } - public function testJsonpBadParam(): void { $engine = new Engine(); @@ -569,16 +501,6 @@ class EngineTest extends TestCase $this->assertEquals(200, $engine->response()->status()); } - public function testJsonpBadParamV2OutputBuffering(): void - { - $engine = new Engine(); - $engine->response()->v2_output_buffering = true; - $engine->jsonp(['key1' => 'value1', 'key2' => 'value2']); - $this->expectOutputString('({"key1":"value1","key2":"value2"});'); - $this->assertEquals('application/javascript; charset=utf-8', $engine->response()->headers()['Content-Type']); - $this->assertEquals(200, $engine->response()->status()); - } - public function testEtagSimple(): void { $engine = new Engine(); diff --git a/tests/FlightTest.php b/tests/FlightTest.php index c74feaa..7c9d60f 100644 --- a/tests/FlightTest.php +++ b/tests/FlightTest.php @@ -275,25 +275,6 @@ class FlightTest extends TestCase $this->assertEquals('test', Flight::response()->getBody()); } - public function testHookOutputBufferingV2OutputBuffering(): void - { - 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 testStreamRoute(): void { $response_mock = new class extends Response { From f528ea6c96488e506490671d8de5b3eed412f8fc Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 1 Apr 2026 11:12:49 -0400 Subject: [PATCH 42/49] more Engine simplifications --- src/Engine.php | 89 ++++++++++++++++++++------------------------------ 1 file changed, 35 insertions(+), 54 deletions(-) diff --git a/src/Engine.php b/src/Engine.php index eefbab5..63e15a6 100644 --- a/src/Engine.php +++ b/src/Engine.php @@ -225,11 +225,9 @@ class Engine /** * Registers the container handler - * - * @param ContainerInterface|callable(class-string $id, array $params): ?T $containerHandler - * Callback function or PSR-11 Container object that sets the container and how it will inject classes - * * @template T of object + * @param ContainerInterface|callable(class-string, array): ?T $containerHandler + * Callback function or PSR-11 Container object that sets the container and how it will inject classes */ public function registerContainerHandler($containerHandler): void { @@ -237,11 +235,7 @@ class Engine } /** - * Maps a callback to a framework method. - * - * @param string $name Method name - * @param callable $callback Callback function - * + * Maps a callback to a framework method * @throws Exception If trying to map over a framework method */ public function map(string $name, callable $callback): void @@ -255,7 +249,6 @@ class Engine /** * Registers a class to a framework method. - * * # Usage example: * ``` * $app = new Engine; @@ -263,13 +256,11 @@ class Engine * * $app->user(); # <- Return a User instance * ``` - * + * @template T of object * @param string $name Method name * @param class-string $class Class name * @param array $params Class initialization parameters - * @param ?Closure(T $instance): void $callback Function to call after object instantiation - * - * @template T of object + * @param ?callable(T): void $callback Function to call after object instantiation * @throws Exception If trying to map over a framework method */ public function register(string $name, string $class, array $params = [], ?callable $callback = null): void @@ -281,39 +272,35 @@ class Engine $this->loader->register($name, $class, $params, $callback); } - /** Unregisters a class to a framework method. */ + /** Unregisters a class to a framework method */ public function unregister(string $methodName): void { $this->loader->unregister($methodName); } /** - * Adds a pre-filter to a method. - * + * Adds a pre-filter to a method * @param string $name Method name - * @param Closure(array &$params, string &$output): (void|false) $callback + * @param callable(array &$params, string &$output): (void|false) $callback */ public function before(string $name, callable $callback): void { - $this->dispatcher->hook($name, 'before', $callback); + $this->dispatcher->hook($name, Dispatcher::FILTER_BEFORE, $callback); } /** - * Adds a post-filter to a method. - * + * Adds a post-filter to a method * @param string $name Method name - * @param Closure(array &$params, string &$output): (void|false) $callback + * @param callable(array &$params, string &$output): (void|false) $callback */ public function after(string $name, callable $callback): void { - $this->dispatcher->hook($name, 'after', $callback); + $this->dispatcher->hook($name, Dispatcher::FILTER_AFTER, $callback); } /** - * Gets a variable. - * + * Gets a variable * @param ?string $key Variable name - * * @return mixed Variable value or `null` if `$key` doesn't exists. */ public function get(?string $key = null) @@ -326,15 +313,14 @@ class Engine } /** - * Sets a variable. - * + * Sets a variable * @param string|iterable $key * Variable name as `string` or an iterable of `'varName' => $varValue` - * @param ?mixed $value Ignored if `$key` is an `iterable` + * @param mixed $value Ignored if `$key` is an `iterable` */ public function set($key, $value = null): void { - if (\is_iterable($key)) { + if (is_iterable($key)) { foreach ($key as $k => $v) { $this->vars[$k] = $v; } @@ -346,10 +332,8 @@ class Engine } /** - * Checks if a variable has been set. - * + * Checks if a variable has been set * @param string $key Variable name - * * @return bool Variable status */ public function has(string $key): bool @@ -358,8 +342,7 @@ class Engine } /** - * Unsets a variable. If no key is passed in, clear all variables. - * + * Unsets a variable. If no key is passed in, clear all variables * @param ?string $key Variable name, if `$key` isn't provided, it clear all variables. */ public function clear(?string $key = null): void @@ -373,8 +356,7 @@ class Engine } /** - * Processes each routes middleware. - * + * Processes each routes middleware * @param Route $route The route to process the middleware for. * @param string $eventName If this is the before or after method. */ @@ -386,6 +368,7 @@ class Engine $middlewares = $eventName === Dispatcher::FILTER_BEFORE ? $route->middleware : array_reverse($route->middleware); + $params = $route->params; foreach ($middlewares as $middleware) { @@ -393,23 +376,23 @@ class Engine $middlewareObject = false; // Closure functions can only run on the before event - if ($eventName === Dispatcher::FILTER_BEFORE && is_object($middleware) === true && ($middleware instanceof Closure)) { + if ($eventName === Dispatcher::FILTER_BEFORE && is_object($middleware) && ($middleware instanceof Closure)) { $middlewareObject = $middleware; // If the object has already been created, we can just use it if the event name exists. - } elseif (is_object($middleware) === true) { - $middlewareObject = method_exists($middleware, $eventName) === true ? [$middleware, $eventName] : false; + } elseif (is_object($middleware)) { + $middlewareObject = method_exists($middleware, $eventName) ? [$middleware, $eventName] : false; // If the middleware is a string, we need to create the object and then call the event. - } elseif (is_string($middleware) === true && method_exists($middleware, $eventName) === true) { + } elseif (is_string($middleware) && method_exists($middleware, $eventName)) { $resolvedClass = null; // if there's a container assigned, we should use it to create the object - if ($this->dispatcher->mustUseContainer($middleware) === true) { + if ($this->dispatcher->mustUseContainer($middleware)) { $resolvedClass = $this->dispatcher->resolveContainerClass($middleware, $params); // otherwise just assume it's a plain jane class, so inject the engine // just like in Dispatcher::invokeCallable() - } elseif (class_exists($middleware) === true) { + } elseif (class_exists($middleware)) { $resolvedClass = new $middleware($this); } @@ -420,14 +403,14 @@ class Engine } // If nothing was resolved, go to the next thing - if ($middlewareObject === false) { + if (!$middlewareObject) { continue; } // This is the way that v3 handles output buffering (which captures output correctly) $useV3OutputBuffering = !$route->is_streamed; - if ($useV3OutputBuffering === true) { + if ($useV3OutputBuffering) { ob_start(); } @@ -445,7 +428,7 @@ class Engine microtime(true) - $start ); - if ($useV3OutputBuffering === true) { + if ($useV3OutputBuffering) { $this->response()->write(ob_get_clean()); } @@ -548,18 +531,16 @@ class Engine $useV3OutputBuffering = !$route->is_streamed; - if ($useV3OutputBuffering === true) { + if ($useV3OutputBuffering) { ob_start(); } // Call route handler $routeStart = microtime(true); - $continue = $this->dispatcher->execute( - $route->callback, - $params - ); + $continue = $this->dispatcher->execute($route->callback, $params); $this->triggerEvent('flight.route.executed', $route, microtime(true) - $routeStart); - if ($useV3OutputBuffering === true) { + + if ($useV3OutputBuffering) { $response->write(ob_get_clean()); } @@ -572,6 +553,7 @@ class Engine $failedMiddlewareCheck = true; break; } + $this->triggerEvent('flight.middleware.after', $route); } @@ -582,7 +564,6 @@ class Engine } $router->next(); - $dispatched = false; } @@ -596,7 +577,7 @@ class Engine } elseif ($dispatched === false) { // Get the previous route and check if the method failed, but the URL was good. $lastRouteExecuted = $router->executedRoute; - if ($lastRouteExecuted !== null && $lastRouteExecuted->matchUrl($request->url) === true && $lastRouteExecuted->matchMethod($request->method) === false) { + if ($lastRouteExecuted !== null && $lastRouteExecuted->matchUrl($request->url) && !$lastRouteExecuted->matchMethod($request->method)) { $this->methodNotFound($lastRouteExecuted); } else { $this->notFound(); From 3024a0d198a9bab7fd44ea73eaa5731e74971762 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 1 Apr 2026 11:27:37 -0400 Subject: [PATCH 43/49] save EventDispatcher instance in Loader for consistency --- src/Engine.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Engine.php b/src/Engine.php index 63e15a6..f7ad03d 100644 --- a/src/Engine.php +++ b/src/Engine.php @@ -148,7 +148,7 @@ class Engine $this->dispatcher->setEngine($this); // Register default components - $this->map('eventDispatcher', static fn(): EventDispatcher => EventDispatcher::getInstance()); + $this->loader->register('eventDispatcher', EventDispatcher::class); $this->loader->register('request', Request::class); $this->loader->register('response', Response::class); $this->loader->register('router', Router::class); From b9b70ac2ae17a46df1d4940d938ef1cbe3a1292a Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 1 Apr 2026 11:46:55 -0400 Subject: [PATCH 44/49] refactor of Loader --- src/core/Loader.php | 81 ++++++++++++++------------------------------- 1 file changed, 25 insertions(+), 56 deletions(-) diff --git a/src/core/Loader.php b/src/core/Loader.php index 025b90c..897c772 100644 --- a/src/core/Loader.php +++ b/src/core/Loader.php @@ -22,79 +22,52 @@ class Loader protected array $instances = []; /** - * Registers a class. * @template T of object - * @param string $name Registry name * @param class-string|callable(): T $class Class name or function to instantiate class * @param array $params Class initialization parameters * @param ?callable(T): void $callback $callback Function to call after object instantiation */ public function register( - string $name, + string $alias, $class, array $params = [], ?callable $callback = null ): void { - unset($this->instances[$name]); - $this->classes[$name] = [$class, $params, $callback]; + $this->classes[$alias] = [$class, $params, $callback]; + unset($this->instances[$alias]); } - /** - * Unregisters a class. - * @param string $name Registry name - */ - public function unregister(string $name): void + public function unregister(string $alias): void { - unset($this->classes[$name]); + unset($this->classes[$alias]); } /** - * Loads a registered class. - * @param string $name Method name - * @param bool $shared Shared instance * @throws Throwable * @return ?object Class instance */ - public function load(string $name, bool $shared = true): ?object + public function load(string $alias, bool $shared = true): ?object { - $obj = null; - - if (isset($this->classes[$name])) { - [$class, $params, $callback] = $this->classes[$name]; - $exists = isset($this->instances[$name]); - - if ($shared) { - $obj = $exists - ? $this->getInstance($name) - : $this->newInstance($class, $params); - - if (!$exists) { - $this->instances[$name] = $obj; - } - } else { - $obj = $this->newInstance($class, $params); - } - - if ($callback && (!$shared || !$exists)) { - call_user_func_array($callback, [$obj]); - } + if (!key_exists($alias, $this->classes)) { + return null; } - return $obj; - } + [$class, $params, $callback] = $this->classes[$alias]; + $instanceExists = key_exists($alias, $this->instances); - /** - * Gets a single instance of a class. - * @param string $name Instance name - * @return ?object Class instance - */ - public function getInstance(string $name): ?object - { - return $this->instances[$name] ?? null; + $obj = $shared && $instanceExists + ? $this->instances[$alias] ?? null + : $this->instances[$alias] = $this->newInstance($class, $params); + + if ($callback && (!$shared || !$instanceExists)) { + $callback($obj); + } + + return $obj; } /** - * Gets a new instance of a class. + * Gets a new instance of a class * @template T of object * @param class-string|callable(): T $class Class name or callback function to instantiate class * @param array $params Class initialization parameters @@ -103,22 +76,18 @@ class Loader */ public function newInstance($class, array $params = []): object { - if (is_callable($class)) { - return call_user_func_array($class, $params); - } - - return new $class(...$params); + return is_callable($class) ? $class(...$params) : new $class(...$params); } + /** - * Gets a registered callable. - * @param string $name Registry name + * Gets a registered callable * @return ?array{class-string|callable(): object, array, ?callable(object): void} * Class information or null if not registered */ - public function get(string $name): ?array + public function get(string $alias): ?array { - return $this->classes[$name] ?? null; + return $this->classes[$alias] ?? null; } /** Resets the object to the initial state */ From 60dffbc6d909e8a7191068591ec58f868c9f82e5 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 1 Apr 2026 12:44:48 -0400 Subject: [PATCH 45/49] move default configurations to Engine::vars definition --- src/Engine.php | 102 ++++++++++++++++---------------------- tests/DocExamplesTest.php | 9 ++-- tests/EngineTest.php | 20 -------- tests/RenderTest.php | 1 - 4 files changed, 45 insertions(+), 87 deletions(-) diff --git a/src/Engine.php b/src/Engine.php index f7ad03d..8ecb042 100644 --- a/src/Engine.php +++ b/src/Engine.php @@ -89,14 +89,22 @@ class Engine ]; /** @var array */ - protected array $vars = []; + protected array $vars = [ + 'flight.base_url' => null, + 'flight.case_sensitive' => false, + 'flight.handle_errors' => true, + 'flight.log_errors' => false, + 'flight.views.path' => './views', + 'flight.views.extension' => '.php', + 'flight.content_length' => true, + ]; protected Loader $loader; protected Dispatcher $dispatcher; protected EventDispatcher $eventDispatcher; /** If the framework has been initialized or not */ - protected bool $initialized = false; + protected bool $initialized = true; /** If the request has been handled or not */ protected bool $requestHandled = false; @@ -138,20 +146,17 @@ class Engine /** Initializes the framework */ public function init(): void { - if ($this->initialized) { - $this->vars = []; - $this->loader->reset(); - $this->dispatcher->reset(); - } - - // Add this class to Dispatcher - $this->dispatcher->setEngine($this); - // Register default components $this->loader->register('eventDispatcher', EventDispatcher::class); $this->loader->register('request', Request::class); - $this->loader->register('response', Response::class); - $this->loader->register('router', Router::class); + + $this->loader->register('response', Response::class, [], function (Response $response): void { + $response->content_length = $this->get('flight.content_length'); + }); + + $this->loader->register('router', Router::class, [], function (Router $router): void { + $router->caseSensitive = $this->get('flight.case_sensitive'); + }); $this->loader->register('view', View::class, [], function (View $view): void { $view->path = $this->get('flight.views.path'); @@ -162,43 +167,22 @@ class Engine $this->dispatcher->set($name, [$this, "_$name"]); } - // Default configuration settings - $this->set('flight.base_url'); - $this->set('flight.case_sensitive', false); - $this->set('flight.handle_errors', true); - $this->set('flight.log_errors', false); - $this->set('flight.views.path', './views'); - $this->set('flight.views.extension', '.php'); - $this->set('flight.content_length', true); - - // Startup configuration - $this->before('start', function (): void { - // Enable error handling - if ($this->get('flight.handle_errors')) { - set_error_handler([$this, 'handleError']); - set_exception_handler([$this, 'handleException']); - } - - // Set case-sensitivity - $this->router()->caseSensitive = $this->get('flight.case_sensitive'); - - // Set Content-Length - $this->response()->content_length = $this->get('flight.content_length'); - }); - - $this->initialized = true; + // Enable error handling + if ($this->get('flight.handle_errors')) { + set_error_handler([$this, 'handleError']); + set_exception_handler([$this, 'handleException']); + } } /** * Custom error handler. Converts errors into exceptions. * - * @param int $errno Error number - * @param string $errstr Error string - * @param string $errfile Error file name - * @param int $errline Error file line number - * - * @return false + * @param int $errno Level of the error raised. + * @param string $errstr Error message. + * @param string $errfile Filename that the error was raised in. + * @param int $errline Line number where the error was raised. * @throws ErrorException + * @return false */ public function handleError(int $errno, string $errstr, string $errfile, int $errline): bool { @@ -209,24 +193,20 @@ class Engine return false; } - /** - * Custom exception handler. Logs exceptions. - * - * @param Throwable $e Thrown exception - */ - public function handleException(Throwable $e): void + /** Custom exception handler. Logs exceptions */ + public function handleException(Throwable $ex): void { if ($this->get('flight.log_errors')) { - error_log($e->getMessage()); // @codeCoverageIgnore + error_log($ex->getMessage()); } - $this->error($e); + $this->error($ex); } /** * Registers the container handler * @template T of object - * @param ContainerInterface|callable(class-string, array): ?T $containerHandler + * @param ContainerInterface|callable(class-string): T $containerHandler * Callback function or PSR-11 Container object that sets the container and how it will inject classes */ public function registerContainerHandler($containerHandler): void @@ -240,11 +220,17 @@ class Engine */ public function map(string $name, callable $callback): void { - if (method_exists($this, $name)) { + $this->ensureMethodNotExists($name)->dispatcher->set($name, $callback); + } + + /** @throws Exception */ + private function ensureMethodNotExists(string $method): self + { + if (method_exists($this, $method)) { throw new Exception('Cannot override an existing framework method.'); } - $this->dispatcher->set($name, $callback); + return $this; } /** @@ -265,11 +251,7 @@ class Engine */ public function register(string $name, string $class, array $params = [], ?callable $callback = null): void { - if (method_exists($this, $name)) { - throw new Exception('Cannot override an existing framework method.'); - } - - $this->loader->register($name, $class, $params, $callback); + $this->ensureMethodNotExists($name)->loader->register($name, $class, $params, $callback); } /** Unregisters a class to a framework method */ diff --git a/tests/DocExamplesTest.php b/tests/DocExamplesTest.php index 33f92bc..0bdacb0 100644 --- a/tests/DocExamplesTest.php +++ b/tests/DocExamplesTest.php @@ -62,12 +62,9 @@ class DocExamplesTest extends TestCase Flight::request()->method = 'GET'; Flight::request()->url = '/'; - $router->get( - '/', - function () { - Flight::response()->write('from resp '); - } - ); + $router->get('/', static function () { + Flight::response()->write('from resp '); + }); Flight::start(); diff --git a/tests/EngineTest.php b/tests/EngineTest.php index b94bb60..f5d8399 100644 --- a/tests/EngineTest.php +++ b/tests/EngineTest.php @@ -134,26 +134,6 @@ class EngineTest extends TestCase $engine->start(); } - public function testStartWithRouteButReturnedValueThrows404V2OutputBuffering(): void - { - $_SERVER['REQUEST_METHOD'] = 'GET'; - $_SERVER['REQUEST_URI'] = '/someRoute'; - - $engine = new class extends Engine { - public function getInitializedVar(): bool - { - 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 testDoubleReturnTrueRoutesContinueIteration(): void { $_SERVER['REQUEST_METHOD'] = 'GET'; diff --git a/tests/RenderTest.php b/tests/RenderTest.php index ab71965..36391f9 100644 --- a/tests/RenderTest.php +++ b/tests/RenderTest.php @@ -21,7 +21,6 @@ class RenderTest extends TestCase public function testRenderView(): void { $this->app->render('hello', ['name' => 'Bob']); - $this->expectOutputString('Hello, Bob!'); } From 80be8eaeeb8dd93d9bef4af3a0a7032c72ea584d Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 1 Apr 2026 12:53:17 -0400 Subject: [PATCH 46/49] mark some Engine properties as private --- src/Engine.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Engine.php b/src/Engine.php index 8ecb042..9e99cdc 100644 --- a/src/Engine.php +++ b/src/Engine.php @@ -89,7 +89,7 @@ class Engine ]; /** @var array */ - protected array $vars = [ + private array $vars = [ 'flight.base_url' => null, 'flight.case_sensitive' => false, 'flight.handle_errors' => true, @@ -107,7 +107,7 @@ class Engine protected bool $initialized = true; /** If the request has been handled or not */ - protected bool $requestHandled = false; + private bool $requestHandled = false; public function __construct() { @@ -342,7 +342,7 @@ class Engine * @param Route $route The route to process the middleware for. * @param string $eventName If this is the before or after method. */ - protected function processMiddleware(Route $route, string $eventName): bool + private function processMiddleware(Route $route, string $eventName): bool { $atLeastOneMiddlewareFailed = false; @@ -358,7 +358,7 @@ class Engine $middlewareObject = false; // Closure functions can only run on the before event - if ($eventName === Dispatcher::FILTER_BEFORE && is_object($middleware) && ($middleware instanceof Closure)) { + if ($eventName === Dispatcher::FILTER_BEFORE && is_object($middleware) && $middleware instanceof Closure) { $middlewareObject = $middleware; // If the object has already been created, we can just use it if the event name exists. From 161bd9817c663e25d2fd6c96b9106e11f70e62b2 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 1 Apr 2026 13:07:55 -0400 Subject: [PATCH 47/49] remove Engine::init --- src/Engine.php | 59 +++++++++++++---------------- tests/DocExamplesTest.php | 2 +- tests/EventSystemTest.php | 2 +- tests/FlightTest.php | 2 +- tests/commands/RouteCommandTest.php | 2 +- 5 files changed, 31 insertions(+), 36 deletions(-) diff --git a/src/Engine.php b/src/Engine.php index 9e99cdc..6557342 100644 --- a/src/Engine.php +++ b/src/Engine.php @@ -113,39 +113,7 @@ class Engine { $this->loader = new Loader(); $this->dispatcher = new Dispatcher(); - $this->init(); - } - - /** - * @param string $name Method name - * @param array $arguments Method parameters - * @throws Throwable - * @return mixed - */ - public function __call(string $name, array $arguments) - { - $callback = $this->dispatcher->get($name); - - if (is_callable($callback)) { - return $this->dispatcher->run($name, $arguments); - } - - if (!$this->loader->get($name)) { - throw new Exception("$name must be a mapped method."); - } - - $shared = empty($arguments) || $arguments[0]; - - return $this->loader->load($name, $shared); - } - - ////////////////// - // Core Methods // - ////////////////// - /** Initializes the framework */ - public function init(): void - { // Register default components $this->loader->register('eventDispatcher', EventDispatcher::class); $this->loader->register('request', Request::class); @@ -174,6 +142,33 @@ class Engine } } + /** + * @param string $name Method name + * @param array $arguments Method parameters + * @throws Throwable + * @return mixed + */ + public function __call(string $name, array $arguments) + { + $callback = $this->dispatcher->get($name); + + if (is_callable($callback)) { + return $this->dispatcher->run($name, $arguments); + } + + if (!$this->loader->get($name)) { + throw new Exception("$name must be a mapped method."); + } + + $shared = empty($arguments) || $arguments[0]; + + return $this->loader->load($name, $shared); + } + + ////////////////// + // Core Methods // + ////////////////// + /** * Custom error handler. Converts errors into exceptions. * diff --git a/tests/DocExamplesTest.php b/tests/DocExamplesTest.php index 0bdacb0..c3ac378 100644 --- a/tests/DocExamplesTest.php +++ b/tests/DocExamplesTest.php @@ -16,7 +16,7 @@ class DocExamplesTest extends TestCase { $_SERVER = []; $_REQUEST = []; - Flight::init(); + Flight::app(); Flight::setEngine(new Engine()); } diff --git a/tests/EventSystemTest.php b/tests/EventSystemTest.php index 9a229b6..4126289 100644 --- a/tests/EventSystemTest.php +++ b/tests/EventSystemTest.php @@ -15,7 +15,7 @@ class EventSystemTest extends TestCase { // Reset the Flight engine before each test to ensure a clean state Flight::setEngine(new Engine()); - Flight::app()->init(); + Flight::app(); Flight::eventDispatcher()->resetInstance(); // Clear any existing listeners } diff --git a/tests/FlightTest.php b/tests/FlightTest.php index 7c9d60f..b407f70 100644 --- a/tests/FlightTest.php +++ b/tests/FlightTest.php @@ -20,7 +20,7 @@ class FlightTest extends TestCase { $_SERVER = []; $_REQUEST = []; - Flight::init(); + Flight::app(); Flight::setEngine(new Engine()); Flight::set('flight.views.path', __DIR__ . '/views'); } diff --git a/tests/commands/RouteCommandTest.php b/tests/commands/RouteCommandTest.php index 3aea0b1..5cea39d 100644 --- a/tests/commands/RouteCommandTest.php +++ b/tests/commands/RouteCommandTest.php @@ -26,7 +26,7 @@ class RouteCommandTest extends TestCase $_SERVER = []; $_REQUEST = []; - Flight::init(); + Flight::app(); Flight::setEngine(new Engine()); } From d8e108295cca2d1f992c3f7430b0622231e2a734 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 1 Apr 2026 13:15:26 -0400 Subject: [PATCH 48/49] simplify error handling custom methods docblocks --- src/Engine.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Engine.php b/src/Engine.php index 6557342..cfbcdf7 100644 --- a/src/Engine.php +++ b/src/Engine.php @@ -170,8 +170,7 @@ class Engine ////////////////// /** - * Custom error handler. Converts errors into exceptions. - * + * Converts errors into exceptions * @param int $errno Level of the error raised. * @param string $errstr Error message. * @param string $errfile Filename that the error was raised in. @@ -188,7 +187,7 @@ class Engine return false; } - /** Custom exception handler. Logs exceptions */ + /** Logs exceptions */ public function handleException(Throwable $ex): void { if ($this->get('flight.log_errors')) { From d6ed21c2a8ed0ded7eb68295d68ed58b4166014e Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 1 Apr 2026 13:21:18 -0400 Subject: [PATCH 49/49] remove Engine::initialized --- src/Engine.php | 9 +++------ tests/EngineTest.php | 8 +------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/Engine.php b/src/Engine.php index cfbcdf7..0acb1d7 100644 --- a/src/Engine.php +++ b/src/Engine.php @@ -103,9 +103,6 @@ class Engine protected Dispatcher $dispatcher; protected EventDispatcher $eventDispatcher; - /** If the framework has been initialized or not */ - protected bool $initialized = true; - /** If the request has been handled or not */ private bool $requestHandled = false; @@ -123,12 +120,12 @@ class Engine }); $this->loader->register('router', Router::class, [], function (Router $router): void { - $router->caseSensitive = $this->get('flight.case_sensitive'); + $router->caseSensitive = $this->vars['flight.case_sensitive']; }); $this->loader->register('view', View::class, [], function (View $view): void { - $view->path = $this->get('flight.views.path'); - $view->extension = $this->get('flight.views.extension'); + $view->path = $this->vars['flight.views.path']; + $view->extension = $this->vars['flight.views.extension']; }); foreach (self::MAPPABLE_METHODS as $name) { diff --git a/tests/EngineTest.php b/tests/EngineTest.php index f5d8399..5e5dfaa 100644 --- a/tests/EngineTest.php +++ b/tests/EngineTest.php @@ -32,13 +32,7 @@ class EngineTest extends TestCase public function testInitBeforeStart(): void { - $engine = new class extends Engine { - public function getInitializedVar() - { - return $this->initialized; - } - }; - $this->assertTrue($engine->getInitializedVar()); + $engine = new Engine; // we need to setup a dummy route $engine->route('/someRoute', function () {});