Completed
Push — master ( 8596bf...15cc44 )
by Derek
02:04
created

Query::encode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 9.4285
cc 1
eloc 4
nc 1
nop 1
crap 1
1
<?php
2
namespace Subreality\Dilmun\Anshar\Http\UriParts;
3
4
use Subreality\Dilmun\Anshar\Utils\StringHelper;
5
6
/**
7
 * Class Query
8
 * @package Subreality\Dilmun\Anshar\Http\UriParts
9
 */
10
class Query
11
{
12
    private $unreserved_pattern   = '\w\-\.~';
13
    private $pct_encoded_pattern  = '%[A-Fa-f0-9]{2}';
14
    private $sub_delims_pattern   = '\!\$&\'\(\)\*\+,;\=';
15
    private $pchar_pattern        = '\:@';
16
    private $unencoded_characters = array(
17
        "/",
18
        "?",
19
        "-",
20
        ".",
21
        "_",
22
        "~",
23
        "!",
24
        "$",
25
        "&",
26
        "'",
27
        "(",
28
        ")",
29
        "*",
30
        "+",
31
        ",",
32
        ";",
33
        "=",
34
        ":",
35
        "@",
36
    );
37
38
    private static $valid_pattern;
39
40
    private $query = "";
41
42
    /**
43
     * Query constructor. Accepts a string representing a URI query. Construction will throw an exception if the
44
     * query is not a string.
45
     *
46
     * Construction accepts strings that have been percent-encoded as well as string that have not been percent-encoded
47
     * and will encode invalid characters.
48
     *
49
     * Construction with a string that includes both encoded and decoded characters will be assumed to be an encoded
50
     * string, resulting in double-encoding.
51
     *
52
     * query       = *( pchar / "/" / "?" )
53
     * pchar       = unreserved / pct-encoded / sub-delims / ":" / "@"
54
     * unreserved  = ALPHA / DIGIT / "-" / "." / "_" / "~"
55
     * pct-encoded = "%" HEXDIG HEXDIG
56
     * sub-delims  = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
57
     *
58
     * @see https://tools.ietf.org/html/rfc3986#appendix-A
59
     *
60
     * @throws \InvalidArgumentException
61
     *
62
     * @param string $query     A string representing a URI query
63
     */
64 14
    public function __construct($query)
65
    {
66 14
        $this->compileValidPattern();
67
68 14
        if (!is_string($query)) {
69 6
            throw new \InvalidArgumentException("Query must be a string");
70 8
        } elseif (!self::isValid($query)) {
71 5
            $query = $this->encode($query);
72 5
        }
73
74 8
        $this->query = $query;
75 8
    }
76
77
    /**
78
     * Returns a string representation of the query
79
     *
80
     * @return string   A string representation of the query
81
     */
82 9
    public function __toString()
83
    {
84 9
        return $this->query;
85
    }
86
87
    /**
88
     * Determines whether a given query adheres to the RFC3986 specification:
89
     *
90
     * query       = *( pchar / "/" / "?" )
91
     * pchar       = unreserved / pct-encoded / sub-delims / ":" / "@"
92
     * unreserved  = ALPHA / DIGIT / "-" / "." / "_" / "~"
93
     * pct-encoded = "%" HEXDIG HEXDIG
94
     * sub-delims  = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
95
     *
96
     * @see https://tools.ietf.org/html/rfc3986#appendix-A
97
     * @see Query::$valid_pattern
98
     *
99
     * @param string $query     The query to check for validity
100
     * @return bool             Returns true if the provided query matches the RFC3986 specification
101
     *                          Returns false otherwise
102
     */
103 19
    public static function isValid($query)
104
    {
105 19
        return (bool) preg_match(self::$valid_pattern, $query);
106
    }
107
108
    /**
109
     * Returns a string representation of the query formatted so that it can be compiled into a complete URI string
110
     * per the Uri object's string specification.
111
     *
112
     * If the query is empty, toUriString returns an empty string; if the query is not empty, toUriString returns the
113
     * query prefixed with a question mark.
114
     *
115
     * @see Uri::__toString
116
     *
117
     * @return string   A string representation of the query formatted for a complete URI string
118
     */
119 2
    public function toUriString()
120
    {
121 2
        $uri_query = $this->query;
122
123 2
        if (!empty($uri_query)) {
124 1
            $uri_query = "?" . $uri_query;
125 1
        }
126
127 2
        return $uri_query;
128
    }
129
130
    /**
131
     * Compiles a regexp pattern based on predefined patterns that define allowed characters for a query. Note that the
132
     * pipe indicates that a query can either contain all defined characters or contain percent encoded characters.
133
     */
134 14
    private function compileValidPattern()
135
    {
136 14
        self::$valid_pattern = '/^([\/\?' .
137 14
            $this->unreserved_pattern .
138 14
            $this->sub_delims_pattern .
139 14
            $this->pchar_pattern .
140 14
            ']|' .
141 14
            $this->pct_encoded_pattern .
142 14
            ')*$/';
143 14
    }
144
145
    /**
146
     * Percent-encodes invalid characters within a given query string. Note that percent-encoding ignores valid
147
     * characters for the query component per RFC3986.
148
     *
149
     * @see Query::unencoded_characters;
150
     * @see https://tools.ietf.org/html/rfc3986#appendix-A
151
     *
152
     * @param string $query     The query string to be percent-encoded
153
     *
154
     * @return string           The query string with invalid characters percent-encoded
155
     */
156 5
    private function encode($query)
157
    {
158 5
        $string_helper = new StringHelper($query);
159
160 5
        $encoded_query = $string_helper->affectChunks("rawurlencode", ...$this->unencoded_characters);
161
162 5
        return $encoded_query;
163
    }
164
}
165