Completed
Push — master ( 1bf41a...2b4767 )
by ignace nyamagana
15:28 queued 50s
created

QueryParser::parsePair()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 4

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 0
loc 21
ccs 15
cts 15
cp 1
rs 9.0534
cc 4
eloc 13
nc 6
nop 3
crap 4
1
<?php
2
/**
3
 * League.Uri (http://uri.thephpleague.com)
4
 *
5
 * @package   League.uri
6
 * @author    Ignace Nyamagana Butera <[email protected]>
7
 * @copyright 2013-2015 Ignace Nyamagana Butera
8
 * @license   https://github.com/thephpleague/uri/blob/master/LICENSE (MIT License)
9
 * @version   4.1.0
10
 * @link      https://github.com/thephpleague/uri/
11
 */
12
namespace League\Uri;
13
14
/**
15
 * a class to parse a URI query string according to RFC3986
16
 *
17
 * @package League.uri
18
 * @author  Ignace Nyamagana Butera <[email protected]>
19
 * @since   4.0.0
20
 */
21
class QueryParser
22
{
23
    /**
24
     * Parse a query string into an associative array
25
     *
26
     * Multiple identical key will generate an array. This function
27
     * differ from PHP parse_str as:
28
     *    - it does not modify or remove parameters keys
29
     *    - it does not create nested array
30
     *
31
     * @param string    $str          The query string to parse
32
     * @param string    $separator    The query string separator
33
     * @param int|false $encodingType The query string encoding mechanism
34
     *
35
     * @return array
36
     */
37 422
    public function parse($str, $separator = '&', $encodingType = PHP_QUERY_RFC3986)
38
    {
39 422
        $res = [];
40 422
        if ('' == $str) {
41 34
            return $res;
42
        }
43 402
        $encodingType = $this->validateEncodingType($encodingType);
44 402
        $decoder = $this->getDecoder($encodingType);
45 402
        foreach (explode($separator, $str) as $pair) {
46 402
            $res = $this->parsePair($res, $decoder, $pair);
47 402
        }
48
49 402
        return $res;
50
    }
51
52
    /**
53
     * validate the encoding type for the query related methods
54
     *
55
     * @param int|false $encodingType
56
     *
57
     * @return int|false
58
     */
59 530
    protected function validateEncodingType($encodingType)
60
    {
61 530
        if (!in_array($encodingType, [PHP_QUERY_RFC3986, PHP_QUERY_RFC1738, false])) {
62 2
            return PHP_QUERY_RFC3986;
63
        }
64
65 528
        return $encodingType;
66
    }
67
68
    /**
69
     * Parse a query string pair
70
     *
71
     * @param array    $res     The associative array to add the pair to
72
     * @param callable $decoder a Callable to decode the query string pair
73
     * @param string   $pair    The query string pair
74
     *
75
     * @return array
76
     */
77 402
    protected function parsePair(array $res, callable $decoder, $pair)
78
    {
79 402
        $param = explode('=', $pair, 2);
80 402
        $key   = $decoder(array_shift($param));
81 402
        $value = array_shift($param);
82 402
        if (null !== $value) {
83 322
            $value = $decoder($value);
84 322
        }
85
86 402
        if (!array_key_exists($key, $res)) {
87 402
            $res[$key] = $value;
88 402
            return $res;
89
        }
90
91 6
        if (!is_array($res[$key])) {
92 6
            $res[$key] = [$res[$key]];
93 6
        }
94 6
        $res[$key][] = $value;
95
96 6
        return $res;
97
    }
98
99
    /**
100
     * Build a query string from an associative array
101
     *
102
     * The method expects the return value from Query::parse to build
103
     * a valid query string. This method differs from PHP http_build_query as:
104
     *
105
     *    - it does not modify parameters keys
106
     *
107
     * @param array     $arr          Query string parameters
108
     * @param string    $separator    Query string separator
109
     * @param int|false $encodingType Query string encoding
110
     *
111
     * @return string
112
     */
113 460
    public function build(array $arr, $separator = '&', $encodingType = PHP_QUERY_RFC3986)
114
    {
115 460
        $encodingType = $this->validateEncodingType($encodingType);
116 460
        $encoder = $this->getEncoder($encodingType);
117
        $arr = array_map(function ($value) {
118 348
            return !is_array($value) ? [$value] : $value;
119 460
        }, $arr);
120
121 460
        $pairs = [];
122 460
        foreach ($arr as $key => $value) {
123 348
            $pairs = array_merge($pairs, $this->buildPair($encoder, $value, $encoder($key)));
124 460
        }
125
126 460
        return implode($separator, $pairs);
127
    }
128
129
    /**
130
     * Build a query key/pair association
131
     *
132
     * @param callable $encoder a callable to encode the key/pair association
133
     * @param array    $value   The query string value
134
     * @param string   $key     The query string key
135
     *
136
     * @return string
137
     */
138 348
    protected function buildPair(callable $encoder, array $value, $key)
139
    {
140
        $reducer = function (array $carry, $data) use ($key, $encoder) {
141 348
            $pair = $key;
142 348
            if (null !== $data) {
143 272
                $pair .= '='.$encoder($data);
144 272
            }
145 348
            $carry[] = $pair;
146
147 348
            return $carry;
148 348
        };
149
150
        return array_reduce($value, $reducer, []);
151
    }
152
153
    /**
154
     * Return the query string decoding mechanism
155
     *
156
     * @param int|false $encodingType
157
     *
158 402
     * @return callable
159
     */
160 402
    protected function getDecoder($encodingType)
161
    {
162 28
        if (PHP_QUERY_RFC3986 === $encodingType) {
163 28
            return function ($value) {
164
                return rawurldecode($value);
165
            };
166 374
        }
167
168 2
        if (PHP_QUERY_RFC1738 === $encodingType) {
169 2
            return function ($value) {
170
                return urldecode($value);
171
            };
172
        }
173 372
174 372
        return function ($value) {
175
            return rawurldecode(str_replace('+', ' ', $value));
176
        };
177
    }
178
179
    /**
180
     * Return the query string encoding mechanism
181
     *
182
     * @param int|false $encodingType
183
     *
184 460
     * @return callable
185
     */
186 460
    protected function getEncoder($encodingType)
187
    {
188 326
        if (PHP_QUERY_RFC3986 === $encodingType) {
189 436
            return function ($value) {
190
                return rawurlencode($value);
191
            };
192 26
        }
193
194 2
        if (PHP_QUERY_RFC1738 === $encodingType) {
195 2
            return function ($value) {
196
                return urlencode($value);
197
            };
198 24
        }
199 22
200 24
        return function ($value) {
201
            return $value;
202
        };
203
    }
204
}
205