zappy-base/src/Routing/Route.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), '/'), '/');
}
}