Elasticquent
Elasticquent copied to clipboard
Searching multiple types/models
Is it possible to search multiple types/models at the same time with Elasticquent?
I'm very interested in the answer too, or functionality
I would also like to know if this is possible. Thanks.
Presently it's not possible in Elastiquent, it is however possible using ElasticSearch:
By not limiting our search to a particular index or type, we have searched across all documents in the cluster. Elasticsearch forwarded the search request in parallel to a primary or replica of every shard in the cluster, gathered the results to select the overall top 10, and returned them to us.
- https://www.elastic.co/guide/en/elasticsearch/guide/current/multi-index-multi-type.html
Changing Elasticquent to search multiple models at the same time will require a bit of modification (and also from the source level would not make sense, currently).
https://github.com/adamfairholm/Elasticquent/blob/master/src/ElasticquentTrait.php#L244-L269
public static function searchByQuery($query = null, $aggregations = null, $sourceFields = null, $limit = null, $offset = null, $sort = null)
{
$instance = new static;
$params = $instance->getBasicEsParams(true, true, true, $limit, $offset);
if ($sourceFields) {
$params['body']['_source']['include'] = $sourceFields;
}
if ($query) {
$params['body']['query'] = $query;
}
if ($aggregations) {
$params['body']['aggs'] = $aggregations;
}
if ($sort) {
$params['body']['sort'] = $sort;
}
$result = $instance->getElasticSearchClient()->search($params);
return new ResultCollection($result, $instance = new static);
}
The problem here is that ResultCollection($result, $instance = new static);
essentially says that the ElasticquentResultCollection item that is returned will be based on the model that you initiate the search on.
Searching multiple indexes at a time will require a new class that performs just a search, and somehow the collection that is returned should be able to differentiate between different models.
Adding this would make the code more complex, and I can't see too many benefits due to adding it.
Usually you don't build a Laravel app based only on one model. What happens if you have to search the whole site for something, is there any other way to do it?
I tried making two different searches (on different types) and then use the merge() method to merge the returned collections, but it didn't work.
I've tried to use merge() too, and doesn't work because is not the same method of \Illuminate\Support\Collection , what I have to do to merge the results was something like this:
$result = new \Illuminate\Support\Collection([]);
//query
$models1 = FirstModel::search($query);
$models2 = SecondModel::search($query);
// work the collection
foreach($models1 as $model1) {
$result->push($model1);
}
foreach($models2 as $model2) {
$result->push($model2);
}
OK, I will look into why the merging is not working a little later tonight, then. It definitely should be OK.
Edit: I said I would look into why the merging is not working a little later tonight, but a quick check shows why merge will not work.
ResultCollection
extends Illuminate\Database\Eloquent\Collection
.
The merge
function creates a new instance of the class you are calling merge on.
@alfchee this will mean in your case that when you are attempting to merge SecondModel
into FirstModel
, all of the values from SecondModel
are being merged into a new FirstModel
instance (which is usually not what you want to do).
This brings me back to my original comment:
Changing Elasticquent to search multiple models at the same time will require a bit of modification (and also from the source level would not make sense, currently).
The best way to do this might be to create a new Facade (called Search
?) that allows you to perform multiple searches based on the models that you want to search on, then merging them similar to what @alfchee has shown above.
+1 for alfchee's method.
I needed to show results from different tables: Articles, Blog entries, Deals, etc. And I needed to display them in order of relevance score, regardless of the type of document.
For sorting based on the documentScore:
$result->sortByDesc(function ($item){ return $item->documentScore(); });
I too would find a nice solution very valuable, as like some said earlier, many Laravel apps are built on many models, with relationships etc.
Anyhow, I got around this using alfchee's method above. It's a little hacky, but it get's my results and I've also grouped them so I can later iterate and "break up" my search results by type etc...
$books = Book::search($query);
$articles = Article::search($query);
$all = array_merge(
array(
'books' => $books->all(),
'articles' => $articles->all()
)
);
//Can now iterate on this in my view etc..
return $all;
I wrote basic && useful function. You can edit search filters or query in if blocks.
public function searchMultipleTypes($tableNames = [], $queryText = '')
{
if(!$tableNames || $queryText == '')
return [];
$all = [];
foreach ($tableNames as $table)
{
if($table == (new Category())->getTable())
{
$params['body']['query']['match']['title'] = $queryText;
$all[$table] = Category::complexSearch($params);
}
else if($table == (new Post())->getTable())
$all[$table] = Post::search($queryText);
else if($table == (new City())->getTable())
$all[$table] = City::search($queryText);
}
return $all;
}
and you can call with:
$searchTables = ['categories', 'posts', 'cities'];
$data = searchMultipleTypes($searchTables, $queryText);