diff --git a/src/App/Adapters/WebAppAdapter.php b/src/App/Adapters/WebAppAdapter.php index b954186b9..7b1ed81ac 100644 --- a/src/App/Adapters/WebAppAdapter.php +++ b/src/App/Adapters/WebAppAdapter.php @@ -29,6 +29,7 @@ use Quantum\App\Exceptions\BaseException; use Quantum\App\Stages\InitDebuggerStage; use Quantum\Middleware\MiddlewareManager; +use Quantum\App\Stages\LoadModulesStage; use Quantum\App\Stages\LoadHelpersStage; use Quantum\Di\Exceptions\DiException; use Quantum\App\Stages\InitHttpStage; @@ -59,6 +60,7 @@ public function __construct(AppContext $context) new SetupErrorHandlerStage(), new InitHttpStage(), new InitDebuggerStage(), + new LoadModulesStage(), ]); $pipeline->run($this->context); @@ -75,8 +77,6 @@ public function start(): ?int return ExitCode::SUCCESS; } - $this->loadModules(); - $matchedRoute = $this->resolveRoute(); if ($matchedRoute === null) { diff --git a/src/App/Stages/LoadModulesStage.php b/src/App/Stages/LoadModulesStage.php new file mode 100644 index 000000000..12996b500 --- /dev/null +++ b/src/App/Stages/LoadModulesStage.php @@ -0,0 +1,56 @@ + + * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org) + * @link http://quantum.softberg.org/ + * @since 3.0.0 + */ + +namespace Quantum\App\Stages; + +use Quantum\Module\Exceptions\ModuleException; +use Quantum\Router\Exceptions\RouteException; +use Quantum\App\Contracts\BootStageInterface; +use Quantum\Di\Exceptions\DiException; +use Quantum\Router\RouteCollection; +use Quantum\Router\RouteBuilder; +use Quantum\Module\ModuleLoader; +use Quantum\App\AppContext; +use ReflectionException; +use Quantum\Di\Di; + +/** + * Class LoadModulesStage + * @package Quantum\App + */ +class LoadModulesStage implements BootStageInterface +{ + /** + * @throws ModuleException|RouteException|DiException|ReflectionException + */ + public function process(AppContext $context): void + { + if (!Di::isRegistered(ModuleLoader::class)) { + Di::register(ModuleLoader::class); + } + + $moduleLoader = Di::get(ModuleLoader::class); + + $collection = (new RouteBuilder())->build( + $moduleLoader->loadModulesRoutes(), + $moduleLoader->getModuleConfigs() + ); + + if (!Di::has(RouteCollection::class)) { + Di::set(RouteCollection::class, $collection); + } + } +} diff --git a/src/App/Traits/WebAppTrait.php b/src/App/Traits/WebAppTrait.php index ebfdd9ec7..1c8d8f647 100644 --- a/src/App/Traits/WebAppTrait.php +++ b/src/App/Traits/WebAppTrait.php @@ -16,7 +16,6 @@ namespace Quantum\App\Traits; -use Quantum\Module\Exceptions\ModuleException; use Quantum\Config\Exceptions\ConfigException; use Quantum\Loader\Exceptions\LoaderException; use Quantum\Router\Exceptions\RouteException; @@ -26,8 +25,6 @@ use Quantum\Di\Exceptions\DiException; use Quantum\ResourceCache\ViewCache; use Quantum\Router\RouteCollection; -use Quantum\Router\RouteBuilder; -use Quantum\Module\ModuleLoader; use Quantum\Router\MatchedRoute; use Quantum\Router\RouteFinder; use Quantum\Debugger\Debugger; @@ -44,26 +41,7 @@ trait WebAppTrait { /** - * @throws ModuleException|RouteException|DiException|ReflectionException - */ - private function loadModules(): void - { - if (!Di::isRegistered(ModuleLoader::class)) { - Di::register(ModuleLoader::class); - } - - $moduleLoader = Di::get(ModuleLoader::class); - - $collection = (new RouteBuilder())->build( - $moduleLoader->loadModulesRoutes(), - $moduleLoader->getModuleConfigs() - ); - - Di::set(RouteCollection::class, $collection); - } - - /** - * @throws RouteException|BaseException|DiException|ReflectionException + * @throws RouteException|BaseException */ private function resolveRoute(): ?MatchedRoute { @@ -156,6 +134,5 @@ private function cleanupRequestContext(): void { request()->setMatchedRoute(null); request()->flush(); - response()->flush(); } } diff --git a/src/Console/Commands/OpenApiCommand.php b/src/Console/Commands/OpenApiCommand.php index d13b866b8..a8250489d 100644 --- a/src/Console/Commands/OpenApiCommand.php +++ b/src/Console/Commands/OpenApiCommand.php @@ -99,14 +99,16 @@ public function exec(): void $moduleLoader = Di::get(ModuleLoader::class); - $builder = new RouteBuilder(); + if (!Di::has(RouteCollection::class)) { + $builder = new RouteBuilder(); - $routeCollection = $builder->build( - $moduleLoader->loadModulesRoutes(), - $moduleLoader->getModuleConfigs() - ); + $routeCollection = $builder->build( + $moduleLoader->loadModulesRoutes(), + $moduleLoader->getModuleConfigs() + ); - Di::set(RouteCollection::class, $routeCollection); + Di::set(RouteCollection::class, $routeCollection); + } $module = $this->getArgument('module'); diff --git a/src/Console/Commands/RouteListCommand.php b/src/Console/Commands/RouteListCommand.php index 493703566..a87f7300f 100644 --- a/src/Console/Commands/RouteListCommand.php +++ b/src/Console/Commands/RouteListCommand.php @@ -65,13 +65,17 @@ public function exec(): void $moduleLoader = Di::get(ModuleLoader::class); - $builder = new RouteBuilder(); - $routeCollection = $builder->build( - $moduleLoader->loadModulesRoutes(), - $moduleLoader->getModuleConfigs() - ); + if (!Di::has(RouteCollection::class)) { + $builder = new RouteBuilder(); + $routeCollection = $builder->build( + $moduleLoader->loadModulesRoutes(), + $moduleLoader->getModuleConfigs() + ); + + Di::set(RouteCollection::class, $routeCollection); + } - Di::set(RouteCollection::class, $routeCollection); + $routeCollection = Di::get(RouteCollection::class); $routes = $routeCollection->all(); diff --git a/tests/Unit/App/Adapters/WebAppAdapterTest.php b/tests/Unit/App/Adapters/WebAppAdapterTest.php index 955cb55f3..46686b0dd 100644 --- a/tests/Unit/App/Adapters/WebAppAdapterTest.php +++ b/tests/Unit/App/Adapters/WebAppAdapterTest.php @@ -34,9 +34,6 @@ public function testWebAppAdapterStartSuccessfully(): void $this->assertEquals(0, $result); $this->assertNull(request()->getMatchedRoute()); $this->assertNull(request()->getUri()); - $this->assertSame([], response()->all()); - $this->assertSame([], response()->allHeaders()); - $this->assertSame(200, response()->getStatusCode()); } public function testWebAppAdapterStartFails(): void @@ -50,9 +47,6 @@ public function testWebAppAdapterStartFails(): void $this->assertSame(0, $result); $this->assertNull(request()->getMatchedRoute()); $this->assertNull(request()->getUri()); - $this->assertSame([], response()->all()); - $this->assertSame([], response()->allHeaders()); - $this->assertSame(200, response()->getStatusCode()); } public function testWebAppAdapterHandlesPageNotFoundGracefully(): void @@ -66,9 +60,6 @@ public function testWebAppAdapterHandlesPageNotFoundGracefully(): void $this->assertSame(0, $result); $this->assertNull(request()->getMatchedRoute()); $this->assertNull(request()->getUri()); - $this->assertSame([], response()->all()); - $this->assertSame([], response()->allHeaders()); - $this->assertSame(200, response()->getStatusCode()); } public function testWebAppAdapterCleansUpOnException(): void @@ -98,8 +89,8 @@ public function send(): void $this->assertNull(request()->getMatchedRoute()); $this->assertNull(request()->getUri()); - $this->assertSame([], response()->all()); - $this->assertSame([], response()->allHeaders()); + $this->assertSame(['foo' => 'bar'], response()->all()); + $this->assertSame('1', response()->getHeader('X-Test')); $this->assertSame(200, response()->getStatusCode()); } } diff --git a/tests/Unit/Console/Commands/OpenApiCommandTest.php b/tests/Unit/Console/Commands/OpenApiCommandTest.php index 3283c4e67..641fd269e 100644 --- a/tests/Unit/Console/Commands/OpenApiCommandTest.php +++ b/tests/Unit/Console/Commands/OpenApiCommandTest.php @@ -5,8 +5,10 @@ use Symfony\Component\Console\Tester\CommandTester; use Quantum\Console\Commands\OpenApiCommand; use Quantum\Tests\Unit\AppTestCase; +use Quantum\Router\RouteCollection; use Quantum\Storage\FileSystem; use ReflectionMethod; +use Quantum\Di\Di; class OpenApiCommandTest extends AppTestCase { @@ -80,6 +82,50 @@ public function testExecShowsErrorWhenModuleIsMissing(): void } } + public function testExecSkipsRouteBuildWhenCollectionAlreadyBound(): void + { + if (!Di::has(RouteCollection::class)) { + Di::set(RouteCollection::class, new RouteCollection()); + } + + $openApiAssets = assets_dir() . DS . 'OpenApiUi'; + $indexCssPath = $openApiAssets . DS . 'index.css'; + $assetsDirCreatedByTest = false; + $indexCssExisted = false; + $indexCssBackup = ''; + + if (!$this->fs->isDirectory($openApiAssets)) { + mkdir($openApiAssets, 0777, true); + $assetsDirCreatedByTest = true; + } + + if ($this->fs->exists($indexCssPath)) { + $indexCssExisted = true; + $indexCssBackup = (string) $this->fs->get($indexCssPath); + } + + $this->fs->put($indexCssPath, '/* stub */'); + + try { + $tester = new CommandTester($this->command); + $tester->execute([ + 'module' => 'MissingModule', + ]); + + $this->assertStringContainsString('The module `MissingModule` not found', $tester->getDisplay()); + } finally { + if ($indexCssExisted) { + $this->fs->put($indexCssPath, $indexCssBackup); + } elseif ($this->fs->exists($indexCssPath)) { + $this->fs->remove($indexCssPath); + } + + if ($assetsDirCreatedByTest && $this->fs->isDirectory($openApiAssets)) { + @rmdir($openApiAssets); + } + } + } + public function testOpenapiRoutesContainsModuleSpecPath(): void { $method = new ReflectionMethod($this->command, 'openapiRoutes'); diff --git a/tests/Unit/Console/Commands/RouteListCommandTest.php b/tests/Unit/Console/Commands/RouteListCommandTest.php index 0286c94e6..3a6861ce1 100644 --- a/tests/Unit/Console/Commands/RouteListCommandTest.php +++ b/tests/Unit/Console/Commands/RouteListCommandTest.php @@ -5,6 +5,8 @@ use Symfony\Component\Console\Tester\CommandTester; use Quantum\Console\Commands\RouteListCommand; use Quantum\Tests\Unit\AppTestCase; +use Quantum\Router\RouteCollection; +use Quantum\Di\Di; class RouteListCommandTest extends AppTestCase { @@ -51,4 +53,18 @@ public function testExecShowsErrorForUnknownModuleFilter(): void $this->assertStringContainsString('The module is not found', $tester->getDisplay()); } + + public function testExecUsesPreBoundRouteCollection(): void + { + if (!Di::has(RouteCollection::class)) { + Di::set(RouteCollection::class, new RouteCollection()); + } + + $tester = new CommandTester($this->command); + $tester->execute([]); + + $output = $tester->getDisplay(); + $this->assertStringContainsString('Routes', $output); + $this->assertStringContainsString('MODULE', $output); + } } diff --git a/tests/Unit/Http/Helpers/HttpHelperTest.php b/tests/Unit/Http/Helpers/HttpHelperTest.php index 6f564b415..edaaabb30 100644 --- a/tests/Unit/Http/Helpers/HttpHelperTest.php +++ b/tests/Unit/Http/Helpers/HttpHelperTest.php @@ -9,7 +9,6 @@ use Quantum\Router\Route; use Quantum\Http\Response; use Quantum\Http\Request; -use Quantum\Di\Di; class HttpHelperTest extends AppTestCase { @@ -25,11 +24,6 @@ public function setUp(): void $this->response = response(); } - public function tearDown(): void - { - request()->flush(); - } - public function testRequestHelperReturnsDiInstance(): void { $this->assertInstanceOf(Request::class, request()); @@ -55,8 +49,6 @@ public function testBaseUrlWithModulePrefix(): void { config()->set('app.base_url', null); - $routeCollection = new RouteCollection(); - $route = new Route( ['GET'], '/signin', @@ -65,10 +57,9 @@ public function testBaseUrlWithModulePrefix(): void ); $route->module('admin')->prefix('admin'); - $routeCollection->add($route); + $routeCollection = new RouteCollection(); - // Register route collection in DI - Di::set(RouteCollection::class, $routeCollection); + $routeCollection->add($route); // Create route finder and find the route $router = new RouteFinder($routeCollection); diff --git a/tests/Unit/Http/RequestTest.php b/tests/Unit/Http/RequestTest.php index 16aeea9b9..e42d80777 100644 --- a/tests/Unit/Http/RequestTest.php +++ b/tests/Unit/Http/RequestTest.php @@ -10,17 +10,6 @@ class RequestTest extends AppTestCase { - public function setUp(): void - { - parent::setUp(); - - } - - public function tearDown(): void - { - request()->flush(); - } - public function testSetGetMethod(): void { $request = request(); diff --git a/tests/Unit/Http/Traits/Request/HttpRequestBodyTest.php b/tests/Unit/Http/Traits/Request/HttpRequestBodyTest.php index daf9eaa27..c598e3cc1 100644 --- a/tests/Unit/Http/Traits/Request/HttpRequestBodyTest.php +++ b/tests/Unit/Http/Traits/Request/HttpRequestBodyTest.php @@ -6,16 +6,6 @@ class HttpRequestBodyTest extends AppTestCase { - public function setUp(): void - { - parent::setUp(); - } - - public function tearDown(): void - { - request()->flush(); - } - public function testRequestSetHasGetDelete(): void { $request = request(); diff --git a/tests/Unit/Http/Traits/Request/HttpRequestFileTest.php b/tests/Unit/Http/Traits/Request/HttpRequestFileTest.php index 79d306c26..b284c0331 100644 --- a/tests/Unit/Http/Traits/Request/HttpRequestFileTest.php +++ b/tests/Unit/Http/Traits/Request/HttpRequestFileTest.php @@ -8,16 +8,6 @@ class HttpRequestFileTest extends AppTestCase { - public function setUp(): void - { - parent::setUp(); - } - - public function tearDown(): void - { - request()->flush(); - } - public function testHasGetFile(): void { $request = request(); diff --git a/tests/Unit/Http/Traits/Request/HttpRequestHeaderTest.php b/tests/Unit/Http/Traits/Request/HttpRequestHeaderTest.php index 3553cb770..3822b1b19 100644 --- a/tests/Unit/Http/Traits/Request/HttpRequestHeaderTest.php +++ b/tests/Unit/Http/Traits/Request/HttpRequestHeaderTest.php @@ -9,13 +9,15 @@ class HttpRequestHeaderTest extends AppTestCase public function setUp(): void { parent::setUp(); + + request()->create('GET', 'http://example.com'); } public function tearDown(): void { - request()->flush(); - server()->flush(); + + parent::tearDown(); } public function testRequestHeaderSetHasGetDelete(): void @@ -39,7 +41,7 @@ public function testRequestHeaderAll(): void { $request = request(); - $this->assertEmpty($request->allHeaders()); + $this->assertFalse($request->hasHeader('X-CUSTOM')); $request->setHeader('X-CUSTOM', 'Custom'); diff --git a/tests/Unit/Http/Traits/Request/HttpRequestQueryTest.php b/tests/Unit/Http/Traits/Request/HttpRequestQueryTest.php index e31e97d8d..edc81b191 100644 --- a/tests/Unit/Http/Traits/Request/HttpRequestQueryTest.php +++ b/tests/Unit/Http/Traits/Request/HttpRequestQueryTest.php @@ -6,16 +6,6 @@ class HttpRequestQueryTest extends AppTestCase { - public function setUp(): void - { - parent::setUp(); - } - - public function tearDown(): void - { - request()->flush(); - } - public function testSetGetQuery(): void { $request = request(); diff --git a/tests/Unit/Http/Traits/Request/HttpRequestRouteTest.php b/tests/Unit/Http/Traits/Request/HttpRequestRouteTest.php index 7d1c86618..57080cf18 100644 --- a/tests/Unit/Http/Traits/Request/HttpRequestRouteTest.php +++ b/tests/Unit/Http/Traits/Request/HttpRequestRouteTest.php @@ -10,9 +10,6 @@ class HttpRequestRouteTest extends AppTestCase { public function tearDown(): void { - request()->setMatchedRoute(null); - request()->flush(); - parent::tearDown(); } diff --git a/tests/Unit/Http/Traits/Request/HttpRequestUrlTest.php b/tests/Unit/Http/Traits/Request/HttpRequestUrlTest.php index 6e0fb70e9..2f5664a09 100644 --- a/tests/Unit/Http/Traits/Request/HttpRequestUrlTest.php +++ b/tests/Unit/Http/Traits/Request/HttpRequestUrlTest.php @@ -6,16 +6,6 @@ class HttpRequestUrlTest extends AppTestCase { - public function setUp(): void - { - parent::setUp(); - } - - public function tearDown(): void - { - request()->flush(); - } - public function testSetGetProtocol(): void { $request = request(); diff --git a/tests/Unit/Router/Helpers/RouteHelpersTest.php b/tests/Unit/Router/Helpers/RouteHelpersTest.php index 8e7e17913..80c3fa192 100644 --- a/tests/Unit/Router/Helpers/RouteHelpersTest.php +++ b/tests/Unit/Router/Helpers/RouteHelpersTest.php @@ -10,14 +10,6 @@ class RouteHelpersTest extends AppTestCase { - public function tearDown(): void - { - request()->setMatchedRoute(null); - request()->flush(); - - parent::tearDown(); - } - public function testHelpersReturnDefaultsWhenNoRouteMatched(): void { $this->assertNull(current_middlewares()); @@ -105,11 +97,9 @@ public function testFindRouteByNameReturnsRouteFromCollection(): void $route->name('Dashboard')->module('Admin'); - $collection = new RouteCollection(); + $collection = $this->getRouteCollection(); $collection->add($route); - Di::set(RouteCollection::class, $collection); - $found = find_route_by_name('dashboard', 'admin'); $this->assertInstanceOf(Route::class, $found); @@ -127,11 +117,9 @@ public function testRouteGroupExistsDetectsGroupInModule(): void $route->group('auth')->module('Web'); - $collection = new RouteCollection(); + $collection = $this->getRouteCollection(); $collection->add($route); - Di::set(RouteCollection::class, $collection); - $this->assertTrue(route_group_exists('auth', 'web')); $this->assertFalse(route_group_exists('guest', 'web')); $this->assertFalse(route_group_exists('auth', 'api')); @@ -146,19 +134,18 @@ public function testRouteMethodAndUri(): void $this->assertSame('api/test', route_uri()); } - public function testFindRouteByNameDependsOnCollectionRegistration(): void + public function testFindRouteByNameDependsOnCollectionState(): void { $this->assertNull( - find_route_by_name('dashboard', 'admin'), - 'Expected null when RouteCollection is not registered' + find_route_by_name('route-that-does-not-exist', 'admin'), + 'Expected null when route does not exist in collection' ); - $collection = new RouteCollection(); - Di::set(RouteCollection::class, $collection); + $collection = $this->getRouteCollection(); $this->assertNull( - find_route_by_name('dashboard', 'admin'), - 'Expected null when RouteCollection is empty' + find_route_by_name('route-that-does-not-exist', 'admin'), + 'Expected null when route does not exist in current collection' ); $route = new Route( @@ -178,19 +165,18 @@ public function testFindRouteByNameDependsOnCollectionRegistration(): void $this->assertSame('DashboardController', $found->getController()); } - public function testRouteGroupExistsDependsOnCollectionRegistration(): void + public function testRouteGroupExistsDependsOnCollectionState(): void { $this->assertFalse( - route_group_exists('auth', 'web'), - 'Expected false when RouteCollection is not registered' + route_group_exists('group-that-does-not-exist', 'web'), + 'Expected false when group does not exist in collection' ); - $collection = new RouteCollection(); - Di::set(RouteCollection::class, $collection); + $collection = $this->getRouteCollection(); $this->assertFalse( - route_group_exists('auth', 'web'), - 'Expected false when RouteCollection is empty' + route_group_exists('group-that-does-not-exist', 'web'), + 'Expected false when group does not exist in current collection' ); $route = new Route( @@ -209,4 +195,16 @@ public function testRouteGroupExistsDependsOnCollectionRegistration(): void ); } + private function getRouteCollection(): RouteCollection + { + if (Di::has(RouteCollection::class)) { + return Di::get(RouteCollection::class); + } + + $collection = new RouteCollection(); + Di::set(RouteCollection::class, $collection); + + return $collection; + } + }