Skip to content

Commit

Permalink
Refectored Awesomite\Chariot\Pattern\PatternRoute
Browse files Browse the repository at this point in the history
  • Loading branch information
bkrukowski committed Mar 23, 2018
1 parent 181413c commit abe0895
Showing 1 changed file with 110 additions and 97 deletions.
207 changes: 110 additions & 97 deletions src/Pattern/PatternRoute.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,133 +62,146 @@ public function __construct(string $pattern, PatternsInterface $patterns)
{
$this->pattern = $pattern;
$this->patterns = $patterns;
$this->compilePattern();
$this->extractParams();
$this->processPattern();
}

public function getRequiredParams(): array
{
return \array_keys($this->explodedParams);
}

private function compilePattern()
private function processPattern()
{
$originalPattern = $this->pattern;
$simplePattern = $this->pattern;

$toCompile = $this->pattern;
$compiledParts = [];
$explodedParams = [];

foreach ($this->processTokens() as list($token, $name, $pattern, $default, $patternName)) {
// simple pattern /item-{{id}}
$simplePattern = $this->replaceFirst($token, '{{' . $name . '}}', $simplePattern);

// compiled pattern #^/item-(?<id>([1-9][0-9]*)|0)$#"
$exploded = \explode($token, $toCompile, 2);
$compiledParts[] = \preg_quote($exploded[0], Patterns::DELIMITER);
$compiledParts[] = "(?<{$name}>{$pattern})";
$compiledParts[] = \preg_quote($exploded[1], Patterns::DELIMITER);

$explodedParams[$name] = [
$default,
Patterns::DELIMITER . '^(' . $pattern . ')$' . Patterns::DELIMITER,
$patternName
];
}

/** @var array $preProcessedParams [['{{ id \d+ }}', '(?<id>\d+)'], ...] */
$preProcessedParams = [];

\preg_replace_callback(
static::PATTERN_VAR,
function ($matches) use ($originalPattern, &$preProcessedParams) {
$str = \substr($matches[0], 2, -2);
$arr = \array_filter(\preg_split('/\\s+/', $str), function ($a) {
return '' !== \trim($a);
});
$arr = \array_values($arr);
switch (\count($arr)) {
case 1:
case 2:
case 3:
$name = $arr[0] ?? null;
$pattern = $arr[1] ?? null;

if (\is_null($pattern)) {
$pattern = $this->patterns->getDefaultPattern();
}
break;

default:
throw new InvalidArgumentException("Invalid url pattern {$originalPattern}");
}
if (0 === \count($compiledParts)) {
$simplePattern = $this->pattern;
$compiledParts[] = \preg_quote($this->pattern, Patterns::DELIMITER);
}
$d = Patterns::DELIMITER;
$compiledPattern = $d . '^' . \implode('', $compiledParts) . '$' . $d;

if (!\preg_match('/^[a-zA-Z0-9_]+$/', $name)) {
throw new InvalidArgumentException("Invalid param name “{$name}” (source: {$originalPattern})");
}
$this->simplePattern = $simplePattern;
$this->compiledPattern = $compiledPattern;
$this->explodedParams = $explodedParams;
}

Patterns::validatePatternName($name);
private function replaceFirst(string $search, string $replace, string $subject): string
{
$exploded = \explode($search, $subject, 2);

if ($patternObj = $this->patterns[$pattern] ?? null) {
/** @var PatternInterface $patternObj */
$pattern = $patternObj->getRegex();
}
return $exploded[0] . $replace . $exploded[1];
}

if (!(new RegexTester())->isSubregex($pattern)) {
throw new InvalidArgumentException("Invalid regex: {$pattern} (source: {$originalPattern})");
}
/**
* Validates, change pattern name to regex and add pattern's name or null
*
* e.g. ['{{ month :int }}', 'name', '(-?[1-9][0-9]*)|0', null, ':int']
*
* @return \Generator
*/
private function processTokens()
{
foreach ($this->getTokensStream() as list($string, $name, $pattern, $default)) {
if (!\preg_match('/^[a-zA-Z0-9_]+$/', $name)) {
throw new InvalidArgumentException("Invalid param name “{$name}” (source: {$this->pattern})");
}

$preProcessedParams[] = [$matches[0], "(?<{$name}>{$pattern})"];
Patterns::validatePatternName($name);

return $matches[0];
},
$this->pattern
);
$patternName = null;
if ($patternObj = $this->patterns[$pattern] ?? null) {
$patternName = $pattern;
/** @var PatternInterface $patternObj */
$pattern = $patternObj->getRegex();
}

$uriPattern = $this->pattern;
$resultParts = [];
if (!(new RegexTester())->isSubregex($pattern)) {
throw new InvalidArgumentException("Invalid regex: {$pattern} (source: {$this->pattern})");
}

foreach ($preProcessedParams as list($original, $replacement)) {
$exploded = \explode($original, $uriPattern, 2);
$resultParts[] = \preg_quote($exploded[0], Patterns::DELIMITER);
$resultParts[] = $replacement;
$uriPattern = $exploded[1];
}
if ('' !== $uriPattern) {
$resultParts[] = \preg_quote($uriPattern, Patterns::DELIMITER);
yield [
$string,
$name,
$pattern,
$default,
$patternName,
];
}

$d = Patterns::DELIMITER;
$this->compiledPattern = $d . '^' . \implode('', $resultParts) . '$' . $d;
}

private function extractParams()
/**
* e.g. [
* // [text, name, pattern, default]
* ['{{ month :int }}', 'month', ':int', null],
* ]
*
* @return array
*/
private function getTokensStream(): array
{
$params = &$this->explodedParams;
$params = [];
$inputPattern = $this->pattern;
$this->simplePattern = \preg_replace_callback(
$preProcessed = [];
\preg_replace_callback(
static::PATTERN_VAR,
function ($matches) use (&$params, $inputPattern) {
$str = \substr($matches[0], 2, -2);
$arr = \array_filter(\preg_split('/\\s+/', $str), function ($a) {
return '' !== \trim($a);
});
$arr = \array_values($arr);
switch (\count($arr)) {
case 1:
case 2:
case 3:
$name = $arr[0] ?? null;
$pattern = $arr[1] ?? null;
$default = $arr[2] ?? null;
if (\is_null($pattern)) {
$pattern = $this->patterns->getDefaultPattern();
}
break;

default:
// @codeCoverageIgnoreStart
throw new InvalidArgumentException("Invalid url pattern {$inputPattern}");
// @codeCoverageIgnoreEnd
}
function ($matches) use (&$preProcessed) {
$arr = $this->paramStrToArr($matches[0]);

$patternName = null;
if ($patternObj = $this->patterns[$pattern] ?? null) {
$patternName = $pattern;
/** @var PatternInterface $patternObj */
$pattern = $patternObj->getRegex();
if (\count($arr) > 3) {
throw new InvalidArgumentException("Invalid url pattern {$this->pattern}");
}

$params[$name] = [
$default,
Patterns::DELIMITER . '^(' . $pattern . ')$' . Patterns::DELIMITER,
$patternName,
];
$name = $arr[0];
$pattern = $arr[1] ?? $this->patterns->getDefaultPattern();
$default = $arr[2] ?? null;

return '{{' . $name . '}}';
$preProcessed[] = [
$matches[0],
$name,
$pattern,
$default
];
},
$this->pattern
);

return $preProcessed;
}

/**
* @param string $paramString e.g. {{ id :int }}
*
* @return string[] e.g. ['id', 'int']
*/
private function paramStrToArr(string $paramString)
{
$str = \substr($paramString, 2, -2);
$result = \array_filter(\preg_split('/\\s+/', $str), function ($a) {
return '' !== \trim($a);
});

return \array_values($result);
}

public function match(string $path, &$params): bool
Expand Down

0 comments on commit abe0895

Please sign in to comment.