laravel-fuzzy-validation maintained by cavalheri
Laravel Fuzzy Validation
Fuzzy validation rules for Laravel with approximate string matching support. Prevent near-duplicate entries such as The Matrix vs Th3 M4tr1x or Café vs Cafe.
Requirements
- PHP 8.3+
- Laravel 13+
Installation
composer require cavalheri/laravel-fuzzy-validation
The package supports auto-discovery. Publish the configuration file when needed:
php artisan vendor:publish --tag=fuzzy-validation-config
Quick Start
Fluent Rule API
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
$validator = Validator::make($request->all(), [
'title' => [
'required',
Rule::fuzzyUnique('movies', 'title')
->threshold(85)
->ignore($movie?->id)
->normalize(),
],
]);
String Syntax
'title' => 'required|fuzzy_unique:movies,title'
You can pass additional parameters:
'title' => 'required|fuzzy_unique:movies,title,90,normalize,driver=jaro_winkler,ignore=1'
Configuration
// config/fuzzy-validation.php
return [
'driver' => env('FUZZY_VALIDATION_DRIVER', 'similar_text'),
'threshold' => (int) env('FUZZY_VALIDATION_THRESHOLD', 85),
'normalization' => [
'lowercase' => true,
'trim' => true,
'collapse_spaces' => true,
'remove_accents' => true,
'leetspeak' => false,
],
'messages' => [
'fuzzy_unique' => 'The :attribute is too similar to an existing value.',
],
];
Features
Similarity Drivers
| Driver | Description |
|---|---|
similar_text |
PHP native similar_text() (default) |
levenshtein |
Levenshtein distance converted to percentage |
jaro_winkler |
Jaro-Winkler algorithm implementation |
Select a driver globally or per rule:
Rule::fuzzyUnique('movies', 'title')->driver('jaro_winkler')
Normalization Pipeline
Enable normalization on a rule:
Rule::fuzzyUnique('movies', 'title')->normalize()
Available normalizers:
- lowercase
- trim
- collapse spaces
- remove accents
- optional leetspeak (
4→a,3→e,1→i,0→o,5→s,7→t)
Enable leetspeak normalization:
Rule::fuzzyUnique('movies', 'title')->normalize()->leetspeak()
Override defaults per rule:
Rule::fuzzyUnique('movies', 'title')->normalize([
'remove_accents' => false,
'leetspeak' => true,
])
Threshold
Similarity is measured from 0 to 100. Higher values require closer matches.
Rule::fuzzyUnique('movies', 'title')->threshold(90)
Ignore Records
Works like Laravel's built-in unique rule:
Rule::fuzzyUnique('movies', 'title')->ignore($movie->id)
Rule::fuzzyUnique('movies', 'title')->ignore($movie)
Rule::fuzzyUnique('movies', 'title')->ignore($uuid, 'uuid')
Additional Constraints
Rule::fuzzyUnique('movies', 'title')->where('published', true)
Rule::fuzzyUnique('movies', 'title')->where(function ($query) {
$query->where('tenant_id', tenant('id'));
})
Custom Similarity Drivers
Implement the SimilarityDriver contract:
use Cavalheri\LaravelFuzzyValidation\Contracts\SimilarityDriver;
final class MyDriver implements SimilarityDriver
{
public function compare(string $first, string $second): float
{
// Return similarity percentage (0-100)
}
}
Register the driver:
use Cavalheri\LaravelFuzzyValidation\Similarity\SimilarityManager;
app(SimilarityManager::class)->extend('my_driver', MyDriver::class);
Use it in validation:
Rule::fuzzyUnique('movies', 'title')->driver('my_driver')
Database Support
The package works with any database supported by Laravel's query builder:
- MySQL
- PostgreSQL
- SQLite
Validation Message
Default message:
The :attribute is too similar to an existing value.
Customize via config or language files:
'fuzzy-validation.messages.fuzzy_unique' => 'This title is too close to an existing movie.',
Testing
composer test
Changelog
See CHANGELOG.md.
License
The MIT License (MIT). See LICENSE for details.