From 08635b549a91294c2e66862f5afe335fe189538b Mon Sep 17 00:00:00 2001 From: rtcoder Date: Tue, 16 Jun 2026 08:45:55 +0200 Subject: [PATCH] Remove legacy app routes and controllers --- Engine/App.php | 1 - Engine/Console/Commands/RouteList.php | 9 --- README.md | 33 +++++---- REFACTORING.md | 24 ++++--- application/controller/HelloController.php | 81 ---------------------- application/routes.php | 11 --- composer.json | 2 - index.php | 6 -- tests/ApiCoreTest.php | 80 +++++++++++++++------ 9 files changed, 93 insertions(+), 154 deletions(-) delete mode 100755 application/controller/HelloController.php delete mode 100644 application/routes.php diff --git a/Engine/App.php b/Engine/App.php index ca5719f..d6246bc 100755 --- a/Engine/App.php +++ b/Engine/App.php @@ -250,7 +250,6 @@ public static function autoload(string $class_name): void } $locations = [ - APP_PATH . '/controller/', APP_PATH . '/model/', APP_ROOT, ]; diff --git a/Engine/Console/Commands/RouteList.php b/Engine/Console/Commands/RouteList.php index 12c6481..5d33409 100644 --- a/Engine/Console/Commands/RouteList.php +++ b/Engine/Console/Commands/RouteList.php @@ -14,15 +14,6 @@ public function execute(mixed ...$args): void $cli = new Cli(); $router = new Router(); (new ModuleLoader())->load()->registerRoutes($router); - $routesFile = APP_PATH . '/routes.php'; - - if (!file_exists($routesFile)) { - $cli->warning('No routes file found.'); - return; - } - - $registerRoutes = require $routesFile; - $registerRoutes($router); $rows = []; foreach ($router->getRoutes() as $route) { diff --git a/README.md b/README.md index 8cdfb73..015b957 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,12 @@ ShiftPHP is a small API-only PHP framework. -Version `0.5` focuses on the HTTP API core: +The current architecture focuses on an API-only modular monolith: -- explicit routes, +- module-owned routes, +- module-owned controllers, +- module-owned services, +- module-owned CLI commands, - controller actions, - JSON responses, - request helpers, @@ -18,18 +21,25 @@ Version `0.5` focuses on the HTTP API core: ## Routing -Routes are registered in `application/routes.php`: +Routes are owned by modules and registered from each module boundary: ```php -use Controllers\HelloController; +namespace Modules\Health; + +use Engine\Modules\AbstractModule; use Engine\Router; use Engine\Routing\AttributeRouteLoader; +use Modules\Health\Controllers\HealthController; -return static function (Router $router): void { - (new AttributeRouteLoader())->load($router, [ - HelloController::class, - ]); -}; +class Module extends AbstractModule +{ + public function registerRoutes(Router $router): void + { + (new AttributeRouteLoader())->load($router, [ + HealthController::class, + ]); + } +} ``` Supported methods: @@ -45,7 +55,7 @@ Supported methods: Controllers extend `Engine\Controller` and return a response object: ```php -namespace Controllers; +namespace Modules\Users\Controllers; use Engine\Controller; use Engine\JsonResponse; @@ -131,8 +141,7 @@ php -S 127.0.0.1:8000 index.php Then open: ```text -http://127.0.0.1:8000/hello -http://127.0.0.1:8000/hello/api/example +http://127.0.0.1:8000/health ``` ## CLI diff --git a/REFACTORING.md b/REFACTORING.md index 3d2cfde..219ce2d 100644 --- a/REFACTORING.md +++ b/REFACTORING.md @@ -1,12 +1,12 @@ -# ShiftPHP 0.5 - API-only refactoring +# ShiftPHP - API-only modular monolith -ShiftPHP 0.5 turns the framework into an API-only HTTP core. View templates, compiled view storage, page assets and MVC rendering are out of scope for this line. +ShiftPHP is moving toward an API-only modular monolith. View templates, compiled view storage, page assets and MVC rendering are out of scope for this line. ## 0.5 Scope Implemented in this branch: -- explicit `application/routes.php` routing, +- module-owned routing, - route parameters with `{name}` placeholders, - controller actions returning response objects, - `Response`, `JsonResponse` and `ResponseEmitter`, @@ -55,14 +55,18 @@ Request ## Routes -Routes live in `application/routes.php`: +Routes live inside modules: ```php -return static function (Router $router): void { - (new AttributeRouteLoader())->load($router, [ - HelloController::class, - ]); -}; +class Module extends AbstractModule +{ + public function registerRoutes(Router $router): void + { + (new AttributeRouteLoader())->load($router, [ + HealthController::class, + ]); + } +} ``` Supported methods: @@ -78,7 +82,7 @@ Supported methods: Controllers receive the current `Request` and `ServiceContainer` through the constructor. Actions should return `Response` or `JsonResponse`. ```php -class HelloController extends \Engine\Controller +class HealthController extends \Engine\Controller { #[Get('/api/{argument}')] public function api(#[PathParam] string $argument, #[QueryParam('include')] ?string $include = null): JsonResponse diff --git a/application/controller/HelloController.php b/application/controller/HelloController.php deleted file mode 100755 index 72b78db..0000000 --- a/application/controller/HelloController.php +++ /dev/null @@ -1,81 +0,0 @@ -json([ - 'message' => 'Hello from ShiftPHP!', - 'timestamp' => date('Y-m-d H:i:s') - ]); - } - - #[Get('/about')] - public function about(): JsonResponse - { - return $this->json([ - 'title' => 'About ShiftPHP', - 'version' => '1.0.0' - ]); - } - - #[Get('/api')] - #[Get('/api/{argument}')] - public function api(#[PathParam] ?string $argument = null, #[QueryParam('include')] ?string $include = null): JsonResponse - { - $arguments = []; - if ($argument !== null) { - $arguments[] = $argument; - } - - return $this->json([ - 'status' => 'success', - 'message' => 'API endpoint working!', - 'data' => [ - 'path' => $this->request->getPath(), - 'method' => $this->request->getMethod(), - 'arguments' => $arguments, - 'include' => $include, - 'routeParams' => $this->request->getRouteParams() - ] - ]); - } - - #[Post('/echo')] - public function echo(#[Body] array $data): JsonResponse - { - return $this->json([ - 'data' => $data, - ]); - } - - #[Post('/created')] - #[Status(201)] - #[Header('X-ShiftPHP-Example', 'created')] - public function created(#[Body('name')] string $name): array - { - return [ - 'name' => $name, - 'created' => true, - ]; - } -} diff --git a/application/routes.php b/application/routes.php deleted file mode 100644 index 57f704f..0000000 --- a/application/routes.php +++ /dev/null @@ -1,11 +0,0 @@ -load($router, [ - HelloController::class, - ]); -}; diff --git a/composer.json b/composer.json index adb04c2..ab950c4 100644 --- a/composer.json +++ b/composer.json @@ -21,9 +21,7 @@ "psr-4": { "Tools\\": "Engine/Tools", "Console\\": "Engine/Console", - "AppConsole\\": "application/console", "Engine\\": "Engine", - "Controllers\\": "application/controller", "Modules\\": "application/modules" } } diff --git a/index.php b/index.php index f0ac462..62724bb 100644 --- a/index.php +++ b/index.php @@ -16,10 +16,4 @@ $modules->registerServices($app->getContainer()); $modules->registerRoutes($app->getRouter()); -$routes = APP_PATH . '/routes.php'; -if (file_exists($routes)) { - $registerRoutes = require $routes; - $registerRoutes($app->getRouter()); -} - $app->start(); diff --git a/tests/ApiCoreTest.php b/tests/ApiCoreTest.php index f39e100..79898c5 100644 --- a/tests/ApiCoreTest.php +++ b/tests/ApiCoreTest.php @@ -1,7 +1,7 @@ json([ + 'data' => [ + 'arguments' => $arguments, + 'include' => $include, + 'routeParams' => $this->request->getRouteParams(), + ], + ]); + } + + #[Post('/created')] + #[Status(201)] + #[Header('X-Test', 'created')] + public function created(#[Body('name')] string $name): array + { + return [ + 'name' => $name, + 'created' => true, + ]; + } +} + function assertSameValue(mixed $expected, mixed $actual, string $message): void { if ($expected !== $actual) { @@ -60,16 +100,16 @@ function makeRequest(string $method, string $uri, string $body = '', array $quer $tests['router matches route params'] = function (): void { $router = new Router(); - $router->get('/hello/api/{argument}', [HelloController::class, 'api']); + $router->get('/test/api/{argument}', [TestAttributeController::class, 'api']); - $match = $router->match(makeRequest('GET', '/hello/api/example')); + $match = $router->match(makeRequest('GET', '/test/api/example')); assertSameValue(['argument' => 'example'], $match->getParameters(), 'Route parameters should be extracted.'); }; $tests['attribute loader registers controller routes'] = function (): void { $router = new Router(); - (new AttributeRouteLoader())->load($router, [HelloController::class]); + (new AttributeRouteLoader())->load($router, [TestAttributeController::class]); $routes = array_map( static fn (\Engine\Route $route): string => $route->getMethod() . ' ' . $route->getPath(), @@ -78,24 +118,20 @@ function makeRequest(string $method, string $uri, string $body = '', array $quer assertSameValue( [ - 'GET /hello', - 'GET /hello/about', - 'GET /hello/api', - 'GET /hello/api/{argument}', - 'POST /hello/echo', - 'POST /hello/created', + 'GET /test/api/{argument}', + 'POST /test/created', ], $routes, - 'Attribute loader should register all HelloController routes.' + 'Attribute loader should register all test controller routes.' ); }; $tests['router returns 405 with Allow header'] = function (): void { $router = new Router(); - $router->get('/hello', [HelloController::class, 'index']); + $router->get('/test/api/{argument}', [TestAttributeController::class, 'api']); try { - $router->match(makeRequest('POST', '/hello')); + $router->match(makeRequest('POST', '/test/api/example')); } catch (HttpError $error) { assertSameValue(405, $error->getStatusCode(), 'Wrong method should return 405.'); assertArrayHasKeyValue('Allow', 'GET', $error->getHeaders(), '405 should expose allowed methods.'); @@ -106,7 +142,7 @@ function makeRequest(string $method, string $uri, string $body = '', array $quer }; $tests['request parses json and headers'] = function (): void { - $request = makeRequest('POST', '/hello/echo', '{"name":"Shift"}'); + $request = makeRequest('POST', '/test/created', '{"name":"Shift"}'); assertSameValue(['name' => 'Shift'], $request->getJson(), 'JSON body should parse.'); assertSameValue('Shift', $request->input('name'), 'Input should read JSON body.'); @@ -114,7 +150,7 @@ function makeRequest(string $method, string $uri, string $body = '', array $quer }; $tests['request rejects malformed json'] = function (): void { - $request = makeRequest('POST', '/hello/echo', '{bad'); + $request = makeRequest('POST', '/test/created', '{bad'); try { $request->getJson(); @@ -136,10 +172,10 @@ function makeRequest(string $method, string $uri, string $body = '', array $quer $tests['app dispatches route to controller'] = function (): void { $router = new Router(); - $router->get('/hello/api/{argument}', [HelloController::class, 'api']); + $router->get('/test/api/{argument}', [TestAttributeController::class, 'api']); $emitter = new CapturingEmitter(); - $app = new App(makeRequest('GET', '/hello/api/demo'), $router, $emitter); + $app = new App(makeRequest('GET', '/test/api/demo'), $router, $emitter); $app->start(); $payload = json_decode($emitter->content, true); @@ -150,10 +186,10 @@ function makeRequest(string $method, string $uri, string $body = '', array $quer $tests['app binds path and query params from attributes'] = function (): void { $router = new Router(); - (new AttributeRouteLoader())->load($router, [HelloController::class]); + (new AttributeRouteLoader())->load($router, [TestAttributeController::class]); $emitter = new CapturingEmitter(); - $app = new App(makeRequest('GET', '/hello/api/demo', '', ['include' => 'details']), $router, $emitter); + $app = new App(makeRequest('GET', '/test/api/demo', '', ['include' => 'details']), $router, $emitter); $app->start(); $payload = json_decode($emitter->content, true); @@ -164,16 +200,16 @@ function makeRequest(string $method, string $uri, string $body = '', array $quer $tests['app applies status header and body attributes'] = function (): void { $router = new Router(); - (new AttributeRouteLoader())->load($router, [HelloController::class]); + (new AttributeRouteLoader())->load($router, [TestAttributeController::class]); $emitter = new CapturingEmitter(); - $app = new App(makeRequest('POST', '/hello/created', '{"name":"Shift"}'), $router, $emitter); + $app = new App(makeRequest('POST', '/test/created', '{"name":"Shift"}'), $router, $emitter); $app->start(); $payload = json_decode($emitter->content, true); assertSameValue(201, $emitter->statusCode, 'Status attribute should override response status.'); - assertArrayHasKeyValue('X-ShiftPHP-Example', 'created', $emitter->headers, 'Header attribute should add response header.'); + assertArrayHasKeyValue('X-Test', 'created', $emitter->headers, 'Header attribute should add response header.'); assertSameValue('Shift', $payload['name'] ?? null, 'Body attribute should bind JSON body key.'); };