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

Validator Laravel Package

alt-three/validator

Alt Three Validator is a lightweight Laravel package that extends the built-in validator with a hash rule. Use it to verify that an input matches a stored hash, such as checking a password against a hashed value, with simple rule syntax in your validation rules.

View on GitHub
Deep Wiki
Context7

Getting Started

alt-three/validator is a very small wrapper around Laravel's Validator factory that answers one question: "how do I validate an array of input from anywhere in my app — not just a controller — and fail loudly with a message bag?" Instead of returning a Validator instance and making every caller check ->fails(), the package's trait throws a package-specific ValidationException carrying the MessageBag.

Minimal steps:

  1. Check compatibility before anything else. The package is archived and last released 2020-07-25, so its illuminate/* constraints target Laravel 5.x/6.x. On a modern app, confirm what you're actually getting:
    composer require alt-three/validator
    composer why-not alt-three/validator   # why did it resolve to an old version / fail?
    
    If it conflicts with Laravel 9+, stop here — see Gotchas for the two-line native replacement.
  2. Register the service provider if your Laravel version doesn't auto-discover it (5.5+ should, if the package declares extra.laravel.providers). Otherwise add it to config/app.php under providers.
  3. Read the source first — it's roughly a trait, an exception, a message-provider contract, and a provider. vendor/alt-three/validator/src is small enough to read in five minutes, and you should: the exact trait name (ValidationTrait), method signature, and exception API are the whole contract. Don't rely on blog posts for this package.
  4. First use case: put the trait on your base controller and validate in an action:
    use AltThree\Validator\ValidationTrait;
    
    class IncidentController extends Controller
    {
        use ValidationTrait;
    
        public function store(Request $request)
        {
            $input = $request->only(['name', 'status', 'message']);
    
            $this->validate($input, [
                'name'    => 'required|string|max:255',
                'status'  => 'required|integer|between:1,4',
                'message' => 'required|string',
            ]);
    
            // input is now trusted; no if ($validator->fails()) branch needed
        }
    }
    
  5. Wire the exception once, globally. Add a branch in app/Exceptions/Handler.php so a thrown ValidationException becomes a 422 JSON body or a redirect-back-with-errors, exactly like Laravel's own validation failure:
    public function render($request, Throwable $e)
    {
        if ($e instanceof \AltThree\Validator\ValidationException) {
            if ($request->expectsJson()) {
                return response()->json(['message' => $e->getMessage(), 'errors' => $e->getMessageBag()], 422);
            }
    
            return redirect()->back()->withInput()->withErrors($e->getMessageBag());
        }
    
        return parent::render($request, $e);
    }
    
    Until you do this, every validation failure is a 500. This is the single most common "it doesn't work" report.

The payoff case: validation that happens outside an HTTP action — an importer, a console command, a queued job, a domain service. $request->validate() can't help you there; a thrown exception can.

Implementation Patterns

Trait on the base controller. Put use ValidationTrait; on App\Http\Controllers\Controller so every controller gets a consistent $this->validate($input, $rules, $messages, $customAttributes) and no one hand-rolls the fails() check. Keeping the $messages/$customAttributes arguments available per-call is useful for one-off field labels.

Explicit input arrays, not $request->all(). The wrapper validates what you hand it but does not strip anything. Always pass $request->only([...]) (or safe()->only([...]) on newer Laravel) so the validated key set and the persisted key set are the same list. This is your whitelist.

Exception-as-control-flow in services. The strongest pattern is to push validation down into a service/job and let it throw, then handle the failure once at the boundary:

final class ImportIncidents
{
    use ValidationTrait;

    public function handle(array $rows): Collection
    {
        return collect($rows)->map(function (array $row, int $i) {
            try {
                $this->validate($row, ['name' => 'required|string', 'status' => 'required|integer']);
            } catch (ValidationException $e) {
                throw new ImportFailed("Row {$i}: ".$e->getMessageBag()->first('name'));
            }

            return Incident::create($row);
        });
    }
}

This is where the package earns its keep: catch, log the bag, and continue/decorate — you don't need a controller to validate.

Message-bag consumption. Every consumer should go through the bag, not the message string: ->all(), ->getMessages(), ->first('email'), ->has('email'). For nested rules (items.*.sku) the keys come back dot-notated, so group them before rendering:

$grouped = collect($e->getMessageBag()->getMessages())
    ->mapWithKeys(fn ($msgs, $key) => [strtok($key, '.') => $msgs]);

Reuse the Laravel pipeline. The wrapper delegates to the bound validator factory, so everything you already use still works: Rule::unique(...), Rule::in(...), Validator::extend(...) custom rules, after hooks, and the translator's locale. You are only changing how failure is reported, not how rules are evaluated. If a rule works with $request->validate(), it works here.

Testing. In unit tests, assert on the exception rather than the response:

$this->expectException(ValidationException::class);
(new StoreIncident)->handle(['name' => '']);

For HTTP tests, withoutExceptionHandling() surfaces the raw exception; with the handler wired, assert assertStatus(422) + assertJsonValidationErrors(['name']), which keeps your tests portable if you later swap to FormRequests.

Migration-friendly shim. If you're moving toward FormRequest / $request->validate(), define your own thin interface (e.g. ProvidesValidationErrors::errors()) and adapt both the package exception and Illuminate\Validation\ValidationException to it in the handler. Then swapping per-endpoint is a no-op for the response layer.

Gotchas and Tips

  • Archived package, old constraints. Last release 2020, zero dependents, no maintenance. On Laravel 8+ you will either fail to resolve or silently pin an ancient version. The native equivalent is throw ValidationException::withMessages($validator->errors()->getMessages()) (Laravel 5.8+) or simply Validator::make($input, $rules)->validate(), which throws Laravel's own ValidationException from anywhere, not just controllers. If you only need this behaviour, that's a two-line replacement and no dependency.
  • Two ValidationException classes. The package's exception and Illuminate\Validation\ValidationException are distinct and have different APIs (getMessageBag() vs. ->errors() / public $validator). Your handler must catch both during any migration, or failures will 500 intermittently depending on which code path threw.
  • Register the handler branch first. Without it the trait's whole value proposition is a 500 page. Test one failing request in both Accept: application/json and browser mode before writing more validation.
  • ConvertEmptyStringsToNull changes rule semantics. An empty form field arrives as null at the validator, so nullable|email passes while required|email fails, and '' vs null behaves differently in sometimes. Validate the same payload shape you persist.
  • Debugging is awkward because it throws. A dd() after $this->validate(...) never runs. Temporarily wrap in try { ... } catch (ValidationException $e) { dd($e->getMessageBag()); }, or log $e->getMessageBag()->all() — the exception message alone is often just "The given data was invalid."
  • No global message customization. The $messages array is per-call only; there's no config file. For app-wide messages, use lang/*/validation.php (the underlying translator) rather than editing call sites.
  • Provider ordering / stale bindings. If you rebind the validator factory or translator in your own provider, ensure it's registered after the package's so the trait resolves your instance. Symptom: your custom rule or locale is ignored only inside the trait.
  • The extension point is "fork it." The trait is ~20 lines. Copying it into App\Support\Validation and deleting the dependency is a legitimate, low-risk move here — you keep the ergonomics, drop an unmaintained package, and regain control of the exception type. Do this before you invest in the package further.
  • Lockfile hygiene. Because the package is effectively frozen, composer update will keep resolving the same version forever while the rest of the app moves on. Add a CI check (composer outdated --direct or composer why alt-three/validator) so the pin is a visible decision rather than an archaeological surprise six months from now.
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