aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/league/uri-interfaces/QueryString.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/league/uri-interfaces/QueryString.php')
-rw-r--r--vendor/league/uri-interfaces/QueryString.php276
1 files changed, 276 insertions, 0 deletions
diff --git a/vendor/league/uri-interfaces/QueryString.php b/vendor/league/uri-interfaces/QueryString.php
new file mode 100644
index 000000000..b35086736
--- /dev/null
+++ b/vendor/league/uri-interfaces/QueryString.php
@@ -0,0 +1,276 @@
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri;
+
+use League\Uri\Exceptions\SyntaxError;
+use League\Uri\KeyValuePair\Converter;
+use Stringable;
+
+use function array_key_exists;
+use function array_keys;
+use function is_array;
+use function rawurldecode;
+use function strpos;
+use function substr;
+
+use const PHP_QUERY_RFC3986;
+
+/**
+ * A class to parse the URI query string.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-3.4
+ */
+final class QueryString
+{
+ private const PAIR_VALUE_DECODED = 1;
+ private const PAIR_VALUE_PRESERVED = 2;
+
+ /**
+ * @codeCoverageIgnore
+ */
+ private function __construct()
+ {
+ }
+
+ /**
+ * Build a query string from a list of pairs.
+ *
+ * @see QueryString::buildFromPairs()
+ * @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.2
+ *
+ * @param iterable<array{0:string, 1:string|float|int|bool|null}> $pairs
+ * @param non-empty-string $separator
+ *
+ * @throws SyntaxError If the encoding type is invalid
+ * @throws SyntaxError If a pair is invalid
+ */
+ public static function build(iterable $pairs, string $separator = '&', int $encType = PHP_QUERY_RFC3986): ?string
+ {
+ return self::buildFromPairs($pairs, Converter::fromEncodingType($encType)->withSeparator($separator));
+ }
+
+ /**
+ * Build a query string from a list of pairs.
+ *
+ * The method expects the return value from Query::parse to build
+ * a valid query string. This method differs from PHP http_build_query as
+ * it does not modify parameters keys.
+ *
+ * If a reserved character is found in a URI component and
+ * no delimiting role is known for that character, then it must be
+ * interpreted as representing the data octet corresponding to that
+ * character's encoding in US-ASCII.
+ *
+ * @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.2
+ *
+ * @param iterable<array{0:string, 1:string|float|int|bool|null}> $pairs
+ *
+ * @throws SyntaxError If the encoding type is invalid
+ * @throws SyntaxError If a pair is invalid
+ */
+ public static function buildFromPairs(iterable $pairs, ?Converter $converter = null): ?string
+ {
+ $keyValuePairs = [];
+ foreach ($pairs as $pair) {
+ if (!is_array($pair) || [0, 1] !== array_keys($pair)) {
+ throw new SyntaxError('A pair must be a sequential array starting at `0` and containing two elements.');
+ }
+
+ $keyValuePairs[] = [(string) Encoder::encodeQueryKeyValue($pair[0]), match(null) {
+ $pair[1] => null,
+ default => Encoder::encodeQueryKeyValue($pair[1]),
+ }];
+ }
+
+ return ($converter ?? Converter::fromRFC3986())->toValue($keyValuePairs);
+ }
+
+ /**
+ * Parses the query string like parse_str without mangling the results.
+ *
+ * @see QueryString::extractFromValue()
+ * @see http://php.net/parse_str
+ * @see https://wiki.php.net/rfc/on_demand_name_mangling
+ *
+ * @param non-empty-string $separator
+ *
+ * @throws SyntaxError
+ */
+ public static function extract(Stringable|string|bool|null $query, string $separator = '&', int $encType = PHP_QUERY_RFC3986): array
+ {
+ return self::extractFromValue($query, Converter::fromEncodingType($encType)->withSeparator($separator));
+ }
+
+ /**
+ * Parses the query string like parse_str without mangling the results.
+ *
+ * The result is similar as PHP parse_str when used with its
+ * second argument with the difference that variable names are
+ * not mangled.
+ *
+ * @see http://php.net/parse_str
+ * @see https://wiki.php.net/rfc/on_demand_name_mangling
+ *
+ * @throws SyntaxError
+ */
+ public static function extractFromValue(Stringable|string|bool|null $query, ?Converter $converter = null): array
+ {
+ return self::convert(self::decodePairs(
+ ($converter ?? Converter::fromRFC3986())->toPairs($query),
+ self::PAIR_VALUE_PRESERVED
+ ));
+ }
+
+ /**
+ * Parses a query string into a collection of key/value pairs.
+ *
+ * @param non-empty-string $separator
+ *
+ * @throws SyntaxError
+ *
+ * @return array<int, array{0:string, 1:string|null}>
+ */
+ public static function parse(Stringable|string|bool|null $query, string $separator = '&', int $encType = PHP_QUERY_RFC3986): array
+ {
+ return self::parseFromValue($query, Converter::fromEncodingType($encType)->withSeparator($separator));
+ }
+
+ /**
+ * Parses a query string into a collection of key/value pairs.
+ *
+ * @throws SyntaxError
+ *
+ * @return array<int, array{0:string, 1:string|null}>
+ */
+ public static function parseFromValue(Stringable|string|bool|null $query, ?Converter $converter = null): array
+ {
+ return self::decodePairs(
+ ($converter ?? Converter::fromRFC3986())->toPairs($query),
+ self::PAIR_VALUE_DECODED
+ );
+ }
+
+ /**
+ * @param array<non-empty-list<string|null>> $pairs
+ *
+ * @return array<int, array{0:string, 1:string|null}>
+ */
+ private static function decodePairs(array $pairs, int $pairValueState): array
+ {
+ $decodePair = static function (array $pair, int $pairValueState): array {
+ [$key, $value] = $pair;
+
+ return match ($pairValueState) {
+ self::PAIR_VALUE_PRESERVED => [(string) Encoder::decodeAll($key), $value],
+ default => [(string) Encoder::decodeAll($key), Encoder::decodeAll($value)],
+ };
+ };
+
+ return array_reduce(
+ $pairs,
+ fn (array $carry, array $pair) => [...$carry, $decodePair($pair, $pairValueState)],
+ []
+ );
+ }
+
+ /**
+ * Converts a collection of key/value pairs and returns
+ * the store PHP variables as elements of an array.
+ */
+ public static function convert(iterable $pairs): array
+ {
+ $returnedValue = [];
+ foreach ($pairs as $pair) {
+ $returnedValue = self::extractPhpVariable($returnedValue, $pair);
+ }
+
+ return $returnedValue;
+ }
+
+ /**
+ * Parses a query pair like parse_str without mangling the results array keys.
+ *
+ * <ul>
+ * <li>empty name are not saved</li>
+ * <li>If the value from name is duplicated its corresponding value will be overwritten</li>
+ * <li>if no "[" is detected the value is added to the return array with the name as index</li>
+ * <li>if no "]" is detected after detecting a "[" the value is added to the return array with the name as index</li>
+ * <li>if there's a mismatch in bracket usage the remaining part is dropped</li>
+ * <li>“.” and “ ” are not converted to “_”</li>
+ * <li>If there is no “]”, then the first “[” is not converted to becomes an “_”</li>
+ * <li>no whitespace trimming is done on the key value</li>
+ * </ul>
+ *
+ * @see https://php.net/parse_str
+ * @see https://wiki.php.net/rfc/on_demand_name_mangling
+ * @see https://github.com/php/php-src/blob/master/ext/standard/tests/strings/parse_str_basic1.phpt
+ * @see https://github.com/php/php-src/blob/master/ext/standard/tests/strings/parse_str_basic2.phpt
+ * @see https://github.com/php/php-src/blob/master/ext/standard/tests/strings/parse_str_basic3.phpt
+ * @see https://github.com/php/php-src/blob/master/ext/standard/tests/strings/parse_str_basic4.phpt
+ *
+ * @param array $data the submitted array
+ * @param array|string $name the pair key
+ * @param string $value the pair value
+ */
+ private static function extractPhpVariable(array $data, array|string $name, string $value = ''): array
+ {
+ if (is_array($name)) {
+ [$name, $value] = $name;
+ $value = rawurldecode((string) $value);
+ }
+
+ if ('' === $name) {
+ return $data;
+ }
+
+ $leftBracketPosition = strpos($name, '[');
+ if (false === $leftBracketPosition) {
+ $data[$name] = $value;
+
+ return $data;
+ }
+
+ $rightBracketPosition = strpos($name, ']', $leftBracketPosition);
+ if (false === $rightBracketPosition) {
+ $data[$name] = $value;
+
+ return $data;
+ }
+
+ $key = substr($name, 0, $leftBracketPosition);
+ if ('' === $key) {
+ $key = '0';
+ }
+
+ if (!array_key_exists($key, $data) || !is_array($data[$key])) {
+ $data[$key] = [];
+ }
+
+ $remaining = substr($name, $rightBracketPosition + 1);
+ if (!str_starts_with($remaining, '[') || !str_contains($remaining, ']')) {
+ $remaining = '';
+ }
+
+ $name = substr($name, $leftBracketPosition + 1, $rightBracketPosition - $leftBracketPosition - 1).$remaining;
+ if ('' === $name) {
+ $data[$key][] = $value;
+
+ return $data;
+ }
+
+ $data[$key] = self::extractPhpVariable($data[$key], $name, $value);
+
+ return $data;
+ }
+}