Weave Code
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

Laravel Cross Eloquent Search Laravel Package

protonemedia/laravel-cross-eloquent-search

Search across multiple Eloquent models with one query. Supports pagination, scoped constraints, eager loading, relationship and nested search, full-text search, cross-model sorting, and ordering by relevance. Works with MySQL, PostgreSQL, and SQLite.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package with composer require protonemedia/laravel-cross-eloquent-search, then start with the basic search across two models:

use ProtoneMedia\LaravelCrossEloquentSearch\Search;

$results = Search::add(Post::class, 'title')
    ->add(Video::class, 'title')
    ->search('laravel');

The first use case is typically building a unified search endpoint (e.g., /api/search?q=...) where frontends need a single paginated list of mixed content types. The Search::add() method lets you declare which models and columns to include — the package handles the underlying UNION queries, relevance scoring, and pagination automatically.

Check the README’s usage examples and the linked YouTube stream (starting at 13:44) for a live walkthrough.

Implementation Patterns

  • Centralized Search Service: Wrap the builder in a dedicated SearchService class to encapsulate common patterns (e.g., role-based model access, tenant filtering). Use when() to conditionally add models based on auth context:

    Search::new()
        ->when($user->isAdmin(), fn($s) => $s->add(InternalNote::class, 'content'))
        ->add(Post::class, 'title')
        ->add(Video::class, 'title')
        ->search($request->input('q'));
    
  • Scoped Queries with Constraints: Pre-filter models using scopes or where clauses passed directly to add():

    Search::add(Post::published(), 'title')
        ->add(Comment::active(), 'body')
        ->search($term);
    
  • Relevance + Date Sorting: Combine orderByRelevance() with fallback columns (e.g., published_at) for a nuanced result ordering:

    Search::add(Post::class, 'title')
        ->add(Video::class, ['title', 'description'])
        ->beginWithWildcard()
        ->orderByRelevance()
        ->orderByDesc('published_at')
        ->paginate(20)
        ->search($term);
    
  • Relationship-aware search: Use dot notation for nested column search across relations, often paired with eager loading:

    Search::add(Post::with(['author', 'comments.user']), 'comments.body')
        ->search('helpful reply');
    
  • Full-text + sound-alike fallback: For production search, combine addFullText() (for performance) with soundsLike() as a fallback for typos:

    $search = Search::new()
        ->addFullText(Post::class, 'title', ['mode' => 'boolean'])
        ->add(Post::class, 'title')
        ->soundsLike();
    
    $term = $request->input('q');
    $results = $search->search($term);
    
  • Paginated API responses: Return paginated results directly in controllers — includes total count and page links:

    return Search::add(Post::class, 'title')
        ->add(Video::class, 'title')
        ->paginate()
        ->withQueryString()
        ->search($request->q);
    

Gotchas and Tips

  • Database driver matters: Full-text and SOUNDS LIKE behave differently per DB: MySQL uses native full-text, PostgreSQL requires pg_trgm, and SQLite uses LIKE fallback. Always test across environments — especially for similarity matching or boolean mode.

  • Wildcard defaults: The package splits input and adds trailing wildcards by default (e.g., laravellaravel%). To match substrings anywhere (%lar%), call beginWithWildcard(). Disable wildcards with endWithWildcard(false) for exact matching (like exactMatch()).

  • Timestamps & ordering: By default, results are sorted by updated_at or primary key if no timestamps exist. Override with orderBy('column') per model after add() — not as a third argument to add() (that’s for relevance scoring column, not sort column).

  • Nested relationship search pitfalls: Search on nested relations (e.g., comments.body) will join tables, but orderByRelevance() is not supported for such queries — use orderBy('column') instead.

  • Eager loading is per-model: When using with(), ensure the relationship exists on the base model, not just in your result set (e.g., if a Video lacks comments, no join occurs for it).

  • Empty or missing search term: Passing null or empty string to search() returns all records (respecting constraints), ordered by your chosen column. Use when() or request validation to block empty searches if undesirable.

  • Pagination limits: simplePaginate() avoids COUNT(*) over the full union, which is faster for large datasets — ideal for infinite scroll. Use withQueryString() to preserve filters in pagination links.

  • Include model type: Call includeModelType() before pagination to add the model class name to each result item — invaluable for rendering mixed UIs (e.g., <search-result :type="item.model_type">).

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.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
milesj/emojibase
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport