18 private static $operatorHash = [
19 '' => [
'prefix' =>
'',
'joiner' =>
',',
'query' =>
false],
20 '+' => [
'prefix' =>
'',
'joiner' =>
',',
'query' =>
false],
21 '#' => [
'prefix' =>
'#',
'joiner' =>
',',
'query' =>
false],
22 '.' => [
'prefix' =>
'.',
'joiner' =>
'.',
'query' =>
false],
23 '/' => [
'prefix' =>
'/',
'joiner' =>
'/',
'query' =>
false],
24 ';' => [
'prefix' =>
';',
'joiner' =>
';',
'query' =>
true],
25 '?' => [
'prefix' =>
'?',
'joiner' =>
'&',
'query' =>
true],
26 '&' => [
'prefix' =>
'&',
'joiner' =>
'&',
'query' =>
true]
30 private static $delims = [
':',
'/',
'?',
'#',
'[',
']',
'@',
'!',
'$',
31 '&',
'\'',
'(',
')',
'*',
'+',
',',
';',
'='];
34 private static $delimsPct = [
'%3A',
'%2F',
'%3F',
'%23',
'%5B',
'%5D',
35 '%40',
'%21',
'%24',
'%26',
'%27',
'%28',
'%29',
'%2A',
'%2B',
'%2C',
38 public function expand($template, array $variables)
40 if (
false === strpos($template,
'{')) {
44 $this->
template = $template;
45 $this->variables = $variables;
47 return preg_replace_callback(
49 [$this,
'expandMatch'],
61 private function parseExpression($expression)
65 if (isset(self::$operatorHash[$expression[0]])) {
66 $result[
'operator'] = $expression[0];
67 $expression = substr($expression, 1);
69 $result[
'operator'] =
'';
72 foreach (explode(
',', $expression) as $value) {
73 $value = trim($value);
75 if ($colonPos = strpos($value,
':')) {
76 $varspec[
'value'] = substr($value, 0, $colonPos);
77 $varspec[
'modifier'] =
':';
78 $varspec[
'position'] = (int) substr($value, $colonPos + 1);
79 } elseif (substr($value, -1) ===
'*') {
80 $varspec[
'modifier'] =
'*';
81 $varspec[
'value'] = substr($value, 0, -1);
83 $varspec[
'value'] = (string) $value;
84 $varspec[
'modifier'] =
'';
86 $result[
'values'][] = $varspec;
99 private function expandMatch(array $matches)
101 static $rfc1738to3986 = [
'+' =>
'%20',
'%7e' =>
'~'];
104 $parsed = self::parseExpression($matches[1]);
105 $prefix = self::$operatorHash[$parsed[
'operator']][
'prefix'];
106 $joiner = self::$operatorHash[$parsed[
'operator']][
'joiner'];
107 $useQuery = self::$operatorHash[$parsed[
'operator']][
'query'];
109 foreach ($parsed[
'values'] as $value) {
110 if (!isset($this->variables[$value[
'value']])) {
114 $variable = $this->variables[$value[
'value']];
115 $actuallyUseQuery = $useQuery;
118 if (is_array($variable)) {
119 $isAssoc = $this->isAssoc($variable);
121 foreach ($variable as $key => $var) {
123 $key = rawurlencode($key);
124 $isNestedArray = is_array($var);
126 $isNestedArray =
false;
129 if (!$isNestedArray) {
130 $var = rawurlencode($var);
131 if ($parsed[
'operator'] ===
'+' ||
132 $parsed[
'operator'] ===
'#'
134 $var = $this->decodeReserved($var);
138 if ($value[
'modifier'] ===
'*') {
140 if ($isNestedArray) {
144 http_build_query([$key => $var]),
148 $var = $key .
'=' . $var;
150 } elseif ($key > 0 && $actuallyUseQuery) {
151 $var = $value[
'value'] .
'=' . $var;
158 if (empty($variable)) {
159 $actuallyUseQuery =
false;
160 } elseif ($value[
'modifier'] ===
'*') {
161 $expanded = implode($joiner, $kvp);
165 $actuallyUseQuery =
false;
173 foreach ($kvp as $k => &$v) {
177 $expanded = implode(
',', $kvp);
180 if ($value[
'modifier'] ===
':') {
181 $variable = substr($variable, 0, $value[
'position']);
183 $expanded = rawurlencode($variable);
184 if ($parsed[
'operator'] ===
'+' || $parsed[
'operator'] ===
'#') {
185 $expanded = $this->decodeReserved($expanded);
189 if ($actuallyUseQuery) {
190 if (!$expanded && $joiner !==
'&') {
191 $expanded = $value[
'value'];
193 $expanded = $value[
'value'] .
'=' . $expanded;
197 $replacements[] = $expanded;
200 $ret = implode($joiner, $replacements);
201 if ($ret && $prefix) {
202 return $prefix . $ret;
220 private function isAssoc(array $array)
222 return $array && array_keys($array)[0] !== 0;
233 private function decodeReserved($string)
235 return str_replace(self::$delimsPct, self::$delims, $string);