Laravel-Search is a search-engine using the models. Search easily, flexible add intelligent on your Laravel website or application.
- Installation
- Usage
- Operators for conditions
- Filter specific words from the search
- Methods and Closures
- Compare configuration settings
Requires PHP 8.1 and Laravel 10 or higher
Install the package via composer:
composer require jdkweb/laravel-search
For configuration settings you need to publish the config
php artisan vendor:publish --provider="Jdkweb\Search\SearchServiceProvider" --tag="config"
In the config is needed for:
- To setup reusable independent search-engine setting.
- Change the list of words to filter from search
- Change cache settings
Publish the config first.
- Define the models that need to be used in search engine
- set default search conditions
- define the output variables.
Base configuration for the search engine in the config-file
'settings' => [
'[CONFIG-NAME]' => [ // Engine configuration name (standard preset: 'default')
'searchQuery' => '[PRESET SEARCH]', // Optional: preset search words, results directly shown
'pagination' => 20, // Optional: search items per page , default = 15
'parameters' => [ // Optional: specific names query strings parameters
'search_query' => '[NAME]', // search terms, default: q
'actual_page' => '[NAME]', // result page, default: p
'actual_filter' => '[NAME]' // result filter, default: f
],
// Model configuration
...
]
]
[
'[MODEL\NAMESPACE]' => [
'searchFields' => [
[COLUMNAME] => [PRIORITY],
...
],
'conditions' => [ // Set search conditions (optional)
[COLUMNNAME] => [VALUE | METHOD | CLOSURE],
...
],
'resultFields' => [
[VARIABLENAME] => [COLUMNNAME | METHOD | CLOSURE],
...
]
]
]
The example below shows the configuration of a search engine named 'global'.
'settings' => [
'default' => [ // Settings name 'default'
'searchQuery' => 'linux', // Preset search 'linux'
'App\Models\Articles' => [ // Model to search: 'Articles'
'searchFields' => [
'title' => 2.5, // Column: title, priority: 2.5 (extra weight)
'lead' => 2, // Column: lead, priority: 2
'body' => 1,
// In query: title LIKE '%[search words]%' OR lead LIKE '%[search words]%' ...
],
'conditions' => [
'active' => 1, // query: active = 1 AND published = 1
'published' => 1, //
],
'resultFields' => [
'title' => 'pagetitle', // pagetitle usable as {{ $title }}
'lead' => 'intotext', // introtext => {{ $lead }}
'url' => 'getSlug', // call the method getSlug() in App\Models\Articles
// Closure to format a date in search result
'date' => fn () => \Carbon\Carbon::parse($this->created_at)->format('d/m/Y')
]
]
],
In short, one can defining multiple search engines. This makes it possible to create multiple specific search sets that behave different. You can create a global search engine or one for a specific page/model.
See example of large configuration file with methods an closures
Renaming the GET variables that appear in the URL
// Default
search?q=some search words&p=1&f=articles
// Modified using the setting below
search?search=some search words&page=1&filter=articles
'settings' => [
'default' => [ // Config name 'default'
'variables' => [
'search_query' => 'search', // search terms
'actual_page' => 'page', // result page
'actual_filter' => 'filter' // result filter
],
'App\Models\Articles' => [
...
It is possible to fire a searchQuery by default.
In config file
'settings' => [
'default' => [
'searchQuery' => 'Adobe', // Set default search
...
Change search result items per page
'settings' => [
'default' => [
'pagination' => 30, // 30 items per page, default is 15
'settings' => [
'default' => [
'pagination' => false, // All results on one page
A setup for three different search engine configurations, each in a other situation with more specific search results
- Global website search
- Book / Chapter search
- Article searching
<?php
// Settings URL parameters
$parameters = [
'search_query' => 'search',
'actual_page' => 'page',
'actual_filter' => 'filter'
];
// App\Models\Books for searching
$books = [
'searchFields' => [
'name' => 3,
'body' => 2,
'intro' => 2
],
'conditions' => [
'active' => 1,
],
'resultFields' => [
'title' => 'name',
'text' => 'intro',
'cover' => function() {
return App\Models\BookCovers::query()
->where('id', $this->id)
->first()
}
'url' => fn() => "/books/".getSlug($this->shortname)
]
];
// App\Models\Chapters (related to Books) for searching
$chapter = [
'searchFields' => [
'name' => 3,
'intro' => 3,
'body' => 2,
],
'conditions' => [
'active' => 1,
'book_id:in' => function () {
return App\Models\Book::query()
->where('active', 1)
->select('id');
}
],
'resultFields' => [
'title' => 'name',
'bookname' => function () {
return App\Models\Book::query()
->where('id', $this->book_id)
->select('name')->first()->name;
},
'text' => 'intro',
'url' => 'getSlug'
]
];
// App\Models\Articles for searching
$articles = [
'searchFields' => [
'title' => 3,
'lead' => 2,
'content' => 1,
],
'conditions' => [
'active' => 1,
'published:<' => time()
],
'resultFields' => [
'title' => 'title',
'text' => 'lead',
'thumbnail' => fn() => $this->getThumbnail(),
'url' => fn() => "/articles/".$this->slug
]
];
// App\Models\Pages for searching
$pages = [
'searchFields' => [
'title' => 3,
'introduction' => 3,
'content' => 2
],
'conditions' => [
'active' => 1,
'parent_id:!=' => 88
],
'resultFields' => [
'title' => 'title',
'text' => 'introduction',
'url' => fn() => "/pages/".getSlug($this->name)
]
];
return [
'settings' => [
'default' => [ // Default search on search page on the website
'parameters' => $parameters,
'pagination' => 20
'App\Models\Articles' => $articles,
'App\Models\Pages' => $pages
'App\Models\Books\Book' => $books,
'App\Models\Books\Chapter' => $chapter
],
'books' => [ // searching in books and chapters
'parameters' => $parameters,
'pagination' => 10
'App\Models\Books\Book' => $books,
'App\Models\Books\Chapter' => $chapter,
],
'articles' => [ // searching in articles
'parameters' => $parameters,
'pagination' => 5
'App\Models\Articles' => $articles,
]
]
];
Without using a config file
$search = app('search') // With default settings
->setSearchQuery('Adobe'); // Optional: preset search words, results directly shown
->setPagination(10) // Optional: search items per page , default = 15
->setParams([ // Optional: specific names query strings parameters
'search_query' => 'search', // search terms
'actual_page' => 'page', // result page
'actual_filter' => 'filter' // result filter
])
->setModel(\App\Models\Articles::class, [ // Database Article table to search in
'title' => 2, // Fieldnames with priority (extra weight)
'lead' => 1.5,
'body' => 1,
])
->setConditions(\App\Models\Articles::class, [
'active' => 1, // active must be true
'created_at:>' => '01-01-2025', // articles created after 01 jan. 2025
])
->showResults(\App\Models\Articles::class, [
'title' => 'name', // pass database field = 'name' as 'title' to results
'lead' => 'intro', // pass database field = 'intro' as 'lead' to results
'url' => 'getSlug' // method in \App\Models\Articles pass as 'url' to results
])
->setModel(\App\Models\User::class, [ // Database User table to search in
'name' => 1, // Fieldnames with priority (extra weight)
'email' => 1,
])
->setConditions(\App\Models\User::class, [
'active' => 1, // active must be true
'public:in' => function () { // public value must be available, from Public model
return App\Models\Public::query()
->where('active', 1)
->select('id');
}
])
->showResults(\App\Models\User::class, [
'title' => function () { // show name of user with the company name (from an other model)
$company_name = App\Models\Companies::query()
->where('user_id', $this->id)
->select('company_name')->first()->company_name
return $this->name . " (" . $company_name .")"
},
'lead' => 'intro',
'url' => fn() => '/users/' . $this->slug // closure arrow function to get slug passed as url to result
]);
}
Laravel-search is working with GET variables (query strings parameters can be renamed)
search?q=some+search+words
By default, if defined, the search engine configuration called 'default' is used.
$search = app('search'); // search with 'default' settings
OR
$search = app('search', ['default']); // search with 'default' settings
// search result
$result = $search->get(); // returning a collection
You can use a custom configuration by calling the settings()
methode
$search = app('search', ['global']); // search with 'global' settings
// search result
$result = $search->get(); // returning a collection
Result:
ordered by relevance
#items: Illuminate\Support\Collection {#3470 ▼
#items: array:15 [▼
0 => Illuminate\Support\Collection {#2268 ▶}
1 => Illuminate\Support\Collection {#3511 ▶}
2 => Illuminate\Support\Collection {#3260 ▶}
3 => Illuminate\Support\Collection {#3076 ▼
#items: array:3 [▼
"title" => "Sed non leo ac massa dignissim condimentum"
"lead" => "Donec efficitur dictum justo vitae auctor. Curabitur eu diam a nisi eleifend tristique eget non augue. Integer sed metus non nisl fringilla venenatis."
"url" => "/articles/sed-non-leo-ac-massa-dignissim",
"date" => "11/01/2025"
]
#escapeWhenCastingToString: false
}
4 => Illuminate\Support\Collection {#2335 ▶}
5 => Illuminate\Support\Collection {#2554 ▶}
....
]
}
The searchable models we defined earlier can be used to create filters (or search groups). As shown in the example above.
Use specific model or group of models for searching
// available filters
$filters = ['all','blog','articles','workshops']
// get filter &f=[FILTER]
$filter = request()->get('f');
$search = match($filter) {
'blog' => app('search', ['blog']);
'articles' => app('search', ['articles']);
'workshops' => app('search', ['workshops']);
default => app('search');
};
$result = $search->get();
In the search conditions is it possible to use operators
Operator | Type | Example | Query Builder |
---|---|---|---|
=, eq | Equal | 'id' => 10 (default) | ->where('id',10) |
'id:=' => 10 | |||
!=, !eq, neq | Unequal | 'id:!=' => 10 | ->where('id','!=', 10) |
'id:neq' => 10 | |||
>, gt | Greater than | 'age:>' => 35 | |
>=, gte | Greater than or equal | 'age:gte' => 35 | ->where('age', '>=', 35) |
<, lt | Less than | 'age:<' => 12 | ->where('age', '<', 12) |
<=, lte | Less than or equal | 'age:lte' => 12 | ->where('age', '<=', 12) |
in | In | 'id:in' => [10,11,12] | ->whereIn('id',[10,11,12]) |
!in, notin | Not in | 'id:!in' => [2,4] | ->whereNotIn('id',[2,4]) |
like | Like | 'title:like' => '%Linux%' | ->where('title', 'LIKE', '%linux%') |
!like, notlike | Not like | 'title:notlike' => '%linux%' | ->where('title', 'NOT LIKE', '%linux%') |
or | Or | 'or:published' => 1 | ->orWhere('published', 1) |
Or | 'or:id:in' => [10,11] | ->orWhereIn('id', [10,11]]) |
'conditions' => [
'book_id:!in' => function () {
return App\Models\Book::query()
->where('active', 0)
->where('published', 0)
->select('id');
}
'or:preview' => 1
],
Language related list of words that are filtered from the search
Removing Linking words (the, and, ...) makes it possible to keep the search results cleaner.
'filter_words' => [
'nl' => 'de, en, of, als, het, een, van, op, ook',
'en' => 'the, or, else, and, like',
'de' => 'das, der, die, und',
'fr' => 'le, la, un, une',
]
The configurations above provide several examples of using methods and Closures.
This makes it possible to relate the models to Closure functions, and methods form (other) models or controllers.
In config file
'resultFields' => [
'title' => 'name',
'lead' => 'intro',
'url' => 'getSlug' // Method getSlug bind to the Articles class
Directly into the script
->showResults(\App\Models\Articles::class, [
'title' => 'name',
'lead' => 'intro',
'url' => 'getSlug' // Method getSlug bind to the Articles class
])
Method in model (\App\Models\Articles)
public function getSlug()
{
return '/articles/' . $this->createSlug();
}
->showResults(\App\Models\Articles::class, [
'title' => 'name',
'lead' => 'intro',
'url' => fn() => '/articles/' . $this->createSlug(); // Arrow function
])
->showResults(\App\Models\Articles::class, [
'title' => 'name',
'lead' => 'intro',
'url' => function() { // Closure
return $this->getSlug() // Method getSlug bind to the Articles class
},
'date' => fn () => \Carbon\Carbon::parse($this->created_at)->format('d/m/Y') // Arrow function
])
Config-file
'settings' => [
'[CONFIG-NAME]' => [
'searchQuery' => [PRESET SEARCH],
'pagination' => [ITEMS_PER_PAGE_OR_FALSE_FOR_ALL]
'parameters' => [
'search_query' => '[NAME]',
'actual_page' => '[NAME]',
'actual_filter' => '[NAME]'
],
'MODEL\NAMESPACE' => [
'searchFields' => [
[COLUMNAME] => [PRIORITY],
...
],
'conditions' => [
[COLUMNNAME] => [VALUE | METHOD | CLOSURE],
...
],
'resultFields' => [
[VARIABLENAME] => [COLUMNNAME | METHOD | CLOSURE],
...
]
]
]
]
$search = app('search', '[CONFIG-NAME]');
$searchResult = $search->get();
// Handle next search via GET-variable (searchQuery is overwritten)
// OR combine config with direct settings
$search->setSearchQuery([NEW SEARCH_WORDS]);
$newsearchResult = $search->get();
Directly embed settings into the script
$search = app('search', ['inline']) // (No) Config-name can give a confict with actual config settings
->setSearchQuery([PRESET SEARCH]);
->setPagination([ITEMS_PER_PAGE_OR_FALSE_FOR_ALL])
->setParams([
'search_query' => '[NAME]',
'actual_page' => '[NAME]',
'actual_filter' => '[NAME]'
])
->setModel([MODEL\NAMESPACE]::class, [
[COLUMNAME] => [PRIORITY],
...
])
->setConditions([MODEL\NAMESPACE]::class, [
[COLUMNNAME] => [VALUE | METHOD | CLOSURE],
...
])
->showResults([MODEL\NAMESPACE]::class, [
[VARIABLENAME] => [COLUMNNAME | METHOD | CLOSURE],
...
])
$searchResult = $search->get();
// Handle next search via GET-variable (setSearchQuery is overwritten)
// OR insert new search words
$search->setSearchQuery([NEW SEARCH WORDS]);
$newsearchResult = $search->get();