|
<?php |
|
|
|
namespace App\Support; |
|
|
|
use ReflectionClass; |
|
|
|
use Laravel\Scout\Builder; |
|
use Laravel\Scout\Engines\Engine; |
|
|
|
use Elasticsearch\Client as Elastic; |
|
|
|
use Illuminate\Support\Str; |
|
use Illuminate\Database\Eloquent\Collection; |
|
|
|
class ElasticSearchEngine extends Engine |
|
{ |
|
/** |
|
* Elasticsearch client. |
|
* |
|
* @var \Elasticsearch\Client |
|
*/ |
|
public $elastic; |
|
|
|
/** |
|
* Create a new engine instance. |
|
* |
|
* @param \Elasticsearch\Client $elastic |
|
* @return void |
|
*/ |
|
public function __construct(Elastic $elastic) |
|
{ |
|
$this->elastic = $elastic; |
|
} |
|
|
|
/** |
|
* Update the given model in the index. |
|
* |
|
* @param Collection $models |
|
* @return void |
|
*/ |
|
public function update($models) |
|
{ |
|
$params['body'] = []; |
|
|
|
$models->each(function ($model) use (&$params) { |
|
$params['body'][] = [ |
|
'update' => [ |
|
'_id' => $model->getKey(), |
|
'_index' => $model->searchableAs(), |
|
'_type' => Str::lower((new ReflectionClass($model))->getShortName()), |
|
], |
|
]; |
|
|
|
$params['body'][] = [ |
|
'doc' => $model->toSearchableArray(), |
|
'doc_as_upsert' => true, |
|
]; |
|
}); |
|
|
|
$this->elastic->bulk($params); |
|
} |
|
|
|
/** |
|
* Remove the given model from the index. |
|
* |
|
* @param Collection $models |
|
* @return void |
|
*/ |
|
public function delete($models) |
|
{ |
|
$params['body'] = []; |
|
|
|
$models->each(function ($model) use (&$params) { |
|
$params['body'][] = [ |
|
'delete' => [ |
|
'_id' => $model->getKey(), |
|
'_index' => $model->searchableAs(), |
|
'_type' => Str::lower((new ReflectionClass($model))->getShortName()), |
|
], |
|
]; |
|
}); |
|
|
|
$this->elastic->bulk($params); |
|
} |
|
|
|
/** |
|
* Perform the given search on the engine. |
|
* |
|
* @param Builder $builder |
|
* @return mixed |
|
*/ |
|
public function search(Builder $builder) |
|
{ |
|
return $this->performSearch($builder, array_filter([ |
|
'filters' => $this->filters($builder), |
|
'size' => $builder->limit, |
|
])); |
|
} |
|
|
|
/** |
|
* Perform the given search on the engine. |
|
* |
|
* @param Builder $builder |
|
* @param int $perPage |
|
* @param int $page |
|
* @return mixed |
|
*/ |
|
public function paginate(Builder $builder, $perPage, $page) |
|
{ |
|
$result = $this->performSearch($builder, [ |
|
'filters' => $this->filters($builder), |
|
'from' => (($page * $perPage) - $perPage), |
|
'size' => $perPage, |
|
]); |
|
|
|
$result['nbPages'] = $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 = []) |
|
{ |
|
$params = [ |
|
'index' => $builder->index ?: $builder->model->searchableAs(), |
|
'body' => ['query' => []], |
|
]; |
|
|
|
if (! empty($builder->query)) { |
|
$params['body']['query']['bool']['must']['match']['_all'] = [ |
|
'query' => $builder->query, |
|
'operator' => 'and', |
|
'fuzziness' => 2, |
|
]; |
|
} |
|
|
|
if (isset($options['from'])) { |
|
$params['body']['from'] = $options['from']; |
|
} |
|
|
|
if (isset($options['size'])) { |
|
$params['body']['size'] = $options['size']; |
|
} |
|
|
|
if (! empty($options['filters'])) { |
|
$params['body']['query']['bool']['filter'] = [$options['filters']]; |
|
} |
|
|
|
if ($builder->orders) { |
|
foreach ($builder->orders as $order) { |
|
$params['body']['sort'][] = [$order['column'] => ['order' => $order['direction']]]; |
|
} |
|
} |
|
|
|
if ($builder->callback) { |
|
return call_user_func( |
|
$builder->callback, |
|
$this->elastic, |
|
$builder->query, |
|
$params |
|
); |
|
} |
|
|
|
return $this->elastic->search($params); |
|
} |
|
|
|
/** |
|
* Get the filter array for the query. |
|
* |
|
* @param Builder $builder |
|
* @return array |
|
*/ |
|
protected function filters(Builder $builder) |
|
{ |
|
return collect($builder->wheres)->map(function ($value, $key) { |
|
return ['term' => [$key => $value]]; |
|
})->values()->all(); |
|
} |
|
|
|
/** |
|
* 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']['hits'])->pluck('_id')->values(); |
|
} |
|
|
|
/** |
|
* Map the given results to instances of the given model. |
|
* |
|
* @param mixed $results |
|
* @param \Illuminate\Database\Eloquent\Model $model |
|
* @return Collection |
|
*/ |
|
public function map($results, $model) |
|
{ |
|
if (count($results['hits']['total']) === 0) { |
|
return Collection::make(); |
|
} |
|
|
|
$keys = collect($results['hits']['hits'])->pluck('_id')->values()->all(); |
|
|
|
$models = $model->whereIn($model->getKeyName(), $keys)->get()->keyBy($model->getKeyName()); |
|
|
|
return collect($results['hits']['hits'])->map(function ($hit) use ($model, $models) { |
|
if (isset($models[$hit['_id']])) { |
|
return $models[$hit['_id']]; |
|
} |
|
})->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']; |
|
} |
|
} |