176 lines
4.9 KiB
PHP
176 lines
4.9 KiB
PHP
<?php
|
|
|
|
namespace WellRESTed\Routing\Route;
|
|
|
|
/**
|
|
* @internal
|
|
*/
|
|
class TemplateRoute extends Route
|
|
{
|
|
/** @var array */
|
|
private $pathVariables = [];
|
|
/** @var array */
|
|
private $explosions = [];
|
|
|
|
/** Regular expression matching a URI template variable (e.g., {id}) */
|
|
public const URI_TEMPLATE_EXPRESSION_RE = '/{([+.\/]?[a-zA-Z0-9_,]+\*?)}/';
|
|
/**
|
|
* Regular expression matching 1 or more unreserved characters.
|
|
* ALPHA / DIGIT / "-" / "." / "_" / "~"
|
|
*/
|
|
private const RE_UNRESERVED = '[0-9a-zA-Z\-._\~%]*';
|
|
|
|
public function getType(): int
|
|
{
|
|
return RouteInterface::TYPE_PATTERN;
|
|
}
|
|
|
|
public function getPathVariables(): array
|
|
{
|
|
return $this->pathVariables;
|
|
}
|
|
|
|
/**
|
|
* Examines a request target to see if it is a match for the route.
|
|
*
|
|
* @param string $requestTarget
|
|
* @return bool
|
|
*/
|
|
public function matchesRequestTarget(string $requestTarget): bool
|
|
{
|
|
$this->pathVariables = [];
|
|
$this->explosions = [];
|
|
|
|
if (!$this->matchesStartOfRequestTarget($requestTarget)) {
|
|
return false;
|
|
}
|
|
|
|
$matchingPattern = $this->getMatchingPattern();
|
|
|
|
if (preg_match($matchingPattern, $requestTarget, $captures)) {
|
|
$this->pathVariables = $this->processMatches($captures);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private function matchesStartOfRequestTarget(string $requestTarget): bool
|
|
{
|
|
$firstVarPos = strpos($this->target, '{');
|
|
if ($firstVarPos === false) {
|
|
return $requestTarget === $this->target;
|
|
}
|
|
return substr($requestTarget, 0, $firstVarPos) === substr($this->target, 0, $firstVarPos);
|
|
}
|
|
|
|
private function processMatches(array $matches): array
|
|
{
|
|
$variables = [];
|
|
|
|
// Isolate the named captures.
|
|
$keys = array_filter(array_keys($matches), 'is_string');
|
|
|
|
// Store named captures to the variables.
|
|
foreach ($keys as $key) {
|
|
$value = $matches[$key];
|
|
|
|
if (isset($this->explosions[$key])) {
|
|
$values = explode($this->explosions[$key], $value);
|
|
$variables[$key] = array_map('urldecode', $values);
|
|
} else {
|
|
$value = urldecode($value);
|
|
$variables[$key] = $value;
|
|
}
|
|
}
|
|
|
|
return $variables;
|
|
}
|
|
|
|
private function getMatchingPattern(): string
|
|
{
|
|
// Convert the template into the pattern
|
|
$pattern = $this->target;
|
|
|
|
// Escape allowable characters with regex meaning.
|
|
$escape = [
|
|
'.' => '\\.',
|
|
'-' => '\\-',
|
|
'+' => '\\+',
|
|
'*' => '\\*'
|
|
];
|
|
$pattern = str_replace(array_keys($escape), array_values($escape), $pattern);
|
|
$unescape = [
|
|
'{\\+' => '{+',
|
|
'{\\.' => '{.',
|
|
'\\*}' => '*}'
|
|
];
|
|
$pattern = str_replace(array_keys($unescape), array_values($unescape), $pattern);
|
|
|
|
// Surround the pattern with delimiters.
|
|
$pattern = "~^{$pattern}$~";
|
|
|
|
$pattern = preg_replace_callback(
|
|
self::URI_TEMPLATE_EXPRESSION_RE,
|
|
[$this, 'uriVariableReplacementCallback'],
|
|
$pattern
|
|
);
|
|
|
|
return $pattern;
|
|
}
|
|
|
|
private function uriVariableReplacementCallback(array $matches): string
|
|
{
|
|
$name = $matches[1];
|
|
$pattern = self::RE_UNRESERVED;
|
|
|
|
$prefix = '';
|
|
$delimiter = ',';
|
|
$explodeDelimiter = ',';
|
|
|
|
// Read the first character as an operator. This determines which
|
|
// characters to allow in the match.
|
|
$operator = $name[0];
|
|
|
|
// Read the last character as the modifier.
|
|
$explosion = (substr($name, -1, 1) === '*');
|
|
|
|
switch ($operator) {
|
|
case '+':
|
|
$name = substr($name, 1);
|
|
$pattern = '.*';
|
|
break;
|
|
case '.':
|
|
$name = substr($name, 1);
|
|
$prefix = '\\.';
|
|
$delimiter = '\\.';
|
|
$explodeDelimiter = '.';
|
|
break;
|
|
case '/':
|
|
$name = substr($name, 1);
|
|
$prefix = '\\/';
|
|
$delimiter = '\\/';
|
|
if ($explosion) {
|
|
$pattern = '[0-9a-zA-Z\-._\~%,\/]*'; // Unreserved + "," and "/"
|
|
$explodeDelimiter = '/';
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Explosion
|
|
if ($explosion) {
|
|
$name = substr($name, 0, -1);
|
|
if ($pattern === self::RE_UNRESERVED) {
|
|
$pattern = '[0-9a-zA-Z\-._\~%,]*'; // Unreserved + ","
|
|
}
|
|
$this->explosions[$name] = $explodeDelimiter;
|
|
}
|
|
|
|
$names = explode(',', $name);
|
|
$results = [];
|
|
foreach ($names as $name) {
|
|
$results[] = "(?<{$name}>{$pattern})";
|
|
}
|
|
return $prefix . join($delimiter, $results);
|
|
}
|
|
}
|