RequestParser   C
last analyzed

Complexity

Total Complexity 53

Size/Duplication

Total Lines 380
Duplicated Lines 0 %

Test Coverage

Coverage 86.92%

Importance

Changes 5
Bugs 0 Features 0
Metric Value
wmc 53
eloc 129
dl 0
loc 380
ccs 113
cts 130
cp 0.8692
rs 6.96
c 5
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A __toString() 0 3 1
A getOffset() 0 3 1
A getLimit() 0 3 1
A setLimit() 0 13 4
A getFilter() 0 3 1
A setOffset() 0 13 4
A setPage() 0 13 4
A getPage() 0 3 1
A __construct() 0 3 1
A getPhrase() 0 3 1
A setFilter() 0 19 4
A parse() 0 10 1
A getQueryHash() 0 17 4
B getQueryParam() 0 28 11
A getSort() 0 3 1
A removeQueryParam() 0 15 2
A setSort() 0 15 4
A setPhrase() 0 11 3
A getQuery() 0 3 2
A setQueryParam() 0 15 2

How to fix   Complexity   

Complex Class

Complex classes like RequestParser often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use RequestParser, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Created by Marcin.
4
 * Date: 16.06.2018
5
 * Time: 13:45
6
 */
7
declare(strict_types=1);
8
9
namespace Mrcnpdlk\Lib\UrlSearchParser;
10
11
use function in_array;
12
use InvalidArgumentException;
13
use function is_array;
14
use function is_string;
15
use Mrcnpdlk\Lib\UrlSearchParser\Criteria\Filter;
16
use Mrcnpdlk\Lib\UrlSearchParser\Criteria\Sort;
17
use Mrcnpdlk\Lib\UrlSearchParser\Exception\InvalidParamException;
18
use RuntimeException;
19
20
class RequestParser
21
{
22
    public const SORT_IDENTIFIER   = 'sort';
23
    public const FILTER_IDENTIFIER = 'filter';
24
    public const LIMIT_IDENTIFIER  = 'limit';
25
    public const OFFSET_IDENTIFIER = 'offset';
26
    public const PAGE_IDENTIFIER   = 'page';
27
    public const PHRASE_IDENTIFIER = 'phrase';
28
    /**
29
     * @var \Mrcnpdlk\Lib\UrlSearchParser\Criteria\Filter
30
     */
31
    private $filter;
32
    /**
33
     * @var int|null
34
     */
35
    private $limit;
36
    /**
37
     * @var int|null
38
     */
39
    private $offset;
40
    /**
41
     * @var int|null
42
     */
43
    private $page;
44
    /**
45
     * @var string|null
46
     */
47
    private $phrase;
48
    /**
49
     * @var array<string,mixed>
50
     */
51
    private $queryParams = [];
52
    /**
53
     * @var \Mrcnpdlk\Lib\UrlSearchParser\Criteria\Sort
54
     */
55
    private $sort;
56
57
    /**
58
     * RequestParser constructor.
59
     *
60
     * @param string $query
61
     *
62
     * @throws \Mrcnpdlk\Lib\UrlSearchParser\Exception\DuplicateParamException
63
     * @throws \Mrcnpdlk\Lib\UrlSearchParser\Exception\EmptyParamException
64
     * @throws \Mrcnpdlk\Lib\UrlSearchParser\Exception\InvalidParamException
65
     */
66 27
    public function __construct(string $query)
67
    {
68 27
        $this->parse($query);
69 17
    }
70
71
    public function __toString(): string
72
    {
73
        return $this->getQuery();
74
    }
75
76
    /**
77
     * @return \Mrcnpdlk\Lib\UrlSearchParser\Criteria\Filter
78
     */
79 5
    public function getFilter(): Filter
80
    {
81 5
        return $this->filter;
82
    }
83
84
    /**
85
     * @param \Mrcnpdlk\Lib\UrlSearchParser\Criteria\Filter $filter
86
     *
87
     * @return $this
88
     */
89 20
    public function setFilter(Filter $filter): self
90
    {
91 20
        $this->filter = $filter;
92
93 20
        $tRes = [];
94 20
        foreach ($filter->toArray() as $item) {
95 4
            if (!array_key_exists($item->param, $tRes)) {
96 4
                $tRes[$item->param] = [];
97
            }
98 4
            $tRes[$item->param][$item->operator] = $item->value;
99
        }
100
101 20
        if (0 === count($tRes)) {
102 16
            unset($this->queryParams[self::FILTER_IDENTIFIER]);
103
        } else {
104 4
            $this->queryParams[self::FILTER_IDENTIFIER] = $tRes;
105
        }
106
107 20
        return $this;
108
    }
109
110
    /**
111
     * @param int|null $default
112
     *
113
     * @return int|null
114
     */
115 2
    public function getLimit(int $default = null): ?int
116
    {
117 2
        return $this->limit ?? $default;
118
    }
119
120
    /**
121
     * @param int|null $limit
122
     *
123
     * @throws \Mrcnpdlk\Lib\UrlSearchParser\Exception\InvalidParamException
124
     *
125
     * @return $this
126
     */
127 20
    public function setLimit(?int $limit): self
128
    {
129 20
        $this->limit = $limit;
130 20
        if (null !== $this->limit && $this->limit < 0) {
131 1
            throw new InvalidParamException('Limit value cannot be lower than 0');
132
        }
133 19
        if (null === $limit) {
134 19
            unset($this->queryParams[self::LIMIT_IDENTIFIER]);
135
        } else {
136
            $this->queryParams[self::LIMIT_IDENTIFIER] = $limit;
137
        }
138
139 19
        return $this;
140
    }
141
142
    /**
143
     * @param int|null $default
144
     *
145
     * @return int|null
146
     */
147 2
    public function getOffset(int $default = null): ?int
148
    {
149 2
        return $this->offset ?? $default;
150
    }
151
152
    /**
153
     * @param int|null $offset
154
     *
155
     * @throws \Mrcnpdlk\Lib\UrlSearchParser\Exception\InvalidParamException
156
     *
157
     * @return $this
158
     */
159 19
    public function setOffset(?int $offset): self
160
    {
161 19
        $this->offset = $offset;
162 19
        if (null !== $this->offset && $this->offset < 0) {
163 1
            throw new InvalidParamException('Offset value cannot be lower than 0');
164
        }
165 18
        if (null === $offset) {
166 18
            unset($this->queryParams[self::OFFSET_IDENTIFIER]);
167
        } else {
168
            $this->queryParams[self::OFFSET_IDENTIFIER] = $offset;
169
        }
170
171 18
        return $this;
172
    }
173
174
    /**
175
     * @param int|null $default
176
     *
177
     * @return int|null
178
     */
179 2
    public function getPage(int $default = null): ?int
180
    {
181 2
        return $this->page ?? $default;
182
    }
183
184
    /**
185
     * @param int|null $page
186
     *
187
     * @throws \Mrcnpdlk\Lib\UrlSearchParser\Exception\InvalidParamException
188
     *
189
     * @return $this
190
     */
191 18
    public function setPage(?int $page): self
192
    {
193 18
        $this->page = $page;
194 18
        if (null !== $this->page && $this->page < 0) {
195 1
            throw new InvalidParamException('Page value cannot be lower than 0');
196
        }
197 17
        if (null === $page) {
198 17
            unset($this->queryParams[self::PAGE_IDENTIFIER]);
199
        } else {
200
            $this->queryParams[self::PAGE_IDENTIFIER] = $page;
201
        }
202
203 17
        return $this;
204
    }
205
206
    /**
207
     * @return string|null
208
     */
209 3
    public function getPhrase(): ?string
210
    {
211 3
        return $this->phrase;
212
    }
213
214
    /**
215
     * @param string|null $phrase
216
     *
217
     * @return $this
218
     */
219 17
    public function setPhrase(?string $phrase): self
220
    {
221 17
        if (null === $phrase || '' === $phrase) {
222 16
            unset($this->queryParams[self::PHRASE_IDENTIFIER]);
223 16
            $this->phrase = null;
224
        } else {
225 1
            $this->phrase                               = $phrase;
226 1
            $this->queryParams[self::PHRASE_IDENTIFIER] = $phrase;
227
        }
228
229 17
        return $this;
230
    }
231
232
    /**
233
     * @param string|null $arg_separator
234
     * @param int         $enc_type
235
     *
236
     * @return string
237
     */
238 1
    public function getQuery(string $arg_separator = null, int $enc_type = \PHP_QUERY_RFC1738): string
239
    {
240 1
        return http_build_query($this->queryParams, null ?? '', $arg_separator ?? (ini_get('arg_separator.output') ?: '&'), $enc_type);
241
    }
242
243
    /**
244
     * @throws \Mrcnpdlk\Lib\UrlSearchParser\Exception
245
     *
246
     * @return string
247
     */
248
    public function getQueryHash(): string
249
    {
250
        try {
251
            $resJson = json_encode($this->queryParams);
252
            if (false === $resJson) {
253
                throw new RuntimeException('Cannot generate json_encode');
254
            }
255
            /** @var string|false $res */
256
            $res = md5($resJson);
257
            if (false === $res) {
258
                throw new RuntimeException('Cannot generate md5 hash');
259
            }
260
        } catch (RuntimeException $e) {
261
            throw new Exception(sprintf('Cannot query hash. %s', $e->getMessage()));
262
        }
263
264
        return $res;
265
    }
266
267
    /**
268
     * @param string      $param
269
     * @param string|null $type    If NULL return value AS IS
270
     * @param mixed|null  $default
271
     *
272
     * @return mixed|null
273
     */
274 27
    public function getQueryParam(string $param, string $type = null, $default = null)
275
    {
276 27
        if (isset($this->queryParams[$param])) {
277 27
            if (null !== $type) {
278 25
                $type = strtolower($type);
279 25
                if (!in_array($type, ['boolean', 'bool', 'integer', 'int', 'float', 'double', 'string', 'array'])) {
280 1
                    throw new InvalidArgumentException(sprintf('Unsupported type [%s]', $type));
281
                }
282
283 24
                $var = $this->queryParams[$param];
284
285 24
                if ('array' === $type && is_string($var)) {
286 2
                    $var = explode(',', $var);
287 23
                } elseif ('string' === $type && is_array($var)) {
288 1
                    $var = implode(',', $var);
289 23
                } elseif (in_array($type, ['boolean', 'bool']) && in_array(strtolower($var), ['true', 'false'])) {
290 2
                    $var = ('true' === strtolower($var));
291 23
                } elseif (!settype($var, $type)) {
292
                    throw new RuntimeException(sprintf('Cannot set type [%s]', $type));
293
                }
294
295 24
                return $var;
296
            }
297
298 2
            return $this->queryParams[$param];
299
        }
300
301 24
        return $default ?? null;
302
    }
303
304
    /**
305
     * @return \Mrcnpdlk\Lib\UrlSearchParser\Criteria\Sort
306
     */
307 4
    public function getSort(): Sort
308
    {
309 4
        return $this->sort;
310
    }
311
312
    /**
313
     * @param \Mrcnpdlk\Lib\UrlSearchParser\Criteria\Sort $sort
314
     *
315
     * @return $this
316
     */
317 24
    public function setSort(Sort $sort): self
318
    {
319 24
        $this->sort = $sort;
320
321 24
        $tRes = [];
322 24
        foreach ($sort->toArray() as $param) {
323 3
            $tRes[] = Sort::DIRECTION_DESC === $param->direction ? Sort::DESC_IDENTIFIER . $param->param : $param->param;
324
        }
325 24
        if (0 === count($tRes)) {
326 21
            unset($this->queryParams[self::SORT_IDENTIFIER]);
327
        } else {
328 3
            $this->queryParams[self::SORT_IDENTIFIER] = implode(Sort::DELIMITER, $tRes);
329
        }
330
331 24
        return $this;
332
    }
333
334
    /**
335
     * @param string $param
336
     *
337
     * @throws \Mrcnpdlk\Lib\UrlSearchParser\Exception\InvalidParamException
338
     *
339
     * @return $this
340
     */
341 2
    public function removeQueryParam(string $param): self
342
    {
343 2
        if (in_array($param, [
344 2
            self::FILTER_IDENTIFIER,
345 2
            self::SORT_IDENTIFIER,
346 2
            self::PHRASE_IDENTIFIER,
347 2
            self::OFFSET_IDENTIFIER,
348 2
            self::LIMIT_IDENTIFIER,
349 2
            self::PAGE_IDENTIFIER,
350 2
        ], true)) {
351 1
            throw new InvalidParamException(sprintf('Cannot remove %s param. Use `set<param name>` with empty arg', $param));
352
        }
353 1
        unset($this->queryParams[$param]);
354
355 1
        return $this;
356
    }
357
358
    /**
359
     * @param string $param
360
     * @param mixed  $value
361
     *
362
     * @throws \Mrcnpdlk\Lib\UrlSearchParser\Exception\InvalidParamException
363
     *
364
     * @return $this
365
     */
366 1
    public function setQueryParam(string $param, $value): self
367
    {
368 1
        if (in_array($param, [
369 1
            self::FILTER_IDENTIFIER,
370 1
            self::SORT_IDENTIFIER,
371 1
            self::PHRASE_IDENTIFIER,
372 1
            self::OFFSET_IDENTIFIER,
373 1
            self::LIMIT_IDENTIFIER,
374 1
            self::PAGE_IDENTIFIER,
375 1
        ], true)) {
376
            throw new InvalidParamException(sprintf('Cannot set %s param. Use `set<param name>` with empty arg', $param));
377
        }
378 1
        $this->queryParams[$param] = $value;
379
380 1
        return $this;
381
    }
382
383
    /**
384
     * @param string $query
385
     *
386
     * @throws \Mrcnpdlk\Lib\UrlSearchParser\Exception\DuplicateParamException
387
     * @throws \Mrcnpdlk\Lib\UrlSearchParser\Exception\EmptyParamException
388
     * @throws \Mrcnpdlk\Lib\UrlSearchParser\Exception\InvalidParamException
389
     */
390 27
    private function parse(string $query): void
391
    {
392 27
        parse_str(rawurldecode($query), $this->queryParams);
393
394 27
        $this->setSort(new Sort($this->getQueryParam(self::SORT_IDENTIFIER, 'string')));
395 24
        $this->setFilter(new Filter($this->getQueryParam(self::FILTER_IDENTIFIER, 'array', [])));
396 20
        $this->setLimit($this->getQueryParam(self::LIMIT_IDENTIFIER, 'int'));
397 19
        $this->setOffset($this->getQueryParam(self::OFFSET_IDENTIFIER, 'int'));
398 18
        $this->setPage($this->getQueryParam(self::PAGE_IDENTIFIER, 'int'));
399 17
        $this->setPhrase($this->getQueryParam(self::PHRASE_IDENTIFIER, 'string'));
400 17
    }
401
}
402