felixkiss/uniquewith-validator
Laravel validation rule for uniqueness across multiple columns. The unique_with rule extends Laravel's built-in unique validator, letting you require combinations such as company_id and email to be unique together, with support for ignoring IDs, extra where clauses, and custom column names.
Architecture fit. felixkiss/uniquewith-validator is a deliberately thin adapter: one service provider that calls Validator::extend('unique_with', ...) and resolves the rule string into a query-builder where chain. It ships no models, no migrations, no config file, and no middleware, so it imposes essentially zero architectural footprint on an existing Laravel app. The problem it solves — "this value must be unique within a scope" — is a recurring one in multi-tenant SaaS and marketplace schemas (email unique per account, slug unique per site, SKU unique per vendor), and it is exactly the case where Laravel's default unique rule is insufficient out of the box.
Integration feasibility. High for the happy path. Installation is a single composer require, and Laravel package auto-discovery wires the provider. Usage is a string rule: 'email' => 'unique_with:users,account_id', with optional positional arguments for an ignored row ID and the primary key column on updates. The main caveat is that everything is positional and stringly-typed — readability and refactorability are poor, and the rule does not participate in the modern Rule object API.
Technical risk. The dominant risk is framework version drift, not code quality. The package's public surface is small and conceptually stable, but release notes/CI coverage need to be verified against our target Laravel major before anything else. Packages of this vintage frequently depend on global helpers that were removed in Laravel 6 (array_get, str_*) and on pre-Rule validation internals; if the last tagged release predates our runtime, we are effectively adopting unmaintained code. Secondary risks: the rule parses table and column names directly out of the rule string (safe when defined statically in code, a SQL-injection vector if anyone ever interpolates request input into it), and it offers no opinion on soft deletes or tenant global scopes.
Strategic read. The composite score (7.61) reflects a mature, well-understood utility; the opportunity score (3.03) reflects the fact that its core capability has been absorbed into the framework. Rule::unique('users')->where(fn ($q) => $q->where('account_id', $accountId))->ignore($id) covers the same ground with first-class framework support, IDE discoverability, and no third-party dependency. This package is a convenience, not a capability we cannot get elsewhere.
Key questions to resolve before adoption
unique_with usage, does a matching composite unique index exist in the schema — and will we add it if not?Stack fit. Appropriate for Laravel 5.x–8.x-era applications that already express validation as pipe-delimited rule strings and where the team wants a one-line rule instead of a closure. It is a poorer fit for greenfield work on Laravel 9+, where Rule::unique() with where()/ignore() is idiomatic and avoids a dependency entirely. If our codebase already uses Rule objects or Form Requests with typed rule arrays, introducing a second, string-based idiom creates inconsistency for marginal gain.
Migration path. Treat this as a replace-or-adopt decision, not a one-way door — the package's entire surface is ~1–2 files and can be replicated in a handful of lines if the dependency sours.
unique_with (legacy app): keep the package if it passes compatibility testing; otherwise migrate each rule mechanically to Rule::unique(...)->where(...)->ignore(...). The mapping is one-to-one and scriptable — audit with a repo-wide search before starting.Compatibility. Two axes matter: Laravel major version and database driver. The query-builder path is portable across MySQL/PostgreSQL/SQLite, but semantics are not — see failure modes below. Verify against the actual driver in production, not SQLite in CI, because case sensitivity and NULL handling diverge sharply between them.
Sequencing
Rule::unique.Maintenance. Low effort, low activity. The upside of a ~150-line package is that the maintenance surface is trivial and the fork cost is negligible. The downside is that upstream activity is likely minimal; assume no proactive fixes and budget a periodic (semi-annual) compatibility check tied to Laravel upgrade cycles. Pin the version in composer.json and let our dependency-update tooling surface breaks rather than floating it.
Support. Community-only: GitHub issues, no vendor SLA. Acceptable for a validator; escalate-worthy if the same logic ever gates financial or compliance-critical operations. Mitigation is the vendor/fork path, which is cheap here.
Scaling. One additional indexed SELECT per validated field per request — negligible at normal volumes provided the composite index exists. Without it, each validation is a table scan, which turns a cheap UX check into a latent performance cliff under load. The real scaling hazard is bulk operations: CSV imports, seeders, and queue jobs that validate row-by-row produce an N+1 query pattern. For bulk paths, bypass the validator and rely on insertOrIgnore/upsert against the unique index, translating constraint violations into a per-row report.
Failure modes
| Failure | Cause | Mitigation |
|---|---|---|
| False "already taken" | Validator sees rows the user shouldn't (soft-deleted, other tenant) | Explicit scope columns in the rule; decide soft-delete policy |
| False "available", then 500 | TOCTOU race between check and insert | Enforce a DB composite unique index; catch QueryException (SQLSTATE 23000) and convert to a validation error |
| Inconsistent behavior across environments | Case-insensitive MySQL collation vs case-sensitive Postgres/SQLite | Test against production driver; normalize case pre-validation |
Duplicate NULLs accepted by DB but rejected by validator |
SQL NULL != NULL semantics in unique indexes |
Define explicit nullable policy per field |
| Validation errors masking DB outages | Connection failure surfaces through the validation path | Verify behavior in the spike; ensure infra errors are distinguishable from user errors |
| Runtime error after upgrade | Removed framework helpers | Version pin + CI matrix on the target Laravel major |
Ramp-up. Negligible. A Laravel developer is productive in under an hour; the only durable knowledge transfer needed is the convention: scoped uniqueness always means (a) a unique_with/Rule::unique scope on the rule and (b) a matching composite index, because the validator is a UX affordance and the index is the actual guarantee. Document that pairing, and the package's opacity stops mattering.
How can I help you explore Laravel packages today?