A practical guide to search Eloquent relationships using Laravel Scout Database Driver
Laravel Scout is a tool for adding full-text search functionality to your Laravel applications. It uses drivers like Algolia, Meilisearch, SQL, etc
Laravel Scout is a tool for adding full-text search functionality to your Laravel applications. It uses drivers like Algolia, Meilisearch, SQL databases, and others to search through your Eloquent models. The package is highly customizable, making it a versatile option for implementing search capabilities in your application.
Let's examine how we can search for Eloquent Model relationships using the Laravel Scout with the Database driver. Remember, the Laravel Scout driver currently supports MySQL and PostgreSQL.
Let’s start by creating a new Laravel application. For this purpose, you can check the Laravel documentation.
Create Models and Migrations
We will make use of two Eloquent models:
Post
Category
Create Category Model
Run the following command to create the Category
model:
php artisan make:model Category -m
This command will create a new Category
model class in the app\Models directory and generate a migration file for creating the categories table in the database.
Open the migration file in the database/migrations directory and add the necessary columns for the categories table. For example:
public function up()
{
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
}
Create Post Model
Run the following command to create the Post model:
php artisan make:model Post -m
The command will create a new Post
model class in the app\Models directory and generate a migration file for creating the posts table in the database.
Open the migration file in the database/migrations directory and add the necessary columns for the posts table. For example:
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('body');
$table->foreignId('category_id')->constrained();
$table->timestamps();
});
}
Apply the Migrations
Assuming you have configured the database for the application, run the following command to apply the migrations and create the posts and categories tables in the database:
php artisan migrate
Generate dummy data using a Database Seeder
Let’s create a database seeder to generate some dummy records for posts and categories tables.
First, create a new seeder class by running the following artisan command:
php artisan make:seeder PostsCategoriesTableSeeder
Open the PostsCategoriesTableSeeder class located in the database/seeds directory and update the run method with the following code:
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class PostsCategoriesTableSeeder extends Seeder
{
use WithoutModelEvents;
public function run()
{
// Generate 5 categories
$data = [
['name' => 'News'],
['name' => 'Sports'],
['name' => 'Entertainment'],
['name' => 'Technology'],
['name' => 'Business'],
];
DB::table('categories')->insert($data);
// Generate 1000 posts
for ($i = 1; $i <= 1000; $i++) {
DB::table('posts')->insert([
'title' => "Post $i",
'body' => "Content for post $i",
'category_id' => rand(1, 5)
]);
}
}
}
This seeder class generates five dummy categories and 1000 dummy posts. The name of each category is generated using a concatenation of a string and an automatically-incrementing id value, and the title, body, and category_id
of each post are generated using a similar method. The category_id
is chosen randomly from the list of categories.
To run the seeder, add the following line to the run method of the DatabaseSeeder
class located in the database/seeds directory:
$this->call(PostsCategoriesTableSeeder::class);
Run the seeder by executing the following artisan command:
php artisan db:seed
That's all there is to it! Now, you have a new Laravel application with both Post
and Category
models with seeded dummy data.
Configure the Application to use Laravel Scout
To configure a Laravel application to use Laravel Scout with the Database Driver, you will need to follow these steps:
First, install the Laravel Scout package:
composer require laravel/scout
Next, publish the Scout configuration file:
php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"
Now in the .env file, set the SCOUT_DRIVER option to database
SCOUT_DRIVER=database
Next, add the Laravel\Scout\Searchable trait to your model(s) to make them searchable:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
class Post extends Model
{
use HasFactory;
use Searchable;
}
After that, define the category()
relationship on the Post
model as follows:
public function category()
{
return $this->belongsTo(Category::class);
}
Finally, define the toSearchableArray() method on your model to specify which fields should be indexed by Laravel Scout:
public function toSearchableArray()
{
return [
'title' => '',
'body' => '',
'categories.name' => '',
];
}
To use Laravel Scout to perform full-text search on your models, you need to define a toSearchableArray()
method on your model. This method should return an array of data that Laravel Scout should index.
The toSearchableArray()
method should include relevant data to the search query. For example, if you are searching for posts that belong to a particular category, you might include the name field of the Category
model in the toSearchableArray()
method.
That's it! You should now be able to use Laravel Scout to perform full-text searches on your models using the database driver.
Let's Search.
To start, create a new SearchController
to test queries.
Create Controller
Run the following command:
php artisan make:controller SearchController --invokable
This will create a new SearchController
class in the app/Http/Controllers directory. The controller class will contain a single __invoke()
method called when the controller is invoked.
Add the Route
To use the SearchController
in your application, you need to define a route for it in the routes/web.php file:
Route::get('/search', SearchController::class);
This route will map a GET request to the /search URL to the __invoke() method of the SearchController
.
Write a Search Query
Open the SearchController
class and add our first query using Laravel Scout.
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\Request;
class SearchController extends Controller
{
public function __invoke(Request $request)
{
$posts = Post::search(trim($request->get('search')) ?? '')
->query(function ($query) {
$query->join('categories', 'posts.category_id', 'categories.id')
->select(['posts.id', 'posts.title', 'posts.body', 'categories.name as category'])
->orderBy('posts.id', 'DESC');
})
->get();
return response()->json(data: $posts, status: 200);
}
}
The Post::search()
method initiates a search query on the posts database table. This method returns an instance of a Builder class. This means that before actually executing the query by the DatabaseEngine you can apply several functions such as where()
, whereIn()
, onlyTrashed()
, orderBy()
, take()
, paginate()
and others to filter down your results further.
The trim($request->get('search')) ?? ''
input parameter retrieves the query string search
from the HTTP request and trims any leading or trailing whitespace or an empty string if the search query string is not present.
The query() function allows you to customize the search query by defining additional constraints or modifying the SELECT
clause of the query. In this case, the query uses a JOIN
clause to join with the categories table to search not only the posts table columns but also the name column of the categories table. Moreover, I am explicitly selecting what columns to return by prefixing the columns with the database name and adding an ORDER BY
clause by specifying the full column name, including the database table. The query fails without prefixing the database table name.
Finally, the get()
method is used to execute the search query and retrieve the matching posts from the database.
Let’s run the application in the browser and provide a search
query string with the value of "technology" to return all posts with the category "technology."
The first part of the results.
The last part of the results.
Let’s run a query against the database to retrieve posts with the category "Technology" and compare.
Database results for the same query.
As you can see, both return the same result!
As you can see, the query()
function on the search() method is a powerful tool for searching through relationships in your Laravel Scout database driver.
You can download a copy of the source code here at GitHub.