Completed
Push — master ( 8d12ff...458df2 )
by Guillaume
02:56
created

UriTemplate::expandMatch()   D

Complexity

Conditions 27
Paths 58

Size

Total Lines 112
Code Lines 65

Duplication

Lines 8
Ratio 7.14 %

Importance

Changes 0
Metric Value
cc 27
eloc 65
nc 58
nop 1
dl 8
loc 112
rs 4.509
c 0
b 0
f 0

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 = [
19
        ''  => ['prefix' => '',  'joiner' => ',', 'query' => false],
20
        '+' => ['prefix' => '',  'joiner' => ',', 'query' => false],
21
        '#' => ['prefix' => '#', 'joiner' => ',', 'query' => false],
22
        '.' => ['prefix' => '.', 'joiner' => '.', 'query' => false],
23
        '/' => ['prefix' => '/', 'joiner' => '/', 'query' => false],
24
        ';' => ['prefix' => ';', 'joiner' => ';', 'query' => true],
25
        '?' => ['prefix' => '?', 'joiner' => '&', 'query' => true],
26
        '&' => ['prefix' => '&', 'joiner' => '&', 'query' => true]
27
    ];
28
29
    /** @var array Delimiters */
30
    private static $delims = [':', '/', '?', '#', '[', ']', '@', '!', '$',
31
        '&', '\'', '(', ')', '*', '+', ',', ';', '='];
32
33
    /** @var array Percent encoded delimiters */
34
    private static $delimsPct = ['%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 = [];
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 = [];
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 = ['+' => '%20', '%7e' => '~'];
102
103
        $replacements = [];
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 = [];
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 View Code Duplication
                        if ($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...
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 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...
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;
0 ignored issues
show
Bug Best Practice introduced by
The expression $array of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

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