Weave 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 Unique Laravel Package

willvincent/laravel-unique

willvincent/laravel-unique provides a simple trait for generating unique values on Eloquent models, making it easy to assign distinct IDs, codes, or tokens to records with minimal configuration in any Laravel application.

View on GitHub
Deep Wiki
Context7

Getting Started

This package extends Laravel's database-backed validation rules so a single exists() check can carry a set of constraint fields — extra column/value pairs that narrow the lookup — without hand-rolling Rule::exists(...)->where(...) closures for every variation.

Install and confirm the version

composer require <vendor>/<package> "^1.2"
php artisan vendor:publish --tag=<package>-config   # only if the README lists a config file

Where to look first

  1. The rule class that wraps exists() — this is the whole package. Read it before anything else; it is short and tells you exactly how constraint fields are normalized, quoted, and appended to the query.
  2. tests/ — specifically the cases covering exists() with more than one constraint. Release 1.2 fixed a bug in exactly that area, so the tests are the best documentation of intended behaviour.
  3. The README's "constraints" section for the accepted input shapes (associative array vs. list of triples vs. nested arrays).

First use case

Scope a uniqueness/exists check to the current tenant instead of writing a closure:

$request->validate([
    'email' => [
        'required',
        'email',
        // instead of: Rule::unique('users')->where('tenant_id', $tenantId)
        new ConstrainedExists('users', 'email', ['tenant_id' => $tenantId]),
    ],
]);

If that validates correctly and the generated SQL shows where tenant_id = ?, you have the mental model right and everything else is composition.

Implementation Patterns

Declarative constraints in Form Requests

The main day-to-day win is that constraint data becomes data, not closures. Build the array from the request or the authenticated user and pass it in:

public function rules(): array
{
    return [
        'slug'      => ['required', 'string', new ConstrainedExists('pages', 'slug', [
            'site_id' => $this->route('site')->id,
            'locale'  => $this->input('locale', 'en'),
        ])],
        'parent_id' => ['nullable', 'integer', new ConstrainedExists('pages', 'id', [
            'site_id' => $this->route('site')->id,
        ])],
    ];
}

Reusable rule factories

Wrap the common scopes once, so controllers and requests never repeat column names:

class TenantExists
{
    public static function make(string $table, string $column): ConstrainedExists
    {
        return new ConstrainedExists($table, $column, [
            'tenant_id' => auth()->user()->tenant_id,
        ]);
    }
}

This also gives you a single place to change when the tenancy column is renamed.

Mirror rule and mirrored query

Every constrained exists() should have a matching write path. If validation says "a page with this slug exists in this site", the lookup you do afterwards must apply the same two predicates — otherwise you validate one row and mutate another. Keep both in the same class (a form request plus a small repository method, or an action class).

Pair with unique() for update flows

For update endpoints, the same constraint set usually applies but the current record must be excluded. Keep the constraint array identical and add the ignore, so the two rules can't drift:

$constraints = ['site_id' => $site->id, 'locale' => $locale];

'email' => [
    'required',
    new ConstrainedExists('users', 'email', $constraints),
    Rule::unique('users', 'email')->where($constraints)->ignore($user->id),
],

Testing

Assert on behaviour, not SQL: assertSessionHasNoErrors() for the happy path, and assertSessionHasErrors('slug') for a row that exists in a different tenant. The second assertion is the one that catches regressions in constraint handling.

Gotchas and Tips

1.2 changes matching behaviour — re-run your suite

The fix is that constraint fields were being wrongly applied in one of the exists() checks. If you worked around the old behaviour (for example by adding a redundant whereRaw, or by duplicating a field inside the constraints array to force it through), those workarounds will now over-constrain or be silently ignored. After upgrading:

  • Diff the SQL your rules generate before and after. DB::listen() or DB::enableQueryLog() in a single test is enough.
  • Add an explicit regression test with two or more constraint fields — a single-field constraint is what let the bug survive.
  • Check any rule that relies on constraint ordering or on the same column appearing twice.

Column ambiguity

Constraints are appended as plain where clauses. If the rule is ever used against a query with joins or a table alias, qualify the constraint keys (users.tenant_id) or you will get an ambiguous-column error that points at the validator rather than at your query.

Soft deletes are not implied

An exists() check sees soft-deleted rows unless the constraint set (or the package's config, if it has one) excludes them. Decide deliberately per rule; getting this wrong means a "deleted" record blocks a slug forever.

Null handling

Constraints with a null value are a common source of silent widening: depending on the builder, where('col', null) may become is null or be dropped. If a field is nullable, skip it from the constraint array explicitly rather than passing null and hoping.

Connection and table prefixes

Constraints run on the connection the rule resolves, not necessarily the default one. For multi-connection apps, confirm which connection the lookup uses; table prefixes are applied by the grammar, so don't hand-prefix the table name.

Validation order and cost

Each constrained exists() is a round trip. Combine constraints into one rule instead of chaining several exists() rules on the same field, and put cheap rules (required, string, max) before it so invalid input never reaches the database.

Extension points

If the package exposes a rule class you can subclass or a where builder hook, prefer extending it over post-processing the rule in the form request. The 1.2 bug is a reminder that constraint assembly is the fragile part — keep your customisations in one adapter class so a future fix is a one-line change for you.

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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings