1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Bardex\Elastic; |
4
|
|
|
|
5
|
|
|
use Bardex\Elastic\SearchResult; |
6
|
|
|
|
7
|
|
|
|
8
|
|
|
/** |
9
|
|
|
* Fluent interface for ElasticSearch |
10
|
|
|
* @package Bardex\Elastic |
11
|
|
|
*/ |
12
|
|
|
class SearchQuery extends Query |
13
|
|
|
{ |
14
|
|
|
/** |
15
|
|
|
* Параметры запроса |
16
|
|
|
* @var array |
17
|
|
|
*/ |
18
|
|
|
protected $params = []; |
19
|
|
|
|
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* Установить имя индекса для поиска |
23
|
|
|
* @param $index |
24
|
|
|
* @return self $this |
25
|
|
|
*/ |
26
|
|
|
public function setIndex($index) |
27
|
|
|
{ |
28
|
|
|
$this->params['index'] = (string) $index; |
29
|
|
|
return $this; |
30
|
|
|
} |
31
|
|
|
|
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* Установить имя типа для поиска |
35
|
|
|
* @param $type |
36
|
|
|
* @return self $this |
37
|
|
|
*/ |
38
|
|
|
public function setType($type) |
39
|
|
|
{ |
40
|
|
|
$this->params['type'] = (string) $type; |
41
|
|
|
return $this; |
42
|
|
|
} |
43
|
|
|
|
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* Выводить перечисленные поля. |
47
|
|
|
* (не обязательный метод, по-умолчанию, выводятся все) |
48
|
|
|
* Методы select() и exclude() могут работать совместно. |
49
|
|
|
* @param array $fields |
50
|
|
|
* @return self $this; |
51
|
|
|
* @example $query->select(['id', 'title', 'brand.id', 'brand.title']); |
52
|
|
|
*/ |
53
|
|
|
public function select(array $fields) |
54
|
|
|
{ |
55
|
|
|
$this->params['body']['_source']['includes'] = $fields; |
56
|
|
|
return $this; |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* Добавить в результаты вычисляемое поле, на скриптовом языке painless или groovy |
62
|
|
|
* @param string $fieldName - имя поля в результатах (если такое поле уже есть в документе, то оно будет заменено) |
63
|
|
|
* @param Script $script - скрипт |
64
|
|
|
* @link https://www.elastic.co/guide/en/elasticsearch/reference/5.0/search-request-script-fields.html |
65
|
|
|
* @return self $this |
66
|
|
|
*/ |
67
|
|
|
public function addScriptField($fieldName, Script $script) |
68
|
|
|
{ |
69
|
|
|
$this->params['body']['script_fields'][$fieldName] = $script->compile(); |
70
|
|
|
return $this; |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* Удалить из выборки поля. |
76
|
|
|
* (не обязательный метод, по-умолчанию, выводятся все) |
77
|
|
|
* Методы select() и exclude() могут работать совместно. |
78
|
|
|
* @param array $fields |
79
|
|
|
* @return self $this; |
80
|
|
|
* @example $query->exclude(['anons', '*.anons']); |
81
|
|
|
*/ |
82
|
|
|
public function exclude(array $fields) |
83
|
|
|
{ |
84
|
|
|
$this->params['body']['_source']['excludes'] = $fields; |
85
|
|
|
return $this; |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
|
89
|
|
|
/** |
90
|
|
|
* Добавить фильтр в raw формате, если готовые методы фильтрации не подходят. |
91
|
|
|
* Для удобства используй готовые методы фильтрации: where(), whereIn(), whereBetween(), whereMatch() |
92
|
|
|
* whereLess() и другие методы where*() |
93
|
|
|
* |
94
|
|
|
* @param string $type - тип фильтрации (term|terms|match|range) |
95
|
|
|
* @param $filter - фильтр |
96
|
|
|
* @link https://www.elastic.co/guide/en/elasticsearch/reference/5.0/query-dsl-terms-query.html |
97
|
|
|
* @return self $this |
98
|
|
|
*/ |
99
|
|
|
public function addFilter($type, $filter) |
100
|
|
|
{ |
101
|
|
|
if (!isset($this->params['body']['query']['bool']['must'])) { |
102
|
|
|
$this->params['body']['query']['bool']['must'] = []; |
103
|
|
|
} |
104
|
|
|
$this->params['body']['query']['bool']['must'][] = [$type => $filter]; |
105
|
|
|
return $this; |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
|
109
|
|
|
/** |
110
|
|
|
* Добавить фильтр точного совпадения, этот фильтр не влияет на поле релевантности _score. |
111
|
|
|
* |
112
|
|
|
* @param $field - поле по которому фильтруем (id, page.categoryId...) |
113
|
|
|
* @param $value - искомое значение |
114
|
|
|
* @example $query->where('channel', 1)->where('page.categoryId', 10); |
115
|
|
|
* @return self $this; |
116
|
|
|
*/ |
117
|
|
|
public function where($field, $value) |
118
|
|
|
{ |
119
|
|
|
$this->addFilter('term', [$field => $value]); |
120
|
|
|
return $this; |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
|
124
|
|
|
/** |
125
|
|
|
* Добавить фильтр совпадения хотя бы одного значения из набора, этот фильтр не влияет на поле релевантности _score. |
126
|
|
|
* |
127
|
|
|
* @param $field - поле по которому фильтруем |
128
|
|
|
* @param $values - массив допустимых значений |
129
|
|
|
* @example $query->whereIn('channel', [1,2,3])->whereIn('page.categoryId', [10,11]); |
130
|
|
|
* @return self $this; |
131
|
|
|
*/ |
132
|
|
|
public function whereIn($field, array $values) |
133
|
|
|
{ |
134
|
|
|
// потому что ES не понимает дырки в ключах |
135
|
|
|
$values = array_values($values); |
136
|
|
|
$this->addFilter('terms', [$field => $values]); |
137
|
|
|
return $this; |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* Добавить фильтр вхождения значение в диапазон (обе границы включительно). |
143
|
|
|
* Можно искать по диапазону дат. |
144
|
|
|
* Этот фильтр не влияет на поле релевантности _score. |
145
|
|
|
* |
146
|
|
|
* @param $field - поле, по которому фильтруем |
147
|
|
|
* @param $min - нижняя граница диапазона (включительно) |
148
|
|
|
* @param $max - верхняя граница диапазона (включительно) |
149
|
|
|
* @param $dateFormat - необязательное поле описание формата даты |
150
|
|
|
* @example $q->whereBetween('created', '01/01/2010', '01/01/2011', 'dd/MM/yyyy'); |
151
|
|
|
* @link https://www.elastic.co/guide/en/elasticsearch/reference/5.0/query-dsl-range-query.html |
152
|
|
|
* @return self $this; |
153
|
|
|
*/ |
154
|
|
View Code Duplication |
public function whereBetween($field, $min, $max, $dateFormat = null) |
|
|
|
|
155
|
|
|
{ |
156
|
|
|
$params = ['gte' => $min, 'lte' => $max]; |
157
|
|
|
if ($dateFormat) { |
158
|
|
|
$params['format'] = $dateFormat; |
159
|
|
|
} |
160
|
|
|
$this->addFilter('range', [$field => $params]); |
161
|
|
|
return $this; |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* Добавить в фильтр сложное условие с вычислениями, на скриптовом языке painless или groovy. |
167
|
|
|
* Использование параметров рекомендуется, для увеличения производительности и эффективности компилирования скриптов. |
168
|
|
|
* |
169
|
|
|
* @param Script $script - скрипт |
170
|
|
|
* @return self $this; |
171
|
|
|
* @link https://www.elastic.co/guide/en/elasticsearch/reference/5.0/query-dsl-script-query.html |
172
|
|
|
* @link https://www.elastic.co/guide/en/elasticsearch/reference/5.0/modules-scripting-painless.html |
173
|
|
|
*/ |
174
|
|
|
public function whereScript(Script $script) |
175
|
|
|
{ |
176
|
|
|
$this->addFilter('script', $script->compile()); |
177
|
|
|
return $this; |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
|
181
|
|
|
/** |
182
|
|
|
* Добавить фильтр "больше или равно" |
183
|
|
|
* @param $field - поле |
184
|
|
|
* @param $value - значение |
185
|
|
|
* @param null $dateFormat - необязательный формат даты |
186
|
|
|
* @return self $this |
187
|
|
|
* @example $query->whereGreaterOrEqual("price", 100) |
188
|
|
|
* @example $query->whereGreaterOrEqual("created", "31/12/2016" , "dd/MM/yyyy") |
189
|
|
|
* @example $query->whereGreaterOrEqual("seller.rating", 4) |
190
|
|
|
*/ |
191
|
|
View Code Duplication |
public function whereGreaterOrEqual($field, $value, $dateFormat = null) |
|
|
|
|
192
|
|
|
{ |
193
|
|
|
$params = ['gte' => $value]; |
194
|
|
|
if ($dateFormat) { |
195
|
|
|
$params['format'] = $dateFormat; |
196
|
|
|
} |
197
|
|
|
$this->addFilter('range', [$field => $params]); |
198
|
|
|
return $this; |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
/** |
202
|
|
|
* Добавить фильтр "больше чем" |
203
|
|
|
* @param $field - поле |
204
|
|
|
* @param $value - значение |
205
|
|
|
* @param null $dateFormat - необязательный формат даты |
206
|
|
|
* @return self $this |
207
|
|
|
* @example $query->whereGreater("price", 100) |
208
|
|
|
* @example $query->whereGreater("created", "31/12/2016" , "dd/MM/yyyy") |
209
|
|
|
* @example $query->whereGreater("seller.rating", 4) |
210
|
|
|
*/ |
211
|
|
View Code Duplication |
public function whereGreater($field, $value, $dateFormat = null) |
|
|
|
|
212
|
|
|
{ |
213
|
|
|
$params = ['gt' => $value]; |
214
|
|
|
if ($dateFormat) { |
215
|
|
|
$params['format'] = $dateFormat; |
216
|
|
|
} |
217
|
|
|
$this->addFilter('range', [$field => $params]); |
218
|
|
|
return $this; |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
/** |
222
|
|
|
* Добавить фильтр "меньше или равно" |
223
|
|
|
* @param $field - поле |
224
|
|
|
* @param $value - значение |
225
|
|
|
* @param null $dateFormat - необязательный формат даты |
226
|
|
|
* @return self $this |
227
|
|
|
* @example $query->whereLessOrEqual("price", 100) |
228
|
|
|
* @example $query->whereLessOrEqual("created", "31/12/2016" , "dd/MM/yyyy") |
229
|
|
|
* @example $query->whereLessOrEqual("seller.rating", 4) |
230
|
|
|
*/ |
231
|
|
View Code Duplication |
public function whereLessOrEqual($field, $value, $dateFormat = null) |
|
|
|
|
232
|
|
|
{ |
233
|
|
|
$params = ['lte' => $value]; |
234
|
|
|
if ($dateFormat) { |
235
|
|
|
$params['format'] = $dateFormat; |
236
|
|
|
} |
237
|
|
|
$this->addFilter('range', [$field => $params]); |
238
|
|
|
return $this; |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
|
242
|
|
|
/** |
243
|
|
|
* Добавить фильтр "меньше чем" |
244
|
|
|
* @param $field - поле |
245
|
|
|
* @param $value - значение |
246
|
|
|
* @param null $dateFormat - - необязательный формат даты |
247
|
|
|
* @return self $this |
248
|
|
|
* @example $query->whereLess("price", 100) |
249
|
|
|
* @example $query->whereLess("created", "31/12/2016" , "dd/MM/yyyy") |
250
|
|
|
* @example $query->whereLess("seller.rating", 4) |
251
|
|
|
*/ |
252
|
|
View Code Duplication |
public function whereLess($field, $value, $dateFormat = null) |
|
|
|
|
253
|
|
|
{ |
254
|
|
|
$params = ['lt' => $value]; |
255
|
|
|
if ($dateFormat) { |
256
|
|
|
$params['format'] = $dateFormat; |
257
|
|
|
} |
258
|
|
|
$this->addFilter('range', [$field => $params]); |
259
|
|
|
return $this; |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
|
263
|
|
|
/** |
264
|
|
|
* Добавить фильтр полнотекстового поиска, этот фильтр влияет на поле релевантности _score. |
265
|
|
|
* |
266
|
|
|
* @param string|arary $field - поле (или массив полей) по которому ищем |
267
|
|
|
* @param $text - поисковая фраза |
268
|
|
|
* @example $query->whereMatch('title', 'яблочная слойка')->setOrderBy('_score', 'desc'); |
269
|
|
|
* @example $query->whereMatch(['title', 'anons'], 'яблочная слойка')->setOrderBy('_score', 'desc'); |
270
|
|
|
* @return self $this; |
271
|
|
|
*/ |
272
|
|
|
public function whereMatch($field, $text) |
273
|
|
|
{ |
274
|
|
|
if (is_array($field)) { |
275
|
|
|
$this->addFilter('multi_match', [ |
276
|
|
|
'query' => $text, |
277
|
|
|
'fields' => $field |
278
|
|
|
]); |
279
|
|
|
} else { |
280
|
|
|
$this->addFilter('match', [$field => $text]); |
281
|
|
|
} |
282
|
|
|
return $this; |
283
|
|
|
} |
284
|
|
|
|
285
|
|
|
/** |
286
|
|
|
* Установить поле сортировки. |
287
|
|
|
* Для сортировки по релевантности существует псевдополе _score (значение больше - релевантность лучше) |
288
|
|
|
* @param $field - поле сортировки |
289
|
|
|
* @param string $order - направление сортировки asc|desc |
290
|
|
|
* @example $query->setOrderBy('_score', 'desc'); |
291
|
|
|
* @return self $this |
292
|
|
|
*/ |
293
|
|
|
public function setOrderBy($field, $order = 'asc') |
294
|
|
|
{ |
295
|
|
|
$this->params['body']['sort'] = []; |
296
|
|
|
$this->addOrderBy($field, $order); |
297
|
|
|
return $this; |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
/** |
301
|
|
|
* Добавить поле сортировки. |
302
|
|
|
* Для сортировки по релевантности существует псевдополе _score (значение больше - релевантность лучше) |
303
|
|
|
* @param $field - поле сортировки |
304
|
|
|
* @param string $order - направление сортировки asc|desc |
305
|
|
|
* @example $query->addOrderBy('_score', 'desc'); |
306
|
|
|
* @example $query->addOrderBy('seller.rating', 'desc'); |
307
|
|
|
* @return self $this |
308
|
|
|
*/ |
309
|
|
|
public function addOrderBy($field, $order = 'asc') |
310
|
|
|
{ |
311
|
|
|
$field = (string) $field; |
312
|
|
|
$order = (string) $order; |
313
|
|
|
if (!isset($this->params['body']['sort'])) { |
314
|
|
|
$this->params['body']['sort'] = []; |
315
|
|
|
} |
316
|
|
|
$this->params['body']['sort'][] = [$field => ['order' => $order]]; |
317
|
|
|
return $this; |
318
|
|
|
} |
319
|
|
|
|
320
|
|
|
|
321
|
|
|
/** |
322
|
|
|
* Установить лимиты выборки |
323
|
|
|
* @param $limit - сколько строк выбирать |
324
|
|
|
* @param int $offset - сколько строк пропустить |
325
|
|
|
* @return self $this; |
326
|
|
|
*/ |
327
|
|
|
public function limit($limit, $offset = 0) |
328
|
|
|
{ |
329
|
|
|
$this->params['body']['size'] = (int) $limit; |
330
|
|
|
$this->params['body']['from'] = (int) $offset; |
331
|
|
|
return $this; |
332
|
|
|
} |
333
|
|
|
|
334
|
|
|
/** |
335
|
|
|
* Получить собранный запрос |
336
|
|
|
* @return array |
337
|
|
|
*/ |
338
|
|
|
public function getQuery() |
339
|
|
|
{ |
340
|
|
|
$params = $this->params; |
341
|
|
|
|
342
|
|
|
if (!isset($params['body']['_source'])) { |
343
|
|
|
$params['body']['_source'] = true; |
344
|
|
|
} |
345
|
|
|
|
346
|
|
|
return $params; |
347
|
|
|
} |
348
|
|
|
|
349
|
|
|
protected function executeQuery(array $query) |
350
|
|
|
{ |
351
|
|
|
return $this->elastic->search($query); |
|
|
|
|
352
|
|
|
} |
353
|
|
|
|
354
|
|
|
|
355
|
|
|
} |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.