src/Router.php
final public static function path_parser(string $path) {
		# path must start with slash
		if ($path[0] != '/') {
			self::throw_error(
				"Router: path invalid in '$path'.");
		}
		# ignore trailing slash
		if ($path != '/')
			$path = rtrim($path, '/');

		# allowed characters in path
		$valid_chars = 'a-zA-Z0-9\_\.\-@%:';
		# param left delimiter
		$elf = '<\{';
		# param right delimiter
		$erg = '>\}';
		# param delimiter
		$delims = "\/${elf}${erg}";
		# non-delimiter
		$non_delims = "[^${delims}]";
		# valid param key
		$valid_key = "[a-z][a-z0-9\_]*";

		if (!preg_match("!^[${valid_chars}${delims}]+$!", $path)) {
			# invalid characters
			self::throw_error(
				"Router: invalid characters in path: '$path'.");
		}

		if (
			preg_match("!${non_delims}[${elf}]!", $path) ||
			preg_match("![${erg}]${non_delims}!", $path)
		) {
			# invalid dynamic path pattern
			self::throw_error(
				"Router: dynamic path not well-formed: '$path'.");
		}

		preg_match_all("!/([$elf][^$erg]+[$erg])!", $path, $tokens,
			PREG_OFFSET_CAPTURE);

		$keys = $symbols = [];
		foreach ($tokens[1] as $token) {

			$key = str_replace(['{','}','<','>'], '', $token[0]);
			if (!preg_match("!^${valid_key}\$!i", $key)) {
				# invalid param key
				self::throw_error(
					"Router: invalid param key: '$path'.");
			}

			$keys[] = $key;
			$replacement = $valid_chars;
			if (strpos($token[0], '{') !== false)
				$replacement .= '/';
			$replacement = '([' . $replacement . ']+)';
			$symbols[] = [$replacement, $token[1], strlen($token[0])];
		}

		if (count($keys) > count(array_unique($keys))) {
			# never allow key reuse to prevent unexpected overrides
			self::throw_error("Router: param key reused: '$path'.");
		}

		# construct regex pattern for all capturing keys
		$idx = 0;
		$pattern = '';
		while ($idx < strlen($path)) {
			$matched = false;
			foreach ($symbols as $symbol) {
				if ($idx < $symbol[1])
					continue;
				if ($idx == $symbol[1]) {
					$matched = true;
					$pattern .= $symbol[0];
					$idx++;
					$idx += $symbol[2] - 1;
				}
			}
			if (!$matched) {
				$pattern .= $path[$idx];
				$idx++;
			}
		}

		return [$pattern, $keys];
	}
final public static function path_parser(string $path) {
		# path must start with slash
		if ($path[0] != '/') {
			self::throw_error(
				"Router: path invalid in '$path'.");
		}
		# ignore trailing slash
		if ($path != '/')
			$path = rtrim($path, '/');

		# allowed characters in path
		$valid_chars = 'a-zA-Z0-9\_\.\-@%:';
		# param left delimiter
		$elf = '<\{';
		# param right delimiter
		$erg = '>\}';
		# param delimiter
		$delims = "\/${elf}${erg}";
		# non-delimiter
		$non_delims = "[^${delims}]";
		# valid param key
		$valid_key = "[a-z][a-z0-9\_]*";

		if (!preg_match("!^[${valid_chars}${delims}]+$!", $path)) {
			# invalid characters
			self::throw_error(
				"Router: invalid characters in path: '$path'.");
		}

		if (
			preg_match("!${non_delims}[${elf}]!", $path) ||
			preg_match("![${erg}]${non_delims}!", $path)
		) {
			# invalid dynamic path pattern
			self::throw_error(
				"Router: dynamic path not well-formed: '$path'.");
		}

		preg_match_all("!/([$elf][^$erg]+[$erg])!", $path, $tokens,
			PREG_OFFSET_CAPTURE);

		$keys = $symbols = [];
		foreach ($tokens[1] as $token) {

			$key = str_replace(['{','}','<','>'], '', $token[0]);
			if (!preg_match("!^${valid_key}\$!i", $key)) {
				# invalid param key
				self::throw_error(
					"Router: invalid param key: '$path'.");
			}

			$keys[] = $key;
			$replacement = $valid_chars;
			if (strpos($token[0], '{') !== false)
				$replacement .= '/';
			$replacement = '([' . $replacement . ']+)';
			$symbols[] = [$replacement, $token[1], strlen($token[0])];
		}

		if (count($keys) > count(array_unique($keys))) {
			# never allow key reuse to prevent unexpected overrides
			self::throw_error("Router: param key reused: '$path'.");
		}

		# construct regex pattern for all capturing keys
		$idx = 0;
		$pattern = '';
		while ($idx < strlen($path)) {
			$matched = false;
			foreach ($symbols as $symbol) {
				if ($idx < $symbol[1])
					continue;
				if ($idx == $symbol[1]) {
					$matched = true;
					$pattern .= $symbol[0];
					$idx++;
					$idx += $symbol[2] - 1;
				}
			}
			if (!$matched) {
				$pattern .= $path[$idx];
				$idx++;
			}
		}

		return [$pattern, $keys];
	}
final public static function path_parser ( string $ path ) { # path must start with slash if ( $ path [ 0 ] != '/' ) { self :: throw_error ( "Router: path invalid in '$path'." ) ; } # ignore trailing slash if ( $ path != '/' ) $ path = rtrim ( $ path , '/' ) ; # allowed characters in path $ valid_chars = 'a-zA-Z0-9\_\.\-@%:' ; # param left delimiter $ elf = '<\{' ; # param right delimiter $ erg = '>\}' ; # param delimiter $ delims = "\/${elf}${erg}" ; # non-delimiter $ non_delims = "[^${delims}]" ; # valid param key $ valid_key = "[a-z][a-z0-9\_]*" ; if ( ! preg_match ( "!^[${valid_chars}${delims}]+$!" , $ path ) ) { # invalid characters self :: throw_error ( "Router: invalid characters in path: '$path'." ) ; } if ( preg_match ( "!${non_delims}[${elf}]!" , $ path ) || preg_match ( "![${erg}]${non_delims}!" , $ path ) ) { # invalid dynamic path pattern self :: throw_error ( "Router: dynamic path not well-formed: '$path'." ) ; } preg_match_all ( "!/([$elf][^$erg]+[$erg])!" , $ path , $ tokens , PREG_OFFSET_CAPTURE ) ; $ keys = $ symbols = [ ] ; foreach ( $ tokens [ 1 ] as $ token ) { $ key = str_replace ( [ '{' , '}' , '<' , '>' ] , '' , $ token [ 0 ] ) ; if ( ! preg_match ( "!^${valid_key}\$!i" , $ key ) ) { # invalid param key self :: throw_error ( "Router: invalid param key: '$path'." ) ; } $ keys [ ] = $ key ; $ replacement = $ valid_chars ; if ( strpos ( $ token [ 0 ] , '{' ) !== false ) $ replacement .= '/' ; $ replacement = '([' . $ replacement . ']+)' ; $ symbols [ ] = [ $ replacement , $ token [ 1 ] , strlen ( $ token [ 0 ] ) ] ; } if ( count ( $ keys ) > count ( array_unique ( $ keys ) ) ) { # never allow key reuse to prevent unexpected overrides self :: throw_error ( "Router: param key reused: '$path'." ) ; } # construct regex pattern for all capturing keys $ idx = 0 ; $ pattern = '' ; while ( $ idx < strlen ( $ path ) ) { $ matched = false ; foreach ( $ symbols as $ symbol ) { if ( $ idx < $ symbol [ 1 ] ) continue ; if ( $ idx == $ symbol [ 1 ] ) { $ matched = true ; $ pattern .= $ symbol [ 0 ] ; $ idx ++ ; $ idx += $ symbol [ 2 ] - 1 ; } } if ( ! $ matched ) { $ pattern .= $ path [ $ idx ] ; $ idx ++ ; } } return [ $ pattern , $ keys ] ; }
Path parser.

This parses route path and returns arrays that will parse
request path.

@param string $path Route path with special enclosing
characters:
- `< >` for dynamic URL parameter without `/`
- `{ }` for dynamic URL parameter with `/`
@return array A duple with values:
- a regular expression to match against request path
- an array containing keys that will be used to create
dynamic variables with whatever matches the previous
regex
@see Router::route() for usage.

@if TRUE
@SuppressWarnings(PHPMD.UnusedLocalVariable)
@SuppressWarnings(PHPMD.CyclomaticComplexity)
@SuppressWarnings(PHPMD.NPathComplexity)
@endif
Path parser .