Reusable whereLike Macro with Relationship and Expression Support
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.