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

Validation Laravel Package

respect/validation

Respect\Validation is a PHP validation engine offering over 150 fully tested validators. Chain rules fluently, as in v::numericVal()->positive()->between(1, 255)->isValid($input), with advanced exception handling and clear documentation.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and pin the major version deliberately.

composer require respect/validation:^3.0   # PHP 8.5+
composer require respect/validation:^2.4   # PHP 8.1–8.4, the pragmatic default for most Laravel apps today

This is the single most important decision you make with this package. Version 3 is a large, genuinely breaking rewrite (PHP 8.5 floor, renamed classes, changed validate() semantics, unified exceptions), and most blog posts, Stack Overflow answers, and existing code you'll inherit are written against 2.x. Check php -v and your composer.json platform constraints before copying any snippet.

Where to look first, in this order:

  1. docs/validators.md / the online Validators List — 150+ validators, alphabetically, each with a runnable example. This is the reference you'll keep a tab open on; nothing else in the package has this density of value.
  2. The Feature Guide — composition, negation, result inspection.
  3. docs/handling-exceptions.md — how to get messages out, which is where most day-to-day friction lives.
  4. Only then the source under src/Validators/ when you need to know exactly how strict a given check is.

Minimal first use case. Validate a single field and branch on the result:

use Respect\Validation\Validator as v;

if (v::email()->isValid($input)) {
    // ...
}

Then move to the assertion style, which is what you'll actually ship:

use Respect\Validation\Exceptions\ValidationException;

try {
    v::numericVal()->positive()->between(1, 255)->assert($input);
} catch (ValidationException $e) {
    echo $e->getMessage();
}

That two-line chain replaces the if (!is_numeric($x) || $x < 1 || $x > 255) block you'd otherwise write, and it gives you a human-readable message for free. As a good first production use case: a DTO/value-object constructor or a form request adapter that validates a settings payload with nested keys (v::key('host', v::stringType()) etc.) — nested structure validation is where this package clearly beats hand-rolled checks.

Implementation Patterns

Pick the right entry point per context. The three invocation styles are not interchangeable:

Method Returns Use when
isValid($input) bool Hot paths, conditionals, pre-checks, anything in a loop
assert($input) void, throws ValidationException Boundaries: HTTP input, console args, config files
validate($input) ResultQuery (v3) / bool (v2) You need the full failure tree, paths, or aggregated messages

In v3, validate() returning a result object is the feature worth designing around:

$result = v::numericVal()->positive()->between(1, 255)->validate($input);

if ($result->hasFailed()) {
    logger()->warning('bad input', ['errors' => $result->getFullMessage()]);
}

Error paths come back in dot notation (.mysql.host must be a string), which maps cleanly onto Laravel's $errors bag if you flatten the tree.

Compose, don't concatenate checks. The fluent chain is the idiomatic unit of reuse:

final class Rules
{
    public static function pageSize(): \Respect\Validation\Validator
    {
        return v::intVal()->between(1, 100);
    }
}

Return the validator object from static factory methods on a domain class. This keeps rule definitions in one place, is trivially testable (self::assertTrue(Rules::pageSize()->isValid(50))), and lets you build prefixed shortcuts consistently — v3 ships v::nullOrEmail(), v::allPositive(), v::notBlank() as sugar over common nullOr/all/not compositions.

Use shortCircuit() + factory() for dependent validation. This is the pattern that justifies the dependency for anything nontrivial:

v::shortCircuit(
    v::key('countryCode', v::countryCode()),
    v::factory(fn ($input) => v::key('subdivisionCode', v::subdivisionCode($input['countryCode'])))
)->assert($payload);

Same mechanism handles confirm-password, date-range ordering, and conditional-required fields — cases where Laravel's required_if becomes unreadable. The dynamic factory() closure receives the input, so downstream rules can be built from upstream values.

Nested and object data. Prefer the explicit key/property validators over manual array_key_exists gymnastics: v::key(), v::keyOptional(), v::keyExists(), v::property(), v::each(v::intVal()), and v::attributes() for PHP-attribute-annotated DTOs. v::attributes() is a nice fit for read models and commands:

class User {
    #[Validators\Email] public string $email;
    #[Validators\Between(18, 120)] public int $age;
}
v::attributes()->assert($user);

Laravel integration: wrap it, don't sprinkle it. The cleanest integration is a small custom rule so respect/validation participates in Laravel's normal $request->validate() / FormRequest pipeline and error bag:

final class RespectRule implements \Illuminate\Contracts\Validation\ValidationRule
{
    public function __construct(private \Respect\Validation\Validator $rule) {}

    public function validate(string $attribute, mixed $value, \Closure $fail): void
    {
        try {
            $this->rule->assert($value);
        } catch (\Respect\Validation\Exceptions\ValidationException $e) {
            $fail($e->getMessage());
        }
    }
}
// $request->validate(['size' => [new RespectRule(Rules::pageSize())]]);

This keeps localization, :attribute substitution, and $errors behaviour inside Laravel while you get the 150 validators (IBAN, country code, subdivisions, VAT, slug, phone via libphonenumber) that Laravel lacks. Do not replace Laravel's validator wholesale — respect/validation is stateless and knows nothing about the database, so unique/exists stays with Laravel.

Custom validators. In v3 you no longer write a companion exception class; extend Respect\Validation\Validators\Core\Simple (or Wrapper/Envelope/Composite when delegating) and declare messages with #[Template]. Extension points by intent: Simple for a leaf check, Envelope to decorate one existing validator, Composite for multi-validator logic, Wrapper for adapting. Register dependencies via the PSR-11 ContainerRegistry if your custom validator needs a repository.

Custom exceptions at the call site are a small but real ergonomic win:

v::email()->assert($input, new DomainException('Invalid email'));

Testing. Assert on isValid() for the happy/sad path matrix and on getMessage()/getFullMessage() only where the wording is part of your contract. Because validators are pure and side-effect free, they make excellent data providers: one array of [input, expected] covers a whole chain.

Gotchas and Tips

The 2.x → 3.x migration will bite you silently. The worst offender: validate() used to return bool and now returns a ResultQuery, which is always truthy. Every if (v::x()->validate($in)) in your codebase becomes a no-op after the upgrade. Grep aggressively before and after:

rg '\->validate\(' --files-with-matches
rg '\->validate\(' --files-with-matches | xargs -n 1 sed --in-place 's,->validate(,->isValid(,g'

That sed replaces every validate call in the tree — scope it to files that actually import the package's namespace, or your Eloquent models will have a bad day.

Renamed validators to watch for (snippets from older versions will 404 on these): Min → GreaterThanOrEqual, Max → LessThanOrEqual, Nullable → NullOr, Optional → UndefOr, Call → After, Callback → Satisfies, KeyValue → Factory, IterableType → IterableVal. Inverted pairs are the sneakiest: NotBlank → not(v::blank()), NotEmpty → not(v::falsy()), NoWhitespace → not(v::spaced()). Also removed outright: Type, Yes/No, Age/MinAge/MaxAge, PrimeNumber, Fibonacci, FilterVar, Uploaded, VideoUrl — several of these have no drop-in replacement, so audit usage before bumping.

HTTP input is strings; validators know the difference. v::intType() on "42" fails; v::intVal() or v::numericVal() passes. Choose *Type validators for already-typed domain objects and *Val for raw request data, and cast at the boundary. The same trap applies to v3's stricter Contains/ContainsAny/In/EndsWith/StartsWith — they are strict by default now, so v::in([1, 2, 3]) no longer accepts "1".

Composites need at least two validators. v::allOf(v::intVal()) throws in v3. If you're building chains dynamically (from config, from a factory closure), guard for the single-validator case.

key() vs keyOptional() vs keyExists(), and nullOr* vs undefOr*. These encode two independent axes — presence and nullability — and mixing them up produces validators that either reject legitimate payloads or silently skip required fields. keyOptional only tolerates an absent key; a present-but-null value still fails. Reach for nullOrEmail() when null is semantically allowed.

Each rejects non-iterables in v3, including stdClass. If you're passing decoded JSON that might be an object, validate the shape first or normalize.

Performance and exception cost. assert() constructs exception objects and renders templates; in tight loops (bulk import, per-row validation) use isValid() and assemble the failure message yourself. Also memoize chain construction — building a long chain per row is wasted work.

Message customization moved. setTemplate() and setName() are gone; use templated() and named(). The {{name}} placeholder is now {{subject}}, so every custom template you copied forward needs a find-and-replace. Placeholder pipes ({{haystack|list:or}}) are the supported way to render arrays readably.

Don't debug through exception types. v2 gave you EmailException, NestedValidationException, etc.; v3 throws one unified ValidationException. Any catch block naming a specific exception class becomes dead code. Catch ValidationException and inspect the result tree via getFullMessage() — the dot-notation paths are the real debugging surface, far more useful than the top-level message.

shortCircuit() is not a general optimization. It deliberately hides later failures. Use it only where a later rule's construction depends on an earlier one passing; everywhere else it just degrades your error output.

Composer conflicts are plausible. Some validators pull ramsey/uuid and sokil/php-isocodes; if your app already pins different majors of those, resolve deliberately rather than letting Composer pick. Similarly, symfony/translation-contracts is used for message translation — a pre-existing Symfony version skew can conflict.

Namespace collision with Laravel. Respect\Validation\Validator versus Illuminate\Support\Facades\Validator in the same file is a guaranteed confusing afternoon. Alias one explicitly (use Respect\Validation\Validator as V;) and never rely on the short v alias from docs inside a class that also touches Laravel's facade.

Upgrade-testing strategy. Because v3's behaviour changes are subtle (strictness, Url semantics, After no longer swallowing callback errors), the cheapest safety net is a characterization test suite over your existing rule chains before bumping: feed each rule a table of representative inputs and snapshot isValid() plus the message. That converts a scary major upgrade into a diff review.

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.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core