Skip to content
This repository was archived by the owner on Nov 4, 2021. It is now read-only.

Commit a7cdf20

Browse files
committed
Added highlight support
1 parent 547bbbf commit a7cdf20

File tree

11 files changed

+249
-63
lines changed

11 files changed

+249
-63
lines changed

README.md

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
[![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg)](https://gitter.im/scout-elasticsearch-driver/Lobby)
66
[![Donate](https://img.shields.io/badge/donate-PayPal-blue.svg)](https://www.paypal.me/ivanbabenko)
77

8-
:coffee: If you like my package, it'd be nice of you [to buy me a cup of coffee](https://www.paypal.me/ivanbabenko).
8+
:beer: If you like my package, it'd be nice of you [to buy me a beer](https://www.paypal.me/ivanbabenko).
99

1010
:octocat: The project has a [chat room on Gitter](https://gitter.im/scout-elasticsearch-driver/Lobby)!
1111

@@ -304,7 +304,20 @@ use ScoutElastic\SearchRule;
304304

305305
class MySearch extends SearchRule
306306
{
307-
// This method returns an array that represents a content of bool query.
307+
// This method returns an array, describes how to highlight the results.
308+
// If null is returned, no highlighting will be used.
309+
public function buildHighlightPayload()
310+
{
311+
return [
312+
'fields' => [
313+
'name' => [
314+
'type' => 'plain'
315+
]
316+
]
317+
];
318+
}
319+
320+
// This method returns an array, that represents bool query.
308321
public function buildQueryPayload()
309322
{
310323
return [
@@ -318,7 +331,8 @@ class MySearch extends SearchRule
318331
}
319332
```
320333

321-
You can read more about bool queries [here](https://www.elastic.co/guide/en/elasticsearch/reference/5.2/query-dsl-bool-query.html).
334+
You can read more about bool queries [here](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html)
335+
and about highlighting [here](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-highlighting.html#search-request-highlighting).
322336

323337
The default search rule returns the following payload:
324338

@@ -377,6 +391,21 @@ App\MyModel::search('Brazil')
377391
->get();
378392
```
379393

394+
To retrieve highlight, use model `highlight` attribute:
395+
396+
```php
397+
// Let's say we highlight field `name` of `MyModel`.
398+
$model = App\MyModel::search('Brazil')
399+
->rule(App\MySearchRule::class)
400+
->first();
401+
402+
// Now you can get raw highlighted value:
403+
$model->highlight->name;
404+
405+
// or string value:
406+
$model->highlight->nameAsString;
407+
```
408+
380409
## Available filters
381410

382411
You can use different types of filters:
@@ -391,7 +420,7 @@ whereBetween($field, $value) | whereBetween('price', [100, 200]) | Checks if a v
391420
whereNotBetween($field, $value) | whereNotBetween('price', [100, 200]) | Checks if a value isn't in a range.
392421
whereExists($field) | whereExists('unemployed') | Checks if a value is defined.
393422
whereNotExists($field) | whereNotExists('unemployed') | Checks if a value isn't defined.
394-
whereRegexp($field, $value, $flags = 'ALL') | whereRegexp('name.raw', 'A.+') | Filters records according to a given regular expression. [Here](https://www.elastic.co/guide/en/elasticsearch/reference/5.2/query-dsl-regexp-query.html#regexp-syntax) you can find more about syntax.
423+
whereRegexp($field, $value, $flags = 'ALL') | whereRegexp('name.raw', 'A.+') | Filters records according to a given regular expression. [Here](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html#regexp-syntax) you can find more about syntax.
395424
whereGeoDistance($field, $value, $distance) | whereGeoDistance('location', [-70, 40], '1000m') | Filters records according to given point and distance from it. [Here](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-geo-distance-query.html) you can find more about syntax.
396425
whereGeoBoundingBox($field, array $value) | whereGeoBoundingBox('location', ['top_left' => [-74.1, 40.73], 'bottom_right' => [-71.12, 40.01]]) | Filters records within given boundings. [Here](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-geo-bounding-box-query.html) you can find more about syntax.
397426
whereGeoPolygon($field, array $points) | whereGeoPolygon('location', [[-70, 40],[-80, 30],[-90, 20]]) | Filters records within given polygon. [Here](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-geo-polygon-query.html) you can find more about syntax.

src/Console/stubs/search_rule.stub

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,17 @@ use ScoutElastic\SearchRule;
66

77
class DummyClass extends SearchRule
88
{
9+
/**
10+
* @inheritdoc
11+
*/
12+
public function buildHighlightPayload()
13+
{
14+
//
15+
}
16+
17+
/**
18+
* @inheritdoc
19+
*/
920
public function buildQueryPayload()
1021
{
1122
//

src/ElasticEngine.php

Lines changed: 48 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -57,36 +57,11 @@ public function delete($models)
5757
$this->indexer->delete($models);
5858
}
5959

60-
protected function buildSearchQueryPayload(Builder $builder, $queryPayload, array $options = [])
61-
{
62-
foreach ($builder->wheres as $clause => $filters) {
63-
if (count($filters) == 0) {
64-
continue;
65-
}
66-
67-
if (!array_has($queryPayload, 'filter.bool.'.$clause)) {
68-
array_set($queryPayload, 'filter.bool.'.$clause, []);
69-
}
70-
71-
$queryPayload['filter']['bool'][$clause] = array_merge(
72-
$queryPayload['filter']['bool'][$clause],
73-
$filters
74-
);
75-
}
76-
77-
$payload = (new TypePayload($builder->model))
78-
->setIfNotEmpty('body._source', $builder->select)
79-
->setIfNotEmpty('body.query.bool', $queryPayload)
80-
->setIfNotEmpty('body.collapse.field', $builder->collapse)
81-
->setIfNotEmpty('body.sort', $builder->orders)
82-
->setIfNotEmpty('body.explain', $options['explain'] ?? null)
83-
->setIfNotEmpty('body.profile', $options['profile'] ?? null)
84-
->setIfNotNull('body.from', $builder->offset)
85-
->setIfNotNull('body.size', $builder->limit);
86-
87-
return $payload->get();
88-
}
89-
60+
/**
61+
* @param Builder $builder
62+
* @param array $options
63+
* @return array
64+
*/
9065
public function buildSearchQueryPayloadCollection(Builder $builder, array $options = [])
9166
{
9267
$payloadCollection = collect();
@@ -95,41 +70,59 @@ public function buildSearchQueryPayloadCollection(Builder $builder, array $optio
9570
$searchRules = $builder->rules ?: $builder->model->getSearchRules();
9671

9772
foreach ($searchRules as $rule) {
73+
$payload = new TypePayload($builder->model);
74+
9875
if (is_callable($rule)) {
99-
$queryPayload = call_user_func($rule, $builder);
76+
$payload->setIfNotEmpty('body.query.bool', call_user_func($rule, $builder));
10077
} else {
10178
/** @var SearchRule $ruleEntity */
10279
$ruleEntity = new $rule($builder);
10380

10481
if ($ruleEntity->isApplicable()) {
105-
$queryPayload = $ruleEntity->buildQueryPayload();
82+
$payload->setIfNotEmpty('body.query.bool', $ruleEntity->buildQueryPayload());
83+
$payload->setIfNotEmpty('body.highlight', $ruleEntity->buildHighlightPayload());
10684
} else {
10785
continue;
10886
}
10987
}
11088

111-
$payload = $this->buildSearchQueryPayload(
112-
$builder,
113-
$queryPayload,
114-
$options
115-
);
116-
11789
$payloadCollection->push($payload);
11890
}
11991
} else {
120-
$payload = $this->buildSearchQueryPayload(
121-
$builder,
122-
['must' => ['match_all' => new stdClass()]],
123-
$options
124-
);
92+
$payload = (new TypePayload($builder->model))
93+
->setIfNotEmpty('body.query.bool.must.match_all', new stdClass());
12594

12695
$payloadCollection->push($payload);
12796
}
12897

129-
return $payloadCollection;
98+
return $payloadCollection->map(function(TypePayload $payload) use ($builder, $options) {
99+
$payload
100+
->setIfNotEmpty('body._source', $builder->select)
101+
->setIfNotEmpty('body.collapse.field', $builder->collapse)
102+
->setIfNotEmpty('body.sort', $builder->orders)
103+
->setIfNotEmpty('body.explain', $options['explain'] ?? null)
104+
->setIfNotEmpty('body.profile', $options['profile'] ?? null)
105+
->setIfNotNull('body.from', $builder->offset)
106+
->setIfNotNull('body.size', $builder->limit);
107+
108+
109+
foreach ($builder->wheres as $clause => $filters) {
110+
$clauseKey = 'body.query.bool.filter.bool.' . $clause;
111+
112+
$clauseValue = array_merge(
113+
$payload->get($clauseKey, []),
114+
$filters
115+
);
116+
117+
$payload->setIfNotEmpty($clauseKey, $clauseValue);
118+
}
119+
120+
return $payload->get();
121+
});
130122
}
131123

132-
protected function performSearch(Builder $builder, array $options = []) {
124+
protected function performSearch(Builder $builder, array $options = [])
125+
{
133126
if ($builder->callback) {
134127
return call_user_func(
135128
$builder->callback,
@@ -194,15 +187,15 @@ public function count(Builder $builder)
194187

195188
$this
196189
->buildSearchQueryPayloadCollection($builder)
197-
->each(function($payload) use (&$count) {
190+
->each(function ($payload) use (&$count) {
198191
$result = ElasticClient::count($payload);
199192

200193
$count = $result['count'];
201194

202195
if ($count > 0) {
203196
return false;
204197
}
205-
});
198+
});
206199

207200
return $count;
208201
}
@@ -250,11 +243,17 @@ public function map($results, $model)
250243
->keyBy($primaryKey);
251244

252245
return Collection::make($results['hits']['hits'])
253-
->map(function($hit) use ($models) {
246+
->map(function ($hit) use ($models) {
254247
$id = $hit['_id'];
255248

256249
if (isset($models[$id])) {
257-
return $models[$id];
250+
$model = $models[$id];
251+
252+
if (isset($hit['highlight'])) {
253+
$model->highlight = new Highlight($hit['highlight']);
254+
}
255+
256+
return $model;
258257
}
259258
})
260259
->filter()

src/Highlight.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace ScoutElastic;
4+
5+
class Highlight
6+
{
7+
/**
8+
* @var array
9+
*/
10+
private $highlight;
11+
12+
/**
13+
* @param array $highlight
14+
*/
15+
public function __construct(array $highlight)
16+
{
17+
$this->highlight = $highlight;
18+
}
19+
20+
/**
21+
* @param $key
22+
* @return array|string|null
23+
*/
24+
public function __get($key)
25+
{
26+
$field = str_replace('AsString', '', $key);
27+
28+
if (isset($this->highlight[$field])) {
29+
$value = $this->highlight[$field];
30+
return $field == $key ? $value : implode(' ', $value);
31+
} else {
32+
return null;
33+
}
34+
}
35+
}

src/Payloads/RawPayload.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,13 @@ public function addIfNotEmpty($key, $value)
6969
return $this->add($key, $value);
7070
}
7171

72-
public function get($key = null)
72+
/**
73+
* @param string|null $key
74+
* @param mixed|null $default
75+
* @return mixed
76+
*/
77+
public function get($key = null, $default = null)
7378
{
74-
return array_get($this->payload, $key);
79+
return array_get($this->payload, $key, $default);
7580
}
7681
}

src/SearchRule.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,17 @@ public function isApplicable()
1818
return true;
1919
}
2020

21+
/**
22+
* @return array|null
23+
*/
24+
public function buildHighlightPayload()
25+
{
26+
return null;
27+
}
28+
29+
/**
30+
* @return array
31+
*/
2132
public function buildQueryPayload()
2233
{
2334
return [

src/Searchable.php

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,17 @@
88
use ScoutElastic\Builders\SearchBuilder;
99
use \Exception;
1010

11-
trait Searchable {
11+
trait Searchable
12+
{
1213
use ScoutSearchable {
1314
ScoutSearchable::bootSearchable as bootScoutSearchable;
1415
}
1516

17+
/**
18+
* @var Highlight|null
19+
*/
20+
private $highlight = null;
21+
1622
private static $isSearchableTraitBooted = false;
1723

1824
public static function bootSearchable()
@@ -88,4 +94,20 @@ public function usesSoftDelete()
8894
{
8995
return in_array(SoftDeletes::class, class_uses_recursive($this));
9096
}
97+
98+
/**
99+
* @param Highlight $value
100+
*/
101+
public function setHighlightAttribute(Highlight $value)
102+
{
103+
$this->highlight = $value;
104+
}
105+
106+
/**
107+
* @return Highlight|null
108+
*/
109+
public function getHighlightAttribute()
110+
{
111+
return $this->highlight;
112+
}
91113
}

0 commit comments

Comments
 (0)