scratch – Blame information for rev 87
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
87 | office | 1 | <?php |
2 | |||
3 | namespace GuzzleHttp; |
||
4 | |||
5 | /** |
||
6 | * Expands URI templates. Userland implementation of PECL uri_template. |
||
7 | * |
||
8 | * @link http://tools.ietf.org/html/rfc6570 |
||
9 | */ |
||
10 | class UriTemplate |
||
11 | { |
||
12 | /** @var string URI template */ |
||
13 | private $template; |
||
14 | |||
15 | /** @var array Variables to use in the template expansion */ |
||
16 | private $variables; |
||
17 | |||
18 | /** @var array Hash for quick operator lookups */ |
||
19 | private static $operatorHash = array( |
||
20 | '' => array('prefix' => '', 'joiner' => ',', 'query' => false), |
||
21 | '+' => array('prefix' => '', 'joiner' => ',', 'query' => false), |
||
22 | '#' => array('prefix' => '#', 'joiner' => ',', 'query' => false), |
||
23 | '.' => array('prefix' => '.', 'joiner' => '.', 'query' => false), |
||
24 | '/' => array('prefix' => '/', 'joiner' => '/', 'query' => false), |
||
25 | ';' => array('prefix' => ';', 'joiner' => ';', 'query' => true), |
||
26 | '?' => array('prefix' => '?', 'joiner' => '&', 'query' => true), |
||
27 | '&' => array('prefix' => '&', 'joiner' => '&', 'query' => true) |
||
28 | ); |
||
29 | |||
30 | /** @var array Delimiters */ |
||
31 | private static $delims = array(':', '/', '?', '#', '[', ']', '@', '!', '$', |
||
32 | '&', '\'', '(', ')', '*', '+', ',', ';', '='); |
||
33 | |||
34 | /** @var array Percent encoded delimiters */ |
||
35 | private static $delimsPct = array('%3A', '%2F', '%3F', '%23', '%5B', '%5D', |
||
36 | '%40', '%21', '%24', '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C', |
||
37 | '%3B', '%3D'); |
||
38 | |||
39 | public function expand($template, array $variables) |
||
40 | { |
||
41 | if (false === strpos($template, '{')) { |
||
42 | return $template; |
||
43 | } |
||
44 | |||
45 | $this->template = $template; |
||
46 | $this->variables = $variables; |
||
47 | |||
48 | return preg_replace_callback( |
||
49 | '/\{([^\}]+)\}/', |
||
50 | [$this, 'expandMatch'], |
||
51 | $this->template |
||
52 | ); |
||
53 | } |
||
54 | |||
55 | /** |
||
56 | * Parse an expression into parts |
||
57 | * |
||
58 | * @param string $expression Expression to parse |
||
59 | * |
||
60 | * @return array Returns an associative array of parts |
||
61 | */ |
||
62 | private function parseExpression($expression) |
||
63 | { |
||
64 | $result = array(); |
||
65 | |||
66 | if (isset(self::$operatorHash[$expression[0]])) { |
||
67 | $result['operator'] = $expression[0]; |
||
68 | $expression = substr($expression, 1); |
||
69 | } else { |
||
70 | $result['operator'] = ''; |
||
71 | } |
||
72 | |||
73 | foreach (explode(',', $expression) as $value) { |
||
74 | $value = trim($value); |
||
75 | $varspec = array(); |
||
76 | if ($colonPos = strpos($value, ':')) { |
||
77 | $varspec['value'] = substr($value, 0, $colonPos); |
||
78 | $varspec['modifier'] = ':'; |
||
79 | $varspec['position'] = (int) substr($value, $colonPos + 1); |
||
80 | } elseif (substr($value, -1) == '*') { |
||
81 | $varspec['modifier'] = '*'; |
||
82 | $varspec['value'] = substr($value, 0, -1); |
||
83 | } else { |
||
84 | $varspec['value'] = (string) $value; |
||
85 | $varspec['modifier'] = ''; |
||
86 | } |
||
87 | $result['values'][] = $varspec; |
||
88 | } |
||
89 | |||
90 | return $result; |
||
91 | } |
||
92 | |||
93 | /** |
||
94 | * Process an expansion |
||
95 | * |
||
96 | * @param array $matches Matches met in the preg_replace_callback |
||
97 | * |
||
98 | * @return string Returns the replacement string |
||
99 | */ |
||
100 | private function expandMatch(array $matches) |
||
101 | { |
||
102 | static $rfc1738to3986 = array('+' => '%20', '%7e' => '~'); |
||
103 | |||
104 | $replacements = array(); |
||
105 | $parsed = self::parseExpression($matches[1]); |
||
106 | $prefix = self::$operatorHash[$parsed['operator']]['prefix']; |
||
107 | $joiner = self::$operatorHash[$parsed['operator']]['joiner']; |
||
108 | $useQuery = self::$operatorHash[$parsed['operator']]['query']; |
||
109 | |||
110 | foreach ($parsed['values'] as $value) { |
||
111 | |||
112 | if (!isset($this->variables[$value['value']])) { |
||
113 | continue; |
||
114 | } |
||
115 | |||
116 | $variable = $this->variables[$value['value']]; |
||
117 | $actuallyUseQuery = $useQuery; |
||
118 | $expanded = ''; |
||
119 | |||
120 | if (is_array($variable)) { |
||
121 | |||
122 | $isAssoc = $this->isAssoc($variable); |
||
123 | $kvp = array(); |
||
124 | foreach ($variable as $key => $var) { |
||
125 | |||
126 | if ($isAssoc) { |
||
127 | $key = rawurlencode($key); |
||
128 | $isNestedArray = is_array($var); |
||
129 | } else { |
||
130 | $isNestedArray = false; |
||
131 | } |
||
132 | |||
133 | if (!$isNestedArray) { |
||
134 | $var = rawurlencode($var); |
||
135 | if ($parsed['operator'] == '+' || |
||
136 | $parsed['operator'] == '#' |
||
137 | ) { |
||
138 | $var = $this->decodeReserved($var); |
||
139 | } |
||
140 | } |
||
141 | |||
142 | if ($value['modifier'] == '*') { |
||
143 | if ($isAssoc) { |
||
144 | if ($isNestedArray) { |
||
145 | // Nested arrays must allow for deeply nested |
||
146 | // structures. |
||
147 | $var = strtr( |
||
148 | http_build_query([$key => $var]), |
||
149 | $rfc1738to3986 |
||
150 | ); |
||
151 | } else { |
||
152 | $var = $key . '=' . $var; |
||
153 | } |
||
154 | } elseif ($key > 0 && $actuallyUseQuery) { |
||
155 | $var = $value['value'] . '=' . $var; |
||
156 | } |
||
157 | } |
||
158 | |||
159 | $kvp[$key] = $var; |
||
160 | } |
||
161 | |||
162 | if (empty($variable)) { |
||
163 | $actuallyUseQuery = false; |
||
164 | } elseif ($value['modifier'] == '*') { |
||
165 | $expanded = implode($joiner, $kvp); |
||
166 | if ($isAssoc) { |
||
167 | // Don't prepend the value name when using the explode |
||
168 | // modifier with an associative array. |
||
169 | $actuallyUseQuery = false; |
||
170 | } |
||
171 | } else { |
||
172 | if ($isAssoc) { |
||
173 | // When an associative array is encountered and the |
||
174 | // explode modifier is not set, then the result must be |
||
175 | // a comma separated list of keys followed by their |
||
176 | // respective values. |
||
177 | foreach ($kvp as $k => &$v) { |
||
178 | $v = $k . ',' . $v; |
||
179 | } |
||
180 | } |
||
181 | $expanded = implode(',', $kvp); |
||
182 | } |
||
183 | |||
184 | } else { |
||
185 | if ($value['modifier'] == ':') { |
||
186 | $variable = substr($variable, 0, $value['position']); |
||
187 | } |
||
188 | $expanded = rawurlencode($variable); |
||
189 | if ($parsed['operator'] == '+' || $parsed['operator'] == '#') { |
||
190 | $expanded = $this->decodeReserved($expanded); |
||
191 | } |
||
192 | } |
||
193 | |||
194 | if ($actuallyUseQuery) { |
||
195 | if (!$expanded && $joiner != '&') { |
||
196 | $expanded = $value['value']; |
||
197 | } else { |
||
198 | $expanded = $value['value'] . '=' . $expanded; |
||
199 | } |
||
200 | } |
||
201 | |||
202 | $replacements[] = $expanded; |
||
203 | } |
||
204 | |||
205 | $ret = implode($joiner, $replacements); |
||
206 | if ($ret && $prefix) { |
||
207 | return $prefix . $ret; |
||
208 | } |
||
209 | |||
210 | return $ret; |
||
211 | } |
||
212 | |||
213 | /** |
||
214 | * Determines if an array is associative. |
||
215 | * |
||
216 | * This makes the assumption that input arrays are sequences or hashes. |
||
217 | * This assumption is a tradeoff for accuracy in favor of speed, but it |
||
218 | * should work in almost every case where input is supplied for a URI |
||
219 | * template. |
||
220 | * |
||
221 | * @param array $array Array to check |
||
222 | * |
||
223 | * @return bool |
||
224 | */ |
||
225 | private function isAssoc(array $array) |
||
226 | { |
||
227 | return $array && array_keys($array)[0] !== 0; |
||
228 | } |
||
229 | |||
230 | /** |
||
231 | * Removes percent encoding on reserved characters (used with + and # |
||
232 | * modifiers). |
||
233 | * |
||
234 | * @param string $string String to fix |
||
235 | * |
||
236 | * @return string |
||
237 | */ |
||
238 | private function decodeReserved($string) |
||
239 | { |
||
240 | return str_replace(self::$delimsPct, self::$delims, $string); |
||
241 | } |
||
242 | } |