w3resource

Laravel (5.7) Laravel Scout

Introduction

Laravel Scout will provide a simple, driver based solution to add full-text search to your Eloquent models. Scout automatically keeps your search indexes in sync with your Eloquent records when using model observers.

Currently, Scout will ship with an Algolia driver; however, writing custom drivers will be simple and you can extend Scout with your own search implementations.

Installation

First, you need to install Scout via the Composer package manager:

composer require laravel/scout

Once you install Scout, you need to publish the Scout configuration using the vendor:publish Artisan command. This command publishes the scout.php configuration file to your config directory:

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

Finally, you need to add the Laravel\Scout\Searchable trait to the model you want to make searchable. This trait registers a model observer to keep the model in sync with your search driver:

<?php

namespace App;

use Laravel\Scout\Searchable;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use Searchable;
}

Queueing

Although, you are not strictly required to use Scout, we will advise you to consider configuring a queue driver before using the library.

If you run a queue worker it allows Scout to queue all operations that sync your model information to your search indexes, thus providing a much better response time for your application's web interface.

After you have configured a queue driver, you should set the value of the queue option in your config/scout.php configuration file to true:

'queue' => true,

Driver Prerequisites

Algolia

When you use the Algolia driver, you have to configure your Algolia id and secret credentials in your config/scout.php configuration file. After your credentials have been configured, you also need to install the Algolia PHP SDK via the Composer package manager:

composer require algolia/algoliasearch-client-php:^2.2

Configuration

Configuring Model Indexes

Each Eloquent model will be synced with a given search "index", this contains all of the searchable records for that model. By default, each model is persisted to an index matching the model's typical "table" name. Typically, this index is the plural form of the model name; however, you can customize the model's index by overriding the searchableAs method on the model:

<?php

namespace App;

use Laravel\Scout\Searchable;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use Searchable;

    /**
     * Gets the index name for the model.
     *
     * @return string
     */
    public function searchableAs()
    {
        return 'posts_index';
    }
}

Configuring Searchable Data

By default, the entire toArray form of a given model is persisted to its search index. If you want to customize the data that is synchronized to the search index, you can override the toSearchableArray method on the model:

<?php

namespace App;

use Laravel\Scout\Searchable;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use Searchable;

    /**
     * Gets the indexable data array for the model.
     *
     * @return array
     */
    public function toSearchableArray()
    {
        $array = $this->toArray();

        // Customize array...

        return $array;
    }
}

Configuring The Model ID

By default, Scout uses the primary key of the model as the unique ID stored in the search index. If you want to customize this behavior, you can override the getScoutKey method on the model:

<?php

namespace App;

use Laravel\Scout\Searchable;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    use Searchable;

    /**
     * Get the value used to index the model.
     *
     * @return mixed
     */
    public function getScoutKey()
    {
        return $this->email;
    }
}

Indexing

Batch Import

In the case where you are installing Scout into an existing project, you may already have database records which you need to import into your search driver. Scout will provide an import Artisan command that you can use to import all of your existing records into your search indexes:

php artisan scout:import "App\Post"

The flush command can be used to remove all of a model's records from your search indexes:

php artisan scout:flush "App\Post"

Adding Records

After you have added the Laravel\Scout\Searchable trait to a model, all you have to do is save a model instance and it is automatically added to your search index. If you have configured Scout to use queues this operation is performed in the background by your queue worker:

$order = new App\Order;

// ...

$order->save();

Adding Via Query

If you want to add a collection of models to your search index via an Eloquent query, you can chain the searchable method onto an Eloquent query. The searchable method chunks the results of the query and then add the records to your search index. Again, if you need configured Scout to use queues, all of the chunks are added in the background by your queue workers:

// Adding via the Eloquent query...
App\Order::where('price', '>', 100)->searchable();

// You can also add records via relationships...
$user->orders()->searchable();

// You can also add records via collections...
$orders->searchable();

You can consider the searchable method as an "upsert" operation. In other words, in the case where the model record is already in your index, it is updated. If it doesn?t exist in the search index, it is added to the index.

Updating Records

If you want to update a searchable model, you only have to update the model instance's properties and then save the model to your database. Scout automatically persists the changes to your search index:

$order = App\Order::find(1);
// Updates the order...
$order->save();

You can also use the searchable method on an Eloquent query to update a collection of models. If the models does not exist in your search index, they are created:

// Updates via Eloquent query...
App\Order::where('price', '>', 100)->searchable();

// You can also update via relationships...
$user->orders()->searchable();

// You can also update via collections...
$orders->searchable();
Removing Records

If you want to remove a record from your index, you need to delete the model from the database. This form of removal is compatible with soft deleted models:

$order = App\Order::find(1);
$order->delete();

If you prefer not to retrieve the model before deleting the record, you can use the unsearchable method on an Eloquent query instance or collection:

// Removes via Eloquent query...
App\Order::where('price', '>', 100)->unsearchable();

// You can also remove via relationships...
$user->orders()->unsearchable();

// You can also remove via collections...
$orders->unsearchable();

Pausing Indexing

Sometimes you may want to perform a batch of Eloquent operations on a model without syncing the model data to your search index. You can do this using the withoutSyncingToSearch method. This method will accept a single callback which is immediately executed. Any model operations that occur within the callback won?t be synced to the model's index:

App\Order::withoutSyncingToSearch(function () {
    // Perform model actions...
});

Conditionally Searchable Model Instances

Sometimes you may want to only make a model searchable under certain conditions. For instance, imagine you have App\Post model that can be in one of two states: "draft" and "published". You may only wish to allow "published" posts to be searchable. To accomplish this, you can define a shouldBeSearchable method on your model:

public function shouldBeSearchable()
{
    return $this->isPublished();
}

The shouldBeSearchable method will only be applied when manipulating models through the save method, queries, or relationships. Directly making models or collections searchable using the searchable method overrides the result of the shouldBeSearchable method:

//  respects "shouldBeSearchable"...
App\Order::where('price', '>', 100)->searchable();

$user->orders()->searchable();

$order->save();

//  overrides "shouldBeSearchable"...
$orders->searchable();

$order->searchable();

Searching

You can begin searching a model by using the search method. The search method will accept a single string that is used to search your models. You have to then chain the get method onto the search query to retrieve the Eloquent models that match the given search query:

$orders = App\Order::search('Star Trek')->get();

Because Scout searches return a collection of Eloquent models, you can even return the results directly from a route or controller and they are automatically converted to JSON:

use Illuminate\Http\Request;

Route::get('/search', function (Request $request) {
    return App\Order::search($request->search)->get();
});

If you want to get the raw results before they are converted to Eloquent models, you have use the raw method:

$orders = App\Order::search('Star Trek')->raw();

Search queries are typically performed on the index specified by the model's searchableAs method. However, you can use the within method to specify a custom index that should be searched instead:

$orders = App\Order::search('Star Trek')
    ->within('tv_shows_popularity_desc')
    ->get();

Where Clauses

Scout will allow you to add simple "where" clauses to your search queries. Currently, these clauses will only support basic numeric equality checks, and are primarily useful for scoping search queries using a tenant ID. Because a search index is not a relational database, more advanced "where" clauses are not supported currently:

$orders = App\Order::search('Star Trek')->where('user_id', 1)->get();

Pagination

Added to retrieving a collection of models, you can paginate your search results using the paginate method. This method returns a Paginator instance just as if you had paginated a traditional Eloquent query:

$orders = App\Order::search('Star Trek')->paginate();

You can specify how many models to retrieve per page by passing the amount as the first argument to the paginate method:

$orders = App\Order::search('Star Trek')->paginate(15);

After you have retrieved the results, you can display the results and render the page links using Blade just as if you had paginated a traditional Eloquent query:

<div class="container">
    @foreach ($orders as $order)
        {{ $order->price }}
    @endforeach
</div>
{{ $orders->links() }}

Soft Deleting

In the case where your indexed models are soft deleting and you have to search your soft deleted models, you should set the soft_delete option of the config/scout.php configuration file to true:

'soft_delete' => true,

When this configuration option is to true, Scout won?t remove soft deleted models from the search index. Rather, it sets a hidden __soft_deleted attribute on the indexed record. Then, you can use the withTrashed or onlyTrashed methods to retrieve the soft deleted records when searching:

// Includes trashed records when retrieving results...
$orders = App\Order::withTrashed()->search('Avengers')->get();

// Only includes trashed records when retrieving results...
$orders = App\Order::onlyTrashed()->search('Avengers')->get();

When you permanently delete a soft deleted model using forceDelete, Scout removes it from the search index automatically.

Customizing Engine Searches

If you want to customize the search behavior of an engine you can pass a callback as the second argument to the search method. For instance, you can use this callback to add geo-location data to your search options before the search query is passed to Algolia:

use Algolia\AlgoliaSearch\SearchIndex;

App\Order::search('Star Trek', function (SearchIndex $algolia, string $query, array $options) {
    $options['body']['query']['bool']['filter']['geo_distance'] = [
        'distance' => '500km',
        'location' => ['lat' => 36, 'lon' => 111],
    ];

    return $algolia->search($query, $options);
})->get();

Custom Engines

Writing The Engine

In the case where one of the built-in Scout search engines doesn't fit your needs, you can write your own custom engine and register it with Scout. Your engine needs to extend the Laravel\Scout\Engines\Engine abstract class. This abstract class contains seven methods that your custom engine must implement:

use Laravel\Scout\Builder;

abstract public function update($models);
abstract public function delete($models);
abstract public function search(Builder $builder);
abstract public function paginate(Builder $builder, $perPage, $page);
abstract public function mapIds($results);
abstract public function map($results, $model);
abstract public function getTotalCount($results);
abstract public function flush($model);

You might find it helpful to review the implementations of these methods on the Laravel\Scout\Engines\AlgoliaEngine class. This class provides you with a good starting point for learning how to implement each of these methods in your own engine.

Registering The Engine

After you have written your custom engine, you can register it with Scout using the extend method of the Scout engine manager. You will need to call the extend method from the boot method of your AppServiceProvider or any other service provider that is used by your application. For instance, in the case where you have written a MySqlSearchEngine, you can register it like so:

use Laravel\Scout\EngineManager;

/**
 * Bootstraps any application services.
 *
 * @return void
 */
public function boot()
{
    resolve(EngineManager::class)->extend('mysql', function () {
        return new MySqlSearchEngine;
    });
}

After your engine has been registered, you can specify it as your default Scout driver in your config/scout.php configuration file:

'driver' => 'mysql',

Builder Macros

If you want to define a custom builder method, you can use the macro method on the Laravel\Scout\Builder class. Typically, "macros" has to be defined within a service provider's boot method:

<?php
namespace App\Providers;

use Laravel\Scout\Builder;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Response;

class ScoutMacroServiceProvider extends ServiceProvider
{
    /**
     * Registers the application's scout macros.
     *
     * @return void
     */
    public function boot()
    {
        Builder::macro('count', function () {
            return $this->engine->getTotalCount(
                $this->engine()->search($this)
            );
        });
    }
}

The macro function will accept a name as its first argument, and a Closure as its second argument. The macro's Closure is executed when calling the macro name from a Laravel\Scout\Builder implementation:

App\Order::search('Star Trek')->count();

Previous: Laravel (5.7) Mocking
Next: Laravel (5.7) Laravel Horizon



Share this Tutorial / Exercise on : Facebook and Twitter