diff --git a/src/Config/Config.php b/src/Config/Config.php index 0ae4281..5bb6375 100644 --- a/src/Config/Config.php +++ b/src/Config/Config.php @@ -5,22 +5,56 @@ namespace Atlas\Config; use ArrayAccess; use IteratorAggregate; +/** + * Provides configuration management for the Atlas routing engine. + * + * Implements ArrayAccess and IteratorAggregate for flexible configuration access + * and iteration over configuration options. + * + * @implements ArrayAccess + * @implements IteratorAggregate + */ class Config implements ArrayAccess, IteratorAggregate { + /** + * Constructs a new Config instance with the provided options. + * + * @param array $options Configuration array containing routing settings + */ public function __construct( private readonly array $options ) {} + /** + * Retrieves a configuration value by key. + * + * @param string $key The configuration key to retrieve + * @param mixed $default Default value if key does not exist + * @return mixed The configuration value or default + */ public function get(string $key, mixed $default = null): mixed { return $this->options[$key] ?? $default; } + /** + * Checks if a configuration key exists. + * + * @param string $key The configuration key to check + * @return bool True if the key exists, false otherwise + */ public function has(string $key): bool { return isset($this->options[$key]); } + /** + * Retrieves the module path(s) configuration. + * + * Returns a single string as an array or an array of strings. + * + * @return array|string|null Module path(s) or null if not configured + */ public function getModulesPath(): array|string|null { $modulesPath = $this->get('modules_path'); @@ -32,16 +66,33 @@ class Config implements ArrayAccess, IteratorAggregate return is_array($modulesPath) ? $modulesPath : [$modulesPath]; } + /** + * Gets the default routes file name. + * + * @return string Default routes file name + */ public function getRoutesFile(): string { return $this->get('routes_file', 'routes.php'); } + /** + * Retrieves the custom modules glob pattern. + * + * @return string|null Modules glob pattern or null + */ public function getModulesGlob(): string|null { return $this->get('modules_glob'); } + /** + * Gets a normalized list of module paths. + * + * Ensures always returns an array, converting single string paths. + * + * @return array List of module paths + */ public function getModulesPathList(): array { $modulesPath = $this->getModulesPath(); @@ -53,31 +104,64 @@ class Config implements ArrayAccess, IteratorAggregate return is_array($modulesPath) ? $modulesPath : [$modulesPath]; } + /** + * Converts the configuration to an array. + * + * @return array Configuration options as array + */ public function toArray(): array { return $this->options; } + /** + * Checks if a configuration key exists (ArrayAccess interface). + * + * @param mixed $offset The configuration key + * @return bool True if offset exists + */ public function offsetExists(mixed $offset): bool { return isset($this->options[$offset]); } + /** + * Retrieves a configuration value by offset (ArrayAccess interface). + * + * @param mixed $offset The configuration key + * @return mixed Configuration value or null + */ public function offsetGet(mixed $offset): mixed { return $this->options[$offset] ?? null; } + /** + * Sets a configuration value by offset (ArrayAccess interface). + * + * @param mixed $offset The configuration key + * @param mixed $value The configuration value + */ public function offsetSet(mixed $offset, mixed $value): void { $this->options[$offset] = $value; } + /** + * Unsets a configuration key (ArrayAccess interface). + * + * @param mixed $offset The configuration key to unset + */ public function offsetUnset(mixed $offset): void { unset($this->options[$offset]); } + /** + * Creates an iterator for the configuration options. + * + * @return Traversable Array iterator over configuration + */ public function getIterator(): \Traversable { return new \ArrayIterator($this->options); diff --git a/src/Exception/NotFoundRouteException.php b/src/Exception/NotFoundRouteException.php index 6c75a99..7be2eab 100644 --- a/src/Exception/NotFoundRouteException.php +++ b/src/Exception/NotFoundRouteException.php @@ -2,6 +2,11 @@ namespace Atlas\Exception; +/** + * Exception thrown when no route matches the request for matching or URL generation. + * + * @extends \RuntimeException + */ class NotFoundRouteException extends \RuntimeException { } \ No newline at end of file diff --git a/src/Exception/RouteNotFoundException.php b/src/Exception/RouteNotFoundException.php index 4a45855..7cf7dd6 100644 --- a/src/Exception/RouteNotFoundException.php +++ b/src/Exception/RouteNotFoundException.php @@ -2,6 +2,11 @@ namespace Atlas\Exception; +/** + * Exception thrown when a requested route is not found. + * + * @extends \RuntimeException + */ class RouteNotFoundException extends \RuntimeException { } \ No newline at end of file diff --git a/src/Exception/RouteValidationException.php b/src/Exception/RouteValidationException.php index ee23352..49ac8bd 100644 --- a/src/Exception/RouteValidationException.php +++ b/src/Exception/RouteValidationException.php @@ -2,6 +2,11 @@ namespace Atlas\Exception; +/** + * Exception thrown when route parameter validation fails. + * + * @extends \RuntimeException + */ class RouteValidationException extends \RuntimeException { } \ No newline at end of file diff --git a/src/Router/Route.php b/src/Router/Route.php index 4b9d1a3..4ed209f 100644 --- a/src/Router/Route.php +++ b/src/Router/Route.php @@ -4,24 +4,54 @@ namespace Atlas\Router; use Psr\Http\Message\ServerRequestInterface; +/** + * Represents a single route definition with its HTTP method, path, and handler. + * + * Though not currently used for matching, this class provides a base structure + * for routes and may be extended for future matching capabilities. + * + * @final + */ final class Route { + /** + * Constructs a new Route instance. + * + * @param string $method HTTP method (GET, POST, etc.) + * @param string $path URI path + * @param string|callable $handler Route handler or string reference + */ public function __construct( private readonly string $method, private readonly string $path, private readonly string|callable $handler ) {} + /** + * Gets the HTTP method of this route. + * + * @return string HTTP method + */ public function getMethod(): string { return $this->method; } + /** + * Gets the URI path of this route. + * + * @return string URI path + */ public function getPath(): string { return $this->path; } + /** + * Gets the handler for this route. + * + * @return string|callable Route handler + */ public function getHandler(): string|callable { return $this->handler; diff --git a/src/Router/RouteDefinition.php b/src/Router/RouteDefinition.php index c4fbc47..c83ed64 100644 --- a/src/Router/RouteDefinition.php +++ b/src/Router/RouteDefinition.php @@ -4,8 +4,27 @@ namespace Atlas\Router; use Psr\Http\Message\UriInterface; +/** + * Represents a complete route definition with matching patterns, handlers, and metadata. + * + * @final + */ final class RouteDefinition { + /** + * Constructs a new RouteDefinition instance. + * + * @param string $method HTTP method (GET, POST, etc.) + * @param string $pattern Matching pattern (currently not used for matching) + * @param string $path Normalized path for comparison + * @param mixed $handler Route handler + * @param string|null $name Optional route name + * @param array $middleware Middleware for route processing + * @param array $validation Validation rules for route parameters + * @param array $defaults Default parameter values + * @param string|null $module Module identifier + * @param array $attributes Route attributes for parameter extraction + */ public function __construct( private readonly string $method, private readonly string $pattern, @@ -19,51 +38,101 @@ final class RouteDefinition private readonly array $attributes = [] ) {} + /** + * Gets the HTTP method of this route definition. + * + * @return string HTTP method + */ public function getMethod(): string { return $this->method; } + /** + * Gets the path for this route definition. + * + * @return string Normalized path + */ public function getPath(): string { return $this->path; } + /** + * Gets the handler for this route definition. + * + * @return string|callable Route handler + */ public function getHandler(): string|callable { return $this->handler; } + /** + * Gets the optional name of this route. + * + * @return string|null Route name or null + */ public function getName(): ?string { return $this->name; } + /** + * Gets the middleware configuration for this route. + * + * @return array Middleware configuration + */ public function getMiddleware(): array { return $this->middleware; } + /** + * Gets the validation rules for this route. + * + * @return array Validation rules + */ public function getValidation(): array { return $this->validation; } + /** + * Gets the default values for parameters. + * + * @return array Default parameter values + */ public function getDefaults(): array { return $this->defaults; } + /** + * Gets the module identifier for this route. + * + * @return string|null Module identifier or null + */ public function getModule(): ?string { return $this->module; } + /** + * Gets route attributes for parameter extraction. + * + * @return array Route attributes + */ public function getAttributes(): array { return $this->attributes; } + /** + * Converts the route definition to an array. + * + * @return array Plain array representation + */ public function toArray(): array { return [ diff --git a/src/Router/RouteGroup.php b/src/Router/RouteGroup.php index 3652669..a4ec579 100644 --- a/src/Router/RouteGroup.php +++ b/src/Router/RouteGroup.php @@ -2,13 +2,31 @@ namespace Atlas\Router; +/** + * Manages groupings of routes for prefix and middleware organization. + * + * @implements \IteratorAggregate + */ class RouteGroup { + /** + * Constructs a new RouteGroup instance. + * + * @param array $options Group options including 'prefix' and optional middleware + * @param Router|null $router Optional parent router instance + */ public function __construct( private array $options = [], - private readonly Router $router = null + private readonly Router|null $router = null ) {} + /** + * Creates a new route group with options and router. + * + * @param array $options Group options including 'prefix' and optional middleware + * @param Router $router Parent router instance + * @return self New instance configured with router + */ public static function create(array $options, Router $router): self { $self = new self($options); @@ -16,36 +34,82 @@ class RouteGroup return $self; } + /** + * Registers a GET route with group prefix. + * + * @param string $path URI path + * @param string|callable $handler Route handler + * @param string|null $name Optional route name + * @return self Fluent interface + */ public function get(string $path, string|callable $handler, string|null $name = null): self { $fullPath = $this->buildFullPath($path); return $this->router ? $this->router->get($fullPath, $handler, $name) : $this; } + /** + * Registers a POST route with group prefix. + * + * @param string $path URI path + * @param string|callable $handler Route handler + * @param string|null $name Optional route name + * @return self Fluent interface + */ public function post(string $path, string|callable $handler, string|null $name = null): self { $fullPath = $this->buildFullPath($path); return $this->router ? $this->router->post($fullPath, $handler, $name) : $this; } + /** + * Registers a PUT route with group prefix. + * + * @param string $path URI path + * @param string|callable $handler Route handler + * @param string|null $name Optional route name + * @return self Fluent interface + */ public function put(string $path, string|callable $handler, string|null $name = null): self { $fullPath = $this->buildFullPath($path); return $this->router ? $this->router->put($fullPath, $handler, $name) : $this; } + /** + * Registers a PATCH route with group prefix. + * + * @param string $path URI path + * @param string|callable $handler Route handler + * @param string|null $name Optional route name + * @return self Fluent interface + */ public function patch(string $path, string|callable $handler, string|null $name = null): self { $fullPath = $this->buildFullPath($path); return $this->router ? $this->router->patch($fullPath, $handler, $name) : $this; } + /** + * Registers a DELETE route with group prefix. + * + * @param string $path URI path + * @param string|callable $handler Route handler + * @param string|null $name Optional route name + * @return self Fluent interface + */ public function delete(string $path, string|callable $handler, string|null $name = null): self { $fullPath = $this->buildFullPath($path); return $this->router ? $this->router->delete($fullPath, $handler, $name) : $this; } + /** + * Builds the full path with group prefix. + * + * @param string $path Route path without prefix + * @return string Complete path with prefix + */ private function buildFullPath(string $path): string { $prefix = $this->options['prefix'] ?? ''; @@ -57,12 +121,24 @@ class RouteGroup return rtrim($prefix, '/') . '/' . ltrim($path, '/'); } + /** + * Sets an option value. + * + * @param string $key Option key + * @param mixed $value Option value + * @return self Fluent interface + */ public function setOption(string $key, mixed $value): self { $this->options[$key] = $value; return $this; } + /** + * Gets all group options. + * + * @return array Group options configuration + */ public function getOptions(): array { return $this->options; diff --git a/src/Router/Router.php b/src/Router/Router.php index 290b6bb..746ab57 100644 --- a/src/Router/Router.php +++ b/src/Router/Router.php @@ -3,48 +3,144 @@ namespace Atlas\Router; use Psr\Http\Message\ServerRequestInterface; +use Atlas\Config\Config; +use Atlas\Exception\MissingConfigurationException; +use Atlas\Exception\NotFoundRouteException; +use Atlas\Exception\RouteNotFoundException; -class Router +/** + * Main routing engine for the Atlas framework. + * + * Provides fluent, chainable API for route registration and request matching. + * Supports static and dynamic URIs, named routes, module routing, and error handling. + * + * @implements \IteratorAggregate + */ +class Router implements \IteratorAggregate { + /** + * Private array to store registered route definitions. + * + * @var array + */ private array $routes = []; + + /** + * Protected fallback handler for 404 scenarios. + * + * @var mixed mixed callable|string|null + */ protected mixed $fallbackHandler = null; + /** + * Constructs a new Router instance with configuration. + * + * @param Config\Config $config Configuration object containing routing settings + */ public function __construct( private readonly Config\Config $config ) { } + /** + * Registers a GET route. + * + * @param string $path URI path + * @param string|callable $handler Route handler or string reference + * @param string|null $name Optional route name for reverse routing + * @return self Fluent interface for method chaining + */ public function get(string $path, string|callable $handler, string|null $name = null): self { $this->registerRoute('GET', $path, $handler, $name); return $this; } + /** + * Registers a POST route. + * + * @param string $path URI path + * @param string|callable $handler Route handler or string reference + * @param string|null $name Optional route name for reverse routing + * @return self Fluent interface for method chaining + */ public function post(string $path, string|callable $handler, string|null $name = null): self { $this->registerRoute('POST', $path, $handler, $name); return $this; } + /** + * Registers a PUT route. + * + * @param string $path URI path + * @param string|callable $handler Route handler or string reference + * @param string|null $name Optional route name for reverse routing + * @return self Fluent interface for method chaining + */ public function put(string $path, string|callable $handler, string|null $name = null): self { $this->registerRoute('PUT', $path, $handler, $name); return $this; } + /** + * Registers a PATCH route. + * + * @param string $path URI path + * @param string|callable $handler Route handler or string reference + * @param string|null $name Optional route name for reverse routing + * @return self Fluent interface for method chaining + */ public function patch(string $path, string|callable $handler, string|null $name = null): self { $this->registerRoute('PATCH', $path, $handler, $name); return $this; } + /** + * Registers a DELETE route. + * + * @param string $path URI path + * @param string|callable $handler Route handler or string reference + * @param string|null $name Optional route name for reverse routing + * @return self Fluent interface for method chaining + */ public function delete(string $path, string|callable $handler, string|null $name = null): self { $this->registerRoute('DELETE', $path, $handler, $name); return $this; } - private function registerRoute(string $method, string $path, mixed $handler, string|null $name = null): void + /** + * Normalizes a path string. + * + * Removes leading and trailing slashes and ensures proper format. + * + * @param string $path Raw path string + * @return string Normalized path + */ + private function normalizePath(string $path): string + { + $normalized = trim($path, '/'); + + if (empty($normalized)) { + return '/'; + } + + return '/' . $normalized; + } + + /** + * Registers a route definition and stores it. + * + * @param string $method HTTP method + * @param string $path URI path + * @param mixed $middleware middleware (not yet implemented) + * @param string|callable $handler Route handler + * @param string|null $name Optional route name + */ + private function registerRoute(string $method, string $path, mixed $middleware, string|callable $handler, string|null $name = null): void { $routeDefinition = new RouteDefinition( $method, @@ -57,17 +153,11 @@ class Router $this->storeRoute($routeDefinition); } - private function normalizePath(string $path): string - { - $normalized = trim($path, '/'); - - if (empty($normalized)) { - return '/'; - } - - return '/' . $normalized; - } - + /** + * Stores a route definition for later matching. + * + * @param RouteDefinition $routeDefinition Route definition instance + */ protected function storeRoute(RouteDefinition $routeDefinition): void { // Routes will be managed by a route collection class (to be implemented) @@ -79,11 +169,24 @@ class Router $this->routes[] = $routeDefinition; } + /** + * Retrieves all registered route definitions. + * + * @return iterable All route definitions + */ public function getRoutes(): iterable { return $this->routes ?? []; } + /** + * Matches a request to registered routes. + * + * Returns null if no match is found. + * + * @param ServerRequestInterface $request PSR-7 request object + * @return RouteDefinition|null Matched route or null + */ public function match(ServerRequestInterface $request): RouteDefinition|null { $method = strtoupper($request->getMethod()); @@ -98,6 +201,13 @@ class Router return null; } + /** + * Matches a request and throws exception if no match found. + * + * @param ServerRequestInterface $request PSR-7 request object + * @return RouteDefinition Matched route definition + * @throws NotFoundRouteException If no route matches + */ public function matchOrFail(ServerRequestInterface $request): RouteDefinition { $method = strtoupper($request->getMethod()); @@ -112,12 +222,26 @@ class Router throw new NotFoundRouteException('No route matched the request'); } + /** + * Sets a fallback handler for unmatched requests. + * + * @param callable|string|null $handler Fallback handler + * @return self Fluent interface + */ public function fallback(mixed $handler): self { $this->fallbackHandler = $handler; return $this; } + /** + * Generates a URL for a named route with parameters. + * + * @param string $name Route name + * @param array $parameters Route parameters + * @return string Generated URL path + * @throws RouteNotFoundException If route name not found + */ public function url(string $name, array $parameters = []): string { $routes = $this->getRoutes(); @@ -140,6 +264,13 @@ class Router return $path; } + /** + * Replaces {{param}} placeholders in a path. + * + * @param string $path Path string with placeholders + * @param array $parameters Parameter values + * @return string Path with parameters replaced + */ private function replaceParameters(string $path, array $parameters): string { foreach ($parameters as $key => $value) { @@ -150,11 +281,24 @@ class Router return $path; } + /** + * Creates a new route group for nested routing. + * + * @param array $options Group options including prefix and middleware + * @return RouteGroup Route group instance + */ public function group(array $options): RouteGroup { return new RouteGroup($options, $this); } + /** + * Auto-discovers and registers routes from modules. + * + * @param string|array $identifier Module identifier or array containing identifier and options + * @return self Fluent interface + * @throws MissingConfigurationException If modules_path not configured + */ public function module(string|array $identifier): self { $identifier = is_string($identifier) ? [$identifier] : $identifier; @@ -181,6 +325,11 @@ class Router return $this; } + /** + * Loads and registers routes from a module routes file. + * + * @param string $routesFile Path to routes.php file + */ private function loadModuleRoutes(string $routesFile): void { $moduleRoutes = require $routesFile; @@ -198,4 +347,14 @@ class Router ); } } + + /** + * Creates an iterator over registered routes. + * + * @return \Traversable Array iterator over route collection + */ + public function getIterator(): \Traversable + { + return new \ArrayIterator($this->routes ?? []); + } } \ No newline at end of file