Completed
Pull Request — master (#150)
by
unknown
03:45
created

SearchQuery   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 370
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 84
dl 0
loc 370
rs 8.8798
c 0
b 0
f 0
wmc 44

32 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 3
A inClass() 0 4 1
A getAdapter() 0 3 1
A setHandler() 0 5 1
A setCriteria() 0 4 1
A getLimit() 0 3 1
A getFilters() 0 3 1
A exclude() 0 4 1
A addFuzzySearchTerm() 0 9 2
A addExclude() 0 6 3
A setPageSize() 0 5 1
A addSearchTerm() 0 9 2
A setStart() 0 4 1
A getSearchTerms() 0 3 1
A page() 0 4 1
A limit() 0 4 1
A setLimit() 0 4 1
A isFiltered() 0 3 4
A __toString() 0 3 1
A getClassFilters() 0 3 1
A filterBy() 0 13 2
A search() 0 4 1
A start() 0 4 1
A getPageSize() 0 3 1
A fuzzysearch() 0 4 1
A addFilter() 0 6 3
A filter() 0 4 1
A addCriteria() 0 4 1
A getExcludes() 0 3 1
A addClassFilter() 0 7 1
A getCriteria() 0 3 1
A getStart() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like SearchQuery 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 SearchQuery, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace SilverStripe\FullTextSearch\Search\Queries;
4
5
use SilverStripe\Dev\Deprecation;
6
use SilverStripe\FullTextSearch\Search\Adapters\SearchAdapterInterface;
7
use SilverStripe\FullTextSearch\Search\Criteria\SearchCriteria;
8
use SilverStripe\FullTextSearch\Search\Criteria\SearchCriteriaInterface;
9
use SilverStripe\View\ViewableData;
10
use stdClass;
11
12
/**
13
 * Represents a search query
14
 *
15
 * API very much still in flux.
16
 */
17
class SearchQuery extends ViewableData
18
{
19
    public static $missing = null;
20
    public static $present = null;
21
22
    public static $default_page_size = 10;
23
24
    /** These are public, but only for index & variant access - API users should not manually access these */
25
26
    public $search = [];
27
28
    public $classes = [];
29
30
    public $require = [];
31
    public $exclude = [];
32
33
    /**
34
     * @var SearchCriteriaInterface[]
35
     */
36
    public $criteria = [];
37
38
    protected $start = 0;
39
    protected $limit = -1;
40
41
    /**
42
     * @var SearchAdapterInterface
43
     */
44
    protected $adapter = null;
45
46
    /** These are the API functions */
47
48
    /**
49
     * SearchQuery constructor.
50
     * @throws \Psr\Container\NotFoundExceptionInterface
51
     */
52
    public function __construct()
53
    {
54
        if (self::$missing === null) {
55
            self::$missing = new stdClass();
56
        }
57
        if (self::$present === null) {
58
            self::$present = new stdClass();
59
        }
60
    }
61
62
    /**
63
     * @param SearchAdapterInterface $adapter
64
     * @return SearchQuery
65
     */
66
    public function setHandler(SearchAdapterInterface $adapter)
67
    {
68
        $this->adapter = $adapter;
69
70
        return $this;
71
    }
72
73
    /**
74
     * @param string $text   Search terms. Exact format (grouping, boolean expressions, etc.) depends on
75
     *                       the search implementation.
76
     * @param array  $fields Limits the search to specific fields (using composite field names)
77
     * @param array  $boost  Map of composite field names to float values. The higher the value,
78
     *                       the more important the field gets for relevancy.
79
     */
80
    public function addSearchTerm($text, $fields = null, $boost = [])
81
    {
82
        $this->search[] = [
83
            'text' => $text,
84
            'fields' => $fields ? (array) $fields : null,
85
            'boost' => $boost,
86
            'fuzzy' => false
87
        ];
88
        return $this;
89
    }
90
91
    /**
92
     * Similar to {@link addSearchTerm()}, but uses stemming and other similarity algorithms
93
     * to find the searched terms. For example, a term "fishing" would also likely find results
94
     * containing "fish" or "fisher". Depends on search implementation.
95
     *
96
     * @param string $text   See {@link addSearchTerm()}
97
     * @param array  $fields See {@link addSearchTerm()}
98
     * @param array  $boost  See {@link addSearchTerm()}
99
     */
100
    public function addFuzzySearchTerm($text, $fields = null, $boost = [])
101
    {
102
        $this->search[] = [
103
            'text' => $text,
104
            'fields' => $fields ? (array) $fields : null,
105
            'boost' => $boost,
106
            'fuzzy' => true
107
        ];
108
        return $this;
109
    }
110
111
    /**
112
     * @return array
113
     */
114
    public function getSearchTerms()
115
    {
116
        return $this->search;
117
    }
118
119
    /**
120
     * @param string $class
121
     * @param bool $includeSubclasses
122
     * @return $this
123
     */
124
    public function addClassFilter($class, $includeSubclasses = true)
125
    {
126
        $this->classes[] = [
127
            'class' => $class,
128
            'includeSubclasses' => $includeSubclasses
129
        ];
130
        return $this;
131
    }
132
133
    /**
134
     * @return array
135
     */
136
    public function getClassFilters()
137
    {
138
        return $this->classes;
139
    }
140
141
    /**
142
     * Similar to {@link addSearchTerm()}, but typically used to further narrow down
143
     * based on other facets which don't influence the field relevancy.
144
     *
145
     * @param string $field  Composite name of the field
146
     * @param mixed  $values Scalar value, array of values, or an instance of SearchQuery_Range
147
     */
148
    public function addFilter($field, $values)
149
    {
150
        $requires = isset($this->require[$field]) ? $this->require[$field] : [];
151
        $values = is_array($values) ? $values : [$values];
152
        $this->require[$field] = array_merge($requires, $values);
153
        return $this;
154
    }
155
156
    /**
157
     * @return array
158
     */
159
    public function getFilters()
160
    {
161
        return $this->require;
162
    }
163
164
    /**
165
     * Excludes results which match these criteria, inverse of {@link addFilter()}.
166
     *
167
     * @param string $field
168
     * @param mixed $values
169
     */
170
    public function addExclude($field, $values)
171
    {
172
        $excludes = isset($this->exclude[$field]) ? $this->exclude[$field] : [];
173
        $values = is_array($values) ? $values : [$values];
174
        $this->exclude[$field] = array_merge($excludes, $values);
175
        return $this;
176
    }
177
178
    /**
179
     * @return array
180
     */
181
    public function getExcludes()
182
    {
183
        return $this->exclude;
184
    }
185
186
    /**
187
     * You can pass through a string value, Criteria object, or Criterion object for $target.
188
     *
189
     * String value might be "SiteTree_Title" or whatever field in your index that you're trying to target.
190
     *
191
     * If you require complex filtering then you can build your Criteria object first with multiple layers/levels of
192
     * Criteria, and then pass it in here when you're ready.
193
     *
194
     * If you have your own Criterion object that you've created that you want to use, you can also pass that in here.
195
     *
196
     * @param string|SearchCriteriaInterface $target
197
     * @param mixed $value
198
     * @param string|null $comparison
199
     * @param AbstractSearchQueryWriter $searchQueryWriter
200
     * @return SearchCriteriaInterface
201
     */
202
    public function filterBy(
203
        $target,
204
        $value = null,
205
        $comparison = null,
206
        AbstractSearchQueryWriter $searchQueryWriter = null
207
    ) {
208
        if (!$target instanceof SearchCriteriaInterface) {
209
            $target = new SearchCriteria($target, $value, $comparison, $searchQueryWriter);
210
        }
211
212
        $this->addCriteria($target);
213
214
        return $target;
215
    }
216
217
    public function setStart($start)
218
    {
219
        $this->start = $start;
220
        return $this;
221
    }
222
223
    /**
224
     * @return int
225
     */
226
    public function getStart()
227
    {
228
        return $this->start;
229
    }
230
231
    public function setLimit($limit)
232
    {
233
        $this->limit = $limit;
234
        return $this;
235
    }
236
237
    /**
238
     * @return int
239
     */
240
    public function getLimit()
241
    {
242
        return $this->limit;
243
    }
244
245
    public function setPageSize($page)
246
    {
247
        $this->setStart($page * self::$default_page_size);
248
        $this->setLimit(self::$default_page_size);
249
        return $this;
250
    }
251
252
    /**
253
     * @return int
254
     */
255
    public function getPageSize()
256
    {
257
        return (int) ($this->getLimit() / $this->getStart());
258
    }
259
260
    /**
261
     * @return bool
262
     */
263
    public function isFiltered()
264
    {
265
        return $this->search || $this->classes || $this->require || $this->exclude;
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->search of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
Bug Best Practice introduced by
The expression $this->exclude of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
Bug Best Practice introduced by
The expression $this->require of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
Bug Best Practice introduced by
The expression $this->classes of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
266
    }
267
268
    /**
269
     * @return SearchAdapterInterface
270
     */
271
    public function getAdapter()
272
    {
273
        return $this->adapter;
274
    }
275
276
    public function __toString()
277
    {
278
        return "Search Query\n";
279
    }
280
281
    /**
282
     * @codeCoverageIgnore
283
     * @deprecated
284
     */
285
    public function search($text, $fields = null, $boost = [])
286
    {
287
        Deprecation::notice('4.0', 'Use addSearchTerm() instead');
288
        return $this->addSearchTerm($text, $fields, $boost);
289
    }
290
291
    /**
292
     * @codeCoverageIgnore
293
     * @deprecated
294
     */
295
    public function fuzzysearch($text, $fields = null, $boost = [])
296
    {
297
        Deprecation::notice('4.0', 'Use addFuzzySearchTerm() instead');
298
        return $this->addFuzzySearchTerm($text, $fields, $boost);
299
    }
300
301
    /**
302
     * @codeCoverageIgnore
303
     * @deprecated
304
     */
305
    public function inClass($class, $includeSubclasses = true)
306
    {
307
        Deprecation::notice('4.0', 'Use addClassFilter() instead');
308
        return $this->addClassFilter($class, $includeSubclasses);
309
    }
310
311
    /**
312
     * @codeCoverageIgnore
313
     * @deprecated
314
     */
315
    public function filter($field, $values)
316
    {
317
        Deprecation::notice('4.0', 'Use addFilter() instead');
318
        return $this->addFilter($field, $values);
319
    }
320
321
    /**
322
     * @codeCoverageIgnore
323
     * @deprecated
324
     */
325
    public function exclude($field, $values)
326
    {
327
        Deprecation::notice('4.0', 'Use addExclude() instead');
328
        return $this->addExclude($field, $values);
329
    }
330
331
    /**
332
     * @codeCoverageIgnore
333
     * @deprecated
334
     */
335
    public function start($start)
336
    {
337
        Deprecation::notice('4.0', 'Use setStart() instead');
338
        return $this->setStart($start);
339
    }
340
341
    /**
342
     * @codeCoverageIgnore
343
     * @deprecated
344
     */
345
    public function limit($limit)
346
    {
347
        Deprecation::notice('4.0', 'Use setLimit() instead');
348
        return $this->setLimit($limit);
349
    }
350
351
    /**
352
     * @codeCoverageIgnore
353
     * @deprecated
354
     */
355
    public function page($page)
356
    {
357
        Deprecation::notice('4.0', 'Use setPageSize() instead');
358
        return $this->setPageSize($page);
359
    }
360
361
    /**
362
     * @return SearchCriteriaInterface[]
363
     */
364
    public function getCriteria()
365
    {
366
        return $this->criteria;
367
    }
368
369
    /**
370
     * @param SearchCriteriaInterface[] $criteria
371
     * @return SearchQuery
372
     */
373
    public function setCriteria($criteria)
374
    {
375
        $this->criteria = $criteria;
376
        return $this;
377
    }
378
379
    /**
380
     * @param SearchCriteriaInterface $criteria
381
     * @return SearchQuery
382
     */
383
    public function addCriteria($criteria)
384
    {
385
        $this->criteria[] = $criteria;
386
        return $this;
387
    }
388
}
389