Looking to hire Laravel developers? Try LaraJobs

laravel-full-name maintained by plin-code

Description
Search and sort Eloquent queries and Filament tables by a person's full name across first_name and last_name columns, with BelongsTo relation support and multi-token matching.
Last update
2026/04/21 16:13 (dev-main)
License
Downloads
0

Comments
comments powered by Disqus

Laravel Fullname

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status PHPStan Action Status Total Downloads

Search and sort Eloquent queries (and Filament tables) by a person's full name stored across two columns (first_name and last_name), either on the main model or on a BelongsTo relation.

What it solves

Filament's built-in ->searchable(['first_name', 'last_name']) searches each column independently, which fails for composite queries like "mario rossi". The native ->sortable(query: ...) works for direct columns but requires repetitive custom join logic for relation-based sort. This package encapsulates the solution once, tested once, documented once.

Installation

composer require plin-code/laravel-full-name

No config file, no migrations, no Blade views, no Artisan command. The service provider auto-registers.

Requirements

  • PHP 8.4
  • Laravel 12 or 13
  • Filament 4 or 5 (optional, only needed for the Filament layer)
  • MySQL 8, PostgreSQL 14+, or SQLite 3

Quick start

Standalone Eloquent

Booking::query()
    ->searchFullName($request->input('q'))
    ->orderByFullName('asc')
    ->paginate();

Booking::query()
    ->searchFullName($request->input('q'), relation: 'user')
    ->orderByFullName('asc', relation: 'user')
    ->paginate();

Filament, direct columns

use Filament\Tables\Columns\TextColumn;

TextColumn::make('full_name')
    ->fullNameSearchable()
    ->fullNameSortable();

Filament, via BelongsTo

TextColumn::make('user.full_name')
    ->fullNameSearchable(relation: 'user')
    ->fullNameSortable(relation: 'user');

Custom column names

TextColumn::make('full_name')
    ->fullNameSearchable(
        firstNameColumn: 'given_name',
        lastNameColumn: 'family_name',
    )
    ->fullNameSortable(
        firstNameColumn: 'given_name',
        lastNameColumn: 'family_name',
    );

The complete API surface lives in docs/api.md.

Performance considerations

The matching strategy uses LOWER(CONCAT(COALESCE(first, ''), ' ', COALESCE(last, ''))) which prevents btree indexes from being used on first_name or last_name. On tables up to a few hundred thousand rows this is typically acceptable for admin panel search. For very large tables, pair this package with a dedicated search engine (Meilisearch, Scout, Algolia) and use this package only for sort.

Matching behavior

The core uses LOWER(CONCAT(COALESCE(first, ''), ' ', COALESCE(last, ''))) matched with LIKE ? ESCAPE '!' in both forward and reversed concatenation forms.

Query Record Matches
mario first_name='Mario', last_name='Rossi' yes
rossi first_name='Mario', last_name='Rossi' yes
mario rossi first_name='Mario', last_name='Rossi' yes
rossi mario first_name='Mario', last_name='Rossi' yes
maria first_name='Marianna', last_name='Rossi' yes (substring, single token)
maria rossi first_name='Marianna', last_name='Rossi' no (multi token)
marianna rossi first_name='Marianna', last_name='Rossi' yes
mario giovanni rossi first_name='Mario Giovanni', last_name='Rossi' yes
rossi mario giovanni first_name='Mario Giovanni', last_name='Rossi' yes
bianchi mario first_name='Mario', last_name='Rossi Bianchi' yes

The asymmetry between single token and multi token queries is intentional and emerges from the SQL pattern. Single token queries use substring match, so maria matches records containing maria anywhere in either column. Multi token queries require the tokens to appear contiguously with the separating space between them in the concatenated first last or last first form, so maria rossi matches Maria Rossi but not Marianna Rossi (the separator space is not present between maria and rossi in the concatenation). Single token queries are exploratory (the user may be typing a prefix), multi token queries target a specific person.

See docs/conventions.md for the rationale behind the naming split between the Eloquent and Filament layers.

Limitations

  1. Only the BelongsTo relation type is supported in v1. HasOne, HasMany, BelongsToMany, MorphTo, and nested relations raise UnsupportedRelationException at query build time.
  2. Accent and diacritic normalization is delegated to the database collation. On MySQL, use utf8mb4_unicode_ci or utf8mb4_0900_ai_ci. On PostgreSQL, consider the unaccent extension if needed.
  3. Fuzzy matching (soundex, metaphone, Levenshtein, trigram) is out of scope.
  4. Single column full name (one name column) is not handled. Filament's native ->searchable(['name']) covers that case already.
  5. Empty or whitespace only search input leaves the query unchanged (no WHERE clause is added).
  6. When combining orderByFullName(relation: ...) with an explicit ->select([...]) on the main query, qualify the column names with the main table name (for example ->select(['test_bookings.id']) rather than ->select(['id'])). The package performs a joinSub under the hood, which can introduce ambiguity for unqualified columns that exist on both tables.

Testing

composer test
composer analyse
composer format

Contributing

Please see CONTRIBUTING for details.

Changelog

Please see CHANGELOG for recent changes.

Credits

License

The MIT License (MIT). Please see License File for more information.