yiisoft/validator
Yii Validator is a standalone PHP validation library with reusable rule sets, nested data support, attribute and value validation, and customizable error messages. It integrates with Yii but works in any PHP project.
yiisoft/validator is the standalone Yii3 validation library, not a Laravel package. There is no service provider auto-discovery, no FormRequest integration, and no php artisan make:rule equivalent. Treat it as a framework-agnostic validation engine you can compose into Laravel where illuminate/validation is a poor fit: imported rows, queue payloads, console commands, DTOs, and domain services that should not depend on the HTTP layer.
Minimal setup:
composer require yiisoft/validator
Look first at:
Yiisoft\Validator\Validator and ValidatorInterfaceYiisoft\Validator\RuleInterfaceYiisoft\Validator\Rule\* for built-in rules such as Required, Length, Email, Regex, Number, Nested, Each, and CallbackYiisoft\Validator\Result and Yiisoft\Validator\ErrorDataSetInterface / data readers if you need to validate objects rather than arraysRuleHandlerResolver and TranslatorInterface for extension and localizationFirst practical use case: validate an imported CSV row before mapping it to an Eloquent model.
use Yiisoft\Validator\Rule\Email;
use Yiisoft\Validator\Rule\Length;
use Yiisoft\Validator\Rule\Required;
use Yiisoft\Validator\Validator;
$validator = new Validator();
$result = $validator->validate($row, [
'email' => [new Required(), new Email()],
'name' => [new Required(), new Length(min: 2, max: 50)],
]);
if ($result->isInvalid()) {
foreach ($result->getErrors() as $error) {
report(sprintf(
'Row %s [%s]: %s',
$rowNumber,
(string) $error->getValuePath(),
$error->getMessage(),
));
}
}
Bottom line: use this package only where you deliberately want a Yii-style rule engine outside Laravel’s HTTP validation stack. For ordinary controller/form validation, Laravel’s native validator remains the lower-friction choice.
Bind a singleton in a service provider. Keep construction in one place so translator and resolver configuration is consistent:
use Yiisoft\Validator\Validator;
use Yiisoft\Validator\ValidatorInterface;
$this->app->singleton(ValidatorInterface::class, fn () => new Validator());
If you need a custom translator or rule handler resolver, inject those dependencies here rather than constructing a validator ad hoc in every action.
Keep Laravel validation at the edge, Yii validation in the core. Use FormRequests and $request->validate() for HTTP shape and authorization. Use yiisoft/validator for data entering through jobs, imports, webhooks, or internal services. This avoids fighting two validation systems over the same request lifecycle.
Bridge Result errors into Laravel’s ValidationException when you must surface them through Laravel conventions:
use Illuminate\Validation\ValidationException;
$messages = [];
foreach ($result->getErrors() as $error) {
$messages[(string) $error->getValuePath()][] = $error->getMessage();
}
if ($messages !== []) {
throw ValidationException::withMessages($messages);
}
Wrap this in a small adapter class, for example YiiValidationAdapter, so controllers and jobs do not depend directly on Yii validator internals.
Validate DTOs and nested structures. Prefer explicit DTOs or arrays over Eloquent models. Use Nested for object graphs and Each for collections:
use Yiisoft\Validator\Rule\Each;
use Yiisoft\Validator\Rule\Nested;
$result = $validator->validate($order, [
'customer' => [new Nested([
'email' => [new Required(), new Email()],
])],
'lines' => [new Each([new Nested([
'sku' => [new Required()],
'quantity' => [new Required(), new Number(min: 1)],
])])],
]);
Use a queue-job validation pattern. Validate the decoded payload at the start of handle(), then throw a domain exception or mark the job failed with structured context. Do not let invalid queue payloads reach model writes.
public function handle(ValidatorInterface $validator): void
{
$result = $validator->validate($this->payload, [
'order_id' => [new Required(), new Number(min: 1)],
'email' => [new Required(), new Email()],
]);
if ($result->isInvalid()) {
throw new InvalidQueuePayload($result);
}
// map to Eloquent only after validation
}
Custom rules: implement RuleInterface and, depending on the package version, the corresponding handler. Register custom handlers through the rule handler resolver if you are not relying on convention-based resolution. Keep custom rules small and stateless; put Laravel-specific concerns such as MessageBag formatting in the adapter, not the rule.
Test the Result, not the framework response. Assert error paths and messages from Result::getErrors() in unit tests. Use Laravel feature tests only for the adapter that converts the result into ValidationException.
Use Callback rules for exploratory or one-off checks. They are useful during integration spikes, but promote repeated callback logic into a named rule once it stabilizes.
illuminate/validation. Familiar concepts such as bail, sometimes, required_if, wildcard paths, and Laravel’s error bag do not map one-to-one. Budget time to rebuild or bridge the rules you actually use.lang-based. Yii validator messages come from Yiisoft\Translator\TranslatorInterface. If your application requires Laravel translation files, implement a translator adapter or accept default messages.MessageBag.skipOnError behavior on the rules you use; otherwise you may collect more errors than expected or miss a later rule. Keep rule arrays ordered from structural checks to format checks.$result->getErrors(), including value path, message, and parameters. For row imports, include the row index in your own context because the validator only knows the data set.Validator. A dedicated Laravel adapter around ValidatorInterface is easier to test and replace than a subclass that mixes Yii rules with Laravel error handling.RuleInterface implementations, a custom RuleHandlerResolver, a TranslatorInterface adapter, and custom data set/readers. Use them sparingly; each one adds a layer Laravel developers on the team will need to learn.How can I help you explore Laravel packages today?