Completed
Push — master ( 585f64...a38077 )
by BENOIT
01:17
created

QueryString::getPairs()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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