diff --git a/src/App/Traits/WebAppTrait.php b/src/App/Traits/WebAppTrait.php index c8b97256..00d7ae28 100644 --- a/src/App/Traits/WebAppTrait.php +++ b/src/App/Traits/WebAppTrait.php @@ -23,6 +23,7 @@ use Quantum\App\Exceptions\BaseException; use Quantum\Di\Exceptions\DiException; use Quantum\Module\ModuleLoader; +use Quantum\Router\RouteBuilder; use DebugBar\DebugBarException; use Quantum\Environment\Server; use Quantum\Debugger\Debugger; @@ -65,7 +66,6 @@ private function initializeDebugger() /** * Load modules * @throws ModuleException - * @throws RouteException */ private function loadModules() { @@ -74,8 +74,10 @@ private function loadModules() $modulesDependencies = $moduleLoader->loadModulesDependencies(); Di::registerDependencies($modulesDependencies); - $modulesRoutes = $moduleLoader->loadModulesRoutes(); - Router::setRoutes($modulesRoutes); + $builder = new RouteBuilder(); + $allRoutes = $builder->build($moduleLoader->loadModulesRoutes(), $moduleLoader->getModuleConfigs()); + + Router::setRoutes($allRoutes); } /** diff --git a/src/Console/Commands/OpenApiCommand.php b/src/Console/Commands/OpenApiCommand.php index c432086d..65348fc1 100644 --- a/src/Console/Commands/OpenApiCommand.php +++ b/src/Console/Commands/OpenApiCommand.php @@ -22,6 +22,7 @@ use Quantum\Libraries\Storage\FileSystem; use Quantum\Di\Exceptions\DiException; use Quantum\Module\ModuleLoader; +use Quantum\Router\RouteBuilder; use Quantum\Console\QtCommand; use Quantum\Router\Router; use ReflectionException; @@ -100,9 +101,12 @@ class OpenApiCommand extends QtCommand */ public function exec() { - $modulesRoutes = ModuleLoader::getInstance()->loadModulesRoutes(); + $moduleLoader = ModuleLoader::getInstance(); - Router::setRoutes($modulesRoutes); + $builder = new RouteBuilder(); + $allRoutes = $builder->build($moduleLoader->loadModulesRoutes(), $moduleLoader->getModuleConfigs()); + + Router::setRoutes($allRoutes); $this->fs = FileSystemFactory::get(); diff --git a/src/Console/Commands/RouteListCommand.php b/src/Console/Commands/RouteListCommand.php index c432b4b8..d8297dd0 100644 --- a/src/Console/Commands/RouteListCommand.php +++ b/src/Console/Commands/RouteListCommand.php @@ -17,12 +17,13 @@ use Quantum\Module\Exceptions\ModuleException; use Quantum\Router\Exceptions\RouteException; use Symfony\Component\Console\Helper\Table; +use Quantum\Router\RouteBuilder; use Quantum\Module\ModuleLoader; use Quantum\Console\QtCommand; use Quantum\Router\Router; /** - * Class ServeCommand + * Class RouteListCommand * @package Quantum\Console */ class RouteListCommand extends QtCommand @@ -53,9 +54,12 @@ class RouteListCommand extends QtCommand public function exec() { try { - $modulesRoutes = ModuleLoader::getInstance()->loadModulesRoutes(); + $moduleLoader = ModuleLoader::getInstance(); - Router::setRoutes($modulesRoutes); + $builder = new RouteBuilder(); + $allRoutes = $builder->build($moduleLoader->loadModulesRoutes(), $moduleLoader->getModuleConfigs()); + + Router::setRoutes($allRoutes); $routes = Router::getRoutes(); diff --git a/src/Module/ModuleLoader.php b/src/Module/ModuleLoader.php index ad60626b..0ae81568 100644 --- a/src/Module/ModuleLoader.php +++ b/src/Module/ModuleLoader.php @@ -21,7 +21,6 @@ use Quantum\App\Exceptions\BaseException; use Quantum\Libraries\Storage\FileSystem; use Quantum\Di\Exceptions\DiException; -use Quantum\Router\Route; use ReflectionException; use Quantum\App\App; use Closure; @@ -42,10 +41,8 @@ class ModuleLoader */ private static $moduleConfigs = []; - /** - * @var array - */ - private static $moduleRoutes = []; + /** @var array */ + private static $moduleRouteClosures = []; /** * @var FileSystem @@ -80,12 +77,6 @@ public static function getInstance(): ModuleLoader return self::$instance; } - /** - * Load modules routes - * @return array - * @throws ModuleException - * @throws RouteException - */ public function loadModulesRoutes(): array { if (empty(self::$moduleConfigs)) { @@ -99,12 +90,33 @@ public function loadModulesRoutes(): array continue; } - $modulesRoutes = array_merge($modulesRoutes, $this->getModuleRoutes($module, new Route([$module => $options]))); + $modulesRoutes[$module] = $this->getModuleRouteDefinitions($module); } return $modulesRoutes; } + private function getModuleRouteDefinitions(string $module): Closure + { + if (isset(self::$moduleRouteClosures[$module])) { + return self::$moduleRouteClosures[$module]; + } + + $moduleRoutesPath = modules_dir() . DS . $module . DS . 'routes' . DS . 'routes.php'; + + if (!$this->fs->exists($moduleRoutesPath)) { + throw ModuleException::moduleRoutesNotFound($module); + } + + $closure = $this->fs->require($moduleRoutesPath, true); + + if (!$closure instanceof Closure) { + throw RouteException::notClosure(); + } + + return self::$moduleRouteClosures[$module] = $closure; + } + /** * @return array * @throws ModuleException @@ -145,19 +157,6 @@ public function getModuleDependencies(string $module): array return self::$moduleDependencies[$module]; } - /** - * @return array - * @throws ModuleException - */ - public function getModuleConfigs(): array - { - if (empty(self::$moduleConfigs)) { - $this->loadModuleConfig(); - } - - return self::$moduleConfigs; - } - /** * @throws ModuleException */ @@ -173,39 +172,24 @@ private function loadModuleConfig() } /** - * @param array $options - * @return bool - */ - private function isModuleEnabled(array $options): bool - { - return $options['enabled'] ?? false; - } - - /** - * @param string $module - * @param Route $route * @return array * @throws ModuleException - * @throws RouteException */ - private function getModuleRoutes(string $module, Route $route): array + public function getModuleConfigs(): array { - $moduleRoutes = modules_dir() . DS . $module . DS . 'routes' . DS . 'routes.php'; - - if (!$this->fs->exists($moduleRoutes)) { - throw ModuleException::moduleRoutesNotFound($module); - } - - if (empty(self::$moduleRoutes[$module])) { - self::$moduleRoutes[$module] = $this->fs->require($moduleRoutes, true); - } - - if (!self::$moduleRoutes[$module] instanceof Closure) { - throw RouteException::notClosure(); + if (empty(self::$moduleConfigs)) { + $this->loadModuleConfig(); } - self::$moduleRoutes[$module]($route); + return self::$moduleConfigs; + } - return $route->getRuntimeRoutes(); + /** + * @param array $options + * @return bool + */ + private function isModuleEnabled(array $options): bool + { + return (bool) ($options['enabled'] ?? false); } } diff --git a/src/Router/Route.php b/src/Router/Route.php index aeb005d0..999d0dcc 100644 --- a/src/Router/Route.php +++ b/src/Router/Route.php @@ -70,13 +70,14 @@ class Route private $virtualRoutes = []; /** - * @param array $module + * @param string $moduleName + * @param array $moduleOptions */ - public function __construct(array $module) + public function __construct(string $moduleName, array $moduleOptions) { $this->virtualRoutes['*'] = []; - $this->moduleName = key($module); - $this->moduleOptions = $module[$this->moduleName]; + $this->moduleName = $moduleName; + $this->moduleOptions = $moduleOptions; } /** @@ -95,7 +96,7 @@ public function add(string $route, string $method, ...$params): Route ]; if (isset($this->moduleOptions['cacheable'])) { - $this->currentRoute['cache_settings']['shouldCache'] = (bool)$this->moduleOptions['cacheable']; + $this->currentRoute['cache_settings']['shouldCache'] = (bool) $this->moduleOptions['cacheable']; } if (is_callable($params[0])) { diff --git a/src/Router/RouteBuilder.php b/src/Router/RouteBuilder.php new file mode 100644 index 00000000..1a49c7d3 --- /dev/null +++ b/src/Router/RouteBuilder.php @@ -0,0 +1,48 @@ + + * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org) + * @link http://quantum.softberg.org/ + * @since 3.0.0 + */ + +namespace Quantum\Router; + +use Closure; + +/** + * Class RouteBuilder + * @package Quantum\Router + */ +class RouteBuilder +{ + /** + * @param array $moduleClosures moduleName => closure(Route $collector): void + * @param array $moduleConfigs moduleName => config options + * @return array + */ + public function build(array $moduleClosures, array $moduleConfigs = []): array + { + $allRoutes = []; + + foreach ($moduleClosures as $module => $closure) { + $options = $moduleConfigs[$module] ?? []; + + $routeCollector = new Route($module, $options); + + $closure($routeCollector); + + foreach ($routeCollector->getRuntimeRoutes() as $runtimeRoute) { + $allRoutes[] = $runtimeRoute; + } + } + + return $allRoutes; + } +} diff --git a/src/Router/Router.php b/src/Router/Router.php index 61764efa..a626a11d 100644 --- a/src/Router/Router.php +++ b/src/Router/Router.php @@ -204,7 +204,7 @@ private function handleRoutePattern(array $route): array $routePattern = '(\/)?'; $routeParams = []; - $lastIndex = (int)array_key_last($routeSegments); + $lastIndex = (int) array_key_last($routeSegments); foreach ($routeSegments as $index => $segment) { $segmentParam = $this->getSegmentParam($segment, $index, $lastIndex); diff --git a/tests/Unit/Module/ModuleLoaderTest.php b/tests/Unit/Module/ModuleLoaderTest.php index 2290166f..b69a56b9 100644 --- a/tests/Unit/Module/ModuleLoaderTest.php +++ b/tests/Unit/Module/ModuleLoaderTest.php @@ -4,7 +4,7 @@ use Quantum\Tests\Unit\AppTestCase; use Quantum\Module\ModuleLoader; -use Quantum\Router\Router; +use Closure; class ModuleLoaderTest extends AppTestCase { @@ -32,13 +32,13 @@ public function testLoadModulesRoutes() { $modulesRoutes = $this->moduleLoader->loadModulesRoutes(); - Router::setRoutes($modulesRoutes); + $this->assertNotEmpty($modulesRoutes); - $this->assertNotEmpty(Router::getRoutes()); + $this->assertIsArray($modulesRoutes); - $this->assertIsArray(Router::getRoutes()); + $this->assertArrayHasKey('Test', $modulesRoutes); - $this->assertCount(2, Router::getRoutes()); + $this->assertInstanceOf(Closure::class, $modulesRoutes['Test']); } public function testLoadModulesDependencies() diff --git a/tests/Unit/Router/RouteBuilderTest.php b/tests/Unit/Router/RouteBuilderTest.php new file mode 100644 index 00000000..0c3554fe --- /dev/null +++ b/tests/Unit/Router/RouteBuilderTest.php @@ -0,0 +1,65 @@ + function (Route $route): void { + $route->get('users', 'UsersController', 'index'); + $route->post('login', 'AuthController', 'login'); + }, + 'Web' => function (Route $route): void { + $route->get('', 'HomeController', 'index'); + }, + ]; + + $configs = [ + 'Api' => ['prefix' => 'api', 'enabled' => true], + 'Web' => ['prefix' => '', 'enabled' => true], + ]; + + $routes = $builder->build($closures, $configs); + + $this->assertIsArray($routes); + $this->assertCount(3, $routes); + + $this->assertSame('GET', $routes[0]['method']); + $this->assertSame('api/users', $routes[0]['route']); + $this->assertSame('Api', $routes[0]['module']); + + $this->assertSame('POST', $routes[1]['method']); + $this->assertSame('api/login', $routes[1]['route']); + $this->assertSame('Api', $routes[1]['module']); + + $this->assertSame('GET', $routes[2]['method']); + $this->assertSame('', $routes[2]['route']); + $this->assertSame('Web', $routes[2]['module']); + } + + public function testRouteBuilderBuildUsesEmptyOptionsWhenModuleConfigNotProvided(): void + { + $builder = new RouteBuilder(); + + $closures = [ + 'Test' => function (Route $route): void { + $route->get('ping', 'PingController', 'index'); + }, + ]; + + $routes = $builder->build($closures, []); + + $this->assertCount(1, $routes); + $this->assertSame('GET', $routes[0]['method']); + $this->assertSame('ping', $routes[0]['route']); + $this->assertSame('Test', $routes[0]['module']); + } +} diff --git a/tests/Unit/Router/RouteTest.php b/tests/Unit/Router/RouteTest.php index 6ea5babf..de197e32 100644 --- a/tests/Unit/Router/RouteTest.php +++ b/tests/Unit/Router/RouteTest.php @@ -14,14 +14,7 @@ public function setUp(): void { parent::setUp(); - $module = [ - 'Test' => [ - 'prefix' => '', - 'endabled' => true, - ], - ]; - - $this->route = new Route($module); + $this->route = new Route('Test', ['prefix' => '', 'endabled' => true]); } public function testCallbackRoute()