Simplify multi-column wildcard searches across model columns, relationships, and raw SQL expressions using a powerful whereLike macro on the Eloquent Builder.
Searching across multiple attributes frequently leads to verbose and repetitive orWhere query chains in controller code.
By registering a custom whereLike macro on Eloquent's Builder, you can search across model columns, dot-notation relationships (user.name), and custom database expressions (DB::raw()) in a single readable call.
Registering the Macro in AppServiceProvider
namespace App\Providers;
use Illuminate\Contracts\Database\Query\Expression;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
Builder::macro('whereLike', function ($attributes, string $searchTerm) {
return $this->where(function (Builder $query) use ($attributes, $searchTerm) {
foreach (Arr::wrap($attributes) as $attribute) {
$query->when(
// Check if attribute is a relationship dot-notation string (e.g. 'user.name')
! ($attribute instanceof Expression) && str_contains((string) $attribute, '.'),
function (Builder $query) use ($attribute, $searchTerm) {
[$relation, $relatedAttribute] = explode('.', (string) $attribute);
$query->orWhereHas($relation, function (Builder $query) use ($relatedAttribute, $searchTerm) {
$query->where($relatedAttribute, 'LIKE', "%{$searchTerm}%");
});
},
function (Builder $query) use ($attribute, $searchTerm) {
// Search on local column or DB::raw expression
$query->orWhere($attribute, 'LIKE', "%{$searchTerm}%");
}
);
}
});
});
}
}
Usage Example
You can pass single columns, related model fields using dot notation, and formatted raw SQL expressions:
use App\Models\Post;
use Illuminate\Support\Facades\DB;
$search = request('search');
$posts = Post::query()
->whereLike([
'title',
'description',
'user.name',
'user.email',
DB::raw('CONCAT(user.first_name, " ", user.last_name)'),
DB::raw('DATE_FORMAT(created_at, "%d/%m/%Y")'),
], $search)
->with('user')
->paginate(15);
How It Works
- Encapsulated Scope: Wraps the entire search clause in a single
$this->where(function ($query) ...)closure so that booleanAND/ORoperator precedence is respected when chained with other query filters. - Relationship Detection: Automatically detects dot notation (
'user.name') and appliesorWhereHassubqueries against the related model. - Expression Compatibility: Supports
DB::raw()expressions without throwing type errors, enabling searches on computed columns and SQL date formats.
Summary
- Use
Builder::macro('whereLike')to consolidate multi-attribute wildcard searches into a single expressive method. - Automatically handles both local table columns and related model attributes via
orWhereHas. - Respects SQL operator grouping and supports raw database expressions.
Related Articles
View all articles →Detect Eloquent Model Attribute Changes Before and After Saving
Use isDirty() and isClean() before saving to inspect unsaved in-memory mutations, and use wasChanged() and getOriginal() after saving to verify persisted changes and access previous values.
Reusable Tappable Scopes for Eloquent and Laravel Scout Queries
Extract query filtering logic into invokable scope classes that work interchangeably across Eloquent query builders and Laravel Scout search instances.