GithubHelp home page GithubHelp logo

matchish / laravel-scout-elasticsearch Goto Github PK

View Code? Open in Web Editor NEW
684.0 14.0 108.0 6.38 MB

Search among multiple models with ElasticSearch and Laravel Scout

License: MIT License

Makefile 1.62% Dockerfile 1.42% PHP 96.96%
laravel scout laravel-scout elasticsearch

laravel-scout-elasticsearch's Introduction

Support Ukraine Import progress report

Build Status Coverage Total Downloads Latest Version License

For Laravel Framework < 6.0.0 use 3.x branch

The package provides the perfect starting point to integrate ElasticSearch into your Laravel application. It is carefully crafted to simplify the usage of ElasticSearch within the Laravel Framework.

It’s built on top of the latest release of Laravel Scout, the official Laravel search package. Using this package, you are free to take advantage of all of Laravel Scout’s great features, and at the same time leverage the complete set of ElasticSearch’s search experience.

If you need any help, stack overflow is the preferred and recommended way to ask support questions.

💕 Features

Don't forget to ⭐ the package if you like it. 🙏

⚠️ Requirements

  • PHP version >= 8.0
  • Laravel Framework version >= 8.0.0
Elasticsearch version ElasticsearchDSL version
>= 8.0 >= 8.0.0
>= 7.0 >= 3.0.0
>= 6.0, < 7.0 < 3.0.0

🚀 Installation

Use composer to install the package:

composer require matchish/laravel-scout-elasticsearch

Set env variables

SCOUT_DRIVER=Matchish\ScoutElasticSearch\Engines\ElasticSearchEngine

The package uses \ElasticSearch\Client from official package, but does not try to configure it, so feel free do it in your app service provider. But if you don't want to do it right now, you can use Matchish\ElasticSearchServiceProvider from the package.
Register the provider, adding to config/app.php

'providers' => [
    // Other Service Providers

    \Matchish\ScoutElasticSearch\ElasticSearchServiceProvider::class
],

Set ELASTICSEARCH_HOST env variable

ELASTICSEARCH_HOST=host:port

or use commas as separator for additional nodes

ELASTICSEARCH_HOST=host:port,host:port

And publish config example for elasticsearch
php artisan vendor:publish --tag config

💡 Usage

Note: This package adds functionalities to Laravel Scout, and for this reason, we encourage you to read the Scout documentation first. Documentation for Scout can be found on the Laravel website.

Index settings and mappings

It is very important to define the mapping when we create an index — an inappropriate preliminary definition and mapping may result in the wrong search results.

To define mappings or settings for index, set config with right value.

For example if method searchableAs returns products string

Config key for mappings should be
elasticsearch.indices.mappings.products
Or you you can specify default mappings with config key elasticsearch.indices.mappings.default

Same way you can define settings

For index products it will be
elasticsearch.indices.settings.products

And for default settings
elasticsearch.indices.settings.default

Eager load

To speed up import you can eager load relations on import using global scopes.

You should configure ImportSourceFactory in your service provider(register method)

use Matchish\ScoutElasticSearch\Searchable\ImportSourceFactory;
...
public function register(): void
{
$this->app->bind(ImportSourceFactory::class, MyImportSourceFactory::class);

Here is an example of MyImportSourceFactory

namespace Matchish\ScoutElasticSearch\Searchable;

final class MyImportSourceFactory implements ImportSourceFactory
{
    public static function from(string $className): ImportSource
    {
        //Add all required scopes
        return new DefaultImportSource($className, [new WithCommentsScope()]);
    }
}

class WithCommentsScope implements Scope {

    /**
     * Apply the scope to a given Eloquent query builder.
     *
     * @param \Illuminate\Database\Eloquent\Builder $builder
     * @param \Illuminate\Database\Eloquent\Model $model
     * @return void
     */
    public function apply(Builder $builder, Model $model)
    {
        $builder->with('comments');
    }
}

You can also customize your indexed data when you save models by leveraging the toSearchableArray method provided by Laravel Scout through the Searchable trait

Example:

class Product extends Model 
{
    use Searchable;

    /**
     * Get the indexable data array for the model.
     *
     * @return array
     */
    public function toSearchableArray()
    {
        $with = [
            'categories',
        ];

        $this->loadMissing($with);

        return $this->toArray();
    }
}

This example will make sure the categories relationship gets always loaded on the model when saving it.

Zero downtime reimport

While working in production, to keep your existing search experience available while reimporting your data, you also can use scout:import Artisan command:

php artisan scout:import

The command create new temporary index, import all models to it, and then switch to the index and remove old index.

Search

To be fully compatible with original scout package, this package does not add new methods.
So how we can build complex queries? There is two ways.
By default, when you pass a query to the search method, the engine builds a query_string query, so you can build queries like this

Product::search('(title:this OR description:this) AND (title:that OR description:that)')

If it's not enough in your case you can pass a callback to the query builder

$results = Product::search('zonga', function(\Elastic\Elasticsearch\Client $client, $body) {

    $minPriceAggregation = new MinAggregation('min_price');
    $minPriceAggregation->setField('price');
    
    $maxPriceAggregation = new MaxAggregation('max_price');
    $maxPriceAggregation->setField('price');
    
    $brandTermAggregation = new TermsAggregation('brand');
    $brandTermAggregation->setField('brand');

    $body->addAggregation($minPriceAggregation);
    $body->addAggregation($brandTermAggregation);
    
    return $client->search(['index' => 'products', 'body' => $body->toArray()])->asArray();
})->raw();

Note : The callback function will get 2 parameters. First one is $client and it is an object of \Elastic\Elasticsearch\Client class from elasticsearch/elasticsearch package. And the second one is $body which is an object of \ONGR\ElasticsearchDSL\Search from ongr/elasticsearch-dsl package. So, while as you can see the example above, $client->search(....) method will return an \Elastic\Elasticsearch\Response\Elasticsearch object. And you need to use asArray() method to get array result. Otherwise, the HitsIteratorAggregate class will throw an error. You can check the issue here.

Conditions

Scout supports only 3 conditions: ->where(column, value) (strict equation), ->whereIn(column, array) and ->whereNotIn(column, array):

Product::search('(title:this OR description:this) AND (title:that OR description:that)')
    ->where('price', 100)
    ->whereIn('type', ['used', 'like new'])
    ->whereNotIn('type', ['new', 'refurbished']);

Scout does not support any operators, but you can pass ElasticSearch terms like RangeQuery as value to ->where():

use ONGR\ElasticsearchDSL\Query\TermLevel\RangeQuery;

Product::search('(title:this OR description:this) AND (title:that OR description:that)')
    ->where('price', new RangeQuery('price', [
        RangeQuery::GTE => 100,
        RangeQuery::LTE => 1000,
    ]);

And if you just want to search using RangeQuery without any query_string, you can call the search() method directly and leave the param empty.

use ONGR\ElasticsearchDSL\Query\TermLevel\RangeQuery;

Product::search()
    ->where('price', new RangeQuery('price', [
        RangeQuery::GTE => 100,
    ]);

Full list of ElasticSearch terms is in vendor/handcraftedinthealps/elasticsearch-dsl/src/Query/TermLevel.

Search amongst multiple models

You can do it with MixedSearch class, just pass indices names separated by commas to the within method.

MixedSearch::search('title:Barcelona or to:Barcelona')
    within(implode(',', [
        (new Ticket())->searchableAs(),
        (new Book())->searchableAs(),
    ]))
->get();

In this example you will get collection of Ticket and Book models where ticket's arrival city or book title is Barcelona

Working with results

Often your response isn't collection of models but aggregations or models with higlights an so on. In this case you need to implement your own implementation of HitsIteratorAggregate and bind it in your service provider

Here is a case

🆓 License

Scout ElasticSearch is an open-sourced software licensed under the MIT license.

laravel-scout-elasticsearch's People

Contributors

ametad avatar amirrezanasiri avatar burakcakirel avatar chrysanthos avatar fayne avatar ganicus avatar hamzamogni avatar hkulekci avatar jackraymund avatar jalmatari avatar mackhankins avatar matchish avatar mauxtin avatar orest-divintari avatar pluiesurlavitre avatar sachinagarwal1337 avatar sinemah avatar stylecibot avatar szabizs avatar vrusua avatar yocmen avatar youanden avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

laravel-scout-elasticsearch's Issues

Solution sort relationship?

I have the tables products and product_variants/

// Product.php
public function variants()
    {
        return $this->hasMany('\App\Entities\ProductVariant');
    }

The products table has columns: name ...

The product_variants table has columns: opt_1, opt_2, opt_3 ...

// config
'products' => [
                'properties' => [
                    'name' => [
                        'type' => 'text',
                        'analyzer' => 'my_analyzer'
                    ]
                ],
            ],
            'variants' => [
                'properties' => [
                    'opt_1' => [
                        'type' => 'text',
                        'analyzer' => 'my_analyzer'
                    ],
                    'opt_2' => [
                        'type' => 'text',
                        'analyzer' => 'my_analyzer'
                    ],
                    'opt_3' => [
                        'type' => 'text',
                        'analyzer' => 'my_analyzer'
                    ],
                ],
            ],

Product::search('name 123 opt 4') Data returned:

array[
    0 => array[
        "id" => 2
        "name" => "name 123",
        variants: [
            0 => array[
                "opt_1": "opt 1",
                "opt_2": "opt 2"
                "opt_3": "opt 3"
            ],
            1 => array[
                "opt_1": "opt 4",
                "opt_2": "opt 5"
                "opt_3": "opt 6"
            ]
        ]
    ]
]

Is there any way when I search: Product::search('name 123 opt 4')

Data returned:

array[
    0 => array[
        "id" => 2
        "name" => "name 123",
        variants: [
            0 => array[
                ""opt_1": "opt 4",
                "opt_2": "opt 5"
                "opt_3": "opt 6"
            ],
            1 => array[
                "opt_1": "opt 1",
                "opt_2": "opt 2"
                "opt_3": "opt 3
            ]
        ]
    ]
]

PHPUnit Testing

I have tried to test with this package but it doesn't find anything when testing. Do you have any recommendations?

Searching empty string

Hi there,

here we are doing a multi filter with aggregations and in some cases the search query string is nothing and what is valuable in those cases is all the logic inside the callback function.

Does anybody know if there is a way to avoid getting 0 results when $string is ''?;

Product::search($stringEmptySometimes, function($client, $body) {
//all the valuable logic ...
});

Regards

Search results return full model, not what the index contains.

I have customized the toSearchableArray method of the models I am sending over to elasticsearch, so that they only contain relevant columns, and they also contain some common columns to make it easier to parse the search results. Here is the entire search related portion of the model:

`use Searchable;
public function searchableAs()
{
return 'journals';
}

public function toSearchableArray()
{
    (!empty($this->employee)) ? $employee = $this->employee->PayrollName : $employee = "";
    (!empty($this->supervisor)) ? $supervisor = $this->supervisor->PayrollName : $supervisor = "";
    (!empty($this->location)) ? $customer = $this->location->Customer : $customer = "";

    $array = array( 
        "Date" => Carbon::parse($this->Date)->format('Y-m-d'),
        "Definition" => $this->Definition,
        "Details" => $this->Details,
        'Employee' => $employee,
        'Supervisor' => $supervisor,
        'Customer' => $customer,
        'SEARCHdate' => Carbon::parse($this->Date)->format('Y-m-d'),
        'SEARCHtitle' => 'Journal - '.$employee.' ('.Carbon::parse($this->Date)->format('Y-m-d').')',
        'SEARCHurl' => '/journals/records/'.$this->id,
        'SEARCHempid' => $this->EmpID
    );

    return $array;
}`

If I use something like dejavu to peak at the actual elasticsearch index named "journals", I can see that it is populated correctly with the chosen & additional columns. However, when I search that index, it does not return the columns contained in the index, but a full instance of the original model:

$searchResults = Mixed::search(Input::get('query')) ->within(implode(',', [ (new Journal())->searchableAs(), (new CorrectiveAction())->searchableAs(), ])) ->orderBy('SEARCHdate', 'desc') ->paginate(10);

Is this expected behavior? If it is, is it possible to change what gets returned by elasticsearch? I was hoping that it would be the exact same schema that I sent to it.

Thank you.

scout:import

php artisan scout:import
Importing [App\Model\frontend\post]

Illuminate\Contracts\Container\BindingResolutionException : Unresolvable dependency resolving [Parameter #0 [ $retries ]] in class Elasticsearch\Transport

What did I miss?

Could not parse host

I can't seem to get the scout:import to work on Homestead. I keep running into couldn't resolve host. I've tried it on an existing and fresh project. What am I missing?

SCOUT_DRIVER=Matchish\ScoutElasticSearch\Engines\ElasticSearchEngine

ELASTICSEARCH_HOST=localhost:9200
Importing [App\User]

   Elasticsearch\Common\Exceptions\RuntimeException  : Could not parse host:

  at /home/vagrant/projects/laravel/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ClientBuilder.php:644
    640|             } elseif (is_array($host)) {
    641|                 $host = $this->normalizeExtendedHost($host);
    642|             } else {
    643|                 $this->logger->error("Could not parse host: ".print_r($host, true));
  > 644|                 throw new RuntimeException("Could not parse host: ".print_r($host, true));
    645|             }
    646|
    647|             $connections[] = $this->connectionFactory->create($host);
    648|         }

  Exception trace:

  1   Elasticsearch\ClientBuilder::buildConnectionsFromHosts()
      /home/vagrant/projects/laravel/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ClientBuilder.php:586

  2   Elasticsearch\ClientBuilder::buildTransport()
      /home/vagrant/projects/laravel/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ClientBuilder.php:542

  Please use the argument -v to see more details.

Getting error "Elasticsearch\Common\Exceptions\BadRequest400Exception" on import command?

Hello, thank you for this package!! I am not sure why am I getting the following error in terminal when I try to do php artisan scout:import

Elasticsearch\Common\Exceptions\BadRequest400Exception : {"error":{"root_cause":[{"type":"mapper_parsing_exception","reason":"No handler for type [keyword] declared on field [id]"}],"type":"mapper_parsing_exception","reason":"Failed to parse mapping [_doc]: No handler for type [keyword] declared on field [id]","caused_by":{"type":"mapper_parsing_exception","reason":"No handler for type [keyword] declared on field [id]"}},"status":400}

I apologise if the issue is with me.

Elasticsearch\Common\Exceptions\BadRequest400Exception

Error:
{"error":{"root_cause":[{"type":"illegal_argument_exception","reason":"The mapping definition cannot be nested under a type [_doc] unless include_type_name is set to true."}],"type":"illegal_argument_exception","reason":"The mapping definition cannot be nested under a type [_doc] unless include_type_name is set to true."},"status":400}

Elasticsearch Info:

{
  "name" : "homestead",
  "cluster_name" : "homestead",
  "cluster_uuid" : "Sm6BquEUSsWCLDusQk-iyg",
  "version" : {
    "number" : "7.3.2",
    "build_flavor" : "default",
    "build_type" : "deb",
    "build_hash" : "1c1faf1",
    "build_date" : "2019-09-06T14:40:30.409026Z",
    "build_snapshot" : false,
    "lucene_version" : "8.1.0",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

This is running matchish/laravel-scout-elasticsearch:^2.0

Driver [Matchish\ScoutElasticSearch\Engines\ElasticSearchEngine] not supported

Hello,

I set the env variable SCOUT_DRIVER to Matchish\ScoutElasticSearch\Engines\ElasticSearchEngine as stated in the readme. Is this setting correct?

I get this error:
Driver [Matchish\ScoutElasticSearch\Engines\ElasticSearchEngine] not supported.

While looking at the default scout config I noticed that this env variable is a string that references a config array lower in the same file.

Question. Is there a way to include results from multiple indices in one call?

Hello, Sergey!

Thanks for very handy package! Really appreciated. First I will briefly describe my use-case. I used Mixed::class as simple as follows:

Mixed::search('foobar')
    ->within('one', 'two')
    ->get()

(TBH I'm aware of paginator available to increase the limit, and in fact I'm using it instead of returning collection like in the example but it doesn't matter in explaining the case)

☝️ The code above by default returns EloquentCollection of 10 models. It doesn't matter how many indices I pass to within method it will always give back first 10 models of the first index ('one' in this example).

I'm looking for an elegant solution to limit results returned from each index, i.e I would like to have an EloquentCollection of - for example - X elements from each index. The only solution I came to is to return everything and transform collection later on (using groupBy's/limits etc) but obviously it doesn't count as efficient and elegant solution since I have around 3 million records in ES. I hoped maybe you could have some clever trick to share with 😅

Thanks for looking into it! 🙏
all the best!

Eager loading relationships

First off, thank you for a fantastic package. We migrated from Algolia to ES about 6 months ago and at the time struggled with the other packages as none of them seemed to work perfectly for our use case. We ended up writing our own engine based on a combination of the other packages’ approaches and some of our own tweaks, but it’s never been great. What I love about this package is that it strikes the right balance of keeping the simplicity of the Scout API while still offering the ability to benefit from the power of ES.

So one of the problems we’ve historically had with Scout is with the performance of bulk importing records at scale (millions of records). For many of our searchable indexes, we need to load additional properties into the record from related models in the database, sometimes even from nested relations. Unfortunately Scout (and indeed this package) do not fare well in this scenario, creating N+1 queries that ultimately end up bringing the database to its knees when bulk importing.

I have a solution for the problem and have implemented it in a fork, but I wanted to check if this is something you’d likely accept a PR for. Essentially I add a method to the searchable models that require eager loaded relations, which returns an array of the relations to eager load. I’ve then changed the PullFromSource class of this package to check if the searchable model has this method, and if so, add a with to the query to eagerly load those relations.

workvivo@c7d5f98

If you think there is a better way to approach this problem I’d love to hear it - I suspect this is an issue for a lot of people who use Scout at scale. This is also an issue with the default Scout Algolia implementation (laravel/scout#329) and they rejected a similar PR there (laravel/scout#342). If you’re open to a PR on this feature, let me know and I’ll tidy up my fork and add some tests for the scenario before submitting a PR.

Thanks again for the fantastic package.

[2.0] Unresolvable dependency resolving

With the new 2.0 release I was trying it out & I get this error:

Unresolvable dependency resolving [Parameter #0 [ <required> $retries ]] in class Elasticsearch\Transport

WhereIn Methods and others

To be fully compatible with original scout package, this package does not add new methods.

I really need a whereIn('id', $array) method for a project I'm working on. Is there a way I can extend the Builder or run a more complex query to accomplish?

Collection::searchable does not exist

error:
Method Illuminate\Database\Eloquent\Collection::searchable does not exist. {"exception":"[object] (BadMethodCallException(code: 0): Method Illuminate\\Database\\Eloquent\\Collection::searchable does not exist. at vendor/laravel/framework/src/Illuminate/Support/Traits/Macroable.php:104)

use Laravel\Scout\Searchable;

class Posts extends Model
{
    use Searchable,\Spatie\Tags\HasTags;

run command :

php artisan scout:import "\codenamepace\Models\Posts"

trace:

[2019-10-20 18:38:52] local.ERROR: Method Illuminate\Database\Eloquent\Collection::searchable does not exist. {"exception":"[object] (BadMethodCallException(code: 0): Method Illuminate\\Database\\Eloquent\\Collection::searchable does not exist. at vendor/laravel/framework/src/Illuminate/Support/Traits/Macroable.php:104)
[stacktrace]
#0 vendor/matchish/laravel-scout-elasticsearch/src/Jobs/Stages/PullFromSource.php(33): Illuminate\\Support\\Collection->__call('searchable', Array)
#1 vendor/matchish/laravel-scout-elasticsearch/src/Jobs/Import.php(41): Matchish\\ScoutElasticSearch\\Jobs\\Stages\\PullFromSource->handle(Object(Elasticsearch\\Client))
#2 vendor/laravel/framework/src/Illuminate/Support/Traits/EnumeratesValues.php(176): Matchish\\ScoutElasticSearch\\Jobs\\Import->Matchish\\ScoutElasticSearch\\Jobs\\{closure}(Object(Matchish\\ScoutElasticSearch\\Jobs\\Stages\\PullFromSource), 2)
#3 vendor/matchish/laravel-scout-elasticsearch/src/Jobs/Import.php(43): Illuminate\\Support\\Collection->each(Object(Closure))
#4 [internal function]: Matchish\\ScoutElasticSearch\\Jobs\\Import->handle(Object(Elasticsearch\\Client))
#5 vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(32): call_user_func_array(Array, Array)
#6 vendor/laravel/framework/src/Illuminate/Support/helpers.php(522): Illuminate\\Container\\BoundMethod::Illuminate\\Container\\{closure}()
#7 vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(90): value(Object(Closure))
#8 vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(34): Illuminate\\Container\\BoundMethod::callBoundMethod(Object(Illuminate\\Foundation\\Application), Array, Object(Closure))
#9 vendor/laravel/framework/src/Illuminate/Container/Container.php(591): Illuminate\\Container\\BoundMethod::call(Object(Illuminate\\Foundation\\Application), Array, Array, NULL)
#10 vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php(94): Illuminate\\Container\\Container->call(Array)
#11 vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(130): Illuminate\\Bus\\Dispatcher->Illuminate\\Bus\\{closure}(Object(Matchish\\ScoutElasticSearch\\Jobs\\Import))
#12 vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(105): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Matchish\\ScoutElasticSearch\\Jobs\\Import))
#13 vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php(98): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#14 vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php(76): Illuminate\\Bus\\Dispatcher->dispatchNow(Object(Matchish\\ScoutElasticSearch\\Jobs\\Import))
#15 vendor/laravel/framework/src/Illuminate/Foundation/Bus/PendingDispatch.php(112): Illuminate\\Bus\\Dispatcher->dispatch(Object(Matchish\\ScoutElasticSearch\\Jobs\\Import))
#16 vendor/laravel/framework/src/Illuminate/Foundation/helpers.php(399): Illuminate\\Foundation\\Bus\\PendingDispatch->__destruct()
#17 vendor/laravel/framework/src/Illuminate/Bus/Queueable.php(177): dispatch(Object(Matchish\\ScoutElasticSearch\\Jobs\\Import))
#18 vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php(131): Matchish\\ScoutElasticSearch\\Jobs\\QueueableJob->dispatchNextJobInChain()
#19 vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php(62): Illuminate\\Queue\\CallQueuedHandler->ensureNextJobInChainIsDispatched(Object(Matchish\\ScoutElasticSearch\\Jobs\\QueueableJob))
#20 vendor/laravel/framework/src/Illuminate/Queue/Jobs/Job.php(88): Illuminate\\Queue\\CallQueuedHandler->call(Object(Illuminate\\Queue\\Jobs\\RedisJob), Array)
#21 vendor/laravel/framework/src/Illuminate/Queue/Worker.php(348): Illuminate\\Queue\\Jobs\\Job->fire()
#22 vendor/laravel/framework/src/Illuminate/Queue/Worker.php(294): Illuminate\\Queue\\Worker->process('redis', Object(Illuminate\\Queue\\Jobs\\RedisJob), Object(Illuminate\\Queue\\WorkerOptions))
#23 vendor/laravel/framework/src/Illuminate/Queue/Worker.php(129): Illuminate\\Queue\\Worker->runJob(Object(Illuminate\\Queue\\Jobs\\RedisJob), 'redis', Object(Illuminate\\Queue\\WorkerOptions))
#24 vendor/laravel/framework/src/Illuminate/Queue/Console/WorkCommand.php(112): Illuminate\\Queue\\Worker->daemon('redis', 'default', Object(Illuminate\\Queue\\WorkerOptions))
#25 vendor/laravel/framework/src/Illuminate/Queue/Console/WorkCommand.php(96): Illuminate\\Queue\\Console\\WorkCommand->runWorker('redis', 'default')
#26 [internal function]: Illuminate\\Queue\\Console\\WorkCommand->handle()
#27 vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(32): call_user_func_array(Array, Array)
#28 vendor/laravel/framework/src/Illuminate/Support/helpers.php(522): Illuminate\\Container\\BoundMethod::Illuminate\\Container\\{closure}()
#29 vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(90): value(Object(Closure))
#30 vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(34): Illuminate\\Container\\BoundMethod::callBoundMethod(Object(Illuminate\\Foundation\\Application), Array, Object(Closure))
#31 vendor/laravel/framework/src/Illuminate/Container/Container.php(591): Illuminate\\Container\\BoundMethod::call(Object(Illuminate\\Foundation\\Application), Array, Array, NULL)
#32 vendor/laravel/framework/src/Illuminate/Console/Command.php(202): Illuminate\\Container\\Container->call(Array)
#33 vendor/symfony/console/Command/Command.php(255): Illuminate\\Console\\Command->execute(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Illuminate\\Console\\OutputStyle))
#34 vendor/laravel/framework/src/Illuminate/Console/Command.php(189): Symfony\\Component\\Console\\Command\\Command->run(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Illuminate\\Console\\OutputStyle))
#35 vendor/symfony/console/Application.php(934): Illuminate\\Console\\Command->run(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
#36 vendor/symfony/console/Application.php(273): Symfony\\Component\\Console\\Application->doRunCommand(Object(Illuminate\\Queue\\Console\\WorkCommand), Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
#37 vendor/symfony/console/Application.php(149): Symfony\\Component\\Console\\Application->doRun(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
#38 vendor/laravel/framework/src/Illuminate/Console/Application.php(90): Symfony\\Component\\Console\\Application->run(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
#39 vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php(131): Illuminate\\Console\\Application->run(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
#40 artisan(37): Illuminate\\Foundation\\Console\\Kernel->handle(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
#41 {main}
"} 

Custom config?

I was looking at this package to replace a custom solution. Are you planning on creating a default config to show people how to create mappings?

Typo search?

Algolia from the start allow for typo in search results. How can I set this in Scout Elasticsearch? It's something from elasticsearch config or I can manipulate it from Laravel config file?

I don't know how to name it exacly but I'm looking for something like searchable attributes configuration, ranking and sorting and typo tolerance.

Search result pagination error in ElasticSearch 7 - engine getTotalCount returns array

Calling Model::search('query')->paginate(10) throws an exception:

PHP Error: Unsupported operand types in /home/vagrant/dev/maze/vendor/laravel/framework/src/Illuminate/Pagination/LengthAwarePaginator.php on line 51

This is caused by ElasticSearchEngine->getTotalCount returning an array instead of an integer value. It returns the hits.total value which is an array with hits.total.value and hits.total.relation keys as documented here.

No data imported after scout:import

I am sure I am just missing something here but just cannot get any data to import into elasticsearch. I am using 7.0.1 with the latest version of your package and have installed into a fresh Laravel install to ensure nothing else was interfering.

Steps To Reproduce

  1. Install fresh version of Laravel (v6.13.1)
  2. Create posts model/migration and seed with 1000 rows.
  3. Install matchish/laravel-scout-elasticsearch, publish config, add service provider.
  4. update config and env as below.
return [
    'host' => env('ELASTICSEARCH_HOST'),
    'indices' => [
        'mappings' => [
            'default' => [
                'properties' => [
                    'id' => [
                        'type' => 'keyword',
                    ]
                ],
            ],
            'posts' => [
                'properties' => [
                    'id' => ['type' => 'keyword'],
                    'name' => ['type' => 'text'],
                ],
            ],
        ],
        'settings' => [
            'default' => [
                'number_of_shards' => 1,
                'number_of_replicas' => 0,
            ],
        ],
    ],
];

image

  1. add searchable to post model as below.
class Post extends Model
{
    use Searchable;

    protected $fillable = [
        'name'
    ];

    public function searchableAs()
    {
        return 'posts';
    }
}
  1. Install elastic search with docker at v7.0.1

  2. php artisan scout:import - this shows me 100% and 6/6 imported. I assume the 6/6 is the rows imported? I have 1000 rows seeded in the database however.

image

  1. Check elasticsearch via Dejavu. I see the mappings were correctly set but it shows no data:

image

[BUG] Default config file causes initial indexing to fail

Describe the bug
After publishing of config file and running import command, there is an error that is probably caused by deprecated usage of type attribute. As it can be seen at official elasticsearch docs.

To Reproduce
Steps to reproduce the behavior:

  1. Publish config with php artisan vendor:publish --tag config
  2. Run indexing command php artisan scout:import
  3. Get error: Root mapping definition has unsupported parameters

Expected behavior
Successful indexing

Additional context
I also tried to remove type attribute from default mapping, what seems to solve unsupported parameters error, however it yielded another error: java.util.ArrayList cannot be cast to java.util.Map. According to google, that error often comes up when some empty field is being passed into elasticsearch. So it looks like there needs some other alteration in the config structure to be done.

What is more, when I have removed overall mappings part, indexing has been finished successfully without any problems. I am using v7.6.1 of elasticsearch-php.

Original config that resulted in unsupported parameters error:

return [
    'host' => env('ELASTICSEARCH_HOST'),
    'indices' => [
        'mappings' => [
            'default' => [
                'properties' => [
                    'id' => [
                        'type' => 'keyword',
                    ],
                ],
            ],
        ],
        'settings' => [
            'default' => [
                'number_of_shards' => 1,
                'number_of_replicas' => 0,
            ],
        ],
    ],
];

Edited config that resulted in java.util.ArrayList error:

return [
    'host' => env('ELASTICSEARCH_HOST'),
    'indices' => [
        'mappings' => [
            'default' => [
                'properties' => [
                    'id' 
                ],
            ],
        ],
        'settings' => [
            'default' => [
                'number_of_shards' => 1,
                'number_of_replicas' => 0,
            ],
        ],
    ],
];

Config that resulted in successful indexing:

return [
    'host' => env('ELASTICSEARCH_HOST'),
    'indices' => [
        'settings' => [
            'default' => [
                'number_of_shards' => 1,
                'number_of_replicas' => 0,
            ],
        ],
    ],
];

Routing meta-field to use join datatype

Not able to connect to AWS Elasticsearch

I am getting following error, when try to connect to AWS Elasticsearch url

Elasticsearch\Common\Exceptions\NoNodesAvailableException : No alive nodes found in your cluster

Indices aren't configured correctly when you dont use the `scout:import` command

If you do not use the scout:import command, indexes are no longer appended with the time(). Which results in the following error:

{
   "error":{
      "root_cause":[
         {
            "type":"index_not_found_exception",
            "reason":"no such index",
            "resource.type":"index_or_alias",
            "resource.id":"index_name",
            "index_uuid":"_na_",
            "index":"index_name"
         }
      ],
      "type":"index_not_found_exception",
      "reason":"no such index",
      "resource.type":"index_or_alias",
      "resource.id":"index_name",
      "index_uuid":"_na_",
      "index":"index_name"
   },
   "status":404
}

Wrong date mapping

For ES7 I am using this config:

return [
    'host' => env('ELASTICSEARCH_HOST'),
    'indices' => [
        'mappings' => [
            'videos' => [
                'properties' => [
                    'id' => [
                        'type' => 'keyword'
                    ],
                    'title' => [
                        'type' => 'text'
                    ],
                    'publishedAt' => [
                        'type' => 'date',
                        'format' => 'date_time'
                    ]
                ],
            ],
            'users' => [
                'properties' => [
                    'id' => [
                        'type' => 'keyword'
                    ],
                    'title' => [
                        'type' => 'text'
                    ]
                ],
            ],
        ],
        'settings' => [
            'default' => [
                'number_of_shards' => 1,
                'number_of_replicas' => 0,
            ],
        ],
    ],
];

Then I have a video model that returns toSearchableArray() like:

[
   "id" => 456,
   "title" => "Video Title",
   "publishedAt" => "2014-02-03T22:56:45.000000Z",
]

Then I do php artisan scout:import and check the new indice mapping with:

curl -X GET "localhost:9200/videos_1570554606/_mapping?pretty"

which returns:

{                           
  "videos_1570554606" : {   
    "mappings" : {          
      "properties" : {     
        "id" : {            
          "type" : "keyword"
        },            
        "publishedAt" : {   
          "type" : "text"   
        },                  
        "title" : {         
          "type" : "text"   
        }               
      }                     
    }                       
  }                         
}

What I expected to return:

{
  "testmap" : {
    "mappings" : {
      "properties" : {
        "id" : {
          "type" : "keyword"
        },
        "publishedAt" : {
          "type" : "date",
          "format" : "date_time"
        },
        "title" : {
          "type" : "text"
        }
      }
    }
  }
}

Return more than 10 rows bug

Shouldn't these two return the same result?

   $results = Product::search('*')->get(); // ok - returns collection with 10 products
   $results = Product::search('*', function (Client $client, Search $body) {
      $body->setSize(100);
      $client->search(['index' => 'products', 'body' => $body->toArray()]);
   })->get(); // returns empty collection

At first the second query returns an error

Argument 1 passed to Matchish\ScoutElasticSearch\ElasticSearch\EloquentHitsIteratorAggregate::__construct() must be of the type array, null given, called in \xxx\vendor\matchish\laravel-scout-elasticsearch\src\Engines\ElasticSearchEngine.php on line 106

which I solved by editing the EloquentHitsIteratorAggregate constructor from

public function __construct(array $results, callable $callback = null)

to

public function __construct(?array $results, callable $callback = null)

(I did not make a PR for this since I just started working with the package and I am not sure if this is actually a bug. Let me know if you need me to make a quick PR)

Difference between this package and others

Hi @matchish and thanks for open sourcing this package of yours! 😃

Any chance you could write a few words on why you created a new package instead of using an existing one?

I have been using this package for a while https://github.com/babenkoivan/scout-elasticsearch-driver and since the original maintainer no longer supports it I was thinking of switching to something else. The second reason was the big number of unsolved issues ( not sure if they are valid or not but they are there).

Anyway, would love to see how this differentiates from others.

Thanks again 😸

orderBy doesn't work

Hi there,

I'm using a large database of posts (100k), however when trying to sort the results, i'm not getting the desired results.

$products = Post::search('*')
    ->orderBy('id', 'desc')
    ->paginate();

I get data, first to last indexed. I need to get last to first indexed. How can I fix it?

laravel/scout#223 (comment)

Adding parameters to query

I see the package is using the ONGR QueryStringQuery by default and a lot is already possible with the elasticsearch query syntax.

In my case, I simply want to set a searchable fields array to the query but cannot see an easy way to append parameters to the ONGR query.

{
    "query_string" : {
        "fields" : ["name", "email"],
        "query" : "this AND that OR thus"
    }
}

Is it possible to amend the query inside the scout search callback, or would this need amendment to the search factory to inject the additional paramaters here?

I know you can get the queries from within the callback with $body->getQueries() but when filters are being used I am not sure its possible to determine which query is from $builder->query... unless we assume there is only going to be a single QueryStringQuery but that does not feel safe.

GetAliases no longer exists.

On the elasticsearch-7 branch you've got an error with GetAliases not found. The new method is just GetAlias(). I'm going to try to get it pulled down / fixed / tested this weekend.

https://github.com/elastic/elasticsearch-php/blob/master/src/Elasticsearch/Namespaces/IndicesNamespace.php#L702

Importing [App\Electronic\Metadata]

   Symfony\Component\Debug\Exception\FatalThrowableError  : Call to undefined method Elasticsearch\Namespaces\IndicesNamespace::getAliases()

  at /home/vagrant/projects/eradmin/vendor/matchish/laravel-scout-elasticsearch/src/Jobs/Stages/CleanUp.php:37
    33|         $searchable = $this->searchable;
    34|         $params = GetAliasParams::anyIndex($searchable->searchableAs());
    35|         try {
    36|             /** @var array $response */
  > 37|             $response = $elasticsearch->indices()->getAliases($params->toArray());
    38|         } catch (Missing404Exception $e) {
    39|             $response = [];
    40|         }
    41|         foreach ($response as $indexName => $data) {

  Exception trace:

  1   Matchish\ScoutElasticSearch\Jobs\Stages\CleanUp::handle(Object(Elasticsearch\Client))
      /home/vagrant/projects/eradmin/vendor/matchish/laravel-scout-elasticsearch/src/Jobs/Import.php:41

  2   Matchish\ScoutElasticSearch\Jobs\Import::Matchish\ScoutElasticSearch\Jobs\{closure}(Object(Matchish\ScoutElasticSearch\Jobs\Stages\CleanUp))
      /home/vagrant/projects/eradmin/vendor/laravel/framework/src/Illuminate/Support/Collection.php:475

  Please use the argument -v to see more details

[BUG] Default settings no longer matches to any mapping

Having multiple mappings but only 1 default settings no longer works. We now have to duplicate the settings key for each mapping.

before
mappings: default, orders, customer
settings: default (can link to all of the above)

now (v4)
mappings: default, orders, customer
settings: default, orders, customer

i.e this is what I had before that was working

'mappings' => [
    'default' => [
        'properties' => [
            'id' => [
                'type' => 'keyword',
            ],
        ],
        'orders' => [
            'properties' => [
                'id' => [
                    'type' => 'keyword',
                ],
                'email' => [
                    'type' => 'text',
                    'analyzer' => 'my_analyzer',
                ],
            ],
        ],
    ],
    'settings' => [
        'default' => [
            'number_of_shards' => 1,
            'number_of_replicas' => 0,
            'analysis' => [
                'analyzer' => [
                    'my_analyzer' => [
                        'tokenizer' => 'ngram_tokenizer',
                    ],
                ],
            ],
        ],
    ]

We now need to add another settings key for the mapping of the order.

This may well be intentional? I think this fits in more with how it is defined in the elasticsearch docs.

Allow custom hits iterator

We need a method to be able to swap out the hits generator on a search by search basis. For example if adding the highlight feature, we need to be able to merge the highlighted html back into the results.

I suggested add this into the ElasticSearchEngine class but obviously needs better implementation.

$model::search($query,
    function (Client $client, Search $body) use ($model, $field) {
        $???->useHitsIterator(HighlighterHitsIteratorAggregate::class);
        $highlight = new Highlight();
        $highlight->addField($field);
        $body->addHighlight($highlight);
        return $client->search(['index' => $model->searchableAs(), 'body' => $body->toArray()]);
    })->paginate(100);

Highlight

Is there a way to add Highlight to search result?

Empty models are being imported

Hello,

Right now I'm returning an empty array in toSearchableArray()

These empty models are still being indexed. I was wondering if it's possible to exclude these from being indexed?

Thanks

[Feature] how to use queue import many-to-many relationshop?

Is your feature request related to a problem? Please describe.
I have a database table with many-to-many relationship Materials (keywords) => TagsKey => Tag
When I tried to import data into elasticsearch, it was not imported at all. I do n’t know if I am already processing the data or where I stopped.

If there is no relationship between these keywords, I have a total of 60k arrays, and all of them are imported in less than 60s. Many-to-many relationships are not completed in 30 minutes, and there is no data in kibana.

Describe the solution you'd like
can see what operations are being performed during the import, for example: the data with the id 1001 is being imported

Describe alternatives you've considered
none

Additional context
image

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        // ElasticSearch 注入
        $this->app->bind(ImportSourceFactory::class, MaterialImport::class);
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
    }
}
class MaterialModel extends Model
{

    use Searchable;
    protected $table = "materials";
    protected $appends = ['keywords'];

    public function getKeywordsAttribute()
    {
        return implode(' ', array_unique(array_filter($this->tags()->pluck('title')->toArray())));
    }

    public function tags()
    {
        return $this->belongsToMany(TagModel::class, MaterialTagModel::class, 'mid', 'tid');
    }

    public function searchableAs()
    {
        return 'materials';
    }
}
final class MaterialImport implements ImportSourceFactory
{
    public static function from(string $className): ImportSource
    {
        return new DefaultImportSource($className, [new MaterialKeywordsScope()]);
    }
}
```php
class MaterialKeywordsScope implements Scope
{
    public function apply(Builder $builder, Model $model)
    {
        $builder->with('tags');
    }
}

Bulk update error

I just updated to 2.0.3 & I get bulk update error on all my PHPUnit tests. I went back to 2.0.2 & it's fine.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.