fdd1744b35
10 Commits
Author | SHA1 | Message | Date | |
---|---|---|---|---|
fdd1744b35 | Update README | |||
64ff0c243d |
Add controller method autowiring
diff --git a/app/Controller/HelloWorldController.php b/app/Controller/HelloWorldController.php index 8361e9d..282a990 100644 --- a/app/Controller/HelloWorldController.php +++ b/app/Controller/HelloWorldController.php @@ -5,6 +5,7 @@ namespace App\Controller; use Ardent\Undercurrent\Attribute\Route; use Ardent\Undercurrent\Http\GenericResponse; use Ardent\Undercurrent\Http\ResponseInterface; +use Ardent\Undercurrent\Http\RouterConfig; use Ardent\Undercurrent\Http\StatusEnum; class HelloWorldController @@ -26,4 +27,21 @@ class HelloWorldController { return new GenericResponse('Hello World!'); } + + #[Route('/routes')] + public function routes(RouterConfig $config): ResponseInterface + { + $routes = implode('<br>', array_map( + fn($route) => $route->getRoute()->path, + $config->getRoutes() + )); + + return new GenericResponse($routes); + } + + #[Route('/world/{name}')] + public function world(string $name): ResponseInterface + { + return new GenericResponse("Hello $name!"); + } } \ No newline at end of file diff --git a/src/Http/GenericRouter.php b/src/Http/GenericRouter.php index b2d8e1d..23e9311 100644 --- a/src/Http/GenericRouter.php +++ b/src/Http/GenericRouter.php @@ -4,6 +4,8 @@ namespace Ardent\Undercurrent\Http; use Ardent\Undercurrent\Container\ClassNotFoundException; use Ardent\Undercurrent\Container\ContainerInterface; +use Exception; +use ReflectionMethod; class GenericRouter implements RouterInterface { @@ -23,10 +25,26 @@ class GenericRouter implements RouterInterface if ($route->getRoute()->path === $request->getUri()) { $controller = $this->container->get($route->getController()); $method = $route->getMethod(); - return $controller->$method(); + return $controller->$method(...$this->autowire($route)); } } throw new RouteNotFoundException($request); } + + private function autowire(RouteConfig $route): array + { + $reflectionMethod = new ReflectionMethod($route->getController(), $route->getMethod()); + + $params = []; + foreach ($reflectionMethod->getParameters() as $parameter) { + $type = $parameter->getType(); + if (!$type) { + throw new Exception('Cannot autowire parameter without type'); + } + $params[] = $this->container->get($type->getName()); + } + + return $params; + } } \ No newline at end of file |
|||
5d6ad29db8 |
Expand the response with headers and set status code
diff --git a/app/Controller/BaseController.php b/app/Controller/BaseController.php deleted file mode 100644 index 8e24148..0000000 --- a/app/Controller/BaseController.php +++ /dev/null @@ -1,16 +0,0 @@ -<?php - -namespace App\Controller; - -use Ardent\Undercurrent\Attribute\Route; -use Ardent\Undercurrent\Http\GenericResponse; -use Ardent\Undercurrent\Http\ResponseInterface; - -class BaseController -{ - #[Route('/hello')] - public function helloWorld(): ResponseInterface - { - return new GenericResponse('Hello World!'); - } -} \ No newline at end of file diff --git a/app/Controller/HelloWorldController.php b/app/Controller/HelloWorldController.php new file mode 100644 index 0000000..8361e9d --- /dev/null +++ b/app/Controller/HelloWorldController.php @@ -0,0 +1,29 @@ +<?php + +namespace App\Controller; + +use Ardent\Undercurrent\Attribute\Route; +use Ardent\Undercurrent\Http\GenericResponse; +use Ardent\Undercurrent\Http\ResponseInterface; +use Ardent\Undercurrent\Http\StatusEnum; + +class HelloWorldController +{ + #[Route('/')] + public function index(): ResponseInterface + { + return new GenericResponse('Index, <a href="/hello">Hello</a>'); + } + + #[Route('/error')] + public function error(): ResponseInterface + { + return new GenericResponse('error', StatusEnum::NOT_FOUND); + } + + #[Route('/hello')] + public function hello(): ResponseInterface + { + return new GenericResponse('Hello World!'); + } +} \ No newline at end of file diff --git a/app/Kernel.php b/app/Kernel.php index a365d39..8b28577 100644 --- a/app/Kernel.php +++ b/app/Kernel.php @@ -2,7 +2,7 @@ namespace App; -use App\Controller\BaseController; +use App\Controller\HelloWorldController; use Ardent\Undercurrent\Container\ContainerInterface; use Ardent\Undercurrent\Kernel\BaseKernel; @@ -11,7 +11,7 @@ class Kernel extends BaseKernel protected function dependencies(ContainerInterface $container): void { $this->addControllers($container, [ - BaseController::class, + HelloWorldController::class, ]); } } \ No newline at end of file diff --git a/src/Container/ContainerInterface.php b/src/Container/ContainerInterface.php index bd50325..7b55a94 100644 --- a/src/Container/ContainerInterface.php +++ b/src/Container/ContainerInterface.php @@ -16,7 +16,7 @@ interface ContainerInterface * @template TClassName * @param class-string<TClassName> $className * @return TClassName - * @throws Exception + * @throws ClassNotFoundException */ public function get(string $className): object; } \ No newline at end of file diff --git a/src/Http/GenericResponse.php b/src/Http/GenericResponse.php index d80d350..18852ff 100644 --- a/src/Http/GenericResponse.php +++ b/src/Http/GenericResponse.php @@ -7,6 +7,7 @@ class GenericResponse implements ResponseInterface public function __construct( private readonly string $body, private readonly StatusEnum $status = StatusEnum::OK, + private readonly array $headers = [], ) { } @@ -20,4 +21,12 @@ class GenericResponse implements ResponseInterface { return $this->body; } + + /** + * @inheritDoc + */ + public function getHeaders(): array + { + return $this->headers; + } } \ No newline at end of file diff --git a/src/Http/GenericRouter.php b/src/Http/GenericRouter.php index 73ce8f1..b2d8e1d 100644 --- a/src/Http/GenericRouter.php +++ b/src/Http/GenericRouter.php @@ -2,9 +2,7 @@ namespace Ardent\Undercurrent\Http; -use App\Controller\BaseController; -use Ardent\Undercurrent\Attribute\Route; -use Ardent\Undercurrent\Collector\ClassAttributeCollector; +use Ardent\Undercurrent\Container\ClassNotFoundException; use Ardent\Undercurrent\Container\ContainerInterface; class GenericRouter implements RouterInterface @@ -16,6 +14,9 @@ class GenericRouter implements RouterInterface { } + /** + * @throws RouteNotFoundException|ClassNotFoundException + */ public function dispatch(RequestInterface $request): ResponseInterface { foreach ($this->config->getRoutes() as $route) { diff --git a/src/Http/ResponseInterface.php b/src/Http/ResponseInterface.php index abdc290..8047eb9 100644 --- a/src/Http/ResponseInterface.php +++ b/src/Http/ResponseInterface.php @@ -7,4 +7,9 @@ interface ResponseInterface public function getStatus(): StatusEnum; public function getBody(): string; + + /** + * @return array<string> + */ + public function getHeaders(): array; } \ No newline at end of file diff --git a/src/Kernel/BaseKernel.php b/src/Kernel/BaseKernel.php index 7122dcc..634cafc 100644 --- a/src/Kernel/BaseKernel.php +++ b/src/Kernel/BaseKernel.php @@ -33,9 +33,16 @@ class BaseKernel $_SERVER['REQUEST_URI'], $_REQUEST, ); - $router = $container->get(RouterInterface::class); - echo $router->dispatch($request)->getBody(); + $response = $router->dispatch($request); + + http_response_code($response->getStatus()->value); + + foreach ($response->getHeaders() as $header) { + header($header); + } + + echo $response->getBody(); } protected function addControllers(ContainerInterface $container, array $controllers): void |
|||
7c13318b9a | Introduce ClassNotFoundException for container | |||
a854480ff1 |
Implement basic version of route matching
Make RouteConfig more readable diff --git a/app/Kernel.php b/app/Kernel.php index 4425355..a365d39 100644 --- a/app/Kernel.php +++ b/app/Kernel.php @@ -10,7 +10,7 @@ class Kernel extends BaseKernel { protected function dependencies(ContainerInterface $container): void { - $this->addRoutes($container, [ + $this->addControllers($container, [ BaseController::class, ]); } diff --git a/src/Attribute/Route.php b/src/Attribute/Route.php index 6bbd272..7129654 100644 --- a/src/Attribute/Route.php +++ b/src/Attribute/Route.php @@ -2,13 +2,15 @@ namespace Ardent\Undercurrent\Attribute; +use Ardent\Undercurrent\Http\MethodEnum; use Attribute; #[Attribute] class Route { public function __construct( - public string $path, + public string $path, + public ?MethodEnum $method = null, ) { } diff --git a/src/Http/GenericRouter.php b/src/Http/GenericRouter.php index fa1bc6a..73ce8f1 100644 --- a/src/Http/GenericRouter.php +++ b/src/Http/GenericRouter.php @@ -11,15 +11,21 @@ class GenericRouter implements RouterInterface { public function __construct( private readonly ContainerInterface $container, - private readonly RouterConfig $config, + private readonly RouterConfig $config, ) { } public function dispatch(RequestInterface $request): ResponseInterface { - $controller = $this->container->get(BaseController::class); - $method = 'helloWorld'; - return $controller->$method(); + foreach ($this->config->getRoutes() as $route) { + if ($route->getRoute()->path === $request->getUri()) { + $controller = $this->container->get($route->getController()); + $method = $route->getMethod(); + return $controller->$method(); + } + } + + throw new RouteNotFoundException($request); } } \ No newline at end of file diff --git a/src/Http/RouteConfig.php b/src/Http/RouteConfig.php new file mode 100644 index 0000000..00c2cb0 --- /dev/null +++ b/src/Http/RouteConfig.php @@ -0,0 +1,31 @@ +<?php + +namespace Ardent\Undercurrent\Http; + +use Ardent\Undercurrent\Attribute\Route; + +class RouteConfig +{ + public function __construct( + private readonly Route $route, + private readonly string $controller, + private readonly string $method + ) + { + } + + public function getRoute(): Route + { + return $this->route; + } + + public function getController(): string + { + return $this->controller; + } + + public function getMethod(): string + { + return $this->method; + } +} \ No newline at end of file diff --git a/src/Http/RouteNotFoundException.php b/src/Http/RouteNotFoundException.php new file mode 100644 index 0000000..081c625 --- /dev/null +++ b/src/Http/RouteNotFoundException.php @@ -0,0 +1,16 @@ +<?php + +namespace Ardent\Undercurrent\Http; + +use Exception; + +class RouteNotFoundException extends Exception +{ + public function __construct(RequestInterface $request) + { + parent::__construct(sprintf( + 'No controller found for uri: %s', + $request->getUri() + )); + } +} \ No newline at end of file diff --git a/src/Http/RouterConfig.php b/src/Http/RouterConfig.php index 60e5af7..eec5cde 100644 --- a/src/Http/RouterConfig.php +++ b/src/Http/RouterConfig.php @@ -2,16 +2,45 @@ namespace Ardent\Undercurrent\Http; +use Ardent\Undercurrent\Attribute\Route; +use ReflectionAttribute; +use ReflectionClass; + class RouterConfig { - public function __construct( - private readonly array $controllers = [], - ) + /** + * @var array<RouteConfig> + */ + private array $routes = []; + + public function addController(string $controller): self { + $reflectionClass = new ReflectionClass($controller); + + foreach ($reflectionClass->getMethods() as $method) { + $attributes = $method->getAttributes(Route::class, ReflectionAttribute::IS_INSTANCEOF); + + if (count($attributes) === 0) { + continue; + } + + $route = $attributes[0]->newInstance(); + + $this->routes[] = new RouteConfig( + $route, + $controller, + $method->getName(), + ); + } + + return $this; } - public function getControllers(): array + /** + * @return array<RouteConfig> + */ + public function getRoutes(): array { - return $this->controllers; + return $this->routes; } } \ No newline at end of file diff --git a/src/Kernel/BaseKernel.php b/src/Kernel/BaseKernel.php index 149aa8b..7122dcc 100644 --- a/src/Kernel/BaseKernel.php +++ b/src/Kernel/BaseKernel.php @@ -38,13 +38,16 @@ class BaseKernel echo $router->dispatch($request)->getBody(); } - protected function addRoutes(ContainerInterface $container, array $routes): void + protected function addControllers(ContainerInterface $container, array $controllers): void { - foreach ($routes as $route) { - $container->add($route); + $config = new RouterConfig(); + + foreach ($controllers as $controller) { + $container->add($controller); + $config->addController($controller); } - $container->add(RouterConfig::class, fn() => new RouterConfig($routes)); + $container->add(RouterConfig::class, fn() => $config); } protected function dependencies(ContainerInterface $container): void |
|||
96e833fb4b |
Move route config to app
diff --git a/app/Kernel.php b/app/Kernel.php index 0e8e047..4425355 100644 --- a/app/Kernel.php +++ b/app/Kernel.php @@ -2,8 +2,16 @@ namespace App; +use App\Controller\BaseController; +use Ardent\Undercurrent\Container\ContainerInterface; use Ardent\Undercurrent\Kernel\BaseKernel; class Kernel extends BaseKernel { + protected function dependencies(ContainerInterface $container): void + { + $this->addRoutes($container, [ + BaseController::class, + ]); + } } \ No newline at end of file diff --git a/src/Http/RouterConfig.php b/src/Http/RouterConfig.php index ad856d6..60e5af7 100644 --- a/src/Http/RouterConfig.php +++ b/src/Http/RouterConfig.php @@ -4,7 +4,6 @@ namespace Ardent\Undercurrent\Http; class RouterConfig { - public function __construct( private readonly array $controllers = [], ) diff --git a/src/Kernel/BaseKernel.php b/src/Kernel/BaseKernel.php index a6732f4..149aa8b 100644 --- a/src/Kernel/BaseKernel.php +++ b/src/Kernel/BaseKernel.php @@ -2,7 +2,6 @@ namespace Ardent\Undercurrent\Kernel; -use App\Controller\BaseController; use Ardent\Undercurrent\Container\ContainerInterface; use Ardent\Undercurrent\Container\GenericContainer; use Ardent\Undercurrent\Http\GenericRequest; @@ -20,12 +19,9 @@ class BaseKernel ->alias(RouterInterface::class, GenericRouter::class) ->alias(ContainerInterface::class, GenericContainer::class) ->add(GenericContainer::class, fn($container) => $container) - ->add(GenericRouter::class) - ->add(BaseController::class); + ->add(GenericRouter::class); - $container->add(RouterConfig::class, fn() => new RouterConfig([ - BaseController::class, - ])); + $this->dependencies($container); $this->render($container); } @@ -41,4 +37,17 @@ class BaseKernel $router = $container->get(RouterInterface::class); echo $router->dispatch($request)->getBody(); } + + protected function addRoutes(ContainerInterface $container, array $routes): void + { + foreach ($routes as $route) { + $container->add($route); + } + + $container->add(RouterConfig::class, fn() => new RouterConfig($routes)); + } + + protected function dependencies(ContainerInterface $container): void + { + } } \ No newline at end of file |
|||
a55d1c3c2e |
Properly implement the router with config and interfaces
Expand the container with aliases and argument autowiring |
|||
e9a636554f |
Add response and request objects and interfaces
diff --git a/README.md b/README.md index 4b64da1..c860957 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ Todos: - [ ] Collect routes properly - [ ] Process routes correctly +- [ ] Add request and response objects diff --git a/app/Controller/BaseController.php b/app/Controller/BaseController.php index aa0a79b..7df9387 100644 --- a/app/Controller/BaseController.php +++ b/app/Controller/BaseController.php @@ -3,12 +3,14 @@ namespace App\Controller; use Ardent\Undercurrent\Attribute\Route; +use Ardent\Undercurrent\Http\GenericResponse; +use Ardent\Undercurrent\Http\ResponseInterface; class BaseController { #[Route('/hello')] - public function HelloWorld(): string + public function HelloWorld(): ResponseInterface { - return 'Hello, World!'; + return new GenericResponse('Hello World!'); } } \ No newline at end of file diff --git a/src/Http/GenericRequest.php b/src/Http/GenericRequest.php new file mode 100644 index 0000000..c758a40 --- /dev/null +++ b/src/Http/GenericRequest.php @@ -0,0 +1,30 @@ +<?php + +namespace Ardent\Undercurrent\Http; + +class GenericRequest implements RequestInterface +{ + + public function __construct( + private readonly MethodEnum $method, + private readonly string $uri, + private readonly array $body, + ) + { + } + + public function getMethod(): MethodEnum + { + return $this->method; + } + + public function getUri(): string + { + return $this->uri; + } + + public function getBody(): array + { + return $this->body; + } +} \ No newline at end of file diff --git a/src/Http/GenericResponse.php b/src/Http/GenericResponse.php new file mode 100644 index 0000000..d80d350 --- /dev/null +++ b/src/Http/GenericResponse.php @@ -0,0 +1,23 @@ +<?php + +namespace Ardent\Undercurrent\Http; + +class GenericResponse implements ResponseInterface +{ + public function __construct( + private readonly string $body, + private readonly StatusEnum $status = StatusEnum::OK, + ) + { + } + + public function getStatus(): StatusEnum + { + return $this->status; + } + + public function getBody(): string + { + return $this->body; + } +} \ No newline at end of file diff --git a/src/Controller/GenericRouter.php b/src/Http/GenericRouter.php similarity index 89% rename from src/Controller/GenericRouter.php rename to src/Http/GenericRouter.php index 28f75b1..0332ce5 100644 --- a/src/Controller/GenericRouter.php +++ b/src/Http/GenericRouter.php @@ -1,6 +1,6 @@ <?php -namespace Ardent\Undercurrent\Controller; +namespace Ardent\Undercurrent\Http; use App\Controller\BaseController; use Ardent\Undercurrent\Attribute\Route; diff --git a/src/Http/MethodEnum.php b/src/Http/MethodEnum.php new file mode 100644 index 0000000..76948c9 --- /dev/null +++ b/src/Http/MethodEnum.php @@ -0,0 +1,16 @@ +<?php + +namespace Ardent\Undercurrent\Http; + +enum MethodEnum: string +{ + case GET = 'GET'; + case POST = 'POST'; +// case PUT = 'PUT'; +// case PATCH = 'PATCH'; +// case DELETE = 'DELETE'; +// case HEAD = 'HEAD'; +// case OPTIONS = 'OPTIONS'; +// case TRACE = 'TRACE'; +// case CONNECT = 'CONNECT'; +} diff --git a/src/Http/RequestInterface.php b/src/Http/RequestInterface.php new file mode 100644 index 0000000..0f564b8 --- /dev/null +++ b/src/Http/RequestInterface.php @@ -0,0 +1,12 @@ +<?php + +namespace Ardent\Undercurrent\Http; + +interface RequestInterface +{ + public function getMethod(): MethodEnum; + + public function getUri(): string; + + public function getBody(): array; +} \ No newline at end of file diff --git a/src/Http/ResponseInterface.php b/src/Http/ResponseInterface.php new file mode 100644 index 0000000..abdc290 --- /dev/null +++ b/src/Http/ResponseInterface.php @@ -0,0 +1,10 @@ +<?php + +namespace Ardent\Undercurrent\Http; + +interface ResponseInterface +{ + public function getStatus(): StatusEnum; + + public function getBody(): string; +} \ No newline at end of file diff --git a/src/Http/RouterInterface.php b/src/Http/RouterInterface.php new file mode 100644 index 0000000..186baac --- /dev/null +++ b/src/Http/RouterInterface.php @@ -0,0 +1,8 @@ +<?php + +namespace Ardent\Undercurrent\Http; + +interface RouterInterface +{ + public function dispatch(RequestInterface $request): ResponseInterface; +} \ No newline at end of file diff --git a/src/Http/StatusEnum.php b/src/Http/StatusEnum.php new file mode 100644 index 0000000..6dce100 --- /dev/null +++ b/src/Http/StatusEnum.php @@ -0,0 +1,10 @@ +<?php + +namespace Ardent\Undercurrent\Http; + +enum StatusEnum: int +{ + case OK = 200; + case FORBIDDEN = 403; + case NOT_FOUND = 404; +} diff --git a/src/Kernel/BaseKernel.php b/src/Kernel/BaseKernel.php index 6bc8ed5..05ecf06 100644 --- a/src/Kernel/BaseKernel.php +++ b/src/Kernel/BaseKernel.php @@ -4,11 +4,12 @@ namespace Ardent\Undercurrent\Kernel; use App\Controller\BaseController; use Ardent\Undercurrent\Container\GenericContainer; -use Ardent\Undercurrent\Controller\GenericRouter; +use Ardent\Undercurrent\Http\GenericRouter; +use Ardent\Undercurrent\Http\ResponseInterface; class BaseKernel { - public function __invoke() + public function __invoke(): void { $container = new GenericContainer(); $container->add(GenericRouter::class); @@ -23,6 +24,6 @@ class BaseKernel $route = $router->getRoute($_SERVER['REQUEST_URI']); $controller = $container->get($route['controller']); $method = $route['method']; - echo $controller->$method(); + echo $controller->$method()->getBody(); } } \ No newline at end of file |
|||
68ccd37f07 | Move public to make it compatible with symfony nginx files | |||
503d8c524a | First working version with non working routes |