Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
8 / 8
CRAP
100.00% covered (success)
100.00%
1 / 1
QueryString
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
8 / 8
13
100.00% covered (success)
100.00%
1 / 1
 getQuerystringParamNameTester
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setQuerystringParamNameTester
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setParams
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 setParam
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 getParams
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setQueryEncoding
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getQueryEncoding
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 render
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * @author: Doug Wilbourne (dougwilbourne@gmail.com)
4 * @version 1.0
5 */
6
7namespace pvc\http\url;
8
9use pvc\http\err\InvalidQueryEncodingException;
10use pvc\http\err\InvalidQuerystringException;
11use pvc\http\err\InvalidQuerystringParamNameException;
12use pvc\interfaces\http\QueryStringInterface;
13use pvc\interfaces\validator\ValTesterInterface;
14
15/**
16 * Class QueryString
17 *
18 * The purpose of the class is to provide an easy way to manipulate querystring parameters and values without
19 * having to do any string manipulation.  Once the parameter names and corresponding values are configured, this
20 * object outputs an actual querystring.  You can build a querystring from scratch with this object.  You can
21 * also hydrate this object with an existing querystring using the ParserQueryString object in the pvc Parser
22 * library.
23 */
24class QueryString implements QueryStringInterface
25{
26    /**
27     * @var array<string, string>
28     *
29     * param name => value pairs.  Because http_build_query's first argument is an array (or an object), and because
30     * it uses the keys to the array to generate parameter names, there is no getting around the fact that the
31     * parameter names must all be unique.  So although a querystring like '?a=4&a=5' is not illegal (and who knows
32     * why you would ever want to do it), you can't generate such a thing using http_build_query, which is what the
33     * render method below uses to generate the querystring.
34     */
35    protected array $params = [];
36
37    /**
38     * @var int
39     * this is the default for http_build_query
40     */
41    protected int $queryEncoding = PHP_QUERY_RFC1738;
42
43    /**
44     * @var ValTesterInterface<string>
45     *
46     * The http_build_query function has a parameter called 'numeric prefix', which will prepend a string (which
47     * must start with a letter) to a numeric array index in order to create a query parameter name.  This class
48     * takes a slightly different approach by testing each proposed parameter name before using it. So you can be as
49     * restrictive or as lax as you would like in creating parameter names, as long as the parameter names are strings.
50     * But in theory, no testing is really required: everything gets url encoded before being transmitted anyway.......
51     */
52    protected ValTesterInterface $querystringParamNameTester;
53
54    /**
55     * getQuerystringParamNameTester
56     * @return ValTesterInterface<string>|null
57     */
58    public function getQuerystringParamNameTester(): ?ValTesterInterface
59    {
60        return $this->querystringParamNameTester ?? null;
61    }
62
63    /**
64     * setQuerystringParamNameTester
65     * @param ValTesterInterface<string> $querystringParamNameTester
66     */
67    public function setQuerystringParamNameTester(ValTesterInterface $querystringParamNameTester): void
68    {
69        $this->querystringParamNameTester = $querystringParamNameTester;
70    }
71
72    /**
73     * setParams
74     * @param array<string, string> $params
75     * @throws InvalidQuerystringException
76     * @throws InvalidQuerystringParamNameException
77     */
78    public function setParams(array $params) : void
79    {
80        foreach ($params as $key => $value) {
81            $this->setParam($key, $value);
82        }
83    }
84
85    /**
86     * addParam
87     * @param string $varName
88     * @param string $value
89     * @throws InvalidQuerystringException
90     * @throws InvalidQuerystringParamNameException
91     *
92     * will overwrite duplicate parameter names
93     */
94    public function setParam(string $varName, string $value): void
95    {
96        if ($varName === '') {
97            throw new InvalidQuerystringException();
98        }
99        $nameTester = $this->getQuerystringParamNameTester();
100        if ($nameTester && !$nameTester->testValue($varName)) {
101            throw new InvalidQuerystringParamNameException();
102        }
103        $this->params[$varName] = $value;
104    }
105
106    /**
107     * getParams
108     * @return array<string, string>
109     */
110    public function getParams(): array
111    {
112        return $this->params;
113    }
114
115    /**
116     * setQueryEncoding
117     * @param int $encoding
118     * @throws InvalidQueryEncodingException
119     */
120    public function setQueryEncoding(int $encoding): void
121    {
122        if (!in_array($encoding, [PHP_QUERY_RFC1738, PHP_QUERY_RFC3986])) {
123            throw new InvalidQueryEncodingException();
124        }
125        $this->queryEncoding = $encoding;
126    }
127
128    /**
129     * getQueryEncoding
130     * @return int
131     */
132    public function getQueryEncoding(): int
133    {
134        return $this->queryEncoding;
135    }
136
137    /**
138     * render
139     * @return string
140     *
141     * the numeric prefix parameter will never be used because the querystringParameterTester ensures the parameter
142     * name starts with a letter.
143     *
144     * although http_build_query provides the ability to make the argument separator something else, it's hard to see
145     * why anyone would really want to do so.  The default is the W3 standard (recommendation). which is '&'
146     *
147     * The method does not prepend the querystring with a '?'.  The '?' is a delimiter in the URL, not really part of
148     * the querystring per se.  The '?' is inserted when the URL is rendered.
149     *
150     *  urlencode / urldecode translate the percent-encoded bits as well as plus signs.  rawurlencode
151     *  and rawurldecode do not translate plus signs, and are designed to be compliant with RFC 3986, which specifies
152     *  the syntaxes for URI's, URN's and URL's.
153     */
154    public function render(): string
155    {
156        $numericPrefix = '';
157        $argSeparator = null;
158        return http_build_query($this->getParams(), $numericPrefix, $argSeparator, $this->queryEncoding);
159    }
160}