Implementasi Elastic Search dan Synonym Filter di Laravel 5.8
Sesuai dengan judul, tulisan kali ini mencoba untuk implementasi elasticsearch di laravel lengkap dengan menambahkan fitur sinonim. Tulisan ini ada 4 bagian, pertama memasang elasticsearch, kedua memasang laravel dan melakukan pencarian sederhana, ketiga implementasi elasticsearch dengan laravel, terakhir menambahkan filter sinonim.
Memasang Elasticsearch
Karena saya tidak mau ribet saya memasang elasticsearch versi docker dengan bantuan docker-compose, di berkas docker-compose.yml saya cukup membuat seperti ini
version: '3.3' services: elasticsearch: image: elasticsearch:6.6.0 ports: - '9200:9200' volumes: - .:/usr/share/elasticsearch/data environment: ES_JAVA_OPTS: '-Xms256m -Xmx256m' network.bind_host: 0.0.0.0 network.host: 0.0.0.0 discovery.type: single-node cluster.name: my-cluster
Lalu jalankan perintah berikut untuk memasang elasticsearch-nya
docker-compose up --build -d
Untuk mengetesnya kita bisa kunjungi 0.0.0.0:9200 dan akan muncul tampilan seperti berikut.
Memasang laravel
Saya anggap yang membaca di sini sudah paham memasang laravel, maka saya akan berfokus kepada pencarian sederhana saja. Pertama saya akan buat model Product yang memiliki daftar produck tertentu. Mari buat model dan migrationnya.
php artisan make:model Product -m
Isi dari model Product
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Product extends Model { protected $guarded = ['id']; }
Ubah migration product menjadi
public function up() { Schema::create('products', function (Blueprint $table) { $table->bigIncrements('id'); $table->string('title'); $table->string('slug'); $table->text('description'); $table->timestamps(); }); }
Kita butuh contoh data untuk melakukan pencarian, kita buat seed manual bukan dengan factory
php artisan make:seeder ProductSeeder
Isi dari seeder saya tambahkan seperti berikut
public function run() { $data = [ ['title'=>'Laptop Series T','slug'=>'laptop-series-t','description'=>'Laptop series t'], ['title'=>'Laptop Series X','slug'=>'laptop-series-x','description'=>'Laptop series x'], ['title'=>'Notebook Series T','slug'=>'notebook-series-t','description'=>'Notebook series t'], ['title'=>'Notebook Series x','slug'=>'notebook-series-x','description'=>'Notebook series x'], ['title'=>'PC','slug'=>'pc','description'=>'daily pc'], ['title'=>'PC Gaming','slug'=>'pc-gaming','description'=>'pc gaming'] ]; Product::insert($data); }
Setelah siap jalankan perintah berikut
php artisan migrate php artisan db:seed --class=ProductSeeder
Untuk membuat pencarian sederhana kita buat langsung di route api saja. Buka berkas api.php dan tambahkan route seperti berikut
Route::get('/products', function () { return \App\Product::all(); }); Route::get('/products/{param}', function ($param) { return \App\Product::where('title','like','%'.$param.'%')->get(); });
Saat akses /products
Saat akses pencarian
Implementasi Elasticsearch
Saya menggunakan paket dari babenkoivan/scout-elasticsearch-driver. Maka dari itu mari pasang terlebih dahulu paket yang dibutuhkan.
composer require babenkoivan/scout-elasticsearch-driver
Jika sudah selesai memasang, kita perlu keluarkan konfigurasinya
php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider" php artisan vendor:publish --provider="ScoutElastic\ScoutElasticServiceProvider"
Setelah di-publish perbarui berkas .env, tambahkan dua parameter berikut
SCOUT_DRIVER='elastic' SCOUT_ELASTIC_HOST=0.0.0.0:9200
Seperti biasa perubahan di .env maka jalankan perintah berikut
php artisan config:cache
Selanjutnya kita akan membuat konfigurasi untuk index di elastic-nya, untuk membuat berkas konfigurasinya jalankan perintah
php artisan make:index-configurator ProductIndexConfigurator
Selanjutnya ubah model Product menjadi seperti berikut
<?php namespace App; use ScoutElastic\Searchable; use Illuminate\Database\Eloquent\Model; class Product extends Model { use Searchable; protected $guarded = ['id']; /** * @var string */ protected $indexConfigurator = ProductIndexConfigurator::class; /** * @var array */ protected $searchRules = [ // ]; /** * @var array */ // mapping ini sesuaikan dengan field di model terkait protected $mapping = [ 'properties' => [ 'title' => [ 'type' => 'text', 'analyzer'=>'standard' ], 'slug' => [ 'type' => 'text', 'analyzer'=>'standard' ], 'description' => [ 'type' => 'text', 'analyzer'=>'standard' ], 'created_at' => [ 'type' => 'date', 'format'=>'yyyy-MM-dd HH:mm:ss' ], ] ]; }
Jika tidak mau ribet, kalian bisa menjalankan perintah berikut agar otomatis terbentuk ( kecuali isi dari mapping ) php artisan make:searchable-model Product –index-configurator=ProductIndexConfigurator
Tapi pastikan model sebelumnya dihapus atau di-rename terlebih dahulu jika sudah ada.
Selanjutnya yang kita butuhkan adalah “mendaftarkan” index nya ke dalam elasticsearch, jalankan perintah berikut untuk membuat index di elastic
php artisan elastic:create-index 'App\ProductIndexConfigurator'
Selanjutnya kita akan melakukan mapping dari model ke index, lakukan perintah berikut:
php artisan elastic:update-mapping 'App\Product'
Selanjutnya kita akan mendaftarkan record dalam database ke elastic menggunakan perintah berikut
php artisan scout:import 'App\Product'
Perintah itu akan menampilkan pesan
Imported [App\Product] models up to ID: 6 All [App\Product] records have been imported.
Untuk mengetes pencarian dengan elastic berhasil atau tidak, kita ubah kode pencariannya menjadi berikut
return \App\Product::search($param)->get();
Jika berhasil pencarian akan normal seperti tadi
Sinonim
Fitur sinonim ini sesuai namanya mencari data yang merupakan sinonim dari pencariannya, misalkan jika saya mendaftarkan sinonim laptop adalah notebook, maka saat saya mencari dengan kata kunci laptop maka produk notebook pun akan muncul, begitupun sebaliknya
Pertama kita harus membuat filter sinonimnya terlabih dahulu, buka berkas ProductIndexConfigurator, lalu pada bagian variable $settings saya ubah menjadi berikut
protected $settings = [ 'analysis'=>[ 'filter' => [ 'product_synonym_filter' => [ 'type'=> 'synonym', 'synonyms' => [ 'laptop, notebook' ] ] ], 'analyzer' => [ 'product_synonyms' => [ 'tokenizer' => 'standard', 'filter'=> [ 'lowercase', 'product_synonym_filter' ] ] ] ], ];
Lalu kita ubah mapping dari model juga menjadi
protected $mapping = [ 'properties' => [ 'title' => [ 'type' => 'text', 'analyzer'=>'product_synonyms' ], 'slug' => [ 'type' => 'text', 'analyzer'=>'standard' ], 'description' => [ 'type' => 'text', 'analyzer'=>'standard' ], 'created_at' => [ 'type' => 'date', 'format'=>'yyyy-MM-dd HH:mm:ss' ], ] ];
Karena kita melakukan perubahan mapping kita tidak bisa dengan mudah cukup me-update index, karena saat kita mapping ulang kita akan mendapatkan pesan error bentrok dengan mapping yang ada, maka cara paling aman adalah dengan menghapus index dan mengulang dari awal.
php artisan elastic:drop-index 'App\ProductIndexConfigurator' php artisan elastic:create-index 'App\ProductIndexConfigurator' php artisan elastic:update-mapping 'App\Product' php artisan scout:import 'App\Product'
Jika suatu saat kita hanya butuh mengubah index tanpa merlu mengubah mapping yuang kita perlukan hanyalah perintah update index:
php artisan elastic:update-index ‘App\ProductIndexConfigurator’
Jika sudah lakukan pencarian kembali dengan kata kunci laptop misalnya, seharusnya akan muncul seperti berikut
Source Code
https://gitlab.com/ariesmaulana/laravel-elastic-synonym
Referensi:
https://packagist.org/packages/babenkoivan/scout-elasticsearch-driver
https://www.elastic.co/guide/en/elasticsearch/guide/current/using-synonyms.html