Menambahkan Fitur Search Menggunakan Laravel Scout Dan Meilisearch - CRUDPRO

Menambahkan Fitur Search Menggunakan Laravel Scout Dan Meilisearch

Sebelumnya, Anda harus menambahkan beberapa pencarian ke aplikasi Anda. Saya sering mengandalkan Algolia dan Laravel Scout. Ini karena kotak itu bekerja dengan baik dan memberikan hasil yang bagus. Tapi sekarang Brock punya (relatif) anak baru, meilisearch. Meilisearch sangat mirip fungsinya dengan Algolia, tetapi merupakan proyek open source yang dibuat menggunakan bahasa pemrograman Rust. Jadi Anda dapat melakukan ini secara lokal secara gratis, atau Anda dapat memulai server dalam produksi menggunakan sesuatu seperti Laravel Forge.

Tutorial ini akan memandu Anda melalui langkah-langkah untuk memulai Meilisearch di Laravel Scout. Ini memungkinkan Anda untuk melihat perbedaan dalam pengaturan dan memutuskan arah mana yang harus dituju. Seperti biasa, saya akan mulai dengan aplikasi Laravel baru dan saya banyak menggunakan Valet secara lokal, jadi saya biasanya menggunakan Laravel-installer tapi tutorial ini harus bekerja dengan baik dengan valet dan docker.

Jalankan salah satu perintah berikut untuk membuat aplikasi baru untuk demo ini.

Menggunakan penginstal Laravel

laravel new search-demo

Gunakan proyek pembuatan composer

composer create-project laravel/laravel search-demo

Laravel membangun dan menggunakan Sail

curl -s "https://laravel.build/search-demo" | bash

Metode apa pun yang Anda pilih, Anda akan melihat proyek Laravel Anda di bawah direktori baru bernama search-demo. Ini berarti ini adalah tempat yang baik untuk memulai.

Hal pertama yang ingin Anda lakukan adalah menjalankan perintah composer berikut untuk menginstal Laravel Scout.

composer require laravel/scout

Ini akan menginstal Scout di aplikasi Laravel Anda dan memungkinkan Anda untuk mulai berinteraksi dengan salah satu driver potensial yang mungkin Anda gunakan untuk pencarian Anda. Langkah selanjutnya adalah menjalankan artisan command berikut untuk mengekspos konfigurasi Laravel Scout.

php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"

Ini akan mengatur config/scout.php yang baru dibuat agar dapat dimodifikasi, tetapi Anda mungkin ingin memodifikasinya, tetapi Anda mungkin ingin menjaga ini tetap standar.

Pada titik ini, ada beberapa opsi, opsi driver. Tutorial ini akan menunjukkan cara menggunakan meilisearch, tetapi berikut adalah opsi yang dapat Anda gunakan sebagai driver Laravel Scout:

  • Argolia: Gunakan layanan pihak ketiga Argolia.
  • Meilisearch: Gunakan layanan meilisearch open source.
  • Collection: Gunakan database sebagai layanan pencarian hanya MySQL dan PostgreSQL yang didukung.
  • null: Jangan gunakan driver yang biasanya digunakan untuk pengujian.

Untuk mulai menggunakan driver meilisearch, anda perlu menginstal paket baru yang memungkinkan scouts menggunakan meilisearch SDK, jadi jalankan perintah composer berikut:

composer require meilisearch/meilisearch-php

Oleh karena itu, dokumentasi Laravel menyatakan bahwa Anda juga perlu menginstal http-interop/http-factory-guzzle, tetapi jika Anda melihat pustaka meilisearch-php, ini termasuk sebagai bagian dari peningkatan dependensi itu. Oleh karena itu, Anda dapat melewatkan ini atau menginstalnya jika Anda merasa lebih nyaman. Langkah selanjutnya adalah mengatur beberapa variabel ENV di file .env Anda.

SCOUT_DRIVER=meilisearch
MEILISEARCH_HOST=http://127.0.0.1:7700
MEILISEARCH_KEY=masterKey

Variabel env MEILISEARCH_KEY menarik. Jika Anda menginstal meilisearch secara lokal, Anda dapat memulai layanan dan memberikan tanda opsional untuk mengaturnya setiap kali Anda menjalankannya. Dalam lingkungan produksi ini harus ditetapkan sebagai tindakan keamanan, tetapi secara lokal Anda dapat membiarkannya kosong jika diinginkan. Saya pribadi menyimpan set ini. Ini adalah kebiasaan yang baik dan mengingatkan Anda bahwa Anda benar-benar perlu mengaturnya.

Laravel Scout diinstal dan meilisearch diinstal dan dikonfigurasi pada sisi client. Langkah selanjutnya, seperti semua aplikasi bagus, adalah memikirkan data Anda. saya butuh datanya. Demo ini menggunakan contoh yang cukup mendasar untuk fokus pada topik meilisearch and Scout sehingga Anda tidak tersesat dalam logika kode demo. Ini akan menjadi aplikasi blog sederhana dengan posting blog dan kategori. Oleh karena itu, Anda dapat mengindeks semua yang Anda butuhkan.

Jalankan artisan command berikut untuk membuat model Eloquent baru yang disebut Kategori. Perhatikan tanda tambahan untuk membuat migration dan factory yang penting di sini.

php artisan make:model Category -mf

Karena kategori kami akan menjadi model yang relatif ringan, kami akan menampilkan kode migration di bawah ini sehingga kami dapat memproses model itu sendiri menggunakan model fillable atau guarded, tergantung pada preferensi pribadi.

public function up()
{
    Schema::create('categories', static function (Blueprint $table): void {
        $table->id();
        $table->string('name');
        $table->string('slug')->unique();
		$table->boolean('searchable')->default(true);
        $table->timestamps();
    });
}

Berikut adalah nama, slug, dan flag Boolean yang dapat dicari. Ini memungkinkan Anda untuk sepenuhnya menyembunyikan kategori tertentu dari pencarian Anda. Ini bisa berguna. Masuk dalam model Eloquent, tetapi biasanya. Langkah selanjutnya adalah membuat factory model.

class CategoryFactory extends Factory
{
    protected $model = Category::class;
    public function definition(): array
    {
        $name = $this->faker->unique()->word();
        return [
            'name' => $name,
            'slug' => Str::slug(
                title: $name,
            ),
            'searchable' => $this->faker->boolean(
                chanceOfGettingTrue: 85,
            ),
        ];
    }

	public function searchable(): static
    {
        return $this->state(fn (array $attributes): array => [
            'searchable' => true,
        ]);
    }

	public function nonsearchable(): static
    {
        return $this->state(fn (array $attributes): array => [
            'searchable' => false,
        ]);
    }
}

Kami telah membuat kemungkinan default bahwa kategori dapat dicari sangat tinggi, tetapi kami telah menambahkan metode status tambahan untuk mengontrol kemungkinan ini untuk tujuan pengujian. Ini memberi Anda cakupan terbaik untuk menguji implementasi Anda.

Selanjutnya, kita membutuhkan model lain, Post, yang merupakan titik masuk utama untuk pencarian, jadi jalankan artisan command berikut:

php artisan make:model Post -mf

Sekali lagi, seperti sebelumnya, ini menunjukkan migration dan memungkinkan Anda untuk menangani properti yang dapat dimasukkan atau dilindungi dari model Anda. Ini adalah preferensi yang sangat pribadi.

public function up(): void
{
    Schema::create('posts', static function (Blueprint $table): void {
        $table->id();
        $table->string('title');
        $table->string('slug')->unique();
        $table->mediumText('content');
		$table->boolean('published')->default(true);
        $table
			->foreignId('category_id')
			->index()->constrained()->cascadeOnDelete();
        $table->timestamps();
    });
}

Langkah selanjutnya adalah mengisi factory untuk model Post.

class PostFactory extends Factory
{
    protected $model = Post::class;
    public function definition(): array
    {
        $title = $this->faker->unique()->sentence();
        return [
            'title' => $title,
            'slug' => Str::slug(
                title: $title,
            ),
            'content' => $this->faker->paragraph(),
            'published' => $this->faker->boolean(
                chanceOfGettingTrue: 85,
            ),
            'category_id' => Category::factory(),
        ];
    }

    public function published(): static
    {
        return $this->state(fn (array $attributes): array => [
            'published' => true,
        ]);
    }

    public function draft(): static
    {
        return $this->state(fn (array $attributes): array => [
            'published' => false,
        ]);
    }
}

Seperti model kategori, model ini memiliki flag Boolean, tetapi kali ini Anda dapat membuat status draf untuk menunjukkan jika model diekspos. Tambahkan metode status tambahan ke factory untuk memberi Anda kontrol yang lebih baik atas ini dalam environtment pengujian Anda.

Terakhir, Anda dapat menambahkan hubungan ke model Anda. Kategori harus posting HasMany dan posting harus dalam kategori BelongsTo.

Sekarang setelah semua data dimodelkan dan tersedia, saya ingin dapat menyemai beberapa data. Tetapi sebelum itu, Anda perlu menginstal meilisearch. Jika Anda menggunakan Laravel Sail, semudah melewati opsi saat menginstruksikan Sail untuk menginstal. Dengan LaravelValet, ini sedikit berbeda. Petunjuk penginstalan untuk ini dapat ditemukan di dokumentasi meilisearch dan relatif mudah dilakukan. Jika Anda mengalami masalah, periksa persyaratan untuk menjalankan meilisearch secara lokal.

Mari kita lihat beberapa seeding data, dengan asumsi bahwa meilisearch sekarang sudah aktif dan berjalan. Tambahkan bilah kemajuan ke seeder untuk memastikannya berfungsi dengan baik, tetapi lewati langkah ini jika Anda tidak membutuhkannya.

class DatabaseSeeder extends Seeder
{
    use WithoutModelEvents;

    public function run(): void
    {
		$categories = Category::factory(10)->create();
		$categories->each(function (Category $category) {
			$this->command->getOutput()->info(
    			message: "Creating posts for category: [$category->name]",
			);
			$bar = $this->command->getOutput()->createProgressBar(100);

			for ($i = 0; $i < 100; $i++) {
				$bar->advance();
				Post::factory()->create();
			}

			$bar->finish();
		});
    }
}

Saya ingin mengontrol perilaku ini, jadi untuk saat ini seeding tidak memerlukan efek samping. Oleh karena itu, gunakan sifat WithoutModelEvents untuk menghentikannya. Apa yang kami lakukan di sini adalah membuat 10 kategori. Kemudian buat bilah kemajuan untuk setiap kategori dan buat 100 posting untuk kategori ini. Ini akan memberi Anda output visual saat Cedar berjalan, mengonfirmasi bahwa ada posting di setiap kategori. Anda dapat mengetahui apa yang tersedia dengan mencari.

Sekarang setelah kita memiliki beberapa data, kita dapat mempertimbangkan untuk membuat model Post dapat dicari. Untuk melakukan ini, cukup tambahkan Seachable trait dari Laravel Scout ke model Anda.

class Post extends Model
{
    use Searchable;
    use HasFactory;

    // Other model stuff here ...
}

Sekarang model Anda dapat dicari, Anda dapat mulai menambahkan beberapa kontrol ke model Anda tentang cara menemukannya. Hampir 99% kemungkinan Anda akan menggunakan model Post, tetapi Anda juga memerlukan kategori. Oleh karena itu, instruksikan model Eloquent untuk selalu memuat model kategori dengannya.

class Post extends Model
{
    use Searchable;
    use HasFactory;

	protected $with = [
		'category'
	];
 
	// Other model stuff here ...
}

Sekarang Anda dapat menambahkan metode baru untuk melihat apakah Laravel Scout dapat menemukan model atau menambahkannya ke file index.

class Post extends Model
{

    use Searchable;
    use HasFactory;

	protected $with = [
		'category'
	];

	public function searchable(): bool
	{
    	return $this->published || $this->category->searchable;
	}

	// Other model stuff here ...
}

Jika posting Anda bersifat publik atau termasuk dalam kategori itu searchable, Anda perlu mengindeksnya. Ini akan memungkinkan Scout untuk menilai kembali apakah model ini perlu diindeks pada saat pembaruan. Langkah selanjutnya adalah mengontrol bagaimana ini diindeks. Saya tidak peduli dengan nama indeks. Biasanya Anda membiarkan ini sebagai standar untuk aplikasi kecil, tetapi Anda dapat menimpanya dengan mengatur nama indeks sendiri menggunakan metode searchableAs. Untuk mengontrol bagaimana data ditambahkan ke meilisearch , Anda perlu menambahkan metode toSearchableArray. Ini memungkinkan Anda untuk menentukan array untuk mengindeks data Anda.

class Post extends Model
{
    use Searchable;
    use HasFactory;
 
	protected $with = [
		'category'
	];

	public function searchable(): bool
	{
    	return $this->published || $this->category->searchable;
	}


	public function toSearchableArray(): array
	{
    	return [
        	'title' => $this->title,
        	'slug' => $this->slug,
        	'content' => $this->content,
        	'category' => [
            	'name' => $this->category->name,
				'slug' => $this->category->slug,
        	]
    	];
	}

	// Other model stuff here ...

}

Tambahkan informasi kategori ke setiap postingan sehingga informasi seperti "judul postingan (nama kategori)" dapat ditampilkan dengan baik di UI postingan itu sendiri.

Terakhir, ada sesuatu yang dapat Anda indeks dan cari, jadi mari impor semua records Postingan ke meilisearch.

php artisan scout:import "App\Models\Post"

Ini harus menunjukkan output dari potongan 500 records yang semuanya ditambahkan ke scout. Sekarang Anda memiliki sesuatu untuk dicari. Anda perlu memikirkan cara mencari. Untuk scout, Anda dapat melakukan pencarian sederhana pada model Anda menggunakan metode pencarian statis. Anda dapat meneruskan query dan mengembalikan model hydrated, atau Anda dapat mulai melihat filter dan sebagainya. Sekarang mari kita lihat pencarian dasar di controller dan refactor dari sana.

class SearchController extends Controller
{
    public function __invoke(Request $request): JsonResponse
    {
        return new JsonResponse(
            data: Post::search(
                query: trim($request->get('search')) ?? '',
            )->get(),
            status: Response::HTTP_OK,
        );
    }
}

Untuk saat ini, mari daftarkan route ini sebagai route API sehingga kita dapat melihat hasilnya tanpa membuat UI.

Route::get(
    'search',
    App\Http\Controllers\SearchController::class
)->name('search');

Sekarang Anda dapat melihat output JSON dari pencarian Anda berdasarkan parameter query pencarian Anda dan melihat serta menguji bagaimana responsnya. Coba telusuri kata-kata keseluruhan atau sebagian. Ini adalah dasar dari Laravel Scout dan Meilisearch, dan sekarang Anda dapat mengindeks dan mencari model Anda. Oleh karena itu, sangat baik dari sudut pandang itu. Langkah selanjutnya adalah memikirkan cara untuk mendapatkan sedikit lebih banyak.

Filternya bagus. Anda bisa mendapatkan hasil yang lebih bertarget dalam pencarian Anda hanya dengan meminta filter. Jadi saya akan menambahkan beberapa filter ke model Postingan untuk memudahkan memfilter query. Ini adalah pendekatan saya, tetapi tidak harus menjadi milik Anda. Jadi ambil apa yang saya coba lakukan hanya dengan sedikit garam dan sesuaikan dengan kebutuhan Anda sendiri.

class Post extends Model
{
    use Searchable;
    use HasFactory;

	protected $with = [
		'category'
	];

	public function searchable(): bool
	{
    	return $this->published || $this->category->searchable;
	}

	public function toSearchableArray(): array
	{
    	return [
        	'title' => $this->title,
        	'slug' => $this->slug,
        	'content' => $this->content,
        	'category' => [
            	'name' => $this->category->name,
				'slug' => $this->category->slug,
        	]
    	];
	}

	public static function getSearchFilterAttributes(): array
	{
    	return [
        	'category.name',
        	'category.slug',
    	];
	}

	// Other model stuff here ...
}

Menambahkan fungsi statis yang mendefinisikan atribut filter pencarian untuk model. Seperti yang Anda lihat, kami ingin dapat memfilter berdasarkan nama kategori atau slug. Langkah selanjutnya adalah membuat perintah untuk mendaftarkan atribut yang dapat difilter ini dengan meilisearch. Scouts tidak memiliki cara untuk melakukannya secara default, jadi saya biasanya menulis perintah konsol untuk melakukan ini.

php artisan make:command Search/SetupSearchFilters

Kemudian tambahkan potongan kode berikut:

class SetupSearchFilters extends Command
{
    protected $signature = 'scout:filters
		{index : The index you want to work with.}
	';

    protected $description = 'Register filters against a search index.';

    public function handle(Client $client): int
	{
        $index = $this->argument(
            key: 'index',
        );

        $model = match($index) {
            'posts' => Post::class,
        };

        try {
            $this->info(
                string: "Updating filterable attributes for [$model] on index [$index]",
            );

            $client->index(
                uid: $index,
            )->updateFilterableAttributes(
                filterableAttributes: $model::getSearchFilterAttributes(),
            );
        } catch (ApiException $exception) {
            $this->warn(
                string: $exception->getMessage(),
            );

            return self::FAILURE;
        }
        return 0;
    }
}

Apa yang Anda lakukan di sini adalah meneruskan indeks dan menggunakan pernyataan match/switch untuk mencocokkannya dengan model jika Anda ingin memperluas apa yang Anda indeks. Kemudian, bergantung pada cara kerja perintah konsol, Anda dapat menyelesaikan client meilisearch dengan metode pegangan dan menggunakannya untuk memperbarui indeks sambil mendapatkan atribut filter pencarian. Jika ini gagal, pengecualian ditampilkan dan kegagalan dikembalikan.

Anda sekarang dapat melakukan ini dengan perintah berikut:

php artisan scout:filters 'posts'

Jika semuanya berjalan sesuai rencana, meilisearch akan mengenali filter yang tersedia di indeks. Mari kita lihat apakah kita bisa melakukannya. Refactor SearchController untuk menerima filter untuk pencarian Anda.

class SearchController extends Controller
{
    public function __invoke(Request $request): JsonResponse
    {
        return new JsonResponse(
            data: Post::search(
                query: $request->get('search'),
				callback: function (Indexes $meilisearch, string $query, array $options) use ($request) {
					if ($request->has(key: 'categry.slug')) {
						$options['filter'] = "category.slug = {$request->get(key: 'category.slug')}";
					}
					return $meilisearch->search(
						query: $query,
						options: $options,
					);
				},
            )->get(),
            status: Response::HTTP_OK,
        );
    }
}

Sekarang, jika Anda menambahkan parameter query lain ke pencarian untuk category.slug={something}, Anda akan mendapatkan hasil filter dari pencarian yang Anda jalankan. Saat ini, milik saya terlihat seperti ini: /api/search?search=rem&category.slug=voluptatibusyang memfilter hasil dengan tepat. Bergantung pada bagaimana data Anda dimodelkan, Anda dapat menyertakan filter untuk nama kategori dan memperluasnya sesuai kebutuhan. Jika diinginkan, Anda juga dapat membuat filter yang memfilter menurut waktu.

Ini hanyalah salah satu cara menggunakan Laravel Scout untuk menerapkan pencarian yang baik di aplikasi Anda dan menyempurnakannya dengan filter sesuai kebutuhan. Ada banyak driver yang tersedia untuk Laravel Scout, dan bukan tidak mungkin untuk membuatnya sendiri. Sebenarnya, sudah ada beberapa driver open source yang dapat Anda gunakan agar sesuai dengan use case Anda.

Bagaimana Anda menangani pencarian aplikasi? Udah coba meilisearch belum? sekian tutorial dari kami semoga bermanfaat :).