UriTemplate::expandMatch()   F
last analyzed

Complexity

Conditions 34
Paths 348

Size

Total Lines 120
Code Lines 76

Duplication

Lines 6
Ratio 5 %

Importance

Changes 0
Metric Value
dl 6
loc 120
rs 3.4111
c 0
b 0
f 0
cc 34
eloc 76
nc 348
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 View Code Duplication
                        if ($parsed['operator'] == '+' || $parsed['operator'] == '#') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 View Code Duplication
                if ($parsed['operator'] == '+' || $parsed['operator'] == '#') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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
}
255