Issues (186)

src/Search/Queries/SearchQuery.php (4 issues)

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
     * @return $this
80
     */
81
    public function addSearchTerm($text, $fields = null, $boost = [])
82
    {
83
        $this->search[] = [
84
            'text' => $text,
85
            'fields' => $fields ? (array) $fields : null,
86
            'boost' => $boost,
87
            'fuzzy' => false
88
        ];
89
        return $this;
90
    }
91
92
    /**
93
     * Similar to {@link addSearchTerm()}, but uses stemming and other similarity algorithms
94
     * to find the searched terms. For example, a term "fishing" would also likely find results
95
     * containing "fish" or "fisher". Depends on search implementation.
96
     *
97
     * @param string $text   See {@link addSearchTerm()}
98
     * @param array  $fields See {@link addSearchTerm()}
99
     * @param array  $boost  See {@link addSearchTerm()}
100
     * @return $this
101
     */
102
    public function addFuzzySearchTerm($text, $fields = null, $boost = [])
103
    {
104
        $this->search[] = [
105
            'text' => $text,
106
            'fields' => $fields ? (array) $fields : null,
107
            'boost' => $boost,
108
            'fuzzy' => true
109
        ];
110
        return $this;
111
    }
112
113
    /**
114
     * @return array
115
     */
116
    public function getSearchTerms()
117
    {
118
        return $this->search;
119
    }
120
121
    /**
122
     * @param string $class
123
     * @param bool $includeSubclasses
124
     * @return $this
125
     */
126
    public function addClassFilter($class, $includeSubclasses = true)
127
    {
128
        $this->classes[] = [
129
            'class' => $class,
130
            'includeSubclasses' => $includeSubclasses
131
        ];
132
        return $this;
133
    }
134
135
    /**
136
     * @return array
137
     */
138
    public function getClassFilters()
139
    {
140
        return $this->classes;
141
    }
142
143
    /**
144
     * Similar to {@link addSearchTerm()}, but typically used to further narrow down
145
     * based on other facets which don't influence the field relevancy.
146
     *
147
     * @param string $field  Composite name of the field
148
     * @param mixed  $values Scalar value, array of values, or an instance of SearchQuery_Range
149
     * @return $this
150
     */
151
    public function addFilter($field, $values)
152
    {
153
        $requires = isset($this->require[$field]) ? $this->require[$field] : [];
154
        $values = is_array($values) ? $values : [$values];
155
        $this->require[$field] = array_merge($requires, $values);
156
        return $this;
157
    }
158
159
    /**
160
     * @return array
161
     */
162
    public function getFilters()
163
    {
164
        return $this->require;
165
    }
166
167
    /**
168
     * Excludes results which match these criteria, inverse of {@link addFilter()}.
169
     *
170
     * @param string $field
171
     * @param mixed $values
172
     * @return $this
173
     */
174
    public function addExclude($field, $values)
175
    {
176
        $excludes = isset($this->exclude[$field]) ? $this->exclude[$field] : [];
177
        $values = is_array($values) ? $values : [$values];
178
        $this->exclude[$field] = array_merge($excludes, $values);
179
        return $this;
180
    }
181
182
    /**
183
     * @return array
184
     */
185
    public function getExcludes()
186
    {
187
        return $this->exclude;
188
    }
189
190
    /**
191
     * You can pass through a string value, Criteria object, or Criterion object for $target.
192
     *
193
     * String value might be "SiteTree_Title" or whatever field in your index that you're trying to target.
194
     *
195
     * If you require complex filtering then you can build your Criteria object first with multiple layers/levels of
196
     * Criteria, and then pass it in here when you're ready.
197
     *
198
     * If you have your own Criterion object that you've created that you want to use, you can also pass that in here.
199
     *
200
     * @param string|SearchCriteriaInterface $target
201
     * @param mixed $value
202
     * @param string|null $comparison
203
     * @param AbstractSearchQueryWriter $searchQueryWriter
204
     * @return SearchCriteriaInterface
205
     */
206
    public function filterBy(
207
        $target,
208
        $value = null,
209
        $comparison = null,
210
        AbstractSearchQueryWriter $searchQueryWriter = null
211
    ) {
212
        if (!$target instanceof SearchCriteriaInterface) {
213
            $target = new SearchCriteria($target, $value, $comparison, $searchQueryWriter);
214
        }
215
216
        $this->addCriteria($target);
217
218
        return $target;
219
    }
220
221
    public function setStart($start)
222
    {
223
        $this->start = $start;
224
        return $this;
225
    }
226
227
    /**
228
     * @return int
229
     */
230
    public function getStart()
231
    {
232
        return $this->start;
233
    }
234
235
    public function setLimit($limit)
236
    {
237
        $this->limit = $limit;
238
        return $this;
239
    }
240
241
    /**
242
     * @return int
243
     */
244
    public function getLimit()
245
    {
246
        return $this->limit;
247
    }
248
249
    public function setPageSize($page)
250
    {
251
        $this->setStart($page * self::$default_page_size);
252
        $this->setLimit(self::$default_page_size);
253
        return $this;
254
    }
255
256
    /**
257
     * @return int
258
     */
259
    public function getPageSize()
260
    {
261
        return (int) ($this->getLimit() / $this->getStart());
262
    }
263
264
    /**
265
     * @return bool
266
     */
267
    public function isFiltered()
268
    {
269
        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...
270
    }
271
272
    /**
273
     * @return SearchAdapterInterface
274
     */
275
    public function getAdapter()
276
    {
277
        return $this->adapter;
278
    }
279
280
    public function __toString()
281
    {
282
        return "Search Query\n";
283
    }
284
285
    /**
286
     * @codeCoverageIgnore
287
     * @deprecated
288
     */
289
    public function search($text, $fields = null, $boost = [])
290
    {
291
        Deprecation::notice('4.0', 'Use addSearchTerm() instead');
292
        return $this->addSearchTerm($text, $fields, $boost);
293
    }
294
295
    /**
296
     * @codeCoverageIgnore
297
     * @deprecated
298
     */
299
    public function fuzzysearch($text, $fields = null, $boost = [])
300
    {
301
        Deprecation::notice('4.0', 'Use addFuzzySearchTerm() instead');
302
        return $this->addFuzzySearchTerm($text, $fields, $boost);
303
    }
304
305
    /**
306
     * @codeCoverageIgnore
307
     * @deprecated
308
     */
309
    public function inClass($class, $includeSubclasses = true)
310
    {
311
        Deprecation::notice('4.0', 'Use addClassFilter() instead');
312
        return $this->addClassFilter($class, $includeSubclasses);
313
    }
314
315
    /**
316
     * @codeCoverageIgnore
317
     * @deprecated
318
     */
319
    public function filter($field, $values)
320
    {
321
        Deprecation::notice('4.0', 'Use addFilter() instead');
322
        return $this->addFilter($field, $values);
323
    }
324
325
    /**
326
     * @codeCoverageIgnore
327
     * @deprecated
328
     */
329
    public function exclude($field, $values)
330
    {
331
        Deprecation::notice('4.0', 'Use addExclude() instead');
332
        return $this->addExclude($field, $values);
333
    }
334
335
    /**
336
     * @codeCoverageIgnore
337
     * @deprecated
338
     */
339
    public function start($start)
340
    {
341
        Deprecation::notice('4.0', 'Use setStart() instead');
342
        return $this->setStart($start);
343
    }
344
345
    /**
346
     * @codeCoverageIgnore
347
     * @deprecated
348
     */
349
    public function limit($limit)
350
    {
351
        Deprecation::notice('4.0', 'Use setLimit() instead');
352
        return $this->setLimit($limit);
353
    }
354
355
    /**
356
     * @codeCoverageIgnore
357
     * @deprecated
358
     */
359
    public function page($page)
360
    {
361
        Deprecation::notice('4.0', 'Use setPageSize() instead');
362
        return $this->setPageSize($page);
363
    }
364
365
    /**
366
     * @return SearchCriteriaInterface[]
367
     */
368
    public function getCriteria()
369
    {
370
        return $this->criteria;
371
    }
372
373
    /**
374
     * @param SearchCriteriaInterface[] $criteria
375
     * @return SearchQuery
376
     */
377
    public function setCriteria($criteria)
378
    {
379
        $this->criteria = $criteria;
380
        return $this;
381
    }
382
383
    /**
384
     * @param SearchCriteriaInterface $criteria
385
     * @return SearchQuery
386
     */
387
    public function addCriteria($criteria)
388
    {
389
        $this->criteria[] = $criteria;
390
        return $this;
391
    }
392
}
393