1<?php
2
3declare(strict_types=1);
4
5namespace GuzzleHttp\Psr7;
6
7final class Query
8{
9    /**
10     * Parse a query string into an associative array.
11     *
12     * If multiple values are found for the same key, the value of that key
13     * value pair will become an array. This function does not parse nested
14     * PHP style arrays into an associative array (e.g., `foo[a]=1&foo[b]=2`
15     * will be parsed into `['foo[a]' => '1', 'foo[b]' => '2'])`.
16     *
17     * @param string   $str         Query string to parse
18     * @param int|bool $urlEncoding How the query string is encoded
19     */
20    public static function parse(string $str, $urlEncoding = true): array
21    {
22        $result = [];
23
24        if ($str === '') {
25            return $result;
26        }
27
28        if ($urlEncoding === true) {
29            $decoder = function ($value) {
30                return rawurldecode(str_replace('+', ' ', (string) $value));
31            };
32        } elseif ($urlEncoding === PHP_QUERY_RFC3986) {
33            $decoder = 'rawurldecode';
34        } elseif ($urlEncoding === PHP_QUERY_RFC1738) {
35            $decoder = 'urldecode';
36        } else {
37            $decoder = function ($str) {
38                return $str;
39            };
40        }
41
42        foreach (explode('&', $str) as $kvp) {
43            $parts = explode('=', $kvp, 2);
44            $key = $decoder($parts[0]);
45            $value = isset($parts[1]) ? $decoder($parts[1]) : null;
46            if (!array_key_exists($key, $result)) {
47                $result[$key] = $value;
48            } else {
49                if (!is_array($result[$key])) {
50                    $result[$key] = [$result[$key]];
51                }
52                $result[$key][] = $value;
53            }
54        }
55
56        return $result;
57    }
58
59    /**
60     * Build a query string from an array of key value pairs.
61     *
62     * This function can use the return value of `parse()` to build a query
63     * string. This function does not modify the provided keys when an array is
64     * encountered (like `http_build_query()` would).
65     *
66     * @param array     $params   Query string parameters.
67     * @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986
68     *                            to encode using RFC3986, or PHP_QUERY_RFC1738
69     *                            to encode using RFC1738.
70     */
71    public static function build(array $params, $encoding = PHP_QUERY_RFC3986): string
72    {
73        if (!$params) {
74            return '';
75        }
76
77        if ($encoding === false) {
78            $encoder = function (string $str): string {
79                return $str;
80            };
81        } elseif ($encoding === PHP_QUERY_RFC3986) {
82            $encoder = 'rawurlencode';
83        } elseif ($encoding === PHP_QUERY_RFC1738) {
84            $encoder = 'urlencode';
85        } else {
86            throw new \InvalidArgumentException('Invalid type');
87        }
88
89        $qs = '';
90        foreach ($params as $k => $v) {
91            $k = $encoder((string) $k);
92            if (!is_array($v)) {
93                $qs .= $k;
94                $v = is_bool($v) ? (int) $v : $v;
95                if ($v !== null) {
96                    $qs .= '='.$encoder((string) $v);
97                }
98                $qs .= '&';
99            } else {
100                foreach ($v as $vv) {
101                    $qs .= $k;
102                    $vv = is_bool($vv) ? (int) $vv : $vv;
103                    if ($vv !== null) {
104                        $qs .= '='.$encoder((string) $vv);
105                    }
106                    $qs .= '&';
107                }
108            }
109        }
110
111        return $qs ? (string) substr($qs, 0, -1) : '';
112    }
113}
114