Skip to content
This repository was archived by the owner on Nov 26, 2022. It is now read-only.

Commit 9ba32e8

Browse files
authored
Merge pull request #4 from thephpleague/bugfix/issue-3
bugfix issue 3
2 parents 00a7c63 + ea63d3f commit 9ba32e8

File tree

4 files changed

+89
-19
lines changed

4 files changed

+89
-19
lines changed

.travis.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ env:
1212

1313
matrix:
1414
include:
15+
- php: 7.1
16+
env:
17+
- COLLECT_COVERAGE=true
18+
- IGNORE_PLATFORMS=false
19+
- RUN_PHPSTAN=true
20+
- VALIDATE_CODING_STYLE=false
1521
- php: 7.2
1622
env:
1723
- COLLECT_COVERAGE=true

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
}
2121
] ,
2222
"require": {
23-
"php": "^7.2"
23+
"php": "^7.1"
2424
},
2525
"require-dev": {
2626
"friendsofphp/php-cs-fixer": "^2.3",

src/Parser/QueryString.php

Lines changed: 64 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@
3838
use function rawurlencode;
3939
use function sprintf;
4040
use function str_replace;
41+
use function str_split;
4142
use function strpos;
42-
use function strtoupper;
4343
use function substr;
4444
use const PHP_QUERY_RFC1738;
4545
use const PHP_QUERY_RFC3986;
@@ -70,6 +70,9 @@ final class QueryString
7070
],
7171
];
7272

73+
private const DECODE_PAIR_VALUE = 1;
74+
private const PRESERVE_PAIR_VALUE = 2;
75+
7376
/**
7477
* @var string
7578
*/
@@ -91,49 +94,86 @@ private function __construct()
9194
* Parses a query string into a collection of key/value pairs.
9295
*
9396
* @param null|mixed $query
97+
*/
98+
public static function parse($query, string $separator = '&', int $enc_type = PHP_QUERY_RFC3986): array
99+
{
100+
$query = self::prepareQuery($query, $enc_type);
101+
if (null === $query) {
102+
return [];
103+
}
104+
105+
if ('' === $query) {
106+
return [['', null]];
107+
}
108+
109+
$retval = [];
110+
foreach (self::getPairs($query, $separator) as $pair) {
111+
$retval[] = self::parsePair((string) $pair, self::DECODE_PAIR_VALUE);
112+
}
113+
114+
return $retval;
115+
}
116+
117+
/**
118+
* Prepare and normalize query before processing.
119+
*
120+
* @param null|mixed $query
94121
*
95-
* @throws TypeError If the query is not stringable or the null value
96122
* @throws MalformedUriComponent If the query string is invalid
123+
* @throws TypeError If the query is not stringable or the null value
97124
* @throws UnknownEncoding If the encoding type is invalid
98125
*/
99-
public static function parse($query, string $separator = '&', int $enc_type = PHP_QUERY_RFC3986): array
126+
private static function prepareQuery($query, int $enc_type): ?string
100127
{
101128
if (!isset(self::ENCODING_LIST[$enc_type])) {
102129
throw new UnknownEncoding(sprintf('Unknown Encoding: %s', $enc_type));
103130
}
104131

105132
if (null === $query) {
106-
return [];
133+
return $query;
107134
}
108135

109-
if (!is_scalar($query) && !method_exists($query, '__toString')) {
110-
throw new TypeError(sprintf('The query must be a scalar, a stringable object or the `null` value, `%s` given', gettype($query)));
136+
if (is_bool($query)) {
137+
return true === $query ? '1' : '0';
111138
}
112139

113-
if (is_bool($query)) {
114-
return [[$query ? '1' : '0', null]];
140+
if (!is_scalar($query) && !method_exists($query, '__toString')) {
141+
throw new TypeError(sprintf('The query must be a scalar, a stringable object or the `null` value, `%s` given', gettype($query)));
115142
}
116143

117144
$query = (string) $query;
118145
if ('' === $query) {
119-
return [['', null]];
146+
return $query;
120147
}
121148

122149
if (1 === preg_match(self::REGEXP_INVALID_CHARS, $query)) {
123150
throw new MalformedUriComponent(sprintf('Invalid query string: %s', $query));
124151
}
125152

126153
if (PHP_QUERY_RFC1738 === $enc_type) {
127-
$query = str_replace('+', ' ', $query);
154+
return str_replace('+', ' ', $query);
155+
}
156+
157+
return $query;
158+
}
159+
160+
private static function getPairs(string $query, string $separator): array
161+
{
162+
if ('' === $separator) {
163+
return str_split($query);
164+
}
165+
166+
if (false === strpos($query, $separator)) {
167+
return [$query];
128168
}
129169

130-
return array_map([self::class, 'parsePair'], (array) explode($separator, $query));
170+
return (array) explode($separator, $query);
131171
}
132172

133173
/**
134174
* Returns the key/value pair from a query string pair.
135175
*/
136-
private static function parsePair(string $pair): array
176+
private static function parsePair(string $pair, int $parseValue): array
137177
{
138178
[$key, $value] = explode('=', $pair, 2) + [1 => null];
139179
$key = (string) $key;
@@ -146,7 +186,7 @@ private static function parsePair(string $pair): array
146186
return [$key, $value];
147187
}
148188

149-
if (1 === preg_match(self::REGEXP_ENCODED_PATTERN, $value)) {
189+
if ($parseValue === self::DECODE_PAIR_VALUE && 1 === preg_match(self::REGEXP_ENCODED_PATTERN, $value)) {
150190
$value = preg_replace_callback(self::REGEXP_ENCODED_PATTERN, [self::class, 'decodeMatch'], $value);
151191
}
152192

@@ -158,10 +198,6 @@ private static function parsePair(string $pair): array
158198
*/
159199
private static function decodeMatch(array $matches): string
160200
{
161-
if (1 === preg_match(self::REGEXP_DECODED_PATTERN, $matches[0])) {
162-
return strtoupper($matches[0]);
163-
}
164-
165201
return rawurldecode($matches[0]);
166202
}
167203

@@ -289,7 +325,17 @@ private static function encodeMatches(array $matches): string
289325
*/
290326
public static function extract($query, string $separator = '&', int $enc_type = PHP_QUERY_RFC3986): array
291327
{
292-
return self::convert(self::parse($query, $separator, $enc_type));
328+
$query = self::prepareQuery($query, $enc_type);
329+
if (null === $query || '' === $query) {
330+
return [];
331+
}
332+
333+
$retval = [];
334+
foreach (self::getPairs($query, $separator) as $pair) {
335+
$retval[] = self::parsePair((string) $pair, self::PRESERVE_PAIR_VALUE);
336+
}
337+
338+
return self::convert($retval);
293339
}
294340

295341
/**

tests/QueryStringTest.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,18 @@ public function testExtractQuery($query, array $expectedData): void
7070
public function extractQueryProvider(): array
7171
{
7272
return [
73+
[
74+
'query' => null,
75+
'expected' => [],
76+
],
77+
[
78+
'query' => false,
79+
'expected' => ['0' => ''],
80+
],
81+
[
82+
'query' => '%25car=%25car',
83+
'expected' => ['%car' => '%car'],
84+
],
7385
[
7486
'query' => '&&',
7587
'expected' => [],
@@ -149,6 +161,12 @@ public function testParse($query, string $separator, array $expected, int $encod
149161
public function parserProvider(): array
150162
{
151163
return [
164+
'empty separator' => [
165+
'foo',
166+
'',
167+
[['f', null], ['o', null], ['o', null]],
168+
PHP_QUERY_RFC3986,
169+
],
152170
'stringable object' => [
153171
new class() {
154172
public function __toString()

0 commit comments

Comments
 (0)