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

webman/validation

Laravel-style validation for the webman framework. Validate request data with expressive rule strings, custom messages, and scene-based rulesets, then reuse the same validator across controllers and services. Lightweight, framework-friendly, and easy to integrate into existing webman applications.

View on GitHub
Deep Wiki
Context7

Getting Started

webman/validation is a thin integration layer between Webman and topthink/think-validate. It gives you three things Webman doesn't ship with: PHP-attribute validation on controller methods, named "scenes" (rule subsets per action), and a global exception handler that turns a failed check into an HTTP response instead of a manual if. If you're coming from Laravel, think of it as a stripped-down FormRequest + $request->validate() combo, with rules declared in dedicated validator classes rather than inline arrays.

Minimal steps:

  1. composer require webman/validation
  2. Restart the workers — Webman keeps code in memory, so composer require alone does nothing until php start.php restart -d (or start in dev).
  3. Inspect what the installer published: config/plugin/webman/validation/ (app/config + language files). Confirm the plugin is enabled and note the exception-handler class name — you'll likely override it later.
  4. Read the attribute class itself before trusting the README: vendor/webman/validation/src/Annotation/Validate.php. With 1 star and 0 dependents, the source is the spec.

First use case — a login endpoint:

// app/validate/LoginValidator.php
namespace app\validate;

use think\Validate;

class LoginValidator extends Validate
{
    protected $rule = [
        'username' => 'require|alphaDash|length:4,32',
        'password' => 'require|length:6,64',
    ];

    protected $message = [
        'username.require' => 'Username is required',
        'username.length'  => 'Username must be 4-32 characters',
    ];

    protected $scene = [
        'login'    => ['username', 'password'],
        'register' => ['username', 'password'], // swap in stricter rules later
    ];
}
// app/controller/AuthController.php
namespace app\controller;

use app\validate\LoginValidator;
use support\Request;
use Webman\Validation\Annotation\Validate;

class AuthController
{
    #[Validate(LoginValidator::class, scene: 'login')]
    public function login(Request $request)
    {
        $data = $request->post();          // already validated by the time you get here
        // ... issue token
        return json(['code' => 0, 'data' => ['token' => $token]]);
    }
}

If the attribute doesn't fire (wrong middleware order, controller invoked from a closure, etc.), fall back to manual invocation — same validator, no magic:

$v = new LoginValidator();
if (!$v->scene('login')->check($request->post())) {
    return json(['code' => 1, 'msg' => $v->getError()], 422);
}

Implementation Patterns

One validator class per resource, one scene per action. This is the idiomatic shape. $rule holds the union of all fields; $scene picks the subset. Controllers stay thin, and the same validator is reusable from queue jobs, console commands, and service classes (where attributes never run).

Class-level attribute + per-method scene. Put #[Validate(UserValidator::class)] on the controller to apply a default, then override with #[Validate(UserValidator::class, scene: 'update')] on individual actions. Keeps boilerplate out of CRUD controllers.

Manual validation in the service layer. Attributes are an HTTP concern. Anywhere else — a Workerman timer, a queue consumer, an import command — instantiate and call check() yourself. Wrap it once:

function validate_or_fail(Validate $v, array $data, ?string $scene = null): array
{
    if ($scene) $v->scene($scene);
    if (!$v->batch(true)->check($data)) {
        throw new \InvalidArgumentException($v->getError());
    }
    return array_intersect_key($data, $v->getRule()); // whitelist
}

Custom rules. think-validate exposes extend() for closures and rule methods on the validator (checkPhone, checkSkuExists, etc.). Register globally in config/bootstrap.php so every worker has them; register per-validator when the rule needs injected dependencies. Anything touching your DB layer is a custom rule — see gotchas.

Whitelisting input. Validation is checking, not filtering. check() does not strip unknown keys, and it does not mutate $request. If you want $request->post() to behave like Laravel's validated(), build the subset yourself (array_intersect_key($data, $v->getRule())) or use think-validate's only().

Consistent API error envelope. Don't leak think-validate's raw strings. Register a custom exception handler in config/exception.php that catches the plugin's validation exception and maps it to {code, msg, errors[]} with a 422. Parse $validator->error (the public array property) rather than getError() if you want field-level detail.

Localization. Messages come from the plugin's lang files, keyed field.rule ('username.require' => '...'). Set the locale per request in a middleware before validation runs (header, user preference, subdomain). Fallback locale lives in the plugin config.

Layering with Webman's upload/JSON handling. For JSON bodies, $request->post() already decodes. For multipart, mix $request->post() with $request->file(). For file rules, see gotchas — you'll write your own.

Gotchas and Tips

  • require ≠ Laravel required. think-validate's require rejects '', null, and [] but accepts 0 and '0'. That's usually what you want for numeric fields, but it will bite you when a 0-valued status passes require and then fails a between:1,10. Use require|integer|between:... deliberately.
  • unique and exists are broken here. They're inherited from think-orm's Db facade. Webman with illuminate/database (the common setup) has no such facade — the rule either throws or silently no-ops. Write a custom rule against your actual DB connection.
  • file / image / fileExt / fileSize expect think\File. Webman hands you Webman\Http\UploadFile (a Symfony UploadedFile). These rules will not behave. Use Symfony's API in a custom rule: isValid(), getClientOriginalExtension(), getSize(), getMimeType().
  • getError() returns one string, not an array. With batch(true) it concatenates with <br> — a JSON-land disaster. Read the $validator->error property or explode('<br', ...), or better, write your own error extractor.
  • A typo in a scene name validates nothing — and passes. scene('login ') or a scene key that drifted from the controller attribute means zero rules run and every request succeeds. Assert your scene exists in a test.
  • Regex rules containing commas break. 'regex:/^\d{1,3}(,\d{3})*$/' gets split on the comma inside the pattern. Use the array form: ['regex', '/^\d{1,3}(,\d{3})*$/'].
  • Don't reuse validator instances across requests. In a long-lived worker, $this->error accumulates. Always new, never a singleton bound into the container.
  • Config and lang file changes require a restart. Workerman workers hold compiled config and opcode in memory; php start.php reload is not always enough for new plugin files. Restart in dev, and make sure your deploy script does too.
  • The attribute only fires on genuine controller dispatch. Routing through closures, custom dispatchers, or a middleware that short-circuits before the annotation middleware will skip validation entirely. If a check "mysteriously doesn't run," log at the top of the action — if it logs, the annotation was bypassed.
  • Nested/array payloads are the weak spot. Dot notation and * wildcards exist in recent think-validate, but support is inconsistent and untested in this plugin. For deeply nested JSON, either flatten to a.b.c keys yourself or write a per-element loop.
  • Extension points worth knowing: subclass a validator and override scene()/message(); append()/remove() to adjust rules at runtime; extend() for global rules; swap the exception handler; swap the lang loader. The plugin is small enough that the source is readable in 20 minutes — do that before opening an issue.
  • Adoption risk. 1 star, 0 dependents, first-release-era maturity. Pin an exact version in composer.json, and keep your custom rules in app/ (not in vendor/) so swapping to hand-rolled think\Validate calls — or to a Laravel-style validator — is a grep away rather than a rewrite.
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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core