1: <?php
2:
3: namespace Guzzle\Parser\UriTemplate;
4:
5: 6: 7: 8: 9:
10: class UriTemplate implements UriTemplateInterface
11: {
12: 13: 14:
15: private $template;
16:
17: 18: 19:
20: private $variables;
21:
22: 23: 24:
25: private static $regex = '/\{([^\}]+)\}/';
26:
27: 28: 29:
30: private static $operatorHash = array(
31: '+' => true, '#' => true, '.' => true, '/' => true, ';' => true, '?' => true, '&' => true
32: );
33:
34: 35: 36:
37: private static $delims = array(
38: ':', '/', '?', '#', '[', ']', '@', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '='
39: );
40:
41: 42: 43:
44: private static $delimsPct = array(
45: '%3A', '%2F', '%3F', '%23', '%5B', '%5D', '%40', '%21', '%24', '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C',
46: '%3B', '%3D'
47: );
48:
49: 50: 51:
52: public function expand($template, array $variables)
53: {
54: $this->template = $template;
55: $this->variables = $variables;
56:
57:
58: if (false === strpos($this->template, '{')) {
59: return $this->template;
60: }
61:
62: return preg_replace_callback(self::$regex, array($this, 'expandMatch'), $this->template);
63: }
64:
65: 66: 67: 68: 69: 70: 71:
72: private function parseExpression($expression)
73: {
74:
75: $operator = '';
76:
77: if (isset(self::$operatorHash[$expression[0]])) {
78: $operator = $expression[0];
79: $expression = substr($expression, 1);
80: }
81:
82: $values = explode(',', $expression);
83: foreach ($values as &$value) {
84: $value = trim($value);
85: $varspec = array();
86: $substrPos = strpos($value, ':');
87: if ($substrPos) {
88: $varspec['value'] = substr($value, 0, $substrPos);
89: $varspec['modifier'] = ':';
90: $varspec['position'] = (int) substr($value, $substrPos + 1);
91: } elseif (substr($value, -1) == '*') {
92: $varspec['modifier'] = '*';
93: $varspec['value'] = substr($value, 0, -1);
94: } else {
95: $varspec['value'] = (string) $value;
96: $varspec['modifier'] = '';
97: }
98: $value = $varspec;
99: }
100:
101: return array(
102: 'operator' => $operator,
103: 'values' => $values
104: );
105: }
106:
107: 108: 109: 110: 111: 112: 113:
114: private function expandMatch(array $matches)
115: {
116: static $rfc1738to3986 = array(
117: '+' => '%20',
118: '%7e' => '~'
119: );
120:
121: $parsed = self::parseExpression($matches[1]);
122: $replacements = array();
123:
124: $prefix = $parsed['operator'];
125: $joiner = $parsed['operator'];
126: $useQueryString = false;
127: if ($parsed['operator'] == '?') {
128: $joiner = '&';
129: $useQueryString = true;
130: } elseif ($parsed['operator'] == '&') {
131: $useQueryString = true;
132: } elseif ($parsed['operator'] == '#') {
133: $joiner = ',';
134: } elseif ($parsed['operator'] == ';') {
135: $useQueryString = true;
136: } elseif ($parsed['operator'] == '' || $parsed['operator'] == '+') {
137: $joiner = ',';
138: $prefix = '';
139: }
140:
141: foreach ($parsed['values'] as $value) {
142:
143: if (!array_key_exists($value['value'], $this->variables) || $this->variables[$value['value']] === null) {
144: continue;
145: }
146:
147: $variable = $this->variables[$value['value']];
148: $actuallyUseQueryString = $useQueryString;
149: $expanded = '';
150:
151: if (is_array($variable)) {
152:
153: $isAssoc = $this->isAssoc($variable);
154: $kvp = array();
155: foreach ($variable as $key => $var) {
156:
157: if ($isAssoc) {
158: $key = rawurlencode($key);
159: $isNestedArray = is_array($var);
160: } else {
161: $isNestedArray = false;
162: }
163:
164: if (!$isNestedArray) {
165: $var = rawurlencode($var);
166: if ($parsed['operator'] == '+' || $parsed['operator'] == '#') {
167: $var = $this->decodeReserved($var);
168: }
169: }
170:
171: if ($value['modifier'] == '*') {
172: if ($isAssoc) {
173: if ($isNestedArray) {
174:
175: $var = strtr(http_build_query(array($key => $var)), $rfc1738to3986);
176: } else {
177: $var = $key . '=' . $var;
178: }
179: } elseif ($key > 0 && $actuallyUseQueryString) {
180: $var = $value['value'] . '=' . $var;
181: }
182: }
183:
184: $kvp[$key] = $var;
185: }
186:
187: if (empty($variable)) {
188: $actuallyUseQueryString = false;
189: } elseif ($value['modifier'] == '*') {
190: $expanded = implode($joiner, $kvp);
191: if ($isAssoc) {
192:
193: $actuallyUseQueryString = false;
194: }
195: } else {
196: if ($isAssoc) {
197:
198:
199: foreach ($kvp as $k => &$v) {
200: $v = $k . ',' . $v;
201: }
202: }
203: $expanded = implode(',', $kvp);
204: }
205:
206: } else {
207: if ($value['modifier'] == ':') {
208: $variable = substr($variable, 0, $value['position']);
209: }
210: $expanded = rawurlencode($variable);
211: if ($parsed['operator'] == '+' || $parsed['operator'] == '#') {
212: $expanded = $this->decodeReserved($expanded);
213: }
214: }
215:
216: if ($actuallyUseQueryString) {
217: if (!$expanded && $joiner != '&') {
218: $expanded = $value['value'];
219: } else {
220: $expanded = $value['value'] . '=' . $expanded;
221: }
222: }
223:
224: $replacements[] = $expanded;
225: }
226:
227: $ret = implode($joiner, $replacements);
228: if ($ret && $prefix) {
229: return $prefix . $ret;
230: }
231:
232: return $ret;
233: }
234:
235: 236: 237: 238: 239: 240: 241:
242: private function isAssoc(array $array)
243: {
244: return (bool) count(array_filter(array_keys($array), 'is_string'));
245: }
246:
247: 248: 249: 250: 251: 252: 253:
254: private function decodeReserved($string)
255: {
256: return str_replace(self::$delimsPct, self::$delims, $string);
257: }
258: }
259: