Completed
Pull Request — master (#2)
by
unknown
01:16
created

Http::getQueryParamsCollapsed()   C

Complexity

Conditions 7
Paths 10

Size

Total Lines 37
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 37
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 22
nc 10
nop 2
1
<?php
2
3
namespace TraderInteractive\Util;
4
5
use Exception;
6
7
/**
8
 * Static class with various HTTP related functions.
9
 */
10
final class Http
11
{
12
    /**
13
     * Parses HTTP headers into an associative array.
14
     *
15
     * Example:
16
     * <code>
17
     * $headers = "HTTP/1.1 200 OK\r\n".
18
     *            "content-type: text/html; charset=UTF-8\r\n".
19
     *            "Server: Funky/1.0\r\n".
20
     *            "Set-Cookie: foo=bar\r\n".
21
     *            "Set-Cookie: baz=quux\r\n".
22
     *            "Folds: are\r\n\treformatted\r\n";
23
     * print_r(\TraderInteractive\HttpUtil::parseHeaders($headers));
24
     * </code>
25
     * The above example will output:
26
     * <pre>
27
     * Array
28
     * (
29
     *     [Response Code] => 200
30
     *     [Response Status] => OK
31
     *     [Content-Type] => text/html; charset=UTF-8
32
     *     [Server] => Funky/1.0
33
     *     [Set-Cookie] => Array
34
     *     (
35
     *       [0] => foo=bar
36
     *       [1] => baz=quux
37
     *     )
38
     *     [Folds] => are reformatted
39
     * )
40
     * </pre>
41
     *
42
     * @param string $rawHeaders string containing HTTP headers
43
     *
44
     * @return array the parsed headers
45
     *
46
     * @throws Exception Thrown if unable to parse the headers
47
     */
48
    public static function parseHeaders(string $rawHeaders) : array
49
    {
50
        $headers = [];
51
        $rawHeaders = preg_replace("/\r\n[\t ]+/", ' ', trim($rawHeaders));
52
        $fields = explode("\r\n", $rawHeaders);
53
        foreach ($fields as $field) {
54
            $match = null;
55
            if (preg_match('/([^:]+): (.+)/m', $field, $match)) {
56
                $key = $match[1];
57
                // convert 'some-header' to 'Some-Header'
58
                $key = strtolower(trim($key));
59
                $key = ucwords(preg_replace('/[\s-]/', ' ', $key));
60
                $key = strtr($key, ' ', '-');
61
62
                $value = trim($match[2]);
63
64
                if (!array_key_exists($key, $headers)) {
65
                    $headers[$key] = $value;
66
                    continue;
67
                }
68
69
                if (!is_array($headers[$key])) {
70
                    $headers[$key] = [$headers[$key]];
71
                }
72
73
                $headers[$key][] = $value;
74
                continue;
75
            }
76
77
            if (preg_match('#([A-Za-z]+) +([^ ]+) +HTTP/([\d.]+)#', $field, $match)) {
78
                $headers = self::addRequestDataToHeaders($match, $headers);
79
                continue;
80
            }
81
82
            if (preg_match('#HTTP/([\d.]+) +(\d{3}) +(.*)#', $field, $match)) {
83
                $headers = self::addResponseDataToHeaders($match, $headers);
84
                continue;
85
            }
86
87
            throw new Exception("Unsupported header format: {$field}");
88
        }
89
90
        return $headers;
91
    }
92
93
    private static function addRequestDataToHeaders(array $match, array $headers) : array
94
    {
95
        $headers['Request Method'] = trim($match[1]);
96
        $headers['Request Url'] = trim($match[2]);
97
        return $headers;
98
    }
99
100
    private static function addResponseDataToHeaders(array $match, array $headers) : array
101
    {
102
        $headers['Response Code'] = (int)$match[2];
103
        $headers['Response Status'] = trim($match[3]);
104
        return $headers;
105
    }
106
107
    /**
108
     * Generate URL-encoded query string
109
     *
110
     * Example:
111
     * <code>
112
     * $parameters = [
113
     *   'param1' => ['value', 'another value'],
114
     *   'param2' => 'a value',
115
     *   'param3' => false,
116
     * ];
117
     *
118
     * $queryString = \TraderInteractive\HttpUtil::buildQueryString($parameters);
119
     *
120
     * echo $queryString
121
     * </code>
122
     *
123
     * Output:
124
     * <pre>
125
     * param1=value&param1=another+value&param2=a+value&param3=false
126
     * </pre>
127
     *
128
     * @param array $parameters An associative array containing parameter key/value(s)
129
     *
130
     * @return string the built query string
131
     */
132
    public static function buildQueryString(array $parameters) : string
133
    {
134
        $queryStrings = [];
135
        foreach ($parameters as $parameterName => $parameterValue) {
136
            $parameterName = rawurlencode($parameterName);
137
138
            if (is_array($parameterValue)) {
139
                foreach ($parameterValue as $eachValue) {
140
                    $eachValue = rawurlencode($eachValue);
141
                    $queryStrings[] = "{$parameterName}={$eachValue}";
142
                }
143
            } elseif ($parameterValue === false) {
144
                $queryStrings[] = "{$parameterName}=false";
145
            } elseif ($parameterValue === true) {
146
                $queryStrings[] = "{$parameterName}=true";
147
            } else {
148
                $parameterValue = rawurlencode($parameterValue);
149
                $queryStrings[] = "{$parameterName}={$parameterValue}";
150
            }
151
        }
152
153
        return implode('&', $queryStrings);
154
    }
155
156
    /**
157
     * Get an array of all url parameters.
158
     *
159
     * @param string $url The url to parse such as http://foo.com/bar/?id=boo&another=wee&another=boo
160
     * @param array $collapsedParams Parameters to collapse. ex. 'id' => ['boo'] to just 'id' => 'boo'. Exception thrown
161
     *                               if more than 1 value
162
     *
163
     * @return array such as ['id' => ['boo'], 'another' => ['wee', 'boo']]
164
     *
165
     * @throws Exception if more than one value in a $collapsedParams param
166
     */
167
    public static function getQueryParams(string $url, array $collapsedParams = []) : array
168
    {
169
        $queryString = parse_url($url, PHP_URL_QUERY);
170
        if (!is_string($queryString)) {
171
            return [];
172
        }
173
174
        $collapsedParams = array_flip($collapsedParams);
175
176
        $result = [];
177
        foreach (explode('&', $queryString) as $arg) {
178
            $name = $arg;
179
            $value = '';
180
            $nameAndValue = explode('=', $arg);
181
            if (isset($nameAndValue[1])) {
182
                list($name, $value) = $nameAndValue;
183
            }
184
185
            $name = rawurldecode($name);
186
            $value = rawurldecode($value);
187
            $collapsed = isset($collapsedParams[$name]);
188
189
            if (!array_key_exists($name, $result)) {
190
                if ($collapsed) {
191
                    $result[$name] = $value;
192
                    continue;
193
                }
194
195
                $result[$name] = [];
196
            }
197
198
            if ($collapsed) {
199
                throw new Exception("Parameter '{$name}' had more than one value but in \$collapsedParams");
200
            }
201
202
            $result[$name][] = $value;
203
        }
204
205
        return $result;
206
    }
207
208
    /**
209
     * Get an array of all url parameters.
210
     *
211
     * @param string $url The url to parse such as http://foo.com/bar/?single=boo&multi=wee&multi=boo
212
     * @param array $expectedArrayParams List of parameter names which are not collapsed.
213
     *
214
     * @return array such as ['single' => 'boo', 'multi' => ['wee', 'boo']] if 'multi' is given in $expectedArrayParams
215
     *
216
     * @throws Exception if a parameter is given as array but not included in the expected array argument
217
     */
218
    public static function getQueryParamsCollapsed(string $url, array $expectedArrayParams = []) : array
219
    {
220
        $queryString = parse_url($url, PHP_URL_QUERY);
221
        if (!is_string($queryString)) {
222
            return [];
223
        }
224
225
        $result = [];
226
        foreach (explode('&', $queryString) as $arg) {
227
            $name = $arg;
228
            $value = '';
229
            $nameAndValue = explode('=', $arg);
230
            if (isset($nameAndValue[1])) {
231
                list($name, $value) = $nameAndValue;
232
            }
233
234
            $name = rawurldecode($name);
235
            $value = rawurldecode($value);
236
237
            if (!array_key_exists($name, $result)) {
238
                $result[$name] = $value;
239
                continue;
240
            }
241
242
            if (!in_array($name, $expectedArrayParams)) {
243
                throw new Exception("Parameter '{$name}' is not expected to be an array, but array given");
244
            }
245
246
            if (!is_array($result[$name])) {
247
                $result[$name] = [$result[$name]];
248
            }
249
250
            $result[$name][] = $value;
251
        }
252
253
        return $result;
254
    }
255
}
256