Last active
October 22, 2023 12:14
-
-
Save lukepolo/b63d303b076a7cd58bbaa54b3b9f0370 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// inside boot() | |
resolve(EngineManager::class)->extend('elasticsearch', function () { | |
return new ElasticsearchEngine(ClientBuilder::fromConfig(config('scout.elasticsearch.config')), config('scout.elasticsearch.index')); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Scout\Engines; | |
use Laravel\Scout\Builder; | |
use Elasticsearch\Client as Elasticsearch; | |
use Illuminate\Database\Eloquent\Collection; | |
use Illuminate\Support\Collection as BaseCollection; | |
use Laravel\Scout\Engines\Engine; | |
class ElasticsearchEngine extends Engine | |
{ | |
/** | |
* The Elasticsearch client instance. | |
* | |
* @var \Elasticsearch\Client | |
*/ | |
protected $elasticsearch; | |
/** | |
* The index name. | |
* | |
* @var string | |
*/ | |
protected $index; | |
/** | |
* Create a new engine instance. | |
* | |
* @param \Elasticsearch\Client $elasticsearch | |
* @return void | |
*/ | |
public function __construct(Elasticsearch $elasticsearch, $index) | |
{ | |
$this->elasticsearch = $elasticsearch; | |
$this->index = $index; | |
} | |
/** | |
* Update the given model in the index. | |
* | |
* @param Collection $models | |
* @return void | |
*/ | |
public function update($models) | |
{ | |
$body = new BaseCollection(); | |
$models->each(function ($model) use ($body) { | |
$array = $model->toSearchableArray(); | |
if (empty($array)) { | |
return; | |
} | |
$body->push([ | |
'index' => [ | |
'_index' => $this->index, | |
'_type' => $model->searchableAs(), | |
'_id' => $model->getKey(), | |
], | |
]); | |
$body->push($array); | |
}); | |
$this->elasticsearch->bulk([ | |
'refresh' => true, | |
'body' => $body->all(), | |
]); | |
} | |
/** | |
* Remove the given model from the index. | |
* | |
* @param Collection $models | |
* @return void | |
*/ | |
public function delete($models) | |
{ | |
$body = new BaseCollection(); | |
$models->each(function ($model) use ($body) { | |
$body->push([ | |
'delete' => [ | |
'_index' => $this->index, | |
'_type' => $model->searchableAs(), | |
'_id' => $model->getKey(), | |
], | |
]); | |
}); | |
$this->elasticsearch->bulk([ | |
'refresh' => true, | |
'body' => $body->all(), | |
]); | |
} | |
/** | |
* Perform the given search on the engine. | |
* | |
* @param Builder $query | |
* @return mixed | |
*/ | |
public function search(Builder $query) | |
{ | |
return $this->performSearch($query, [ | |
'filters' => $this->filters($query), | |
'size' => $query->limit ?: 10000, | |
]); | |
} | |
/** | |
* Perform the given search on the engine. | |
* | |
* @param Builder $query | |
* @param int $perPage | |
* @param int $page | |
* @return mixed | |
*/ | |
public function paginate(Builder $query, $perPage, $page) | |
{ | |
$result = $this->performSearch($query, [ | |
'filters' => $this->filters($query), | |
'size' => $perPage, | |
'from' => (($page * $perPage) - $perPage), | |
]); | |
$result['nbPages'] = (int) ceil($result['hits']['total'] / $perPage); | |
return $result; | |
} | |
/** | |
* Perform the given search on the engine. | |
* | |
* @param Builder $builder | |
* @param array $options | |
* @return mixed | |
*/ | |
protected function performSearch(Builder $builder, array $options = []) | |
{ | |
$filters = []; | |
$matches[] = [ | |
'match' => [ | |
'_all' => [ | |
'query' => $builder->query, | |
'fuzziness' => 1 | |
] | |
] | |
]; | |
if (array_key_exists('filters', $options) && $options['filters']) { | |
foreach ($options['filters'] as $field => $value) { | |
if(is_numeric($value)) { | |
$filters[] = [ | |
'term' => [ | |
$field => $value, | |
], | |
]; | |
} elseif(is_string($value)) { | |
$matches[] = [ | |
'match' => [ | |
$field => [ | |
'query' => $value, | |
'operator' => 'and' | |
] | |
] | |
]; | |
} | |
} | |
} | |
$query = [ | |
'index' => $this->index, | |
'type' => $builder->model->searchableAs(), | |
'body' => [ | |
'query' => [ | |
'bool' => [ | |
'filter' => $filters, | |
'must' => $matches | |
], | |
], | |
], | |
]; | |
if (array_key_exists('size', $options)) { | |
$query['size'] = $options['size']; | |
} | |
if (array_key_exists('from', $options)) { | |
$query['from'] = $options['from']; | |
} | |
if ($builder->callback) { | |
return call_user_func( | |
$builder->callback, | |
$this->elasticsearch, | |
$query | |
); | |
} | |
return $this->elasticsearch->search($query); | |
} | |
/** | |
* Get the filter array for the query. | |
* | |
* @param Builder $query | |
* @return array | |
*/ | |
protected function filters(Builder $query) | |
{ | |
return $query->wheres; | |
} | |
/** | |
* Pluck and return the primary keys of the given results. | |
* | |
* @param mixed $results | |
* @return \Illuminate\Support\Collection | |
*/ | |
public function mapIds($results) | |
{ | |
return collect($results['hits'])->pluck('objectID')->values(); | |
} | |
/** | |
* Map the given results to instances of the given model. | |
* | |
* @param mixed $results | |
* @param \Illuminate\Database\Eloquent\Model $model | |
* @return \Illuminate\Database\Eloquent\Collection | |
*/ | |
public function map($results, $model) | |
{ | |
if (count($results['hits']) === 0) { | |
return Collection::make(); | |
} | |
$keys = collect($results['hits']['hits']) | |
->pluck('_id') | |
->values() | |
->all(); | |
$models = $model->whereIn( | |
$model->getQualifiedKeyName(), $keys | |
)->get()->keyBy($model->getKeyName()); | |
return Collection::make($results['hits']['hits'])->map(function ($hit) use ($model, $models) { | |
if(isset($models[$hit['_source'][$model->getKeyName()]])) { | |
return $models[$hit['_source'][$model->getKeyName()]]; | |
} | |
})->filter(); | |
} | |
/** | |
* Get the total count from a raw result returned by the engine. | |
* | |
* @param mixed $results | |
* @return int | |
*/ | |
public function getTotalCount($results) | |
{ | |
return $results['hits']['total']; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Works like a charm!