fuzzy-similarity maintained by x-laravel
Fuzzy Similarity
Fuzzy (string-based) similarity for Laravel Eloquent models: compare two records, or rank a collection against a query, by how closely their text matches.
It measures how the text is written, not what it means. For meaning-based (vector) similarity see x-laravel/embedding; the two packages can be used on the same model side by side.
How It Works
- Add the
FuzzyComparabletrait and declare the compared columns with#[FuzzyOn] - Define one or more named slots per model; different models are compared through the same slot name (one model's
nameagainst another'stitle) - Both sides are normalized first — by default to lower-case ASCII, so
Crème Brûléeequalscreme-bruleeandSão PauloequalsSao Paulo - Scores range from
0.0to100.0; the algorithm is configurable per call or globally
Requirements
- PHP ^8.4 with
intlandmbstring - Laravel ^12.0 | ^13.0
Installation
composer require x-laravel/fuzzy-similarity
Optionally publish the config file:
php artisan vendor:publish --tag=fuzzy-similarity-config
Setup
use Illuminate\Database\Eloquent\Model;
use XLaravel\FuzzySimilarity\Attributes\FuzzyOn;
use XLaravel\FuzzySimilarity\Concerns\FuzzyComparable;
use XLaravel\FuzzySimilarity\Contracts\HasFuzzySimilarity;
#[FuzzyOn('name')]
#[FuzzyOn(['name', 'city'], slot: 'location')]
class Company extends Model implements HasFuzzySimilarity
{
use FuzzyComparable;
}
#[FuzzyOn('title')]
class Article extends Model implements HasFuzzySimilarity
{
use FuzzyComparable;
}
A slot's columns are joined with a space; blank values are skipped. Override toFuzzyText(string $slot) to build the text yourself.
Usage
Compare two records
$company->fuzzySimilarityTo($otherCompany); // default slot
$company->fuzzySimilarityTo($otherCompany, slot: 'location');
$company->fuzzySimilarityTo($article); // Company name vs Article title
$company->fuzzySimilarityTo('Acme Corporation'); // plain string
$company->fuzzySimilarityTo('Corporation Acme', algorithm: 'token_set');
Rank a collection
$ranked = Company::rankByFuzzy($candidates, 'Acme Corporation', limit: 20, threshold: 40.0);
$ranked->first()->fuzzy_score; // 0.0 – 100.0
The query may also be a model. Each ranked model gets a fuzzy_score attribute; ranking happens in PHP, so pass an already narrowed-down collection.
Compare strings directly
use XLaravel\FuzzySimilarity\FuzzySimilarity;
app(FuzzySimilarity::class)->compare('São Paulo', 'sao-paulo'); // 100.0
Algorithms
| Key | Behaviour |
|---|---|
similar_text (default) |
PHP's similar_text() percentage, computed over characters instead of bytes. Gives the same result as the native function on ASCII input. |
token_set |
Ignores word order and repeated words. A string whose words all appear in the other scores 100 (Acme Corporation vs Acme Corporation International). |
Add your own by implementing XLaravel\FuzzySimilarity\Contracts\Algorithm and registering it under algorithms in the config, or pass its class name as algorithm:.
Normalizers
| Class | Behaviour |
|---|---|
AsciiNormalizer (default) |
Transliterates to lower-case ASCII (é → e, ß → ss, ø → o), turns - _ . · and dashes into spaces, drops invisible characters (zero-width space, BOM, word joiner…), collapses whitespace. |
LowercaseNormalizer |
Lower-cases and collapses whitespace only. |
Set normalizer in the config to any class implementing XLaravel\FuzzySimilarity\Contracts\Normalizer.
AsciiNormalizer keeps the last 10,000 results in a per-process cache, so a name compared against thousands of candidates is folded once, and long-running processes (Octane, queue workers) reuse results across requests. When the cache is full the oldest entry is dropped. Call AsciiNormalizer::flushCache() to empty it.
Testing
docker compose --profile php84 run --rm php84
docker compose --profile php85 run --rm php85
License
MIT