Completed
Push — master ( fd943c...fbdc82 )
by BENOIT
01:17
created

QueryString::getDefaultRenderer()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 0
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace BenTools\QueryString;
4
5
use BenTools\QueryString\Renderer\NativeRenderer;
6
use BenTools\QueryString\Renderer\QueryStringRendererInterface;
7
8
final class QueryString
9
{
10
    /**
11
     * @var array
12
     */
13
    private $params = [];
14
15
    /**
16
     * @var QueryStringRendererInterface
17
     */
18
    private $renderer;
19
20
    /**
21
     * @var QueryStringRendererInterface
22
     */
23
    private static $defaultRenderer;
24
25
    /**
26
     * QueryString constructor.
27
     * @param array|null                       $params
28
     * @param QueryStringRendererInterface|null $renderer
29
     * @throws \InvalidArgumentException
30
     */
31
    protected function __construct(?array $params = [], QueryStringRendererInterface $renderer = null)
32
    {
33
        $params = $params ?? [];
34
        foreach ($params as $key => $value) {
35
            $this->params[(string) $key] = $value;
36
        }
37
        $this->renderer = $renderer ?? self::getDefaultRenderer();
38
    }
39
40
    /**
41
     * @param array $params
42
     * @param QueryStringRendererInterface|null $renderer
43
     * @return QueryString
44
     */
45
    private static function createFromParams(array $params, QueryStringRendererInterface $renderer = null): self
46
    {
47
        return new self($params, $renderer);
48
    }
49
50
    /**
51
     * @param \Psr\Http\Message\UriInterface $uri
52
     * @param QueryStringRendererInterface|null $renderer
53
     * @return QueryString
54
     * @throws \TypeError
55
     */
56
    private static function createFromUri($uri, QueryStringRendererInterface $renderer = null): self
57
    {
58
        if (!is_a($uri, 'Psr\Http\Message\UriInterface')) {
59
            throw new \TypeError(
60
                sprintf(
0 ignored issues
show
Unused Code introduced by
The call to TypeError::__construct() has too many arguments starting with sprintf('Argument 1 pass...($uri) : gettype($uri)).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
61
                    'Argument 1 passed to %s() must implement interface Psr\Http\Message\UriInterface, %s given, called',
62
                    __METHOD__,
63
                    is_object($uri) ? get_class($uri) : gettype($uri)
64
                )
65
            );
66
        }
67
        $qs = $uri->getQuery();
68
        $params = [];
69
        parse_str($qs, $params);
70
        return new self($params, $renderer);
71
    }
72
73
    /**
74
     * @param string $qs
75
     * @param QueryStringRendererInterface|null $renderer
76
     * @return QueryString
77
     */
78
    private static function createFromString(string $qs, QueryStringRendererInterface $renderer = null): self
79
    {
80
        $params = [];
81
        parse_str($qs, $params);
82
        return new self($params, $renderer);
83
    }
84
85
    /**
86
     * @param QueryStringRendererInterface|null $renderer
87
     * @return QueryString
88
     * @throws \RuntimeException
89
     */
90
    public static function createFromCurrentLocation(QueryStringRendererInterface $renderer = null): self
91
    {
92
        if (!isset($_SERVER['REQUEST_URI'])) {
93
            throw new \RuntimeException('$_SERVER[\'REQUEST_URI\'] has not been set.');
94
        }
95
        return self::createFromString($_SERVER['REQUEST_URI'], $renderer);
96
    }
97
98
    /**
99
     * @return QueryString
100
     * @throws \RuntimeException
101
     */
102
    public function withCurrentLocation(): self
103
    {
104
        return self::createFromCurrentLocation($this->renderer);
105
    }
106
107
    /**
108
     * @param          $input
109
     * @return QueryString
110
     * @throws \InvalidArgumentException
111
     */
112
    public static function factory($input = null, QueryStringRendererInterface $renderer = null): self
113
    {
114
        if (is_array($input)) {
115
            return self::createFromParams($input, $renderer);
116
        } elseif (is_a($input, 'Psr\Http\Message\UriInterface')) {
117
            return self::createFromUri($input, $renderer);
118
        } elseif (is_string($input)) {
119
            return self::createFromString($input, $renderer);
120
        } elseif (null === $input) {
121
            return self::createFromParams([], $renderer);
122
        }
123
        throw new \InvalidArgumentException(sprintf('Expected array, string or Psr\Http\Message\UriInterface, got %s', is_object($input) ? get_class($input) : gettype($input)));
124
    }
125
126
    /**
127
     * @return array
128
     */
129
    public function getParams(): ?array
130
    {
131
        return $this->params;
132
    }
133
134
    /**
135
     * @param string $key
136
     * @param array  ...$deepKeys
137
     * @return mixed|null
138
     */
139
    public function getParam(string $key, ...$deepKeys)
140
    {
141
        $param = $this->params[$key] ?? null;
142
        foreach ($deepKeys as $key) {
143
            if (!isset($param[$key])) {
144
                return null;
145
            }
146
            $param = $param[$key];
147
        }
148
        return $param;
149
    }
150
151
    /**
152
     * @param string $key
153
     * @return bool
154
     */
155
    public function hasParam(string $key, ...$deepKeys): bool
156
    {
157
        return [] === $deepKeys ? array_key_exists($key, $this->params) : null !== $this->getParam($key, ...$deepKeys);
158
    }
159
160
    /**
161
     * @param string $key
162
     * @param        $value
163
     * @return QueryString
164
     */
165
    public function withParam(string $key, $value): self
166
    {
167
        $clone = clone $this;
168
        $clone->params[$key] = $value;
169
        return $clone;
170
    }
171
172
    /**
173
     * @param array $params
174
     * @return QueryString
175
     */
176
    public function withParams(array $params): self
177
    {
178
        $clone = clone $this;
179
        $clone->params = [];
180
        foreach ($params as $key => $value) {
181
            $clone->params[(string) $key] = $value;
182
        }
183
        return $clone;
184
    }
185
186
    /**
187
     * @param string $key
188
     * @param array  ...$deepKeys
189
     * @return QueryString
190
     */
191
    public function withoutParam(string $key, ...$deepKeys): self
192
    {
193
        $clone = clone $this;
194
195
        // $key does not exist
196
        if (!isset($clone->params[$key])) {
197
            return $clone;
198
        }
199
200
        // $key exists and there are no $deepKeys
201
        if ([] === $deepKeys) {
202
            unset($clone->params[$key]);
203
            return $clone;
204
        }
205
206
        // Deepkeys
207
        $clone->params[$key] = $this->removeFromPath($clone->params[$key], ...$deepKeys);
208
        return $clone;
209
    }
210
211
    /**
212
     * @return QueryStringRendererInterface
213
     */
214
    public function getRenderer(): QueryStringRendererInterface
215
    {
216
        return $this->renderer;
217
    }
218
219
    /**
220
     * @param QueryStringRendererInterface $renderer
221
     * @return QueryString
222
     */
223
    public function withRenderer(QueryStringRendererInterface $renderer): self
224
    {
225
        $clone = clone $this;
226
        $clone->renderer = $renderer;
227
        return $clone;
228
    }
229
230
    /**
231
     * @return string
232
     */
233
    public function __toString(): string
234
    {
235
        return $this->renderer->render($this);
236
    }
237
238
    /**
239
     * @param array $array
240
     * @return bool
241
     */
242
    private function isAnIndexedArray(array $array): bool
243
    {
244
        $keys = array_keys($array);
245
        return $keys === array_filter($keys, 'is_int');
246
    }
247
248
    /**
249
     * @param array $params
250
     * @param array ...$keys
251
     * @return array
252
     */
253
    private function removeFromPath(array $params, ...$keys): array
254
    {
255
        $nbKeys = count($keys);
256
        $lastIndex = $nbKeys - 1;
257
        $cursor = &$params;
258
259
        foreach ($keys as $k => $key) {
260
            if (!isset($cursor[$key])) {
261
                return $params; // End here if not found
262
            }
263
264
            if ($k === $lastIndex) {
265
                unset($cursor[$key]);
266
                if (is_array($cursor) && $this->isAnIndexedArray($cursor)) {
267
                    $cursor = array_values($cursor);
268
                }
269
                break;
270
            }
271
272
            $cursor = &$cursor[$key];
273
        }
274
275
        return $params;
276
    }
277
278
    /**
279
     * Returns the default renderer.
280
     *
281
     * @return QueryStringRendererInterface
282
     */
283
    public static function getDefaultRenderer(): QueryStringRendererInterface
284
    {
285
        if (!isset(self::$defaultRenderer)) {
286
            self::restoreDefaultRenderer();
287
        }
288
        return self::$defaultRenderer;
289
    }
290
291
    /**
292
     * Changes default renderer.
293
     *
294
     * @param QueryStringRendererInterface $defaultRenderer
295
     */
296
    public static function setDefaultRenderer(QueryStringRendererInterface $defaultRenderer): void
297
    {
298
        self::$defaultRenderer = $defaultRenderer;
299
    }
300
301
    /**
302
     * Restores the default renderer.
303
     */
304
    public static function restoreDefaultRenderer(): void
305
    {
306
        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...
307
    }
308
}
309