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
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
Loading history...
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
Loading history...
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
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 |
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.