Properly implement the router with config and interfaces
Expand the container with aliases and argument autowiring
This commit is contained in:
@ -9,8 +9,8 @@ use ReflectionClass;
|
||||
class ClassAttributeCollector
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $path,
|
||||
private readonly string $attribute,
|
||||
private readonly string $path,
|
||||
private readonly ?string $attribute = null,
|
||||
)
|
||||
{
|
||||
}
|
||||
@ -32,7 +32,7 @@ class ClassAttributeCollector
|
||||
return $classes;
|
||||
}
|
||||
|
||||
private function getClassFromFile(mixed $file)
|
||||
private function getClassFromFile(\SplFileInfo $file): ?string
|
||||
{
|
||||
$contents = file_get_contents($file->getPathname());
|
||||
$tokens = token_get_all($contents);
|
||||
@ -50,12 +50,13 @@ class ClassAttributeCollector
|
||||
$classFound = true;
|
||||
continue;
|
||||
}
|
||||
if ($namespaceFound && $token[0] === T_STRING) {
|
||||
$namespace .= $token[1] . '\\';
|
||||
if ($namespaceFound && $token[0] === T_NAME_QUALIFIED) {
|
||||
$namespace = $token[1];
|
||||
$namespaceFound = false;
|
||||
continue;
|
||||
}
|
||||
if ($classFound && $token[0] === T_STRING) {
|
||||
$class = $token[1];
|
||||
$class .= $token[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -63,9 +64,8 @@ class ClassAttributeCollector
|
||||
if (!$class) {
|
||||
return null;
|
||||
}
|
||||
$class = $namespace . $class;
|
||||
$attributes = $this->getClassAttributes($class);
|
||||
if (!in_array($this->attribute, $attributes)) {
|
||||
$class = $namespace . "\\" . $class;
|
||||
if ($this->attribute && !in_array($this->attribute, $this->getClassAttributes($class))) {
|
||||
return null;
|
||||
}
|
||||
return $class;
|
||||
|
36
src/Collector/RouteCollector.php
Normal file
36
src/Collector/RouteCollector.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Ardent\Undercurrent\Collector;
|
||||
|
||||
use Ardent\Undercurrent\Attribute\Route;
|
||||
|
||||
class RouteCollector
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $path,
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public function collect(): array
|
||||
{
|
||||
$routes = [];
|
||||
$classCollector = new ClassAttributeCollector($this->path);
|
||||
$classes = $classCollector->collect();
|
||||
|
||||
foreach ($classes as $class) {
|
||||
$reflection = new \ReflectionClass($class);
|
||||
$reflectionMethods = $reflection->getMethods();
|
||||
|
||||
foreach ($reflectionMethods as $reflectionMethod) {
|
||||
$attributes = $reflectionMethod->getAttributes(Route::class, \ReflectionAttribute::IS_INSTANCEOF);
|
||||
if (count($attributes) === 0) {
|
||||
continue;
|
||||
}
|
||||
// todo: get the attribute, classname and route
|
||||
}
|
||||
}
|
||||
|
||||
return $routes;
|
||||
}
|
||||
}
|
22
src/Container/ContainerInterface.php
Normal file
22
src/Container/ContainerInterface.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Ardent\Undercurrent\Container;
|
||||
|
||||
use Exception;
|
||||
|
||||
interface ContainerInterface
|
||||
{
|
||||
public function add(
|
||||
string $className,
|
||||
?callable $definition = null,
|
||||
bool $singleton = true
|
||||
): self;
|
||||
|
||||
/**
|
||||
* @template TClassName
|
||||
* @param class-string<TClassName> $className
|
||||
* @return TClassName
|
||||
* @throws Exception
|
||||
*/
|
||||
public function get(string $className): object;
|
||||
}
|
@ -3,19 +3,20 @@
|
||||
namespace Ardent\Undercurrent\Container;
|
||||
|
||||
use Exception;
|
||||
use ReflectionClass;
|
||||
|
||||
class GenericContainer
|
||||
class GenericContainer implements ContainerInterface
|
||||
{
|
||||
private array $definitions = [];
|
||||
|
||||
private array $instances = [];
|
||||
|
||||
private array $aliases = [];
|
||||
|
||||
public function add(string $className, ?callable $definition = null, bool $singleton = true): self
|
||||
{
|
||||
if (!$definition) {
|
||||
$definition = function () use ($className) {
|
||||
return new $className();
|
||||
};
|
||||
$definition = fn() => $this->autowire($className);
|
||||
}
|
||||
|
||||
$this->definitions[$className] = [
|
||||
@ -26,29 +27,57 @@ class GenericContainer
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function alias(string $alias, string $className): self
|
||||
{
|
||||
$this->aliases[$alias] = $className;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TClassName
|
||||
* @param class-string<TClassName> $className
|
||||
* @return TClassName
|
||||
* @throws Exception
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function get(string $className): object
|
||||
{
|
||||
if (isset($this->aliases[$className])) {
|
||||
$className = $this->aliases[$className];
|
||||
}
|
||||
if (!isset($this->definitions[$className])) {
|
||||
throw new Exception("Class $className not found in container");
|
||||
}
|
||||
|
||||
$definition = $this->definitions[$className]['definition'];
|
||||
|
||||
if ($this->definitions[$className]['singleton']) {
|
||||
if (isset($this->instances[$className])) {
|
||||
return $this->instances[$className];
|
||||
}
|
||||
$instance = $definition();
|
||||
$instance = $definition($this);
|
||||
$this->instances[$className] = $instance;
|
||||
} else {
|
||||
$instance = $definition();
|
||||
$instance = $definition($this);
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
private function autowire(string $className): ?object
|
||||
{
|
||||
$reflection = new ReflectionClass($className);
|
||||
$constructor = $reflection->getConstructor();
|
||||
if (!$constructor) {
|
||||
return new $className();
|
||||
}
|
||||
|
||||
$params = [];
|
||||
foreach ($constructor->getParameters() as $parameter) {
|
||||
$type = $parameter->getType();
|
||||
if (!$type) {
|
||||
throw new Exception("Parameter {$parameter->getName()} in $className has no type");
|
||||
}
|
||||
$params[] = $this->get($type->getName());
|
||||
}
|
||||
|
||||
return new $className(...$params);
|
||||
}
|
||||
}
|
@ -5,27 +5,21 @@ namespace Ardent\Undercurrent\Http;
|
||||
use App\Controller\BaseController;
|
||||
use Ardent\Undercurrent\Attribute\Route;
|
||||
use Ardent\Undercurrent\Collector\ClassAttributeCollector;
|
||||
use Ardent\Undercurrent\Container\ContainerInterface;
|
||||
|
||||
class GenericRouter
|
||||
class GenericRouter implements RouterInterface
|
||||
{
|
||||
private array $routes = [];
|
||||
|
||||
public function __construct()
|
||||
public function __construct(
|
||||
private readonly ContainerInterface $container,
|
||||
private readonly RouterConfig $config,
|
||||
)
|
||||
{
|
||||
$collector = new ClassAttributeCollector(
|
||||
__DIR__ . '/../../app/Controller',
|
||||
Route::class,
|
||||
);
|
||||
|
||||
$this->routes = $collector->collect();
|
||||
}
|
||||
|
||||
public function getRoute(string $route): array
|
||||
public function dispatch(RequestInterface $request): ResponseInterface
|
||||
{
|
||||
// return $this->routes[0];
|
||||
return [
|
||||
'controller' => BaseController::class,
|
||||
'method' => 'HelloWorld',
|
||||
];
|
||||
$controller = $this->container->get(BaseController::class);
|
||||
$method = 'helloWorld';
|
||||
return $controller->$method();
|
||||
}
|
||||
}
|
18
src/Http/RouterConfig.php
Normal file
18
src/Http/RouterConfig.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Ardent\Undercurrent\Http;
|
||||
|
||||
class RouterConfig
|
||||
{
|
||||
|
||||
public function __construct(
|
||||
private readonly array $controllers = [],
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public function getControllers(): array
|
||||
{
|
||||
return $this->controllers;
|
||||
}
|
||||
}
|
@ -3,27 +3,42 @@
|
||||
namespace Ardent\Undercurrent\Kernel;
|
||||
|
||||
use App\Controller\BaseController;
|
||||
use Ardent\Undercurrent\Container\ContainerInterface;
|
||||
use Ardent\Undercurrent\Container\GenericContainer;
|
||||
use Ardent\Undercurrent\Http\GenericRequest;
|
||||
use Ardent\Undercurrent\Http\GenericRouter;
|
||||
use Ardent\Undercurrent\Http\ResponseInterface;
|
||||
use Ardent\Undercurrent\Http\MethodEnum;
|
||||
use Ardent\Undercurrent\Http\RouterConfig;
|
||||
use Ardent\Undercurrent\Http\RouterInterface;
|
||||
|
||||
class BaseKernel
|
||||
{
|
||||
public function __invoke(): void
|
||||
{
|
||||
$container = new GenericContainer();
|
||||
$container->add(GenericRouter::class);
|
||||
$container->add(BaseController::class);
|
||||
$container = (new GenericContainer());
|
||||
$container
|
||||
->alias(RouterInterface::class, GenericRouter::class)
|
||||
->alias(ContainerInterface::class, GenericContainer::class)
|
||||
->add(GenericContainer::class, fn($container) => $container)
|
||||
->add(GenericRouter::class)
|
||||
->add(BaseController::class);
|
||||
|
||||
$container->add(RouterConfig::class, fn() => new RouterConfig([
|
||||
BaseController::class,
|
||||
]));
|
||||
|
||||
$this->render($container);
|
||||
}
|
||||
|
||||
private function render(GenericContainer $container): void
|
||||
{
|
||||
$router = $container->get(GenericRouter::class);
|
||||
$route = $router->getRoute($_SERVER['REQUEST_URI']);
|
||||
$controller = $container->get($route['controller']);
|
||||
$method = $route['method'];
|
||||
echo $controller->$method()->getBody();
|
||||
$request = new GenericRequest(
|
||||
MethodEnum::from($_SERVER['REQUEST_METHOD']),
|
||||
$_SERVER['REQUEST_URI'],
|
||||
$_REQUEST,
|
||||
);
|
||||
|
||||
$router = $container->get(RouterInterface::class);
|
||||
echo $router->dispatch($request)->getBody();
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user