3939use function sprintf ;
4040use function str_replace ;
4141use function strpos ;
42- use function strtoupper ;
4342use function substr ;
4443use const PHP_QUERY_RFC1738 ;
4544use const PHP_QUERY_RFC3986 ;
@@ -70,6 +69,9 @@ final class QueryString
7069 ],
7170 ];
7271
72+ private const DECODE_PAIR_VALUE = 1 ;
73+ private const PRESERVE_PAIR_VALUE = 2 ;
74+
7375 /**
7476 * @var string
7577 */
@@ -91,49 +93,75 @@ private function __construct()
9193 * Parses a query string into a collection of key/value pairs.
9294 *
9395 * @param null|mixed $query
96+ */
97+ public static function parse ($ query , string $ separator = '& ' , int $ enc_type = PHP_QUERY_RFC3986 ): array
98+ {
99+ $ query = self ::prepareQuery ($ query , $ separator , $ enc_type );
100+ if (null === $ query ) {
101+ return [];
102+ }
103+
104+ if (is_bool ($ query )) {
105+ return [[$ query ? '1 ' : '0 ' , null ]];
106+ }
107+
108+ if ('' === $ query ) {
109+ return [['' , null ]];
110+ }
111+
112+ $ retval = [];
113+ foreach ((array ) explode ($ separator , $ query ) as $ pair ) {
114+ $ retval [] = self ::parsePair ((string ) $ pair , self ::DECODE_PAIR_VALUE );
115+ }
116+
117+ return $ retval ;
118+ }
119+
120+ /**
121+ * Prepare and normalize query before processing.
122+ *
123+ * @param null|mixed $query
94124 *
95125 * @throws TypeError If the query is not stringable or the null value
96126 * @throws MalformedUriComponent If the query string is invalid
97127 * @throws UnknownEncoding If the encoding type is invalid
128+ *
129+ * @return null|string|bool
98130 */
99- public static function parse ($ query , string $ separator = ' & ' , int $ enc_type = PHP_QUERY_RFC3986 ): array
131+ private static function prepareQuery ($ query , string $ separator , int $ enc_type)
100132 {
101133 if (!isset (self ::ENCODING_LIST [$ enc_type ])) {
102134 throw new UnknownEncoding (sprintf ('Unknown Encoding: %s ' , $ enc_type ));
103135 }
104136
105- if (null === $ query ) {
106- return [] ;
137+ if (null === $ query || is_bool ( $ query ) ) {
138+ return $ query ;
107139 }
108140
109141 if (!is_scalar ($ query ) && !method_exists ($ query , '__toString ' )) {
110142 throw new TypeError (sprintf ('The query must be a scalar, a stringable object or the `null` value, `%s` given ' , gettype ($ query )));
111143 }
112144
113- if (is_bool ($ query )) {
114- return [[$ query ? '1 ' : '0 ' , null ]];
115- }
116-
117145 $ query = (string ) $ query ;
118146 if ('' === $ query ) {
119- return [[ '' , null ]] ;
147+ return $ query ;
120148 }
121149
122150 if (1 === preg_match (self ::REGEXP_INVALID_CHARS , $ query )) {
123151 throw new MalformedUriComponent (sprintf ('Invalid query string: %s ' , $ query ));
124152 }
125153
126154 if (PHP_QUERY_RFC1738 === $ enc_type ) {
127- $ query = str_replace ('+ ' , ' ' , $ query );
155+ return str_replace ('+ ' , ' ' , $ query );
128156 }
129157
130- return array_map ([ self ::class, ' parsePair ' ], ( array ) explode ( $ separator , $ query)) ;
158+ return $ query ;
131159 }
132160
133161 /**
134162 * Returns the key/value pair from a query string pair.
135163 */
136- private static function parsePair (string $ pair ): array
164+ private static function parsePair (string $ pair, int $ parseValue ): array
137165 {
138166 [$ key , $ value ] = explode ('= ' , $ pair , 2 ) + [1 => null ];
139167 $ key = (string ) $ key ;
@@ -146,7 +174,7 @@ private static function parsePair(string $pair): array
146174 return [$ key , $ value ];
147175 }
148176
149- if (1 === preg_match (self ::REGEXP_ENCODED_PATTERN , $ value )) {
177+ if ($ parseValue === self :: DECODE_PAIR_VALUE && 1 === preg_match (self ::REGEXP_ENCODED_PATTERN , $ value )) {
150178 $ value = preg_replace_callback (self ::REGEXP_ENCODED_PATTERN , [self ::class, 'decodeMatch ' ], $ value );
151179 }
152180
@@ -158,10 +186,6 @@ private static function parsePair(string $pair): array
158186 */
159187 private static function decodeMatch (array $ matches ): string
160188 {
161- if (1 === preg_match (self ::REGEXP_DECODED_PATTERN , $ matches [0 ])) {
162- return strtoupper ($ matches [0 ]);
163- }
164-
165189 return rawurldecode ($ matches [0 ]);
166190 }
167191
@@ -289,7 +313,21 @@ private static function encodeMatches(array $matches): string
289313 */
290314 public static function extract ($ query , string $ separator = '& ' , int $ enc_type = PHP_QUERY_RFC3986 ): array
291315 {
292- return self ::convert (self ::parse ($ query , $ separator , $ enc_type ));
316+ $ query = self ::prepareQuery ($ query , $ separator , $ enc_type );
317+ if (null === $ query || '' === $ query ) {
318+ return [];
319+ }
320+
321+ if (is_bool ($ query )) {
322+ return [$ query ? '1 ' : '0 ' => '' ];
323+ }
324+
325+ $ retval = [];
326+ foreach ((array ) explode ($ separator , $ query ) as $ pair ) {
327+ $ retval [] = self ::parsePair ((string ) $ pair , self ::PRESERVE_PAIR_VALUE );
328+ }
329+
330+ return self ::convert ($ retval );
293331 }
294332
295333 /**
0 commit comments