Completed
Push — master ( 53dd23...58e367 )
by BENOIT
01:13
created

QueryString::setDefaultParser()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace BenTools\QueryString;
4
5
use BenTools\QueryString\Parser\NativeParser;
6
use BenTools\QueryString\Parser\QueryStringParserInterface;
7
use BenTools\QueryString\Renderer\NativeRenderer;
8
use BenTools\QueryString\Renderer\QueryStringRendererInterface;
9
use Traversable;
10
11
final class QueryString
12
{
13
    /**
14
     * @var array
15
     */
16
    private $params = [];
17
18
    /**
19
     * @var QueryStringRendererInterface
20
     */
21
    private $renderer;
22
23
    /**
24
     * @var QueryStringRendererInterface
25
     */
26
    private static $defaultRenderer;
27
28
    /**
29
     * @var QueryStringParserInterface
30
     */
31
    private static $defaultParser;
32
33
    /**
34
     * QueryString constructor.
35
     * @param array|null                       $params
36
     * @throws \InvalidArgumentException
37
     */
38
    protected function __construct(?array $params = [])
39
    {
40
        $params = $params ?? [];
41
        foreach ($params as $key => $value) {
42
            $this->params[(string) $key] = $value;
43
        }
44
        $this->renderer = self::getDefaultRenderer();
45
    }
46
47
    /**
48
     * @param array $params
49
     * @return QueryString
50
     */
51
    private static function createFromParams(array $params): self
52
    {
53
        return new self($params);
54
    }
55
56
    /**
57
     * @param \Psr\Http\Message\UriInterface  $uri
58
     * @param QueryStringParserInterface $queryStringParser
59
     * @return QueryString
60
     */
61
    private static function createFromUri($uri, QueryStringParserInterface $queryStringParser): self
62
    {
63
        return self::createFromString($uri->getQuery(), $queryStringParser);
64
    }
65
66
    /**
67
     * @param string                     $string
68
     * @param QueryStringParserInterface $queryStringParser
69
     * @return QueryString
70
     */
71
    private static function createFromString(string $string, QueryStringParserInterface $queryStringParser): self
72
    {
73
        return new self($queryStringParser->parse($string));
74
    }
75
76
    /**
77
     * @param QueryStringParserInterface|null $queryStringParser
78
     * @return QueryString
79
     * @throws \RuntimeException
80
     */
81
    public static function createFromCurrentLocation(QueryStringParserInterface $queryStringParser = null): self
82
    {
83
        if (!isset($_SERVER['REQUEST_URI'])) {
84
            throw new \RuntimeException('$_SERVER[\'REQUEST_URI\'] has not been set.');
85
        }
86
        return self::createFromString($_SERVER['REQUEST_URI'], $queryStringParser ?? self::getDefaultParser());
87
    }
88
89
    /**
90
     * @return QueryString
91
     * @throws \RuntimeException
92
     */
93
    public function withCurrentLocation(): self
94
    {
95
        return self::createFromCurrentLocation();
96
    }
97
98
    /**
99
     * @param                                 $input
100
     * @param QueryStringParserInterface|null $queryStringParser
101
     * @return QueryString
102
     * @throws \InvalidArgumentException
103
     * @throws \TypeError
104
     */
105
    public static function factory($input = null, QueryStringParserInterface $queryStringParser = null): self
106
    {
107
        if (is_array($input)) {
108
            return self::createFromParams($input);
109
        } elseif (null === $input) {
110
            return self::createFromParams([]);
111
        } elseif (is_a($input, 'Psr\Http\Message\UriInterface')) {
112
            return self::createFromUri($input, $queryStringParser ?? self::getDefaultParser());
113
        } elseif (is_string($input)) {
114
            return self::createFromString($input, $queryStringParser ?? self::getDefaultParser());
115
        }
116
        throw new \InvalidArgumentException(sprintf('Expected array, string or Psr\Http\Message\UriInterface, got %s', is_object($input) ? get_class($input) : gettype($input)));
117
    }
118
119
    /**
120
     * @return array
121
     */
122
    public function getParams(): ?array
123
    {
124
        return $this->params;
125
    }
126
127
    /**
128
     * @param string $key
129
     * @param array  ...$deepKeys
130
     * @return mixed|null
131
     */
132
    public function getParam(string $key, ...$deepKeys)
133
    {
134
        $param = $this->params[$key] ?? null;
135
        foreach ($deepKeys as $key) {
136
            if (!isset($param[$key])) {
137
                return null;
138
            }
139
            $param = $param[$key];
140
        }
141
        return $param;
142
    }
143
144
    /**
145
     * @param string $key
146
     * @return bool
147
     */
148
    public function hasParam(string $key, ...$deepKeys): bool
149
    {
150
        return [] === $deepKeys ? array_key_exists($key, $this->params) : null !== $this->getParam($key, ...$deepKeys);
151
    }
152
153
    /**
154
     * Yield key => value pairs.
155
     *
156
     * @param bool $decodeKeys
157
     * @param bool $decodeValues
158
     * @return Traversable
159
     */
160
    public function getPairs(bool $decodeKeys = false, bool $decodeValues = false): Traversable
161
    {
162
        return new Pairs((string) $this, $decodeKeys, $decodeValues, $this->getRenderer()->getSeparator());
163
    }
164
165
    /**
166
     * @param string $key
167
     * @param        $value
168
     * @return QueryString
169
     */
170
    public function withParam(string $key, $value): self
171
    {
172
        $clone = clone $this;
173
        $clone->params[$key] = $value;
174
        return $clone;
175
    }
176
177
    /**
178
     * @param array $params
179
     * @return QueryString
180
     */
181
    public function withParams(array $params): self
182
    {
183
        $clone = clone $this;
184
        $clone->params = [];
185
        foreach ($params as $key => $value) {
186
            $clone->params[(string) $key] = $value;
187
        }
188
        return $clone;
189
    }
190
191
    /**
192
     * @param string $key
193
     * @param array  ...$deepKeys
194
     * @return QueryString
195
     */
196
    public function withoutParam(string $key, ...$deepKeys): self
197
    {
198
        $clone = clone $this;
199
200
        // $key does not exist
201
        if (!isset($clone->params[$key])) {
202
            return $clone;
203
        }
204
205
        // $key exists and there are no $deepKeys
206
        if ([] === $deepKeys) {
207
            unset($clone->params[$key]);
208
            return $clone;
209
        }
210
211
        // Deepkeys
212
        $clone->params[$key] = $this->removeFromPath($clone->params[$key], ...$deepKeys);
213
        return $clone;
214
    }
215
216
    /**
217
     * @return QueryStringRendererInterface
218
     */
219
    public function getRenderer(): QueryStringRendererInterface
220
    {
221
        return $this->renderer;
222
    }
223
224
    /**
225
     * @param QueryStringRendererInterface $renderer
226
     * @return QueryString
227
     */
228
    public function withRenderer(QueryStringRendererInterface $renderer): self
229
    {
230
        $clone = clone $this;
231
        $clone->renderer = $renderer;
232
        return $clone;
233
    }
234
235
    /**
236
     * @return string
237
     */
238
    public function __toString(): string
239
    {
240
        return $this->renderer->render($this);
241
    }
242
243
    /**
244
     * @param array $array
245
     * @return bool
246
     */
247
    private function isAnIndexedArray(array $array): bool
248
    {
249
        $keys = array_keys($array);
250
        return $keys === array_filter($keys, 'is_int');
251
    }
252
253
    /**
254
     * @param array $params
255
     * @param array ...$keys
256
     * @return array
257
     */
258
    private function removeFromPath(array $params, ...$keys): array
259
    {
260
        $nbKeys = count($keys);
261
        $lastIndex = $nbKeys - 1;
262
        $cursor = &$params;
263
264
        foreach ($keys as $k => $key) {
265
            if (!isset($cursor[$key])) {
266
                return $params; // End here if not found
267
            }
268
269
            if ($k === $lastIndex) {
270
                unset($cursor[$key]);
271
                if (is_array($cursor) && $this->isAnIndexedArray($cursor)) {
272
                    $cursor = array_values($cursor);
273
                }
274
                break;
275
            }
276
277
            $cursor = &$cursor[$key];
278
        }
279
280
        return $params;
281
    }
282
283
    /**
284
     * Returns the default renderer.
285
     *
286
     * @return QueryStringRendererInterface
287
     */
288
    public static function getDefaultRenderer(): QueryStringRendererInterface
289
    {
290
        if (!isset(self::$defaultRenderer)) {
291
            self::restoreDefaultRenderer();
292
        }
293
        return self::$defaultRenderer;
294
    }
295
296
    /**
297
     * Changes default renderer.
298
     *
299
     * @param QueryStringRendererInterface $defaultRenderer
300
     */
301
    public static function setDefaultRenderer(QueryStringRendererInterface $defaultRenderer): void
302
    {
303
        self::$defaultRenderer = $defaultRenderer;
304
    }
305
306
    /**
307
     * Restores the default renderer.
308
     */
309
    public static function restoreDefaultRenderer(): void
310
    {
311
        self::$defaultRenderer = NativeRenderer::factory();
0 ignored issues
show
Documentation Bug introduced by
It seems like \BenTools\QueryString\Re...tiveRenderer::factory() of type object<self> is incompatible with the declared type object<BenTools\QueryStr...tringRendererInterface> of property $defaultRenderer.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
312
    }
313
314
    /**
315
     * Returns the default parser.
316
     *
317
     * @return QueryStringParserInterface
318
     */
319
    public static function getDefaultParser(): QueryStringParserInterface
320
    {
321
        if (!isset(self::$defaultParser)) {
322
            self::restoreDefaultParser();
323
        }
324
        return self::$defaultParser;
325
    }
326
327
    /**
328
     * Changes default parser.
329
     *
330
     * @param QueryStringParserInterface $defaultParser
331
     */
332
    public static function setDefaultParser(QueryStringParserInterface $defaultParser): void
333
    {
334
        self::$defaultParser = $defaultParser;
335
    }
336
337
    /**
338
     * Restores the default parser.
339
     */
340
    public static function restoreDefaultParser(): void
341
    {
342
        self::$defaultParser = new NativeParser();
343
    }
344
}
345