Issues (1)

php-src/Handler.php (1 issue)

1
<?php
2
3
namespace kalanis\kw_address_handler;
4
5
6
/**
7
 * Class Handler
8
 * @package kalanis\kw_address_handler
9
 * Class for basic work with address. It allows access for aboject manipulating the address. updating params when they come from string representation.
10
 * It has internal representation of address.
11
 */
12
class Handler
13
{
14
    protected ?Sources\Sources $source = null;
15
    protected Params $params;
16
17
    /**
18
     * @param Sources\Sources|null $address
19
     * @throws HandlerException
20
     */
21 7
    public function __construct(?Sources\Sources $address = null)
22
    {
23 7
        $this->params = new Params();
24 7
        $this->setSource($address);
25 7
    }
26
27
    /**
28
     * @param Sources\Sources|null $sources
29
     * @throws HandlerException
30
     * @return $this
31
     */
32 7
    public function setSource(?Sources\Sources $sources): self
33
    {
34 7
        if (empty($sources)) {
35 6
            return $this;
36
        }
37 6
        $this->source = $sources;
38 6
        $this->parse();
39 6
        return $this;
40
    }
41
42
    /**
43
     * @throws HandlerException
44
     */
45 6
    protected function parse(): void
46
    {
47 6
        $parts = parse_url($this->getSourceClass()->getAddress());
48 6
        if ((false !== $parts) && isset($parts['path'])) {
49 6
            $this->getSourceClass()->setPath($parts['path']);
50 6
            if (!isset($parts['query'])) {
51 1
                $parts['query'] = '';
52
            }
53 6
            $this->params->setParamsData(static::http_parse_query($parts['query']));
54
        }
55 6
    }
56
57
    /**
58
     * Returns an address inside the object.
59
     * @return Sources\Sources|null
60
     */
61 2
    public function getSource(): ?Sources\Sources
62
    {
63 2
        return $this->source;
64
    }
65
66
    /**
67
     * Returns object accessing parsed params inside the address
68
     * @return Params
69
     */
70 6
    public function getParams(): Params
71
    {
72 6
        return $this->params;
73
    }
74
75
    /**
76
     * Get address if there is anything to parse
77
     * @throws HandlerException
78
     * @return string|null
79
     */
80 6
    public function getAddress(): ?string
81
    {
82 6
        return $this->source ? $this->rebuild()->getSourceClass()->getAddress() : null;
83
    }
84
85
    /**
86
     * @throws HandlerException
87
     * @return $this
88
     */
89 6
    protected function rebuild(): self
90
    {
91 6
        $parts = parse_url($this->getSourceClass()->getAddress());
92 6
        if (false !== $parts) {
93 6
            if (!isset($parts['query'])) {
94 1
                $parts['query'] = '';
95
            }
96 6
            $queryArray = static::http_parse_query($parts['query']);
97 6
            foreach ($this->params->getParamsData() as $paramName => $paramValue) {
98 5
                $queryArray[$paramName] = $paramValue;
99
            }
100 6
            foreach ($queryArray as $paramName => $paramValue) {
101 5
                if (!$this->params->offsetExists($paramName)) {
102 1
                    unset($queryArray[$paramName]);
103
                }
104
            }
105 6
            $parts['query'] = http_build_query($queryArray);
106 6
            $this->getSourceClass()->setAddress($this->buildAddress($parts));
107
        }
108 6
        return $this;
109
    }
110
111
    /**
112
     * Parses http query string into an array
113
     *
114
     * @author Alxcube <[email protected]>
115
     *
116
     * @param string $queryString String to parse
117
     * @param non-empty-string $argSeparator Query arguments separator
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string.
Loading history...
118
     * @param integer $decType Decoding type
119
     * @return array<int, string>
120
     * @codeCoverageIgnore for now - external source
121
     */
122 6
    public static function http_parse_query(string $queryString, string $argSeparator = '&', int $decType = PHP_QUERY_RFC1738): array
123
    {
124 6
        if (empty($queryString)) { return []; }
125 5
        $result = [];
126 5
        $parts  = explode($argSeparator, $queryString);
127
128 5
        foreach ($parts as $part) {
129 5
            list($paramName, $paramValue) = array_pad(explode('=', $part, 2), 2, '');
130
131
            switch ($decType) {
132 5
                case PHP_QUERY_RFC3986:
133
                    $paramName  = rawurldecode($paramName);
134
                    $paramValue = rawurldecode($paramValue);
135
                    break;
136
137 5
                case PHP_QUERY_RFC1738:
138
                default:
139 5
                    $paramName  = urldecode($paramName);
140 5
                    $paramValue = urldecode($paramValue);
141 5
                    break;
142
            }
143
144
145 5
            if (preg_match_all('/\[([^\]]*)\]/m', $paramName, $matches)) {
146
                $paramName = substr($paramName, 0, intval(strpos($paramName, '[')));
147
                $keys = array_merge([$paramName], $matches[1]);
148
            } else {
149 5
                $keys = [$paramName];
150
            }
151
152 5
            $target = &$result;
153
154 5
            foreach ($keys as $index) {
155 5
                if ('' === $index) {
156
                    if (isset($target)) {
157
                        if (is_array($target)) {
158
                            $intKeys = array_filter(array_keys($target), 'is_int');
159
                            $index   = count($intKeys) ? max($intKeys)+1 : 0;
160
                        } else {
161
                            $target = [$target];
162
                            $index  = 1;
163
                        }
164
                    } else {
165
                        $target = [];
166
                        $index  = 0;
167
                    }
168 5
                } elseif (isset($target[$index]) && !is_array($target[$index])) {
169
                    $target[$index] = [$target[$index]];
170
                }
171
172 5
                $target = &$target[$index];
173
            }
174
175 5
            if (is_array($target)) {
176
                $target[] = $paramValue;
177
            } else {
178 5
                $target = $paramValue;
179
            }
180
        }
181
182 5
        return $result;
183
    }
184
185
    /**
186
     * Build an address from parse_url parts. The generated address will be a relative address if a scheme or host are not provided.
187
     * @param array<string, int|string> $parts array of parse_url parts
188
     * @return string
189
     * @codeCoverageIgnore for now
190
     */
191 6
    protected function buildAddress(array $parts): string
192
    {
193 6
        $url = $scheme = '';
194
195 6
        if (isset($parts['scheme'])) {
196
            $scheme = $parts['scheme'];
197
            $url .= $scheme . ':';
198
        }
199
200 6
        if (isset($parts['host'])) {
201
            $url .= '//';
202
            if (isset($parts['user'])) {
203
                $url .= $parts['user'];
204
                if (isset($parts['pass'])) {
205
                    $url .= ':' . $parts['pass'];
206
                }
207
                $url .= '@';
208
            }
209
210
            $url .= $parts['host'];
211
212
            // Only include the port if it is not the default port of the scheme
213
            if (isset($parts['port'])
214
                && !(('http' == $scheme && 80 == $parts['port']) || ('https' == $scheme && 443 == $parts['port']))
215
            ) {
216
                $url .= ':' . $parts['port'];
217
            }
218
        }
219
220
        // Add the path component if present
221 6
        if (isset($parts['path']) && (0 !== strlen(strval($parts['path'])))) {
222
            // Always ensure that the path begins with '/' if set and something is before the path
223 5
            if ($url && strval($parts['path'])[0] != '/' && '/' != substr($url, -1)) {
224
                $url .= '/';
225
            }
226 5
            $url .= $parts['path'];
227
        }
228
229
        // Add the query string if present
230 6
        if (isset($parts['query'])) {
231 6
            $url .= '?' . $parts['query'];
232
        }
233
234
        // Ensure that # is only added to the url if fragment contains anything.
235 6
        if (isset($parts['fragment'])) {
236
            $url .= '#' . $parts['fragment'];
237
        }
238
239 6
        return $url;
240
    }
241
242
    /**
243
     * Returns the object with address
244
     * @throws HandlerException
245
     * @return Sources\Sources
246
     */
247 7
    protected function getSourceClass(): Sources\Sources
248
    {
249 7
        if (empty($this->source)) {
250 1
            throw new HandlerException('Set the source first!');
251
        }
252 6
        return $this->source;
253
    }
254
}
255