Subversion Repositories php-qbpwcf

Rev

Details | Last modification | View Log | RSS feed

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