The HTTP verbs applicable to this route. */ private array $methods = []; /** * @var array A list of middlewares that should be run before executing this route. */ private array $middlewares = []; /** * @var array List of variables taken from the pattern. These are used to pass parameters to the callback function. */ private array $vars = []; /** * Route constructor. * * The constructor will initialize a Route object with a pattern, list of applicable HTTP methods and the callback * to run for this route. Optionally a list of middlewares to run can also be passed (but can also be set later). */ public function __construct(string $pattern, string|array $methods, string|array|callable $callback, array $middlewares = []) { if ($methods === []) { throw new \InvalidArgumentException('HTTP methods argument was empty; must contain at least one method'); } if(is_string($methods)) $methods = [$methods]; foreach($methods as $key => $method) { $methods[$key] = strtoupper($method); if (!in_array($methods[$key], ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH', 'HEAD'])) { throw new \InvalidArgumentException("Invalid method '{$method}' specified."); } } $this->pattern = $pattern; $this->callback = $callback; $this->methods = $methods; $this->middlewares = $middlewares; } /** * Get the name for the route. * * @return string */ public function getName(): string { return $this->name; } /** * Set the name for the route. * * @param string $name * @return $this */ public function setName(string $name): static { $this->name = $name; return $this; } /** * Get the url pattern for the route. * * @return string */ public function getPattern(): string { return $this->pattern; } public function setPattern(string $pattern): static { $this->pattern = $pattern; return $this; } /** * Get the callback function for this route. * * @return string|array|callable */ public function getCallback(): string|array|callable { return $this->callback; } /** * Set the callback function for this route. * * @param string|array|callable $callback * @return $this */ public function setCallback(string|array|callable $callback): static { $this->callback = $callback; return $this; } /** * Get the HTTP methods applicable to this route. * * @return string[] */ public function getMethods(): array { return $this->methods; } /** * Set the HTTP methods applicable to this route. * * @param string|string[] $methods * @return $this */ public function setMethods(string|array $methods): static { if(is_string($methods)) { $methods = [$methods]; } $this->methods = $methods; return $this; } /** * Get the list of middlewares to be run for this route. * * @return string[] */ public function getMiddlewares(): array { return $this->middlewares; } public function setMiddlewares(array $middlewares): static { $this->middlewares = $middlewares; return $this; } public function addMiddleware(string|array $middleware): static { if(is_string($middleware)) { $middleware = [$middleware]; } foreach($middleware as $value) { if(!in_array($value, $this->middlewares)) { $this->middlewares[] = $value; } } return $this; } /** * @return array */ public function getVarsNames(): array { preg_match_all('/{[^}]*}/', $this->pattern, $matches); return reset($matches) ?? []; } /** * @return bool */ public function hasVars(): bool { return $this->getVarsNames() !== []; } /** * @return string[] */ public function getVars(): array { return $this->vars; } /** * @param string $path * @param string $method * @return bool */ public function match(string $path, string $method): bool { $regex = $this->getPattern(); foreach ($this->getVarsNames() as $variable) { $varName = trim($variable, '{\}'); $regex = str_replace($variable, '(?P<' . $varName . '>[^/]++)', $regex); } if (in_array($method, $this->getMethods()) && preg_match('#^' . $regex . '$#sD', self::trimPath($path), $matches)) { $values = array_filter($matches, static function ($key) { return is_string($key); }, ARRAY_FILTER_USE_KEY); foreach ($values as $key => $value) { $this->vars[$key] = $value; } return true; } return false; } /** * @return mixed */ public function run() { //TODO: middlewares //TODO: allow for automatically injecting dependencies for function arguments if(is_callable($this->callback)) { return call_user_func_array($this->callback, $this->vars); } elseif (is_array($this->callback) && [0,1] == array_keys($this->callback)) { $class = $this->callback[0]; $method = $this->callback[1]; if(class_exists($class) && method_exists($class, $method)) { $controller = new $class(); return $controller->$method(...$this->vars); } } throw new \RuntimeException("Could not resolve callable function for route."); } /** * @param string $path * @return string */ protected static function trimPath(string $path): string { return '/' . rtrim(ltrim(trim($path), '/'), '/'); } }