*/ private array $methods = []; /** * @var array */ private array $middlewares = []; /** * @var array */ private array $vars = []; /** * Route constructor. */ 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; } /** * @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 string */ public function getName(): string { return $this->name; } /** * @param string $name * @return $this */ public function setName(string $name): static { $this->name = $name; return $this; } /** * @return string */ public function getPattern(): string { return $this->pattern; } /** * @return string|array|callable */ public function getCallback(): string|array|callable { return $this->callback; } /** * @return array|string[] */ public function getMethods(): array { return $this->methods; } /** * @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; } /** * @return string[] */ public function getMiddlewares(): array { return $this->middlewares; } /** * @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 */ public static function trimPath(string $path): string { return '/' . rtrim(ltrim(trim($path), '/'), '/'); } }