260 lines
6.5 KiB
PHP
260 lines
6.5 KiB
PHP
<?php
|
|
|
|
namespace CrocWork\Zappy\Routing;
|
|
|
|
class Route
|
|
{
|
|
/**
|
|
* @var string Name for the route. Multple routes can have the same name, linking them.
|
|
*/
|
|
private string $name;
|
|
|
|
/**
|
|
* @var string Pattern for this route.
|
|
*/
|
|
private string $pattern;
|
|
|
|
/**
|
|
* @var string|array|callable The callback to run when executing the route.
|
|
*/
|
|
private $callback;
|
|
|
|
/**
|
|
* @var array<string> The HTTP verbs applicable to this route.
|
|
*/
|
|
private array $methods = [];
|
|
|
|
/**
|
|
* @var array<string> A list of middlewares that should be run before executing this route.
|
|
*/
|
|
private array $middlewares = [];
|
|
|
|
/**
|
|
* @var array<string> 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), '/'), '/');
|
|
}
|
|
} |