Completed
Push — master ( 2b1385...7c6a84 )
by Thomas
07:21
created

UriTemplate::expandMatch()   D

Complexity

Conditions 27
Paths 58

Size

Total Lines 112
Code Lines 65

Duplication

Lines 8
Ratio 7.14 %

Importance

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