Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Eloquent Search Map Laravel Package

aviator/eloquent-search-map

Eloquent Search Map is a Laravel package that maps search requests onto Eloquent query scopes, letting you turn URL filters into clean, reusable queries. Describe searchable fields once and keep controllers slim.

View on GitHub
Deep Wiki
Context7

Getting Started

Know what you're adopting first. This package is archived, last released in November 2017, and has no dependents. Treat it as a reference implementation rather than a dependency. The realistic getting-started path is: clone it, read it, and vendor the two or three files you actually need into app/Support/.

If you still want to install it:

composer require aviator/eloquent-search-map

Expect the 2017-era composer.json constraints (php ^7.0, illuminate/support ~5.x) to block installation on Laravel 8+. You'll need --ignore-platform-reqs or a repositories fork with relaxed constraints — and even then, nothing guarantees the query builder calls still behave.

Where to look first, in order:

  1. composer.json — confirm the Laravel/PHP ranges before wasting time.
  2. src/ — the whole package is small; the service provider and the search-map trait/class are the entire public surface.
  3. The trait's main entry point (typically a search() scope or a SearchMap resolver) — this is where request input is translated into where() calls.

First use case. The package's central idea is that a model declares a map from request keys to query constraints, and a scope consumes an array of input and applies only the keys present in the map:

use Aviator\EloquentSearchMap\Searchable;

class Post extends Model
{
    use Searchable;

    protected $searchMap = [
        'title'      => ['column' => 'title',       'operator' => 'like'],
        'status'     => ['column' => 'status',      'operator' => '='],
        'created_by' => ['column' => 'user_id',     'operator' => '='],
        'after'      => ['column' => 'created_at',  'operator' => '>='],
    ];
}
// Controller
$posts = Post::search($request->only(['title', 'status', 'after']))
    ->paginate(25);

The value proposition is the map: it is simultaneously the query logic and the whitelist, so a user can't smuggle ?password_like=x into your query.

Implementation Patterns

Per-endpoint maps, not per-model. The instinct is one $searchMap on the model. In practice the public listing page and the admin export need different filters, and cramming both into one array means the public endpoint silently accepts admin-only keys. Define the map at the call site and pass it in, or expose a scopeSearchIn() variant, if the trait allows it.

Treat the map as the security boundary. The genuinely good pattern here is that columns and operators come from your code, never from the request. Preserve that property in anything you copy: never accept operator or column from input, even "just for the admin panel."

Compose with ordinary scopes. The search call returns a Builder, so chain normally:

$results = Post::search($filters)
    ->where('published', true)
    ->with('author:id,name')
    ->orderByDesc('created_at')
    ->paginate(25);

Normalize and pre-filter the input before it reaches the scope. Trim, cast empty strings to null, drop them:

$filters = collect($request->only(array_keys(Post::searchMapKeys())))
    ->reject(fn ($v) => $v === null || $v === '')
    ->all();

This single step prevents the most common bug (see below) far more reliably than hoping the package handles it.

Sorting and pagination belong outside the map. If the package supports an orderBy key, whitelist the column list explicitly rather than passing $request->sort through. Same for per_page — clamp it.

If the package won't install, reimplement it in ~30 lines. This is the pattern worth keeping, and it's cheaper than fighting Composer:

public function scopeSearch(Builder $query, array $filters): Builder
{
    foreach ($this->searchMap as $key => $constraint) {
        if (! array_key_exists($key, $filters) || $filters[$key] === null || $filters[$key] === '') {
            continue;
        }

        $query->where(
            $constraint['column'],
            $constraint['operator'],
            $constraint['operator'] === 'like' ? "%{$filters[$key]}%" : $filters[$key]
        );
    }

    return $query;
}

Note that this version already fixes the empty-string and wildcard-escaping issues the original likely has.

Gotchas and Tips

Archived is the headline gotcha. No security patches, no PHP 8 support guarantees, no fix if a future Laravel changes builder internals. If you depend on it, fork it into your own namespace and pin it, or vendor the trait. Do not leave an unmaintained 2017 query builder in a code path that touches user input without a test suite around it.

LIKE '%%' from empty strings. Naive map implementations apply the constraint whenever the key exists in the input array, even if its value is ''. A search form that submits every field — most do — will generate WHERE title LIKE '%%' AND status = '', which returns nothing and looks like a broken search. The fix is the reject call above, plus checking the package's actual existence test by reading the source.

Unescaped wildcards. If the map uses like, user input of % or _ becomes a wildcard. Wrap with addcslashes($value, '%_\\') and append ESCAPE '\\' for exactness, or accept the fuzziness deliberately.

Leading wildcards defeat indexes. %term% cannot use a B-tree index. Fine on small tables, a table scan on anything real. For search-heavy features, put a FULLTEXT index or Scout/Meilisearch behind it instead.

Type juggling from HTTP. Query strings arrive as strings. ?status=0 compared against a boolean column, or an array ?status[]=a passed to where(), will produce confusing SQL. Validate with a FormRequest before the scope; the map only guards which columns, not what values.

Mixing where and orWhere. If the package emits orWhere for certain operators, an ungrouped chain will leak past your other conditions. Inspect the generated SQL for any or and wrap it in a closure if needed.

Debugging: dump SQL before executing.

$q = Post::search(['title' => 'laravel']);
dd($q->toSql(), $q->getBindings());

Nine times out of ten the problem is a constraint you didn't expect to be applied — trace it back to the input array, not the map.

Extension points are limited and trait-based. If you need different operator handling, you're overriding a trait method — watch for conflicts if your model already uses a trait with the same method name (insteadof in the use block, or just copy the trait and edit it). Overridable protected methods around constraint resolution are the only realistic hook; there is no event or macro system here.

No tests shipped, presumably. Whatever you copy, write three or four feature tests: empty input returns everything, an unknown key is ignored, a mapped key filters, and an empty-string value is ignored. Those four catch nearly every regression in this kind of package.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource