DEV Community

Dendi Handian
Dendi Handian

Posted on • Updated on

Elasticsearch Eloquent Integration for Laravel

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

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:

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
Enter fullscreen mode Exit fullscreen mode

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:

'providers' => [
    ...
    Elasticquent\ElasticquentServiceProvider::class,
],
Enter fullscreen mode Exit fullscreen mode

And the vendor:publish command for this package should be work now:

php artisan vendor:publish --provider="Elasticquent\ElasticquentServiceProvider"
Enter fullscreen mode Exit fullscreen mode

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

config\elasticquent.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'),

);

Enter fullscreen mode Exit fullscreen mode

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:

ELASTICSEARCH_HOST=elasticsearch
ELASTICSEARCH_PORT=9200
Enter fullscreen mode Exit fullscreen mode

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

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';
    }
}

Enter fullscreen mode Exit fullscreen mode

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

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');
    }
}

Enter fullscreen mode Exit fullscreen mode

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

<?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),
    ];
});
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

Then add the created books data to the elasticsearch using this code:

\App\Book::addAllToIndex();
Enter fullscreen mode Exit fullscreen mode

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:

2020-04-05-17h22-52

Raw result here

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",
       },
     ],
   }
>>> 
Enter fullscreen mode Exit fullscreen mode

And here is the search result in Postman:

2020-04-05-18h17-50

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 
Enter fullscreen mode Exit fullscreen mode

Latest comments (4)

Collapse
 
orodriguez88 profile image
orodriguez88

please help

Elasticsearch/Common/Exceptions/NoNodesAvailableException with message 'No alive nodes found in your cluster'

Collapse
 
dendihandian profile image
Dendi Handian

try to access your elasticsearch base URL to make sure it alive and try to create any example index to make sure it works.

Collapse
 
amir__jani profile image
amir jani

Thanks for your straight tutorial.

Collapse
 
dendihandian profile image
Dendi Handian • Edited

You're welcome, glad to hear it.