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

selective/validation

Selective Validation is a Laravel package for validating only the request fields you choose. It simplifies partial updates, conditional forms, and API endpoints by applying rules to selected attributes instead of the full payload, with an easy, lightweight integration.

View on GitHub
Deep Wiki
Context7

Getting Started

Install it with Composer and confirm the PHP floor in composer.json (the package targets modern PHP 7.4/8.x, with 2.3.0 adding explicit PHP 8.5 support):

composer require selective/validation

The public surface is deliberately tiny, so read these four files first, in this order:

  1. src/Validation.php — the accumulator object (addError(), fails(), getErrors()).
  2. src/ValidationError.php — the value object for a single error (getField(), getMessage()).
  3. src/Exception/ValidationException.php — the throwable wrapper used at boundaries.
  4. src/Factory/ValidationFactory.php — the DI-friendly way to obtain a fresh Validation.

First use case: validating a decoded request payload in a service/action class and returning a 422 if anything is wrong.

use Selective\Validation\Factory\ValidationFactory;

$factory = new ValidationFactory();
$validation = $factory->createValidation();

$data = $request->all();

if (empty($data['username'])) {
    $validation->addError('username', 'Username is required');
}

if (!filter_var($data['email'] ?? '', FILTER_VALIDATE_EMAIL)) {
    $validation->addError('email', 'Invalid email address');
}

if ($validation->fails()) {
    $errors = [];
    foreach ($validation->getErrors() as $error) {
        $errors[$error->getField()][] = $error->getMessage();
    }

    return response()->json(['message' => 'Validation failed', 'errors' => $errors], 422);
}

Notice the key difference from Laravel's validator: nothing throws on the first failure. You collect every problem, then decide what to do — that is the notification pattern, and it is the reason to reach for this package.

Implementation Patterns

One validator class per input. Wrap the checks in a small, injectable class so controllers stay thin and the logic is unit-testable without HTTP:

final class CreateUserValidator
{
    public function __construct(private ValidationFactory $factory) {}

    public function validate(array $data): Validation
    {
        $v = $this->factory->createValidation();

        if (empty($data['username'])) {
            $v->addError('username', 'Username is required');
        }
        if (strlen($data['password'] ?? '') < 8) {
            $v->addError('password', 'Password must be at least 8 characters');
        }

        return $v;
    }
}

Throw at the boundary, catch centrally. Inside domain/service code, throw ValidationException (wrapping the Validation) and let a single handler render it. In Laravel, register it in app/Exceptions/Handler.php:

use Selective\Validation\Exception\ValidationException;

public function render($request, Throwable $e)
{
    if ($e instanceof ValidationException) {
        $errors = [];
        foreach ($e->getValidation()->getErrors() as $error) {
            $errors[$error->getField()][] = $error->getMessage();
        }
        return response()->json(['message' => 'Validation failed', 'errors' => $errors], 422);
    }
    return parent::render($request, $e);
}

For Slim/PSR-15 projects, the same behaviour is a three-line middleware that catches ValidationException and emits the JSON body — no framework coupling needed.

Bind the factory in the container. In AppServiceProvider::register(), bind ValidationFactoryInterface to ValidationFactory; then validators are autowired and easy to fake in tests:

$this->app->bind(
    \Selective\Validation\Factory\ValidationFactoryInterface::class,
    \Selective\Validation\Factory\ValidationFactory::class
);

Where it actually earns its keep in Laravel. Use it for domain-level and non-HTTP validation: console commands, queued jobs, importers, and DTOs where FormRequest is unavailable or where you want to report all import row errors rather than abort on the first. Keep FormRequest for HTTP shape/auth concerns; don't duplicate the same rules in both.

Translation. Messages are plain strings passed to addError(). If you need i18n, route them through a translator (the package ships a TranslatorInterface abstraction) or build a field→message catalog in your validator class. Because the package does not own the message text, you control tone and locale completely.

Testing. Assert on fails() and inspect getErrors() directly — no HTTP layer required:

$validation = (new CreateUserValidator(new ValidationFactory()))->validate([]);
$this->assertTrue($validation->fails());
$this->assertSame('username', $validation->getErrors()[0]->getField());

Merging results. There is no built-in merge(); to compose sub-validators, iterate getErrors() on each child and re-addError() the field/message onto the parent. It is three lines and keeps composition explicit.

Gotchas and Tips

  • No short-circuiting means unguarded access. Because you keep validating after a failure, use ?? defaults and cast before validating. strlen($data['password']) on a missing key is a fatal error, not a validation error.
  • Presence vs. validity is your job. The package has no required/nullable semantics — it is a bag of errors. Decide explicitly whether a field is absent, empty, or invalid, or you will emit misleading messages.
  • Nested and array fields need flattening. There is no dot-notation resolver; flatten keys yourself (items.0.name) so the client can map errors back to inputs.
  • Ordering is insertion order. If you snapshot/compare error arrays in tests or return them in a stable API contract, sort by field before asserting.
  • Error codes are limited. If clients need machine-readable codes alongside messages, extend ValidationError (or wrap it in your own DTO) rather than parsing message strings.
  • Throw only at the boundary. ValidationException is for crossing layers. Do not use it for loop control inside validators; return the Validation object instead.
  • Tiny surface, tiny maintenance risk. With ~35 stars and no dependents, treat the API as stable but pin the version and skim the changelog on upgrades. Upgrades have been low-risk so far — 2.3.0 is a pure compatibility bump (PHP 8.5), no API changes — but pinning ^2.3 keeps that predictable. The upside: the whole library is a few hundred lines, so vendoring or replacing it later is trivial. Wrap it behind your own Validator interface if that risk matters to you.
  • Debugging. Log $validation->getErrors() (field + message pairs) rather than var_dump-ing the object; and when a ValidationException bubbles up uncaught, check your handler registration first — a missing catch is the usual cause of a 500 instead of a 422.
  • Extension points. Custom ValidationFactoryInterface for pre-seeded validations, a custom translator for locales, and middleware/exception handlers for transport. That covers essentially every integration need without forking.
  • Don't replace Laravel validation wholesale. Laravel's validator gives you unique/exists, conditional rules, nested arrays, and localization for free. Reach for selective/validation when the notification pattern or framework independence is the point — typically action classes, importers, and shared domain code.
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.
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
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin