subcon.town/libs/zap/Routing/Route.php

190 lines
4.3 KiB
PHP

<?php
namespace Subcon\Zap\Routing;
class Route
{
/**
* @var string
*/
private string $name;
/**
* @var string
*/
private string $pattern;
/**
* @var string|array|callable
*/
private $callback;
/**
* @var array<string>
*/
private array $methods = [];
/**
* @var array<string>
*/
private array $middlewares = [];
/**
* @var array<string>
*/
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), '/'), '/');
}
}