Elasticsearch Eloquent Integration for Laravel
Tags: elasticsearch, laravel
Published: 2020-04-05 11:25:56

I decided to do a simple demo of integrating elasticsearch into laravel. To get started quickly, I just found these packages:

- [cviebrock/laravel-elasticsearch](https://github.com/cviebrock/laravel-elasticsearch)
- [sleimanx2/plastic](https://github.com/sleimanx2/plastic)
- [elasticquent/Elasticquent](https://github.com/elasticquent/Elasticquent)

By looking at each overview in their GitHub page, I will have quick analyzes. `cviebrock/laravel-elasticsearch` seems to do the low-level integration that you can customize easily later, but it's not quite simple for this demo. Then `sleimanx2/plastic` and `elasticquent/Elasticquent` seems will do a simple integration without too much code, but which one will I use? I can't do both at once for the moment, so I will compare it based on their last maintenance updates (last commits) and the repository stars. And `elasticquent/Elasticquent` is worth to try.

_______________________________________

## Prerequisites

Don't forget to prepare your own laravel app (the database should be configured as well) and set up the elasticsearch server. If you have no idea how to set up the elasticsearch server, I have solutions for you:
- [Elasticsearch in Laradock](https://dev.to/dendihandian/elasticsearch-in-laradock-nm4) (if you're using laradock environment)
- [ELK Stack Local Development using Docker ELK](https://dev.to/dendihandian/elk-stack-local-development-using-docker-elk-6k7) (If you're not using laradock (WAMP, Laragon, etc), but we still use docker after all. You have to admit docker is popular these days...)

And you should be able to access it using Postman, Curl or etc.

_______________________________________

## Installing `elasticquent/elasticquent` and configure

Go to your project directory using CLI and execute this `composer` command:
```
composer require elasticquent/elasticquent
```

It seems by the time I used this package, the laravel auto-discovery doesn't work in this package and in the package's github page was cleary not stated that this package is discoverable or not. So, we need to add it the provider:

`config/app.php`:
```php
'providers' => [
    ...
    Elasticquent\ElasticquentServiceProvider::class,
],
```

And the `vendor:publish` command for this package should be work now:
```
php artisan vendor:publish --provider="Elasticquent\ElasticquentServiceProvider"
```

The `config\elasticquent.php` file should be generated, but I would like to customize it into like this:

`config\elasticquent.php`:
```php
<?php

$elasticsearch_host = env('ELASTICSEARCH_HOST', 'localhost');
$elasticsearch_port = env('ELASTICSEARCH_PORT', '9200');

return array(

    /*
    |--------------------------------------------------------------------------
    | Custom Elasticsearch Client Configuration
    |--------------------------------------------------------------------------
    |
    | This array will be passed to the Elasticsearch client.
    | See configuration options here:
    |
    | http://www.elasticsearch.org/guide/en/elasticsearch/client/php-api/current/_configuration.html
    */

    'config' => [
        'hosts'     => [ "{$elasticsearch_host}:{$elasticsearch_port}" ],
        'retries'   => 1,
    ],

    /*
    |--------------------------------------------------------------------------
    | Default Index Name
    |--------------------------------------------------------------------------
    |
    | This is the index name that Elasticquent will use for all
    | Elasticquent models.
    */

    'default_index' => env('ELASTICQUENT_DEFAULT_INDEX', 'elasticquent'),

);

```

If you're using any elasticsearch service that accessible by your app at `localhost:9200`, then it should be fine.

If you're using Laradock, then add these parameters to the app's `.env`:
```env
ELASTICSEARCH_HOST=elasticsearch
ELASTICSEARCH_PORT=9200
``` 

## Preparing the code

We need to create a model, migration, and factory to find out whether this package is working or not. So first, let's create `Book` model using `php artisan make:model Book` and modify the generated file:

`app\Book.php`:

```php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Elasticquent\ElasticquentTrait;

class Book extends Model
{
    use ElasticquentTrait;

    /**
     * The elasticsearch settings.
     *
     * @var array
     */
    protected $indexSettings = [
        'analysis' => [
            'char_filter' => [
                'replace' => [
                    'type' => 'mapping',
                    'mappings' => [
                        '&=> and '
                    ],
                ],
            ],
            'filter' => [
                'word_delimiter' => [
                    'type' => 'word_delimiter',
                    'split_on_numerics' => false,
                    'split_on_case_change' => true,
                    'generate_word_parts' => true,
                    'generate_number_parts' => true,
                    'catenate_all' => true,
                    'preserve_original' => true,
                    'catenate_numbers' => true,
                ]
            ],
            'analyzer' => [
                'default' => [
                    'type' => 'custom',
                    'char_filter' => [
                        'html_strip',
                        'replace',
                    ],
                    'tokenizer' => 'whitespace',
                    'filter' => [
                        'lowercase',
                        'word_delimiter',
                    ],
                ],
            ],
        ],
    ];

    function getIndexName()
    {
        return 'books';
    }

    function getTypeName()
    {
        return 'books';
    }
}

```

and then let's create the migration using `php artisan make:migration create_books_table` and modify it into like this:


`database\migrations\xxxx_yy_zz_tttttt_create_books_table.php`:
```php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateBooksTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('books', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('title');
            $table->string('slug');
            $table->unsignedInteger('year')->nullable();
            $table->text('description')->nullable();

            $table->unsignedBigInteger('author_id')->nullable();
            $table->unsignedBigInteger('publisher_id')->nullable();

            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('books');
    }
}

```

And finally to create the book factory using `php artisan make:factory BookFactory` and modify it into like this:

```php
<?php

/** @var \Illuminate\Database\Eloquent\Factory $factory */

use App\Book;
use Faker\Generator as Faker;
use Illuminate\Support\Str;

$factory->define(Book::class, function (Faker $faker) {

    $title = Str::title($faker->words($nb = 5, $asText = true));
    $slug = Str::slug($title);
    $description = $faker->paragraphs($nb = 3, $asText = true);
    $year = rand(1900, 2020);

    return [
        'title' => $title,
        'slug' => $slug,
        'description' => $description,
        'year' => $year,
        'author_id' => rand(1, 1000),
        'publisher_id' => rand(1, 1000),
    ];
});
```

## Indexing and Mapping the data to elasticsearch and do the search demo

Now all the preparations should be completed, let's make some data using factory and laravel's tinker console. Use `php artisan tinker` to enter the console and execute this code to create books data:

```
factory('App\Book', 5)->create();
```

Then add the created books data to the elasticsearch using this code:
```
\App\Book::addAllToIndex();
```

If there is no error that occurred during the above processes, then everything should stored to the elasticsearch. Let's use Postman to check it out:

<img src="https://i.ibb.co/1m0TPby/2020-04-05-17h22-52.png" alt="2020-04-05-17h22-52" border="0">

> Raw result [here](https://gist.github.com/drizzersilverberg/3c5c741ccd9ef619635d156ad0065a0e)

I have tried the `\App\Book::search()` and it doesn't get the result, but if I try using `\App\Book::complexSearch()` it still work like this:

```
>>> \App\Book::complexSearch(array(
...     'body' => array(
...         'query' => array(
...             'match' => array(
...                 'title' => 'Odio'
...             )
...         )
...     )
... ));
=> Elasticquent\ElasticquentResultCollection {#3155
     all: [
       App\Book {#3154
         id: 8,
         title: "Odio Et Consectetur Nobis Est",
         slug: "odio-et-consectetur-nobis-est",
         year: 2001,
         description: """
           Alias et dolorem natus blanditiis et at ducimus. Rerum suscipit dolorem nesciunt voluptates quia quas omnis. Veniam est qui quis.\n
           \n
           Minima nemo deserunt omnis nemo assumenda qui omnis. Sed ullam reiciendis vitae rerum amet. Deserunt officiis reprehenderit beatae minus. Possimus incidunt quia et.\n
           \n
           Officiis voluptas natus culpa voluptates deserunt. Possimus et doloribus quis sint. Sed magni in dolorem in consequatur quibusdam. Et ipsum neque rerum sed. Quas saepe dolorem rerum rerum quis velit sit.
           """,
         author_id: 638,
         publisher_id: 794,
         created_at: "2020-04-05 00:25:11",
         updated_at: "2020-04-05 00:25:11",
       },
     ],
   }
>>> 
```

And here is the search result in Postman:

<img src="https://i.ibb.co/fXx0xWn/2020-04-05-18h17-50.png" alt="2020-04-05-18h17-50" border="0"> 

I think that's all to give you an idea about how laravel and elasticsearch works. Have fun exploring and experimenting it!

_______________________________________

```
versions used:
 - laravel: 6.0 LTS
 - elasticquent/elasticquent: "^1.0" (dev-master)
 - elasticsearch: 7.5.1 
```