Completed
Push — master ( 9819c1...f2d6e9 )
by Rafael
06:19
created

SearchRequest   C

Complexity

Total Complexity 60

Size/Duplication

Total Lines 635
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 95.65%

Importance

Changes 0
Metric Value
wmc 60
lcom 1
cbo 3
dl 0
loc 635
ccs 176
cts 184
cp 0.9565
rs 5.7727
c 0
b 0
f 0

41 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 17 2
A getId() 0 4 1
A mergeArguments() 0 11 1
A prefixWithNamespace() 0 4 1
A getActiveFacetNames() 0 11 1
A getActiveFacetValuesByName() 0 14 2
A getActiveFacets() 0 7 2
A getActiveFacetCount() 0 4 1
A setActiveFacets() 0 7 1
A addFacetValue() 0 13 2
A removeFacetValue() 0 19 4
A removeAllFacetValuesByName() 0 12 1
A removeAllFacets() 0 7 1
A getHasFacetValue() 0 5 1
A getHasSorting() 0 5 1
A getSorting() 0 5 1
A getSortingPart() 0 10 3
A getSortingName() 0 4 1
A getSortingDirection() 0 4 1
A removeSorting() 0 7 1
A setSorting() 0 8 1
A setPage() 0 7 1
A getPage() 0 5 1
A setGroupItemPage() 0 7 1
A getGroupItemPage() 0 5 1
B getHighestGroupPage() 0 16 5
A setRawQueryString() 0 7 1
A getRawUserQuery() 0 6 2
A getRawUserQueryIsEmptyString() 0 15 3
A getRawUserQueryIsNull() 0 6 1
A setResultsPerPage() 0 8 1
A getStateChanged() 0 4 1
A getResultsPerPage() 0 5 1
A getContextSystemLanguageUid() 0 4 1
A getContextPageUid() 0 4 1
A getContextTypoScriptConfiguration() 0 4 1
A reset() 0 6 1
B getCopyForSubRequest() 0 27 4
A getArgumentNameSpace() 0 4 1
A getAsArray() 0 4 1
A getArguments() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like SearchRequest 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 SearchRequest, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace ApacheSolrForTypo3\Solr\Domain\Search;
4
5
/***************************************************************
6
 *  Copyright notice
7
 *
8
 *  (c) 2015-2016 Timo Schmidt <[email protected]>
9
 *  All rights reserved
10
 *
11
 *  This script is part of the TYPO3 project. The TYPO3 project is
12
 *  free software; you can redistribute it and/or modify
13
 *  it under the terms of the GNU General Public License as published by
14
 *  the Free Software Foundation; either version 2 of the License, or
15
 *  (at your option) any later version.
16
 *
17
 *  The GNU General Public License can be found at
18
 *  http://www.gnu.org/copyleft/gpl.html.
19
 *
20
 *  This script is distributed in the hope that it will be useful,
21
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23
 *  GNU General Public License for more details.
24
 *
25
 *  This copyright notice MUST APPEAR in all copies of the script!
26
 ***************************************************************/
27
28
use ApacheSolrForTypo3\Solr\System\Configuration\TypoScriptConfiguration;
29
use ApacheSolrForTypo3\Solr\System\Util\ArrayAccessor;
30
use TYPO3\CMS\Core\Utility\ArrayUtility;
31
32
/**
33
 * The searchRequest is used to act as an api to the arguments that have been passed
34
 * with GET and POST.
35
 *
36
 * @author Timo Schmidt <[email protected]>
37
 */
38
class SearchRequest
39
{
40
    /**
41
     * @var string
42
     */
43
    protected $id;
44
45
    /**
46
     * Default namespace overwritten with the configured plugin namespace.
47
     *
48
     * @var string
49
     */
50
    protected $argumentNameSpace = 'tx_solr';
51
52
    /**
53
     * Arguments that should be kept for sub requests.
54
     *
55
     * Default values, overwritten in the constructor with the namespaced arguments
56
     *
57
     * @var array
58
     */
59
    protected $persistentArgumentsPaths = ['tx_solr:q', 'tx_solr:filter', 'tx_solr:sort'];
60
61
    /**
62
     * @var bool
63
     */
64
    protected $stateChanged = false;
65
66
    /**
67
     * @var ArrayAccessor
68
     */
69
    protected $argumentsAccessor;
70
71
    /**
72
     * The sys_language_uid that was used in the context where the request was build.
73
     * This could be different from the "L" parameter and and not relevant for urls,
74
     * because typolink itself will handle it.
75
     *
76
     * @var int
77
     */
78
    protected $contextSystemLanguageUid;
79
80
    /**
81
     * The page_uid that was used in the context where the request was build.
82
     *
83
     * The pageUid is not relevant for the typolink additionalArguments and therefore
84
     * a separate property.
85
     *
86
     * @var int
87
     */
88
    protected $contextPageUid;
89
90
    /**
91
     * @var TypoScriptConfiguration
92
     */
93
    protected $contextTypoScriptConfiguration;
94
95
    /**
96
     * @var array
97
     */
98
    protected $persistedArguments = [];
99
100
    /**
101
     * @param array $argumentsArray
102
     * @param int $pageUid
103
     * @param int $sysLanguageUid
104
     * @param TypoScriptConfiguration $typoScriptConfiguration
105 86
     */
106
    public function __construct(array $argumentsArray = [], $pageUid = 0, $sysLanguageUid = 0, TypoScriptConfiguration $typoScriptConfiguration = null)
107 86
    {
108 86
        $this->stateChanged = true;
109 86
        $this->persistedArguments = $argumentsArray;
110 86
        $this->contextPageUid = $pageUid;
111 86
        $this->contextSystemLanguageUid = $sysLanguageUid;
112 86
        $this->contextTypoScriptConfiguration = $typoScriptConfiguration;
113
        $this->id = spl_object_hash($this);
114
115 86
        // overwrite the plugin namespace and the persistentArgumentsPaths
116 44
        if (!is_null($typoScriptConfiguration)) {
117
            $this->argumentNameSpace = $typoScriptConfiguration->getSearchPluginNamespace();
118
        }
119 86
120 86
        $this->persistentArgumentsPaths = [$this->argumentNameSpace . ':q', $this->argumentNameSpace . ':filter', $this->argumentNameSpace . ':sort', $this->argumentNameSpace . ':groupPage'];
121 86
        $this->reset();
122
    }
123
124
    /**
125
     * @return string
126 28
     */
127
    public function getId()
128 28
    {
129
        return $this->id;
130
    }
131
132
    /**
133
     * Can be used do merge arguments into the request arguments
134
     *
135
     * @param array $argumentsToMerge
136
     * @return SearchRequest
137 1
     */
138
    public function mergeArguments(array $argumentsToMerge)
139 1
    {
140 1
        ArrayUtility::mergeRecursiveWithOverrule(
141 1
            $this->persistedArguments,
142
            $argumentsToMerge
143
        );
144 1
145
        $this->reset();
146 1
147
        return $this;
148
    }
149
150
    /**
151
     * Helper method to prefix an accessor with the arguments namespace.
152
     *
153
     * @param string $path
154
     * @return string
155 68
     */
156
    protected function prefixWithNamespace($path)
157 68
    {
158
        return $this->argumentNameSpace . ':' . $path;
159
    }
160
161
    /**
162
     * @return array
163 25
     */
164
    public function getActiveFacetNames()
165 25
    {
166 25
        $activeFacets = $this->getActiveFacets();
167
        $facetNames = [];
168 25
169 2
        array_map(function($activeFacet) use (&$facetNames) {
170 25
            $facetNames[] = substr($activeFacet, 0, strpos($activeFacet, ':'));
171
        }, $activeFacets);
172 25
173
        return $facetNames;
174
    }
175
176
    /**
177
     * Returns all facet values for a certain facetName
178
     * @param string $facetName
179
     * @return array
180 36
     */
181
    public function getActiveFacetValuesByName($facetName)
182 36
    {
183 36
        $values = [];
184
        $activeFacets = $this->getActiveFacets();
185 36
186 9
        array_map(function($activeFacet) use (&$values, $facetName) {
187 9
            $parts = explode(':', $activeFacet, 2);
188 9
            if ($parts[0] === $facetName) {
189
                $values[] = $parts[1];
190 36
            }
191
        }, $activeFacets);
192 36
193
        return $values;
194
    }
195
196
    /**
197
     * @return array
198 43
     */
199
    public function getActiveFacets()
200 43
    {
201 43
        $path = $this->prefixWithNamespace('filter');
202
        $pathValue = $this->argumentsAccessor->get($path, []);
203 43
204
        return is_array($pathValue) ? $pathValue : [];
205
    }
206
207
    /**
208
     * @return int
209 2
     */
210
    public function getActiveFacetCount()
211 2
    {
212
        return count($this->getActiveFacets());
213
    }
214
215
    /**
216
     * @param $activeFacets
217
     *
218
     * @return SearchRequest
219 37
     */
220
    protected function setActiveFacets($activeFacets = [])
221 37
    {
222 37
        $path = $this->prefixWithNamespace('filter');
223
        $this->argumentsAccessor->set($path, $activeFacets);
224 37
225
        return $this;
226
    }
227
228
    /**
229
     * Adds a facet value to the request.
230
     *
231
     * @param string $facetName
232
     * @param mixed $facetValue
233
     *
234
     * @return SearchRequest
235 32
     */
236
    public function addFacetValue($facetName, $facetValue)
237 32
    {
238 1
        if ($this->getHasFacetValue($facetName, $facetValue)) {
239
            return $this;
240
        }
241 32
242 32
        $facetValues = $this->getActiveFacets();
243 32
        $facetValues[] = $facetName . ':' . $facetValue;
244
        $this->setActiveFacets($facetValues);
245 32
246 32
        $this->stateChanged = true;
247
        return $this;
248
    }
249
250
    /**
251
     * Removes a facet value from the request.
252
     *
253
     * @param string $facetName
254
     * @param mixed $facetValue
255
     *
256
     * @return SearchRequest
257 4
     */
258
    public function removeFacetValue($facetName, $facetValue)
259 4
    {
260
        if (!$this->getHasFacetValue($facetName, $facetValue)) {
261
            return $this;
262 4
        }
263 4
        $facetValues = $this->getActiveFacets();
264
        $facetValueToLookFor = $facetName . ':' . $facetValue;
265 4
266 4
        foreach ($facetValues as $index => $facetValue) {
267 4
            if ($facetValue === $facetValueToLookFor) {
268 4
                unset($facetValues[$index]);
269
                break;
270
            }
271
        }
272 4
273 4
        $this->setActiveFacets($facetValues);
274 4
        $this->stateChanged = true;
275
        return $this;
276
    }
277
278
    /**
279
     * Removes all facet values from the request by a certain facet name
280
     *
281
     * @param string $facetName
282
     *
283
     * @return SearchRequest
284 3
     */
285
    public function removeAllFacetValuesByName($facetName)
286 3
    {
287 3
        $facetValues = $this->getActiveFacets();
288 2
        $facetValues = array_filter($facetValues, function($facetValue) use ($facetName) {
289 2
            $parts = explode(':', $facetValue, 2);
290 3
            return $parts[0] !== $facetName;
291
        });
292 3
293 3
        $this->setActiveFacets($facetValues);
294 3
        $this->stateChanged = true;
295
        return $this;
296
    }
297
298
    /**
299
     * Removes all active facets from the request.
300
     *
301
     * @return SearchRequest
302 4
     */
303
    public function removeAllFacets()
304 4
    {
305 4
        $path = $this->prefixWithNamespace('filter');
306 4
        $this->argumentsAccessor->reset($path);
307 4
        $this->stateChanged = true;
308
        return $this;
309
    }
310
311
    /**
312
     * @param string $facetName
313
     * @param mixed $facetValue
314
     * @return bool
315 36
     */
316
    public function getHasFacetValue($facetName, $facetValue)
317 36
    {
318 36
        $facetNameAndValueToCheck = $facetName . ':' . $facetValue;
319
        return in_array($facetNameAndValueToCheck, $this->getActiveFacets());
320
    }
321
322
    /**
323
     * @return bool
324 40
     */
325
    public function getHasSorting()
326 40
    {
327 40
        $path = $this->prefixWithNamespace('sort');
328
        return $this->argumentsAccessor->has($path);
329
    }
330
331
    /**
332
     * Returns the sorting string in the url e.g. title asc.
333
     *
334
     * @return string
335 39
     */
336
    public function getSorting()
337 39
    {
338 39
        $path = $this->prefixWithNamespace('sort');
339
        return $this->argumentsAccessor->get($path, '');
340
    }
341
342
    /**
343
     * Helper function to get the sorting configuration name or direction.
344
     *
345
     * @param int $index
346
     * @return string
347 39
     */
348
    protected function getSortingPart($index)
349 39
    {
350 39
        $sorting = $this->getSorting();
351 37
        if ($sorting === '') {
352
            return null;
353
        }
354 2
355 2
        $parts = explode(' ', $sorting);
356
        return isset($parts[$index]) ? $parts[$index] : null;
357
    }
358
359
    /**
360
     * Returns the sorting configuration name that is currently used.
361
     *
362
     * @return string
363 39
     */
364
    public function getSortingName()
365 39
    {
366
        return $this->getSortingPart(0);
367
    }
368
369
    /**
370
     * Returns the sorting direction that is currently used.
371
     *
372
     * @return string
373 38
     */
374
    public function getSortingDirection()
375 38
    {
376
        return mb_strtolower($this->getSortingPart(1));
377
    }
378
379
    /**
380
     * @return SearchRequest
381 28
     */
382
    public function removeSorting()
383 28
    {
384 28
        $path = $this->prefixWithNamespace('sort');
385 28
        $this->argumentsAccessor->reset($path);
386 28
        $this->stateChanged = true;
387
        return $this;
388
    }
389
390
    /**
391
     * @param string $sortingName
392
     * @param string $direction (asc or desc)
393
     *
394
     * @return SearchRequest
395 29
     */
396
    public function setSorting($sortingName, $direction = 'asc')
397 29
    {
398 29
        $value = $sortingName . ' ' . $direction;
399 29
        $path = $this->prefixWithNamespace('sort');
400 29
        $this->argumentsAccessor->set($path, $value);
401 29
        $this->stateChanged = true;
402
        return $this;
403
    }
404
405
    /**
406
     * Method to set the paginated page of the search
407
     *
408
     * @param int $page
409
     * @return SearchRequest
410 13
     */
411
    public function setPage($page)
412 13
    {
413 13
        $this->stateChanged = true;
414 13
        $path = $this->prefixWithNamespace('page');
415 13
        $this->argumentsAccessor->set($path, $page);
416
        return $this;
417
    }
418
419
    /**
420
     * Returns the passed page.
421
     *
422
     * @return int|null
423 41
     */
424
    public function getPage()
425 41
    {
426 41
        $path = $this->prefixWithNamespace('page');
427
        return $this->argumentsAccessor->get($path);
428
    }
429
430
    /**
431
     * Can be used to paginate within a groupItem.
432
     *
433
     * @param string $groupName e.g. type
434
     * @param string $groupItemValue e.g. pages
435 38
     * @param int $page
436
     * @return SearchRequest
437 38
     */
438 38
    public function setGroupItemPage(string $groupName, string $groupItemValue, int $page): SearchRequest
439 38
    {
440 38
        $this->stateChanged = true;
441
        $path = $this->prefixWithNamespace('groupPage:' . $groupName . ':' . $groupItemValue);
442
        $this->argumentsAccessor->set($path, $page);
443
        return $this;
444
    }
445
446
    /**
447
     * Retrieves the current page for this group item.
448 41
     *
449
     * @param string $groupName
450 41
     * @param string $groupItemValue
451 41
     * @return int
452 41
     */
453
    public function getGroupItemPage(string $groupName, string $groupItemValue): int
454
    {
455
        $path = $this->prefixWithNamespace('groupPage:' . $groupName . ':' . $groupItemValue);
456
        return max(1, (int)$this->argumentsAccessor->get($path));
457
    }
458
459
    /**
460
     * Retrieves the highest page of the groups.
461
     *
462 38
     * @return int
463
     */
464 38
    public function getHighestGroupPage()
465 38
    {
466
        $max = 1;
467 38
        $path = $this->prefixWithNamespace('groupPage');
468 4
        $groupPages = $this->argumentsAccessor->get($path);
469
        foreach ($groupPages as $groups) {
0 ignored issues
show
Bug introduced by
The expression $groupPages of type array|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
470
            if (!is_array($groups)) continue;
471 34
            foreach ($groups as $groupItemPage) {
472
                if ($groupItemPage > $max) {
473
                    $max = $groupItemPage;
474
                }
475 34
            }
476
        }
477
478
        return $max;
479
    }
480
481
    /**
482
     * Method to overwrite the query string.
483
     *
484 40
     * @param string $rawQueryString
485
     * @return SearchRequest
486 40
     */
487 40
    public function setRawQueryString($rawQueryString)
488 40
    {
489
        $this->stateChanged = true;
490
        $path = $this->prefixWithNamespace('q');
491
        $this->argumentsAccessor->set($path, $rawQueryString);
492
        return $this;
493
    }
494
495
    /**
496
     * Returns the passed rawQueryString.
497 43
     *
498
     * @return string|null
499 43
     */
500 43
    public function getRawUserQuery()
501 43
    {
502
        $path = $this->prefixWithNamespace('q');
503 43
        $query = $this->argumentsAccessor->get($path, null);
504
        return is_null($query) ? $query : (string)$query;
505
    }
506
507
    /**
508
     * Method to check if the query string is an empty string
509 1
     * (also empty string or whitespaces only are handled as empty).
510
     *
511 1
     * When no query string is set (null) the method returns false.
512
     * @return bool
513
     */
514
    public function getRawUserQueryIsEmptyString()
515
    {
516
        $path = $this->prefixWithNamespace('q');
517
        $query = $this->argumentsAccessor->get($path, null);
518 42
519
        if ($query === null) {
520 42
            return false;
521 42
        }
522
523
        if (trim($query) === '') {
524
            return true;
525
        }
526
527 30
        return false;
528
    }
529 30
530
    /**
531
     * This method returns true when no querystring is present at all.
532
     * Which means no search by the user was triggered
533
     *
534
     * @return bool
535 30
     */
536
    public function getRawUserQueryIsNull()
537 30
    {
538
        $path = $this->prefixWithNamespace('q');
539
        $query = $this->argumentsAccessor->get($path, null);
540
        return $query === null;
541
    }
542
543
    /**
544
     * Sets the results per page that are used during search.
545 48
     *
546
     * @param int $resultsPerPage
547 48
     * @return SearchRequest
548
     */
549
    public function setResultsPerPage($resultsPerPage)
550
    {
551
        $path = $this->prefixWithNamespace('resultsPerPage');
552
        $this->argumentsAccessor->set($path, $resultsPerPage);
553
        $this->stateChanged = true;
554
555 84
        return $this;
556
    }
557 84
558 84
    /**
559 84
     * @return bool
560
     */
561
    public function getStateChanged()
562
    {
563
        return $this->stateChanged;
564
    }
565
566
    /**
567
     * Returns the passed resultsPerPage value
568 35
     * @return int|null
569
     */
570 35
    public function getResultsPerPage()
571
    {
572
        $path = $this->prefixWithNamespace('resultsPerPage');
573
        return $this->argumentsAccessor->get($path);
574
    }
575
576
    /**
577
     * @return int
578
     */
579
    public function getContextSystemLanguageUid()
580
    {
581 35
        return $this->contextSystemLanguageUid;
582 35
    }
583 35
584 35
    /**
585
     * @return int
586
     */
587
    public function getContextPageUid()
588 35
    {
589 35
        return $this->contextPageUid;
590 35
    }
591 35
592 35
    /**
593
     * Get contextTypoScriptConfiguration
594
     *
595
     * @return TypoScriptConfiguration
596
     */
597
    public function getContextTypoScriptConfiguration()
598
    {
599 22
        return $this->contextTypoScriptConfiguration;
600
    }
601 22
602
    /**
603
     * Assigns the last known persistedArguments and restores their state.
604
     *
605
     * @return SearchRequest
606
     */
607 44
    public function reset()
608
    {
609 44
        $this->argumentsAccessor = new ArrayAccessor($this->persistedArguments);
610
        $this->stateChanged = false;
611
        return $this;
612
    }
613
614
    /**
615
     * This can be used to start a new sub request, e.g. for a faceted search.
616
     *
617 32
     * @param bool $onlyPersistentArguments
618 32
     * @return SearchRequest
619
     */
620
    public function getCopyForSubRequest($onlyPersistentArguments = true)
621
    {
622
        if (!$onlyPersistentArguments) {
623
            // create a new request with all data
624
            $argumentsArray = $this->argumentsAccessor->getData();
625
            return new SearchRequest(
626
                $argumentsArray,
627
                $this->contextPageUid,
628
                $this->contextSystemLanguageUid,
629
                $this->contextTypoScriptConfiguration
630
            );
631
        }
632
633
        $arguments = new ArrayAccessor();
634
        foreach ($this->persistentArgumentsPaths as $persistentArgumentPath) {
635
            if ($this->argumentsAccessor->has($persistentArgumentPath)) {
636
                $arguments->set($persistentArgumentPath, $this->argumentsAccessor->get($persistentArgumentPath));
637
            }
638
        }
639
640
        return new SearchRequest(
641
            $arguments->getData(),
642
            $this->contextPageUid,
643
            $this->contextSystemLanguageUid,
644
            $this->contextTypoScriptConfiguration
645
        );
646
    }
647
648
    /**
649
     * @return string
650
     */
651
    public function getArgumentNameSpace()
652
    {
653
        return $this->argumentNameSpace;
654
    }
655
656
    /**
657
     * @return array
658
     */
659
    public function getAsArray()
660
    {
661
        return $this->argumentsAccessor->getData();
662
    }
663
664
    /**
665
     * Returns only the arguments as array.
666
     *
667
     * @return array
668
     */
669
    public function getArguments() {
670
        return $this->argumentsAccessor->get($this->argumentNameSpace, []);
671
    }
672
}
673