Closed excellentingenuity closed 7 years ago
It looks like as of 3 days ago, the elasticsearch engine was pulled from the repo looking at the commit history...
Taylor said some time ago that he would not be providing further updates to the elasticsearch driver, that it was too much work to maintain in the core.
I can see why, elasticsearch seems to change their api quite frequently, and the results it will produce vary greatly depending on the methods used on the driver level.
There are several packages out there which support elasticsearch, here but in the end you would probably be best off writing your own driver based on your desired version of the elasticsearch php api and customize the indexes yourself.
Just for an example, here is my driver, based off the one that was in the scout repo. My driver utilizes another custom class which gathers all the mappings from the searchable models. It also uses a 'prefix' and 'ngram' filter for better typeahead matching. (the default driver returned way too many irrelevant results when searching simple things like usernames) You might be able to copy-paste this into your app, but I would suggest reading up on the php api docs and the underlying rest api and get farmiliar with how things work, first.
<?php
namespace App\Search;
use App\Builders\ElasticMapping;
use Elasticsearch\ClientBuilder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Collection as BaseCollection;
use Laravel\Scout\Builder;
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() {
$client = ClientBuilder::create()->build();
$this->elasticsearch = $client;
$this->index = config('scout.elasticsearch5.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;
}
if (!$this->elasticsearch->indices()->exists(['index' => $this->index])) {
$params = [
'index' => $this->index,
'body' => [
'settings' => [
'number_of_shards' => 3,
'number_of_replicas' => 2,
"analysis" => [
"filter" => [
"ngram_filter" => [
"type" => "ngram",
"min_gram" => 3,
"max_gram" => 8
]
],
"analyzer" => [
"index_ngram" => [
"type" => "custom",
"tokenizer" => "keyword",
"filter" => ["ngram_filter", "lowercase"]
],
"search_ngram" => [
"type" => "custom",
"tokenizer" => "keyword",
"filter" => "lowercase"
]
],
],
],
'mappings' => ElasticMapping::gather(),
]
];
$this->elasticsearch->indices()->create($params);
}
$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 $builder
* @param array $options
* @return mixed
*/
protected function performSearch(Builder $builder, array $options = []) {
$filters = [];
$term = strtolower($builder->query);
foreach (explode(' ', $term) as $piece) {
$matches[] = [
'prefix' => ["_all" => $piece]
];
}
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;
}
/**
* 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);
//dd($result);
return $result;
}
/**
* Map the given results to instances of the given model.
*
* @param mixed $results
* @param \Illuminate\Database\Eloquent\Model $model
* @return Collection|BaseCollection
*/
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) {
$record = isset($models[$hit['_source'][$model->getKeyName()]])
? $models[$hit['_source'][$model->getKeyName()]] : $model;
$record->highlights = [];
return $record;
})->filter()->values();
}
/**
* 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'];
}
}
Here is the class that I use to gather model mappings
<?php
/**
* Created by PhpStorm.
* User: darryl
* Date: 12/9/2016
* Time: 12:18 PM
*/
namespace App\Builders;
use App\Models\User;
class ElasticMapping {
public static function gather() {
$path = base_path() . '/app/Models/';
$files = scandir($path);
$mappings = [];
foreach ($files as $name) {
if ($name !== '.' && $name !== '..') {
$class_name = '\App\Models\\' . substr($name, 0, strlen($name) - 4);
$class = new $class_name;
if (method_exists($class, 'search')) {
if (method_exists($class, 'getFieldMappings')) {
$mappings[$class->searchableAs()] = [
'_source' => [
'enabled' => true,
],
'properties' => $class->getFieldMappings(),
];
} else {
//$mappings[$class->searchableAs()] = [];
}
}
}
}
return $mappings;
}
public static function getModels() {
$path = base_path() . '/app/Models/';
$files = scandir($path);
$classes = [];
$operator = \Sentinel::getUser();
foreach ($files as $name) {
if ($name !== '.' && $name !== '..') {
$class_name = '\App\Models\\' . substr($name, 0, strlen($name) - 4);
$class = new $class_name;
if (method_exists($class, 'search')) {
if (!$class->viewPermission || $operator->hasAccess($class->viewPermission)) {
$classes[$class->searchableAs()] = $class;
}
}
}
}
return $classes;
}
}
Driver [elasticsearch] not supported.
am also facing this same issue
here is a sample of what i have https://gist.github.com/lukepolo/b63d303b076a7cd58bbaa54b3b9f0370
Client builder is part of the elastic search package I believe , I can't check it out as I'm not at my computer
@lukepolo sorry ! Thank you I have found a solution! use Laravel\Scout\EngineManager; use Elasticsearch\ClientBuilder as ClientBuilder;
Driver [elasticsearch] not supported. am also facing this same issue
This is working well for us...
This is probably because Taylor is working on it right now but even with composer requiring the elasticsearch/elasticsearch package scout is unable to find the elastic search driver. I cannot find any other search results with this particular error so I am raising it here. Using: "laravel/scout": "^2.0", "elasticsearch/elasticsearch": "^5.0" On: Laravel 5.3.28